分片上传不能塞进,因浏览器对提交的处理是原子性的,文件被打包成一个multipart请求体,分片元数据作为普通文本字段无法与文件内容关联,导致后端无法识别分片顺序和内容。

分片上传不能塞进 <form></form>,否则后端收不到分片元数据,拼出来的文件直接损坏。
为什么 <form enctype="multipart/form-data"></form> 和分片上传互斥
浏览器对 <form></form> 提交的处理是原子性的:整个文件被打包成一个 multipart 请求体,用随机 boundary 分隔字段。哪怕你在 FormData 里 append 了 chunkIndex 和 uploadId,它们也只是普通文本字段,和文件内容不在同一个 part 里——服务端无法从 $_FILES 中提取“第 2 片”的二进制流,只能拿到一堆散落的字符串参数。
常见错误现象包括:
- 后端收到的
$_FILES['file']是空或不完整 - 所有分片都存成了独立小文件,但合并脚本找不到连续
chunkIndex - 用户暂停后刷新页面,进度归零,且无续传依据
File.slice() 切片时必须严格对齐字节偏移与索引
切片不是“按块数均分”,而是按字节范围截取。错一位,后续所有分片都会偏移,最终文件头损坏。
正确做法:
- 用
for (let i = 0; i * chunkSize 控制循环,而非 <code>Math.ceil(file.size / chunkSize)(浮点误差会导致最后一片被截断) -
start = i * chunkSize,end = Math.min(start + chunkSize, file.size),确保不越界 -
chunkIndex必须从0开始,且和服务端约定一致;用1起始会丢首片 - 不要复用
Date.now()或文件名生成uploadId,并发选同名文件时必然冲突;改用crypto.randomUUID()
前端状态维护的关键字段必须在首次选中文件时就固化
页面刷新后 File 对象不可恢复,所以唯一标识、总片数、分片大小、上传起点这些状态必须立刻计算并缓存到 sessionStorage 或组件 data 中。
容易忽略的点:
- 仅靠
file.name + file.size生成 ID 不可靠——同名不同内容、同内容不同名都会撞车 - 哈希必须基于内容:取前 1MB 用
SubtleCrypto.digest()算 SHA-256,比全量快且足够区分 - 进度条更新别直接绑定
value++,要按实际完成的chunkIndex加权计算:Math.round((completedCount / totalChunks) * 100) - 失败重试只应重发失败索引,不要清空已成功分片再全量重传
HTML 结构只需提供入口与反馈,不参与逻辑编排
表单标签在这里只是壳,它的唯一作用是触发选择和展示 UI 状态。任何试图用 name、form 属性或 submit 事件驱动分片的行为,都是在和协议层对抗。
最小可用结构示例:
<input type="file" id="fileInput" accept="video/*"><button id="uploadBtn">开始上传</button> <div id="progress"></div>
注意:
- 不要包裹在
<form></form>里,除非你同时实现了 fallback 整文件提交逻辑 - 不要设
name属性——它对分片流程无意义,反而可能误导后端解析 -
accept可过滤类型,但不可替代后端 MIME 校验 - 多个文件上传时,每个
File必须有独立uploadId,不能共用一个
最易被绕过的细节是:uploadId 和 chunkIndex 的生成时机。它们必须在用户点击“选择文件”之后、点击“上传”之前就确定,并持久化。一旦进入上传队列,这两个值就不能再变——哪怕网络抖动、页面崩溃、用户切换标签页,续传也得靠它们对上号。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











