分片上传必须绕开form表单机制,因为multipart/form-data是原子性一次性提交,而分片需多次独立请求并携带chunk_index等元数据,二者在http协议层互斥。

分片上传不能用 enctype="multipart/form-data" 表单提交,这是协议层的硬性限制——它和分片在根本上互斥。
为什么分片上传必须绕开 form 表单机制
表单提交是原子操作:整个 <form></form> 一次性发出去,浏览器按 enctype 规则打包,multipart/form-data 会把所有字段+文件拼成一个带 boundary 的大 body。而分片上传本质是多次独立 HTTP 请求,每次只传一段字节范围 + 元数据(如 chunk_index、file_id),服务端要能识别“这是第几片、属于哪个文件、总共几片”。这两者逻辑无法共存。
常见错误现象:<form enctype="multipart/form-data"></form> 里塞个 <input type="file">,再用 JS 拿 File 对象切片、用 fetch 发请求——表面看代码跑通了,但后端收不到文件内容,或 $_FILES 始终为空,因为浏览器根本没走 multipart 流程,只是发了个空 body 或错乱的 FormData。
- 只要用了
File.prototype.slice(),就必须手动构造请求,放弃<form></form>提交路径 -
enctype只对原生表单 submit 生效,对 JS 手动发请求无效 - 服务端不能依赖
$_FILES或req.files,得从php://input或 raw body 解析二进制,再从$_POST或 JSON body 读元数据
multipart/form-data 是文件上传的唯一合法表单编码方式
如果你不做分片,只是普通文件上传,enctype="multipart/form-data" 不是可选项,而是强制要求。原因很简单:只有它能保留二进制原始字节。
对比来看:
-
application/x-www-form-urlencoded会对所有内容做 URL 编码,0xFF 0xD8这类 JPEG 文件头会被破坏,后端收到的是乱码或截断数据 -
text/plain不编码但不带Content-Disposition和filename,服务端无法区分字段和文件,也拿不到原始 MIME 类型 -
multipart/form-data把每个字段拆成独立 part,用随机 boundary 分隔,每个 part 自带Content-Disposition: form-data; name="xxx"; filename="a.jpg"和Content-Type: image/jpeg,文件字节原样传递
典型错误:表单写了 method="post" 却漏掉 enctype,结果后端 request.files 为空;或者 ASP.NET Web Forms 里只在 .aspx 写 enctype,没在 Page_Load 中赋值 this.Form.Enctype,导致实际渲染仍为默认值。
fetch 传 FormData 时 Content-Type 头的坑
用 fetch 手动发分片时,如果封装了 FormData,千万别显式设置 Content-Type 头。
浏览器调用 new FormData() 后发请求,会自动添加 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxx,这个 boundary 是动态生成的,服务端靠它解析各 part。一旦你手动加了 headers: { 'Content-Type': 'multipart/form-data' },就覆盖了浏览器自动生成的带 boundary 的完整头,服务端收不到 boundary,整个 body 解析失败,返回 400 Bad Request 或空数据。
- 正确做法:直接传
formData,不设Content-Type头,让浏览器自己处理 - 错误写法:
fetch(url, { method: 'POST', headers: { 'Content-Type': 'multipart/form-data' }, body: formData }) - 如果要用
fetch传纯二进制(比如 ArrayBuffer),就得自己拼 boundary、构造 body,成本高,一般只在特殊协议场景下才这么做
分片上传的 key 命名必须含唯一标识
每个分片请求里的 FormData,文件字段的 key 名不能叫 uploaded_file 或 file 这种通用名。否则多个文件并发上传时,服务端无法区分哪片属于哪个文件。
必须带上下文标识,例如:
-
file_id=abc123&chunk_index=5&total_chunks=12作为额外字段传 - 文件字段 key 写成
chunk_abc123_5或统一用chunk,但靠file_id+chunk_index关联 - 服务端收到后,根据
file_id归集所有分片,按chunk_index排序合并,最后校验total_chunks是否收齐
容易被忽略的是:前端切片用 file.slice(start, end) 时,start 和 end 是字节偏移量,不是 chunk 序号;服务端合并时若没按字节顺序拼接,会导致文件损坏——比如第 0 片传了 0–999999 字节,第 1 片却传了 1000000–1999999 字节,中间断了一段,最终文件打不开。











