原生<input type="file">不生成分片编号,必须由前端JavaScript显式计算并附加;分片编号是严格连续的整数(如0,1,2…),需与后端校验逻辑对齐,不可用随机数或时间戳,应通过Blob.slice()配合循环用索引i生成,并通过服务端状态校验确保一致性。

原生 <input type="file"> 不生成分片编号,也不切片——它只交出一个 File 对象。分片编号必须由前端代码显式计算并附加,不能靠 HTML 自动产生。
为什么不能靠 HTML 或表单属性生成分片编号
HTML 表单本身没有“分片”概念,enctype="multipart/form-data" 只负责把整个文件打包进请求体,不拆、不分、不编号。所谓“自动生成”,其实是开发者用 JavaScript 基于文件大小和预设分片大小算出来的索引值。
常见错误现象:index is undefined 或后端收不到 chunkIndex 字段,往往是因为 FormData 中漏传、拼写错误(比如传了 chunkId 但后端只认 index),或没在循环里正确递增变量。
- 分片编号本质是整数,从
0或1开始,严格连续(如[0,1,2,3]),不能跳、不能重复 - 编号必须和服务端校验逻辑对齐:如果后端按
0-起始存,前端就不能传1-起始的index - 别用
Math.random()或时间戳当编号——断点续传会彻底失效
怎么用 Blob.slice() + 循环生成带编号的分片
核心是两步:先算总片数,再在 for 循环中用 i 当编号,调用 file.slice() 切出对应块。
立即学习“前端免费学习笔记(深入)”;
示例关键片段:
const shardSize = 4 * 1024 * 1024; // 4MB
const shardCount = Math.ceil(file.size / shardSize);
<p>for (let i = 0; i < shardCount; i++) {
const start = i * shardSize;
const end = Math.min(file.size, start + shardSize);
const chunk = file.slice(start, end);</p><p>const formData = new FormData();
formData.append('data', chunk);
formData.append('index', i); // ← 这就是分片编号,务必传
formData.append('total', shardCount);
formData.append('file_id', fileId); // 建议用文件哈希,非 name</p><p>await fetch('/upload', { method: 'POST', body: formData });
}-
file.slice()返回新Blob,不复制内存,开销低 - 编号用
i最安全;若需1-起始,统一改formData.append('index', i + 1)并同步调整后端逻辑 - 避免在循环里直接
await fetch()串行上传——性能差;可用Promise.allSettled()控制并发数(如最多 4 片并行)
上传失败后如何保证编号不乱、不重传
编号本身不会错,但“重试哪一片”容易出错。关键不是记住“上次传到第几片”,而是每次上传前向服务端查已存编号列表。
实操建议:
- 上传前先发
GET /upload/status?file_id=xxx,服务端返回类似[0,1,3]的已成功分片索引数组 - 前端比对本地总片数与服务端返回,只对
!serverUploaded.includes(i)的i发起上传 - localStorage 缓存编号状态不可靠:用户清缓存、换设备、多标签同时上传都会导致冲突
- 每个分片请求必须带
index和唯一file_id(推荐用文件内容 MD5 或 SHA-1,而非 name)
真正难的不是生成编号,而是让编号在中断、重试、并发、跨设备场景下始终和服务端状态一致。这要求前后端对“已完成”的定义完全同步——不能信前端本地计数,只信服务端持久化记录。



















