绝望之地绿色中文版
523M · 2025-11-06
在Spring Boot中实现大文件分片下载和断点续传功能,需结合HTTP Range协议和文件流处理技术。以下是完整实现方案:
Range请求头指定下载范围(如Range: bytes=0-999),服务器返回206 Partial Content状态码及对应内容片段。Range头续传未完成部分。@GetMapping("/download")
public ResponseEntity<InputStreamResource> downloadFile(
@RequestHeader(value = "Range", required = false) String rangeHeader,
HttpServletRequest request) throws IOException {
// 获取文件信息
File file = new File("/path/to/largefile.zip");
long fileSize = file.length();
// 解析Range头
long start = 0, end = fileSize - 1;
if (rangeHeader != null) {
String[] ranges = rangeHeader.replace("bytes=", "").split("-");
start = Long.parseLong(ranges[0]);
if (ranges.length > 1) {
end = Long.parseLong(ranges[1]);
}
}
// 校验范围合法性
if (start >= fileSize || end >= fileSize || start > end) {
return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
.header("Content-Range", "bytes */" + fileSize)
.build();
}
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(end - start + 1);
headers.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);
headers.setHeader("Accept-Ranges", "bytes");
// 返回分片数据流
try (InputStream is = new FileInputStream(file);
InputStreamResource resource = new InputStreamResource(is)) {
is.skip(start);
return new ResponseEntity<>(resource, headers, HttpStatus.PARTIAL_CONTENT);
}
}
0-999)。Range: bytes=1000-,服务器从断点处继续传输。// 分片下载逻辑
async function downloadFileWithResume(fileId, fileName) {
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
let downloadedBytes = 0;
// 获取文件总大小
const response = await fetch(`/api/files/${fileId}/size`);
const totalSize = parseInt(await response.text());
// 检查本地是否有已下载部分
const tempFile = await checkLocalProgress(fileName);
if (tempFile) {
downloadedBytes = tempFile.size;
}
// 分片下载
while (downloadedBytes < totalSize) {
const end = Math.min(downloadedBytes + CHUNK_SIZE - 1, totalSize - 1);
const response = await fetch(`/api/files/${fileId}/download`, {
headers: { Range: `bytes=${downloadedBytes}-${end}` }
});
if (!response.ok) throw new Error("下载失败");
const blob = await response.blob();
await appendToFile(tempFile, blob);
downloadedBytes += blob.size;
// 更新进度
updateProgress(downloadedBytes / totalSize);
}
// 完成后重命名文件
await renameTempFile(tempFile, fileName);
}
// 检查本地进度
async function checkLocalProgress(fileName) {
const tempPath = `/temp/${fileName}.part`;
return new Promise(resolve => {
fs.stat(tempPath, (err, stats) => {
if (err) resolve(null);
else resolve(fs.createWriteStream(tempPath, { flags: 'a' }));
});
});
}
内存管理
使用InputStream流式传输,避免大文件加载到内存。
并发控制
通过线程池限制同时下载的分片数:
@Service
public class DownloadService {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public void downloadChunk(String fileId, long start, long end) {
executor.submit(() -> {
// 分片下载逻辑
});
}
}
进度存储
使用Redis记录每个文件的下载进度:
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void saveProgress(String fileId, long downloadedBytes) {
redisTemplate.opsForValue().set(fileId, String.valueOf(downloadedBytes));
}
正常下载
curl -H "Range: bytes=0-999" http://localhost:8080/download
断点续传
中断后重新发起相同请求,验证是否从断点续传。
多线程下载
使用工具(如Postman)模拟多线程分片请求。
Content-Range实现视频边下边播。通过上述方案,可高效实现大文件的分片下载和断点续传,支持弱网环境和超大文件传输。完整代码示例可参考GitHub仓库(需替换实际存储路径)。