分片合并失败是因为框架提前读空了body;gin默认在c.formfile等操作中会完整读取r.body,导致后续io.copy只能读到eof,必须在handler开头调用c.request.parsemultipartform(0)跳过解析以保留body可读性。

为什么分片合并总失败?因为框架提前读空了 body
Gin 默认会在 c.FormFile、c.PostForm 或隐式调用 c.Request.ParseMultipartForm 时,把整个 r.Body 读完——哪怕你只想要一个查询参数。分片上传的 body 是纯二进制流,一旦被提前消费,后续 io.Copy 就只能读到 EOF,合并自然失败。
必须在 handler 开头禁用 multipart 解析
绕过 Gin 自动解析的唯一可靠方式,是在 handler 最开始就执行:
c.Request.ParseMultipartForm(0)
传 0 表示跳过解析,保留原始 r.Body 可读性。这行代码必须放在任何可能触发 body 读取的操作之前,比如:
- 不能在它之后调用
c.FormFile、c.MultipartForm - 不能依赖
c.PostForm("chunk_index"),改用c.Query("chunk_index")或c.Request.URL.Query().Get("chunk_index") - 若用了
gin-contrib/multiform等中间件,确认它没内部调用ParseMultipartForm;有则禁用
分片写入临时文件要防并发覆盖
多个分片请求可能并发到达,直接用相同路径 os.Create 写文件会导致数据错乱或覆盖。正确做法是:
- 用
os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_EXCL)——O_EXCL保证文件不存在才创建,失败即说明已存在,可返回冲突错误 - 分片路径建议含唯一标识,如
./uploads/{upload_id}/{chunk_index} - 写入前加限流:
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 5*1024*1024)(限制单片 ≤5MB) - 读取时用
io.CopyN(dst, src, expectedSize)或io.LimitReader(c.Request.Body, expectedSize),确保不多读也不少读
合并逻辑本身不复杂,但顺序和校验容易漏
合并不是简单按序 cat 文件,关键点在于:
- 必须先校验所有分片是否齐全:查
chunk_index是否连续、总数是否匹配total_chunks参数 - 按
chunk_index数值升序拼接,不是字符串排序(避免 "10" 排在 "2" 前) - 合并后建议计算最终文件哈希(如 SHA256),与客户端上传前计算的
file_hash对比,防止传输损坏 - 合并完成后立即删分片,避免磁盘爆满;但删除动作应异步或加重试,别阻塞响应
最常被忽略的是:分片上传和合并是两个独立请求,合并接口必须能根据 upload_id 安全定位对应的所有分片目录,且该目录不能被其他上传意外复用——upload_id 必须全局唯一且带有效期。











