c.formfile()超大文件上传失败的根本原因是parsemultipartform受r.maxmultipartmemory默认32mib限制,请求体超限时直接返回400错误,c.formfile()根本未执行;正确方案是用c.request.multipartreader()流式处理分片,配合安全校验与合并逻辑。

为什么c.FormFile()在超大文件上传时直接失败
根本原因不是 Gin “不支持”大文件,而是 c.FormFile() 依赖 ParseMultipartForm,而该方法受 r.MaxMultipartMemory 严格限制——默认仅 32 MiB。一旦请求体(含所有分片或单个超大文件)超过该值,Gin 在解析阶段就返回 http: request body too large,c.FormFile() 根本不会被调用,err 为 nil 但后续逻辑全失效。
常见表现:小文件 OK,100MB 视频上传直接 400;日志里看不到 no such file,只有笼统的 size 错误;你检查了 err 却没发现它根本没走到 FormFile 这步。
-
r.MaxMultipartMemory必须在gin.Default()之后、任何路由注册之前设置,设为 0 表示不限制内存——但这是危险操作,可能被恶意请求拖垮服务 - 即使调高
MaxMultipartMemory,单次读取整个 multipart body 仍会吃光内存或触发超时,这不是可扩展方案 - 真正可行的路径是绕过
FormFile,改用c.Request.MultipartReader()或原始io.ReadCloser流式处理
如何用 MultipartReader 手动接收分片而不爆内存
分片上传不靠 Gin 自动解析,而靠你手动拉取 multipart boundary 流。关键在于:不把整个请求体加载进内存,而是边读边写入临时文件或缓冲区。
核心步骤是调用 c.Request.MultipartReader() 得到 multipart.Reader,再循环 NextPart() 区分元数据字段(如 identifier、chunkNumber)和文件流部分。对文件 part,直接 io.Copy(dst, part),不经过 SaveUploadedFile。
- 必须提前创建临时目录,例如
os.MkdirAll(fmt.Sprintf("/tmp/uploads/%s", identifier), 0755),否则os.Create报no such file or directory - 不要用
c.FormValue("chunkNumber")—— 分片请求中该字段可能不在表单头里,或大小写不一致;务必从part.FormName()中提取 - 每个分片文件建议加
.part后缀,合并前不重命名,避免并发时误读未写完的文件 - 若前端用
Content-Range头(如bytes 5242880-10485759/104857600),需用c.GetHeader("Content-Range")解析偏移,并用os.OpenFile(..., os.O_WRONLY|os.O_CREATE)+file.Seek()写入对应位置
保存前必须做的三道安全过滤
直接拼接 file.Filename 到路径是高危行为。用户可控的文件名可能含 ../../etc/passwd,导致任意路径写入。同时,空文件、超大单分片、恶意 MIME 类型都需拦截。
- 用
filepath.Base(file.Filename)截掉所有路径字符,只保留纯文件名;再白名单校验后缀(如只允许.jpg、.mp4) - 在
io.Copy前加计数器或io.LimitReader(part, maxChunkSize),防止单个分片远超预期(如声明 5MB 却传了 500MB) - 对首字节做 magic bytes 检查(如
png开头是\x89PNG),比单纯依赖Content-Type更可靠;可用file.Open()后读前 16 字节判断 - 临时目录权限设为
0700(而非0755),避免其他系统用户窥探未合并分片
合并分片时最容易忽略的两个细节
所有分片收齐后,合并看似简单,但两处极易出错:一是文件描述符未正确关闭导致“text file busy”错误;二是分片序号缺失或乱序导致最终文件损坏。
- 合并前必须确认
chunkNumber从 0 或 1 开始连续,且总数等于totalChunks;缺一个就拒绝合并,返回 400 并提示“分片不完整” - 用
os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)追加写入每个分片,而不是反复os.Create覆盖——后者会导致已写内容丢失 - 合并完成后,用
os.RemoveAll(tempDir)清理,但必须确保所有*os.File已Close(),否则 Linux 下会报device or resource busy - 最终文件应重新计算 MD5 并与客户端传来的
fileMd5对比,不一致则删除并返回校验失败,防止网络损坏未被发现











