分片上传后需手动合并,Gin仅接收请求,合并逻辑须自行实现:校验分片完整性、顺序及大小,加锁防并发,用os.OpenFile+io.Copy流式拼接避免OOM。

分片上传后怎么合并成完整文件
合并不是自动发生的,必须由你显式调用逻辑把所有已接收的分片按序读取、拼接写入目标文件。Gin 不提供任何合并能力,它只负责接收 HTTP 请求——分片存储、索引管理、顺序校验、完整性检查全得自己实现。
合并前必须确认哪些状态
直接合并会出错,尤其在断点续传场景下。务必在合并前验证以下几点:
-
identifier对应的所有分片文件是否全部存在(检查chunkNumber是否覆盖1到totalChunks) - 每个分片文件大小是否符合预期(比如最后一片可能小于标准分片大小,但其他片必须等于
ChunkSize) - 可选但强烈建议:对每个分片计算局部 hash,和服务端记录或前端传来的 checksum 比对,防止传输损坏
- 避免并发合并:同一
identifier的合并操作需加锁(如用sync.Map记录进行中任务,或依赖数据库行锁)
用 os.OpenFile + io.Copy 合并比 ioutil.WriteFile 更安全
大文件合并不能一次性读进内存,否则容易 OOM。正确做法是打开目标文件为追加写模式,逐个打开分片、流式拷贝:
dst, err := os.OpenFile(finalPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer dst.Close()
for i := 1; i <= totalChunks; i++ {
chunkPath := getChunkPath(identifier, i)
src, err := os.Open(chunkPath)
if err != nil {
return fmt.Errorf("missing chunk %d: %w", i, err)
}
_, err = io.Copy(dst, src) // 不要忽略返回的 written bytes
src.Close()
if err != nil {
return err
}
}
注意:os.O_TRUNC 确保从头开始写;io.Copy 是流式、低内存占用;不要用 ioutil.ReadFile + os.WriteFile,那会把整个文件加载进内存。
合并完成后必须清理临时分片
合并成功 ≠ 上传完成。不删分片会导致磁盘持续增长、重复合并风险、identifier 冲突等问题。清理动作应放在合并成功后的 defer 或事务末尾,且需容忍失败(比如异步清理或后台定时任务兜底):
- 用
os.RemoveAll(tempDir)删除整个identifier目录最稳妥,比逐个os.Remove分片更可靠 - 若使用数据库记录分片状态,合并成功后要同步更新
IsFinish = true并清空ExaFileChunk关联表 - 别在合并失败时删分片——要保留现场用于重试或排查
最容易被忽略的是:合并逻辑里没处理“部分分片缺失但 totalChunks 已到”的边界情况,比如前端误报 totalChunks=10,实际只传了 9 片,此时强行合并会漏数据且无提示。必须校验数量、大小、顺序三者一致才执行写入。


















