gin 默认不支持大文件分片上传,因其 parsemultipartform 仅按标准 multipart 解析整个请求体(默认限32mb内存),不识别 chunknumber 等分片元字段;需手动调用 multipartreader 提取字段与文件流,由业务逻辑实现分片接收、暂存、合并及断点续传。

为什么 Gin 默认不支持大文件分片上传
Gin 的 c.Request.ParseMultipartForm 会把整个 multipart body 读进内存或临时磁盘,但默认限制是 32MB(MaxMemory),且不识别分片元信息(如 chunkNumber、totalChunks、identifier)。直接调用 c.FormFile("file") 会失败或阻塞,尤其当单个分片也超限、或前端未按标准协议组织字段时。
关键点在于:Gin 本身不解析分片语义,你得自己从 c.Request.MultipartReader() 或原始 body 中提取字段和文件数据,再交由业务逻辑组装。
- 别依赖
c.FormValue获取所有字段——部分分片请求可能不含全部表单字段,或字段名大小写/编码不一致(比如前端传uploadIdentifier,后端硬写identifier) - 避免在 handler 中直接
c.SaveUploadedFile——它会尝试完整读取,对大分片易 OOM - 务必校验
Content-Range头(若使用 byte-range 方式)或chunkNumber/totalChunks组合,否则无法判断是否为合法分片
如何安全接收并暂存单个分片
推荐用 c.Request.MultipartReader() 手动遍历 part,区分元数据字段与文件流。对文件 part,用 io.CopyN 或带 buffer 的 io.Copy 写入临时目录(如 /tmp/uploads/{identifier}/),并记录分片索引与大小。
示例关键逻辑:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
mr, err := c.Request.MultipartReader()
if err != nil { ... }
for {
part, err := mr.NextPart()
if err == io.EOF { break }
if part.FormName() == "identifier" {
identifier = part.Value()
} else if part.FormName() == "chunkNumber" {
chunkNum, _ = strconv.Atoi(part.Value())
} else if part.FormName() == "file" {
dst, _ := os.Create(fmt.Sprintf("/tmp/uploads/%s/%d", identifier, chunkNum))
io.Copy(dst, part) // 不用 SaveUploadedFile
dst.Close()
}
}
- 临时目录需提前创建并设好权限,避免并发写冲突(可用
os.MkdirAll+0755) - 每个分片文件建议加后缀(如
.part),合并前不改名,防止误读未完成分片 - 若前端用
Content-Range: bytes 1000-1999/100000,则用c.GetHeader("Content-Range")解析起始偏移,写入时 seek 到对应位置(需用os.OpenFile(..., os.O_WRONLY|os.O_CREATE))
怎样合并分片并校验完整性
当收到最后一个分片(chunkNumber == totalChunks),触发合并。顺序读取 /tmp/uploads/{identifier}/{1..N}.part,用 os.OpenFile 以 os.O_APPEND 模式写入目标文件。合并后必须校验,不能只信前端传的 totalSize。
- 合并前检查所有分片是否存在、大小匹配(例如第
i个分片应为size/N,末片可略大) - 合并后计算最终文件的
sha256.Sum256,与前端传的fileHash字段比对(若提供) - 用
os.Rename替代os.Copy合并,避免中间态占用双倍空间;但 rename 前要确保目标路径所在磁盘有足够空间 - 合并失败时,保留分片目录至少 24 小时,供客户端发起续传请求(通过
GET /upload/status?identifier=xxx返回已传分片列表)
断点续传的 HTTP 接口怎么设计才实用
不要只做“上传”一个 POST 接口。至少补三个配套 endpoint:
-
POST /upload:接收分片(含 identifier、chunkNumber、totalChunks 等) -
GET /upload/status?identifier=xxx:返回 JSON,如{"uploadedChunks": [1,2,4], "totalChunks": 5},前端据此跳过已传分片 -
DELETE /upload/clean?identifier=xxx:清理残留分片(用户取消上传时调用)
注意:identifier 必须全局唯一(可用 UUID v4 或文件名+时间戳哈希),且服务端需缓存其元数据(如总大小、预期分片数、最后更新时间),用 sync.Map 或 Redis 存储,避免每次查目录。
容易被忽略的是超时清理:对 2 小时内无新分片写入的 identifier,后台 goroutine 应自动删除其临时目录,否则磁盘会被碎片占满。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










