如何实现断点续传和分片上传?

1. 为什么需要分片上传与断点续传

在 Web 应用中,上传大文件(如视频、安装包、数据集)时,如果采用传统的单次上传方式,往往会遇到两个突出问题:一是网络波动导致上传中断后必须从头重传,浪费时间和带宽;二是单个请求体过大,容易触发网关超时或服务器内存压力。分片上传和断点续传正是为了解决这两个问题而设计的。

分片上传的核心思路是把一个大文件切成多个较小的分片,逐个上传到服务器;断点续传则是在上传中断后,能够从已上传的分片之后继续,而不是重新上传整个文件。两者通常配合使用,共同提升大文件上传的稳定性和效率。

2. 整体流程设计

一个完整的断点续传与分片上传流程,通常包含以下几个阶段:

  • 初始化上传:前端向后端发起创建上传任务的请求,携带文件名、文件大小、分片大小等信息,后端返回一个唯一的上传任务 ID。
  • 计算分片信息:前端根据分片大小将文件切分为多个分片,并为每个分片生成序号和校验值(如 MD5)。
  • 逐片上传:前端按顺序(或并发)将每个分片上传到后端,后端保存分片并记录已上传状态。
  • 查询已上传分片:上传中断后,前端向后端查询该任务已成功上传的分片列表,据此跳过已完成的分片。
  • 合并文件:所有分片上传完成后,前端通知后端执行合并操作,后端将分片按顺序拼接为完整文件。

3. 前端实现要点

前端主要负责文件切片、上传调度、进度展示和断点恢复。下面以 JavaScript 为例,说明核心实现思路。

3.1 文件切片

利用 HTML5 的 File API,可以通过 slice 方法将文件切分为多个 Blob 分片:

javascript 复制代码
const CHUNK_SIZE = 5 * 1024 * 1024; // 每个分片 5MB

function createChunks(file) {
  const chunks = [];
  let start = 0;
  while (start < file.size) {
    const end = Math.min(start + CHUNK_SIZE, file.size);
    chunks.push(file.slice(start, end));
    start = end;
  }
  return chunks;
}

3.2 上传分片

每个分片通过独立的请求上传,并携带任务 ID 和分片序号,便于后端识别和排序:

javascript 复制代码
async function uploadChunk(taskId, chunk, index) {
  const formData = new FormData();
  formData.append('taskId', taskId);
  formData.append('index', index);
  formData.append('chunk', chunk);

  const response = await fetch('/api/upload/chunk', {
    method: 'POST',
    body: formData
  });
  return response.json();
}

3.3 断点续传

上传中断后,重新开始时先查询已上传的分片,只上传缺失的部分:

javascript 复制代码
async function resumeUpload(taskId, chunks) {
  const res = await fetch(`/api/upload/status?taskId=${taskId}`);
  const { uploadedIndexes } = await res.json();

  const tasks = chunks.map((chunk, index) => {
    if (uploadedIndexes.includes(index)) {
      return Promise.resolve({ index, skipped: true });
    }
    return uploadChunk(taskId, chunk, index).then(() => ({ index, skipped: false }));
  });

  await Promise.all(tasks);
  await mergeFile(taskId);
}

4. 后端实现要点

后端负责接收分片、记录上传状态、校验完整性以及合并文件。下面以 Java Spring Boot 为例说明核心接口设计。

4.1 初始化上传任务

java 复制代码
@PostMapping("/api/upload/init")
public Map<String, Object> initUpload(@RequestBody InitUploadRequest request) {
    String taskId = UUID.randomUUID().toString();
    // 保存任务信息:文件名、总大小、分片大小、分片总数
    uploadTaskService.createTask(taskId, request);
    return Map.of("taskId", taskId, "chunkSize", request.getChunkSize());
}

4.2 接收分片

java 复制代码
@PostMapping("/api/upload/chunk")
public ResponseEntity<?> uploadChunk(
        @RequestParam String taskId,
        @RequestParam int index,
        @RequestParam MultipartFile chunk) {
    // 校验任务存在,保存分片到临时目录
    uploadTaskService.saveChunk(taskId, index, chunk);
    return ResponseEntity.ok().build();
}

4.3 查询已上传分片

java 复制代码
@GetMapping("/api/upload/status")
public Map<String, Object> getUploadStatus(@RequestParam String taskId) {
    List<Integer> uploadedIndexes = uploadTaskService.getUploadedIndexes(taskId);
    return Map.of("uploadedIndexes", uploadedIndexes);
}

4.4 合并分片

java 复制代码
@PostMapping("/api/upload/merge")
public ResponseEntity<?> merge(@RequestParam String taskId) {
    uploadTaskService.mergeChunks(taskId);
    return ResponseEntity.ok().build();
}

5. 关键细节与注意事项

  • 分片大小选择:分片过小会导致请求数量过多,增加网络开销;分片过大会失去分片上传的优势。一般建议 1MB 到 10MB 之间,可根据网络环境动态调整。
  • 并发控制:并发上传多个分片可以提升速度,但需要控制并发数,避免占用过多连接或触发服务端限流。
  • 完整性校验:每个分片上传时可携带 MD5 值,服务端校验后存储;合并后也可对整体文件做一次校验,确保数据完整。
  • 任务过期清理:长时间未完成的上传任务应设置过期时间,定期清理临时分片,避免占用磁盘空间。
  • 秒传支持:在初始化时计算整个文件的 MD5,若服务端已存在相同文件,可直接返回秒传结果,跳过实际上传。

6. 总结

断点续传和分片上传的实现,核心在于将大文件拆分为独立可管理的小分片,并通过任务状态记录实现中断后的精准续传。前端负责切片和调度,后端负责存储、状态管理和合并,两者配合即可显著提升大文件上传的稳定性和用户体验。实际项目中,还可以结合并发控制、完整性校验和秒传机制,进一步优化上传效率。

相关推荐
captain3761 分钟前
网络原理(7)-NAT(Network Address Translation)
java·网络·网络协议·java-ee
爱研究的小梁30 分钟前
异构链路科普:5G/4G、卫星、专网、WiFi、光纤,如何聚合工作?
网络·网络协议·udp·信息与通信
网硕互联的小客服39 分钟前
如何用Claude Code重构遗留系统?
网络
程序猿乐锅1 小时前
【黑马点评 | 第二篇】Redis 缓存更新策略与商铺缓存实现
java·网络·redis·spring·mybatis
科技小登1 小时前
业务层四道闸:DDoS+CC、API、Bot 与全球加速的分层防护设计与选型
网络·安全
wuyk5551 小时前
《WiFi 嵌入式物联网开发全套实战》| 第 12 章 Linux 系统 WiFi 常用命令全集:iw/iwlist/ip/wpa_cli 实战
linux·网络·stm32·物联网·tcp/ip
IT小白杨1 小时前
跨境电商账号关联归因链路拆解与可落地环境配置
服务器·网络·经验分享·安全·指纹浏览器
网硕互联的小客服1 小时前
可以把服务器当成DNS服务器使用吗?如何操作?
运维·服务器·网络
IMPYLH2 小时前
HTML 的 <small> 元素
前端·网络·html
和裕2 小时前
平口开槽箱 vs 飞机盒 vs 扣底盒:自动化、展示效果与成本核心区别
大数据·运维·网络·人工智能·算法·自动化