c.formfile不适用于多文件流式接收,因其会将全部文件提前读入内存导致oom;正确做法是禁用自动解析,用c.request.multipartreader()逐个消费part。

为什么c.FormFile不适用于多文件流式接收
c.FormFile 本质是调用 ParseMultipartForm 后从已解析的 *multipart.Form 中取单个字段,它会把整个请求体(含所有文件)提前读入内存并解析——对多文件上传尤其危险。比如 5 个 200MB 文件,即使只取第一个,Gin 也会尝试把全部 1GB 数据塞进内存,极易触发 runtime: out of memory 或被中间件(如 Nginx)直接截断。
正确做法:用c.Request.MultipartReader()逐个消费part
真正可控的多文件流式接收必须绕过 Gin 的自动表单解析,直接操作原始请求体。关键前提是:**禁用自动 multipart 解析**(即不调用 ParseMultipartForm,也不依赖 c.FormFile 或 c.MultipartForm())。
- 在路由 handler 开头立即调用
c.Request.MultipartReader(),它返回标准multipart.Reader - 循环调用
reader.NextPart(),每个part是一个io.Reader,可独立处理 - 用
part.FormName()判断是否为文件字段(如"files"),用part.FileName()获取原始文件名 - 对每个匹配的 part,直接
io.Copy(dst, part)写入磁盘或远端存储,内存占用恒定
常见错误:curl 传多文件时Content-Type写死了
用 curl -F "files=@a.txt" -F "files=@b.zip" 上传多文件时,curl 会自动生成正确的 Content-Type: multipart/form-data; boundary=...。如果手动加了 -H "Content-Type: application/json" 或其他类型,Gin 就完全无法识别 multipart 结构,MultipartReader() 会返回 err != nil,且错误信息模糊(常为 invalid multipart)。
- 务必移除所有手动设置的
Content-Type头 - 确认前端 FormData.append("files", file1) 和 append("files", file2) 使用相同 key,后端才能按 name 匹配多个 part
- 若需校验文件数量或类型,应在
part.FormName() == "files"分支内做,而非依赖FormValue
边界与陷阱:boundary 被提前读走、文件句柄泄漏
c.Request.Body 是单次读取流,一旦被 MultipartReader() 消费完,后续再调用 c.PostForm 或 c.DefaultPostForm 就会返回空值——因为 body 已 EOF。这是最易忽略的隐性耦合。
- 如需同时读取表单字段(如
user_id)和文件,必须在NextPart()循环中识别非文件 part(part.FormName() != "files"),并用io.ReadAll(part)提前读出其内容 - 每个打开的目标文件(
os.Create)必须配对defer f.Close(),否则too many open files错误会在并发稍高时立刻出现 -
part.FileName()可能为空(如纯文本字段),需判空;真实文件名应结合业务逻辑重命名,避免路径遍历(如用filepath.Base()清洗)











