c.formfile() 只能获取同名文件中的第一个,多文件需用 c.multipartform() 从 form.file["files"] 切片中遍历处理,须判空且逐个保存并校验大小。

c.FormFile() 只能取单个文件,多文件必须用 c.MultipartForm()。Gin 本身不提供“一键取全部文件”的封装函数,直接调用 c.FormFile("files") 会只拿到第一个同名文件,其余被丢弃——这是最常踩的坑。
为什么 c.FormFile("files") 拿不到多个文件
HTTP 表单中多个同名 <input type="file" name="files" multiple> 提交时,底层是多个 multipart.FileHeader 共享同一个字段名。而 c.FormFile() 内部只调用 form.File[name] 并取索引 0 的元素,后续文件被忽略。
- 现象:前端传 3 个文件,后端只保存第 1 个,控制台无报错
- 本质:Go 的
http.Request.MultipartForm.File是map[string][]*multipart.FileHeader,c.FormFile()只读form.File[name][0] - 正确做法:先调用
c.ParseMultipartForm()(可选,Gin 通常自动触发),再从c.Request.MultipartForm.File中按 key 取 slice
如何安全遍历并保存所有同名上传文件
关键不是“怎么取”,而是“取完之后怎么校验和保存”。必须检查 form.File["files"] 是否为 nil 或空 slice,否则 panic。
- 用
form, err := c.MultipartForm()获取完整表单结构,错误时直接返回 HTTP 400 - 判断
form.File["files"]是否存在且长度 > 0,不能只判 nil(Go 中空 slice 不为 nil) - 每个
*multipart.FileHeader都要单独调用c.SaveUploadedFile(),路径需唯一(比如加时间戳或 UUID) - 建议对每个文件做大小限制:
if header.Size > 10(10MB)
form, err := c.MultipartForm()
if err != nil {
c.JSON(400, gin.H{"error": "parse multipart form failed: " + err.Error()})
return
}
files := form.File["files"]
if len(files) == 0 {
c.JSON(400, gin.H{"error": "no files uploaded"})
return
}
for _, f := range files {
dst := "./uploads/" + fmt.Sprintf("%d_%s", time.Now().UnixNano(), f.Filename)
if err := c.SaveUploadedFile(f, dst); err != nil {
c.JSON(500, gin.H{"error": "save failed: " + err.Error()})
return
}
}
接收混合参数(文件 + 普通表单字段)的注意事项
文件和普通字段(如 title、category)必须在同一个 multipart/form-data 请求里提交,且不能混用 Content-Type: application/json —— 这会导致整个表单解析失败,c.PostForm() 和 c.MultipartForm() 全为空。
- 前端必须用
FormData构造请求,或 curl 用--form "title=abc" --form "upload=@a.jpg" - 服务端用
c.PostForm("title")取文本字段,c.MultipartForm()取文件,两者互不影响 - 不要在同一个 handler 里混用
c.ShouldBindJSON()和文件操作——JSON 解析会消费 request body,后续无法读 multipart
多文件上传真正的复杂点不在语法,而在边界:空 slice 判定、并发保存冲突、文件名重名覆盖、磁盘空间不足、恶意超大文件。生产环境必须逐个加固,而不是依赖框架默认行为。











