分片上传必须暴露三个接口:/api/chunk/check(查询已传分片)、/api/chunk/upload(接收单个分片)、/api/chunk/merge(合并所有分片),缺一不可,否则断点续传无法成立。

分片上传必须暴露哪三个接口
分片上传不是“把文件切开扔上去”就完事,服务端必须提供可被客户端调用的三类能力:查询已传分片、接收单个分片、合并所有分片。缺一不可,否则断点续传无法成立。
-
/api/chunk/check:接收fileMd5和可选chunkNumber,返回已上传的分片序号列表(如[1,2,4]),客户端据此跳过重传 -
/api/chunk/upload:接收fileMd5、chunkNumber、chunkTotal和chunkData(multipart/form-data中的chunk字段),存为临时文件,路径建议为./chunks/{fileMd5}/{chunkNumber} -
/api/chunk/merge:接收fileMd5、fileName、chunkTotal,按序读取所有分片、拼接写入目标文件,成功后清理./chunks/{fileMd5}/目录
注意:fileMd5 必须由客户端计算并传入,不能靠服务端重新算——否则网络中断重试时 MD5 可能因流读取不完整而错乱。
FormFile 用错会 panic,怎么安全取分片数据
很多人在 /api/chunk/upload 接口里直接写 c.FormFile("chunk") == nil 判断,这是典型错误。Go 中接口变量即使底层值为 nil,其本身也可能非 nil,导致后续 io.Copy 调用 panic。
- 正确做法只依赖
err:如果c.FormFile("chunk")返回非nil错误,说明字段缺失、不是文件或解析失败,直接返回400 - 只有
err == nil时,file才可安全用于io.Copy或c.SaveUploadedFile - 额外校验
file.Size是否符合预期(比如是否等于前端声明的分片大小),防止恶意篡改
示例片段:
file, err := c.FormFile("chunk")
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing or invalid chunk"})
return
}
// 此时 file 可安全使用
分片存储路径和合并顺序怎么设计才不出错
临时分片若存在竞态或路径混乱,合并时就会丢片、错序、甚至覆盖。关键在于隔离性 + 确定性。
- 根目录必须以
fileMd5命名(如./chunks/ab12cd34...),避免不同文件分片混在一起 - 每个分片文件名必须带序号且固定格式,推荐
{chunkNumber}.bin,不用file.Filename——后者可能含特殊字符或重复名 - 合并时用
filepath.Glob("./chunks/{md5}/*.bin")获取全部分片路径,再按chunkNumber解析排序,不要依赖系统文件列表顺序(不可靠) - 合并前务必检查实际分片数量是否等于
chunkTotal,少于则返回409 Conflict,拒绝合并
路径穿越风险也要防:fileMd5 需做白名单校验(只允许十六进制字符),否则攻击者传 ../../etc/passwd 就可能写到任意位置。
为什么不能省略分片元数据持久化
仅靠文件系统目录结构记录分片状态,在高并发或服务重启后极易丢失上下文。比如两个用户同时上传同名文件(MD5 相同),或某次合并中途崩溃,下次请求就无法判断哪些分片真正有效。
- 生产环境必须将分片状态落库或存 Redis:至少记录
fileMd5、chunkNumber、uploadedAt - 合并接口触发前,先查 DB 确认所有分片标记为 uploaded,而非只看文件是否存在
- 临时目录定期清理(如 24 小时未完成的
fileMd5目录自动删除),但 DB 记录要保留审计线索
本地磁盘存分片只是权宜之计;真正的健壮性来自元数据可追溯,而不是“文件还在就以为没丢”。











