Gin不支持分片上传开箱即用,需手动实现分片接收、合并、断点续传及唯一性校验;使用c.FormFile("chunk")提取分片时须保证前后端字段名一致,并通过router.MaxMultipartMemory调整单分片大小限制。

分片上传在 Gin 中不是开箱即用的功能,必须自己组装逻辑;Gin 本身不处理分片合并、断点续传或唯一性校验,这些得靠你补全。
如何用 c.FormFile 正确接收单个分片
分片上传的每个请求本质是普通 multipart 表单,Gin 用 c.FormFile 提取文件字段即可,但要注意字段名一致性(如统一叫 chunk)和大小限制。
- 确保前端 POST 时使用
Content-Type: multipart/form-data,且分片字段名与后端硬编码一致,例如:c.FormFile("chunk") - Gin 默认限制单次请求体最大 32MB,若单个分片超限,需提前调
gin.SetMode(gin.ReleaseMode)并设置MaxMultipartMemory,如:router.MaxMultipartMemory = 128 << 20 // 128MB
- 不要用
c.MultipartForm()全量解析——它会把所有分片一次性读进内存,失去“流式接收”意义
怎么安全生成和验证分片唯一标识(upload_id 和 chunk_index)
前端必须传 upload_id(全局唯一上传会话 ID)和 chunk_index(从 0 开始的整数),后端不做生成,只做校验;否则无法支持断点续传或并发上传同文件。
-
upload_id应由前端生成(推荐 UUID v4),后端仅校验其格式(如正则^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$),不依赖 session 或 cookie -
chunk_index必须是数字类型,用c.PostForm("chunk_index")获取后转strconv.Atoi,失败直接 400;跳号或负数应拒绝 - 避免用文件名做 key——同一文件多次上传会导致覆盖,必须绑定
upload_id+chunk_index作为存储路径或 Redis key
分片落盘策略:临时目录 vs 内存缓冲
小分片(≤1MB)可暂存内存,大分片必须写磁盘;无论哪种,都要加超时清理,否则磁盘会被残留分片占满。
- 内存缓冲用
bytes.Buffer或io.Copy(ioutil.Discard, src)预检大小,超过阈值立刻切到磁盘(os.CreateTemp("/tmp", "chunk-*")) - 临时文件命名必须含
upload_id和chunk_index,如:/tmp/upload_abc123_chunk_5,方便后续合并时 glob 查找 - 务必设置定时任务(如 goroutine +
time.Ticker)扫描/tmp下 2 小时未更新的upload_*文件并删除——Gin 不提供生命周期钩子
合并分片时为什么 os.OpenFile with os.O_APPEND 比逐个 io.Copy 更可靠
合并阶段若用多个 io.Copy 往同一文件写,容易因打开模式或并发导致内容错乱;而 os.O_APPEND 保证每次 write 自动 seek 到末尾,天然线程安全。
- 合并前先用
os.Stat确认所有分片文件存在且大小非零,缺失任一片返回 409 Conflict - 目标文件用
os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)打开,每次io.Copy一个分片句柄进去 - 合并完成后立即计算最终文件的
sha256.Sum256,与前端传来的total_hash对比,不一致就删掉目标文件并返回 500
最易被忽略的是清理时机——分片上传成功后,临时目录里的单个分片文件不会自动消失,必须显式 unlink;更麻烦的是,如果合并中途出错(比如磁盘满),已写入的部分目标文件和残留分片都得原子性回滚,这部分逻辑没法交给 Gin 帮你兜底。


















