首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >大文件分片上传:断点续传、秒传与并发控制的完整实现

大文件分片上传:断点续传、秒传与并发控制的完整实现

原创
作者头像
用户5598620
发布2026-09-11 09:05:18
发布2026-09-11 09:05:18
50
举报

上传几百兆甚至几个 G 的视频、安装包时,普通的整文件上传体验很差:网络一抖就要从头再来,弱网下几乎传不完,进度条也无法准确反映状态。生产环境的大文件上传普遍采用"分片上传":客户端把文件切成固定大小的块并行上传,服务端合并,并在此基础上实现断点续传、秒传和失败重试。本文按客户端切分、服务端接收、合并、秒传判定、并发与可靠性这条主线,给出一套完整实现。

一、整体流程先理清

一次分片上传的完整生命周期是:

  1. 客户端计算文件指纹(hash),先问服务端这个文件是否已存在(秒传判断)、是否有未传完的分片(断点信息);
  2. 按固定大小(如 5MB)切片,逐片或并发上传,每片带文件 id、分片序号、分片 hash;
  3. 服务端校验每片并暂存,返回已收到的分片列表;
  4. 全部传完后客户端发起合并请求,服务端按序号拼接成完整文件、校验总 hash;
  5. 校验通过后清理临时分片,返回最终文件地址。

关键点是:上传状态以服务端记录为准,客户端随时可以根据"已收到分片列表"跳过已传部分,这就是断点续传的基础。

二、文件指纹:秒传与去重的依据

用文件内容 hash(如基于 web worker 算 SHA-256,或先用抽样+大小+修改时间做快速指纹)作为唯一标识。先调用预检接口:

代码语言:javascript
复制
async function precheck(fileHash, fileSize) {
  const res = await fetch("/upload/precheck", {
    method: "POST",
    body: JSON.stringify({ fileHash, fileSize })
  });
  return res.json(); // { uploaded:true }秒传 | { uploaded:false, chunks:[2,5]已存在 }
}

服务端若发现该 hash 的完整文件已存在,直接返回已有地址实现"秒传";若只传了一部分,返回已存在的分片序号,客户端跳过这些分片。大文件全量 hash 较慢,可以先用"文件大小 + 首尾若干字节 + 抽样块"算快速指纹做初判,合并时再做完整校验兜底。

三、客户端切片与并发上传

代码语言:javascript
复制
function sliceFile(file, chunkSize = 5 * 1024 * 1024) {
  const chunks = [];
  for (let i = 0; i < file.size; i += chunkSize) {
    chunks.push({ index: chunks.length, blob: file.slice(i, i + chunkSize) });
  }
  return chunks;
}

async function uploadChunks(fileHash, chunks, doneSet, concurrency = 3) {
  const queue = chunks.filter(c => !doneSet.has(c.index));
  const workers = Array.from({ length: concurrency }, worker);
  let cursor = 0;
  async function worker() {
    while (cursor < queue.length) {
      const chunk = queue[cursor++];
      const fd = new FormData();
      fd.append("fileHash", fileHash);
      fd.append("index", chunk.index);
      fd.append("chunk", chunk.blob);
      await retry(() => fetch("/upload/chunk", { method: "POST", body: fd }), 3);
    }
  }
  await Promise.all(workers);
}

并发数要克制:并发太高会挤占带宽、加重服务端压力,一般 3~6 个为宜;每个分片要带独立重试,单片失败不应导致整个任务失败。

四、服务端接收与合并

服务端把每个分片暂存到以文件 hash 命名的目录,文件名用序号保证顺序:

代码语言:javascript
复制
app.post("/upload/chunk", async (ctx) => {
  const { fileHash, index } = ctx.request.body;
  const buf = ctx.request.files.chunk;
  const dir = path.join(TMP_DIR, fileHash);
  await fs.mkdir(dir, { recursive: true });
  await fs.writeFile(path.join(dir, `${index}.part`), buf.data); // 幂等覆盖写
  const uploaded = await fs.readdir(dir);
  ctx.body = { ok: true, uploaded: uploaded.map(f => Number(f.split(".")[0])) };
});

合并时严格按序号读取、用流拼接,避免一次性把大文件读进内存:

代码语言:javascript
复制
async function mergeChunks(fileHash, total, finalName) {
  const dest = fs.createWriteStream(path.join(FILE_DIR, finalName));
  for (let i = 0; i < total; i++) {
    const part = path.join(TMP_DIR, fileHash, `${i}.part`);
    if (!fs.existsSync(part)) throw new Error(`missing chunk ${i}`);
    await pipeline(fs.createReadStream(part), dest, { end: false });
  }
  dest.end();
}

合并后校验整体大小与总 hash,确认无缺失、无损坏,再清理临时目录。

五、断点续传与可靠性

断点续传不需要特殊机制,靠的就是"服务端记录 + 上传前预检":页面刷新、网络中断后,重新用文件指纹查询已传分片,跳过即可。要让它真正可靠,需要注意:

  • 分片写入幂等:同一片重传直接覆盖,不报错、不重复计数;
  • 临时分片设过期清理:用户传一半放弃会留下垃圾文件,需要定时任务清理超过一定时长未合并的临时目录;
  • 合并要校验完整性:缺片直接拒绝并返回缺失序号,让客户端补传,而不是拼出损坏文件;
  • 上传进度以服务端为准:客户端本地记录可能丢失,进度恢复要问服务端。

六、踩坑清单

  • 不分片直接整传,弱网下一次失败全部重来;
  • 分片序号用字符串排序导致 10 排在 2 前面,合并顺序错乱,务必按数值排序;
  • 合并时一次性 readFile 全部分片,几个 G 文件直接撑爆内存,要用流式拼接;
  • 分片重传报"已存在"而失败,没做幂等覆盖;
  • 只在前端记进度,清缓存后无法续传;
  • 并发开太多,浏览器连接被占满、服务端瞬时压力过大;
  • 不校验总 hash,拼接出错或被篡改也发现不了;
  • 临时分片不清理,磁盘被半途而废的上传慢慢占满。

七、工程落地建议

落地时建议固定分片大小(5~10MB 是较平衡的选择,过小会增加请求数,过大则单片重试成本高),服务端用对象存储的分片上传能力承接最终文件、本地只做短暂中转或直传签名,避免应用服务器成为带宽和存储瓶颈。客户端把 hash 计算放进 Web Worker 防止阻塞主线程,大文件提供"快速指纹初判 + 合并时完整校验"兼顾速度与正确性。还要处理好多端场景:同一账号在不同设备续传时,进度查询要基于文件指纹而不是设备本地。最后用弱网模拟、中途断网、杀进程重进、并发重试等手段反复验证,确认任意时刻中断都能无损恢复。

八、上线前复盘清单

  1. 是否实现文件指纹预检,已存在文件能否秒传、未传完能否续传;
  2. 分片大小与并发数是否经过带宽和服务端压测;
  3. 分片上传与合并是否幂等,重传是否安全;
  4. 合并是否流式、是否按数值序号、是否校验总 hash;
  5. 中断、刷新、换设备后能否依据服务端记录恢复;
  6. 临时分片是否有过期清理,最终文件是否走对象存储;
  7. 弱网、断连、杀进程等异常下是否验证过无损续传。

结语

分片上传的价值不只是"能传大文件",更是把一次脆弱的长传输变成了许多次可靠、可重试、可恢复的小传输。抓住"文件指纹定身份、服务端记录定进度、流式合并保内存、完整校验保正确"这几点,断点续传、秒传和并发上传就能在同一套机制里自然成立。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、整体流程先理清
  • 二、文件指纹:秒传与去重的依据
  • 三、客户端切片与并发上传
  • 四、服务端接收与合并
  • 五、断点续传与可靠性
  • 六、踩坑清单
  • 七、工程落地建议
  • 八、上线前复盘清单
  • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档