Gin不解析分片元信息,前端必须显式传chunkIndex、totalChunks等字段;若缺失,后端应拒绝请求并返回400错误,不可依赖文件名解析序号或默认值。

分片上传时前端没传 chunkIndex 或 totalChunks 怎么办
Gin 本身不处理分片元信息,必须靠前端显式传递。常见错误是前端只传了 file 字段和当前分块二进制数据,后端却试图靠文件名解析序号(比如 xxx_part_2),这极易被绕过或出错。
建议强制要求前端在表单中携带以下字段:
-
filename:原始文件名(用于合并后还原) -
chunkIndex:从 0 开始的整数(不是 1) -
totalChunks:总分块数(用于判断是否收齐) -
identifier:客户端生成的唯一 ID(如基于文件名+大小的 hash),避免不同用户上传同名文件时冲突
后端用 c.PostForm("chunkIndex") 解析,别用 c.Query —— 分片请求通常是 POST multipart/form-data,参数不在 URL 里。
c.FormFile("file") 读大分块会 OOM 吗
会。默认 Gin 的 c.FormFile 会把整个分块加载进内存,如果单块 100MB,10 个并发就吃掉 1GB 内存。这不是 Gin 的 bug,而是 net/http.Request.ParseMultipartForm 的默认行为。
解决方法是提前设置 MaxMultipartMemory 并手动流式读取:
router.MaxMultipartMemory = 32 << 20 // 32MB,够大多数分块用 // 然后用 c.MultipartReader() 获取 reader,再 io.Copy 到磁盘临时文件
关键点:
- 不要调
c.FormFile,它内部会触发完整解析并缓存 - 用
c.MultipartReader()+multipart.Reader.NextPart()手动遍历表单项 - 找到
file字段后,用io.Copy(tempFile, part)直接落盘
合并分块时如何避免竞态和重复写入
多个分块请求可能并发到达,若都检查“是否收齐 → 合并 → 删除分块”,就会出现重复合并、文件损坏或 panic(如两个 goroutine 同时打开同一临时文件写入)。
推荐方案是「原子标记 + 单点合并」:
- 每个分块存为
/tmp/uploads/{identifier}_{chunkIndex} - 收齐后(即
chunkIndex == totalChunks-1且本地已存在全部0..N-1文件),写一个标记文件/tmp/uploads/{identifier}.merged.lock - 合并逻辑先
os.OpenFile(..., os.O_CREATE|os.O_EXCL)尝试创建 lock 文件,失败说明别人已在合并,直接 return - 成功则执行合并(
os.Create最终文件 + 多次io.Copy拼接),完成后删所有分块 + lock 文件
不用加全局 mutex,文件系统级别的 O_EXCL 更轻量、跨进程有效。
合并后怎么保证文件完整性不被篡改
前端传来的 totalChunks 和 chunkIndex 可被恶意修改,不能作为唯一依据。真实场景中必须校验内容。
建议在上传前让前端计算整个文件的 md5 或 sha256(Web API 的 SubtleCrypto.digest 支持),随首块一起传上来,存为 expectedHash。
合并完成后立即计算最终文件哈希:
h := sha256.New()
f, _ := os.Open(finalPath)
io.Copy(h, f)
actual := fmt.Sprintf("%x", h.Sum(nil))
若 actual != expectedHash,立刻删除文件并返回 400 错误。这能发现网络丢包、磁盘写坏、中间代理篡改等问题。
注意:不要在合并过程中逐块校验——那样无法发现拼接顺序错误;必须对合并后的完整文件做一次终验。


















