应循环调用ctx.FormFile并分别检查file==nil和err!=nil以获取全部同名文件,保存前需验证file.Size>0,优先使用file.Open()配合io.Copy而非ctx.SaveUploadedFile()。

如何用 ctx.FormFile 正确读取多个同名文件
Gin 默认不自动解析 multipart/form-data 中的多个同名 <input type="file" name="files" multiple>,必须手动循环调用 ctx.FormFile,不能只调一次就认为拿全了。
常见错误是写成:file, _ := ctx.FormFile("files") —— 这只会拿到第一个文件,其余被丢弃。
- 正确做法:用
ctx.MultipartForm()获取全部文件头,再逐个form.File["files"][i]拿;或更稳妥地用循环 +ctx.FormFile配合错误判断 -
ctx.FormFile内部会移动 multipart reader 的偏移,所以必须在单次请求中按顺序调用,不能反复重试 - 如果前端用了
name="files"但没加multiple,后端仍可能收到多个同名项(比如用户重复点击上传),行为不可靠,建议统一要求前端带multiple
保存文件前必须检查 err != nil 和 file == nil
很多同学直接解包 file, err := ctx.FormFile("files") 后就开写,结果遇到空文件或解析失败时 panic 或写入空内容。
ctx.FormFile 在无对应字段、字段非文件、或解析出错时,返回 nil, err;但有时 err 是 http.ErrMissingFile,有时是 io.EOF,不能只判 err != nil 就跳过。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 每次调用
ctx.FormFile后,先检查file == nil,再检查err != nil,二者独立判断 - 当
file == nil && err == nil时,说明该次调用已无更多同名文件,应终止循环 - 保存前务必用
file.Size > 0过滤空文件,避免生成 0 字节垃圾文件
用 file.Open() 而不是 ctx.SaveUploadedFile() 控制流
ctx.SaveUploadedFile() 看似方便,但它内部直接调用 os.Create 并硬编码覆盖模式,无法做权限控制、无法复用已有文件句柄、无法限制大小、也无法在保存前校验内容(比如图片 magic bytes)。
- 正确路径:先
f, err := file.Open(),得到multipart.File接口,再用io.Copy写入你可控的*os.File - 务必在
defer f.Close()前打开目标文件,否则可能因源文件句柄提前关闭导致 copy 失败 - 若需限制单文件大小,应在
file.Size上做拦截,而不是依赖中间件——因为中间件对 multipart 解析时机不可控,且 Gin 的MaxMultipartMemory只影响内存缓冲,不影响磁盘临时文件
注意 FormValue 和 FormFile 的混合使用边界
一个 multipart 请求里既有文件又有文本字段(如 title、category),别想当然地混用 ctx.FormValue 和 ctx.FormFile 的调用顺序。
Gin 的 ctx.Request.ParseMultipartForm() 是惰性触发的,第一次调用 FormValue 或 FormFile 时才真正解析。但一旦开始解析,后续所有字段都从同一份内存/临时磁盘缓存读取。
- 安全做法:先一次性调用
ctx.Request.MultipartForm,显式触发解析,再分别取form.Value和form.File - 如果先调
FormFile再调FormValue,没问题;但如果先调FormValue再多次调FormFile,第二次起可能返回nil, nil(因为 reader 已耗尽) - 最稳方案:用
form, _ := ctx.MultipartForm(),然后遍历form.File["files"]和读取form.Value["title"]
file == nil 判断,线上就可能攒一堆 0 字节文件。

















