应使用ctx.MultipartForm()获取同名多文件切片,再遍历调用file.Open()读取并保存;ctx.FormFile仅返回首个文件,ctx.SaveFile不支持多文件。

如何用 ctx.FormFile 获取多个同名文件
Fiber 默认不自动解析 multipart 表单中的多文件字段,必须显式调用 ctx.FormFile 多次或遍历获取。浏览器上传多个文件时,HTML 中 会把所有文件以相同字段名 files 提交,但后端不会自动聚合成切片——Fiber 的 ctx.FormFile 每次只返回第一个匹配项,后续调用才轮到下一个。
正确做法是循环调用 ctx.FormFile,直到返回错误:
for i := 0; ; i++ {
file, err := ctx.FormFile(fmt.Sprintf("files[%d]", i))
if err != nil {
break // 没有更多 files[i] 字段
}
// 处理 file
}
但更常见且兼容性更好的方式是:前端仍用 name="files"(不带索引),后端用 ctx.MultipartForm() 手动解析:
form, err := ctx.MultipartForm()
if err != nil {
ctx.Status(fiber.StatusBadRequest).SendString("parse failed")
return
}
files := form.File["files"] // []*multipart.FileHeader
- 这是最稳妥的路径,
form.File是 map[string][]*multipart.FileHeader,同名字段自动聚合为 slice - 注意:必须确保 HTML input 的
name完全一致(大小写、空格、括号都敏感) - 如果前端用了
name="files[]",某些浏览器或代理可能转义为files[],服务端收不到,建议统一用name="files"
保存文件前必须调用 file.Open() 和 defer f.Close()
*multipart.FileHeader 只是元信息(文件名、大小、头信息),不是可读数据流。直接尝试读取会 panic 或返回空内容。
每个文件需单独打开:
for _, file := range files {
src, err := file.Open()
if err != nil {
ctx.Status(fiber.StatusInternalServerError).SendString("open failed")
return
}
defer src.Close() // 注意:这里 defer 在循环里会累积,应改用显式 close 或移到子函数中
<pre class="brush:php;toolbar:false;">dst, err := os.Create("./uploads/" + file.Filename)
if err != nil {
ctx.Status(fiber.StatusInternalServerError).SendString("create failed")
return
}
defer dst.Close()
if _, err = io.Copy(dst, src); err != nil {
ctx.Status(fiber.StatusInternalServerError).SendString("copy failed")
return
}
}
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 循环内
defer src.Close()实际不会立即执行,所有 defer 会在 handler 返回时集中触发,可能导致文件句柄泄漏或读取错乱 - 推荐封装为独立函数,或在循环内用
func(){...}()立即执行关闭 - 务必检查
file.Size,防止超大文件阻塞内存或磁盘(Fiber 默认限制 4MB,可通过fiber.Config{BodyLimit: 10 * 1024 * 1024}调整)
为什么 ctx.SaveFile() 不适合多文件批量保存
ctx.SaveFile() 是 Fiber 封装的便捷方法,但它内部调用的是 ctx.FormFile() + 单次保存,只处理第一个同名文件,对多文件场景完全无效。
- 它等价于
file, _ := ctx.FormFile(name); file.Save(dst),无法感知form.File[name]切片 - 若强行传入
"files",只会保存第一个选中的文件,其余静默丢失 - 没有错误提示,容易误以为全部成功
- 自定义文件名、路径拼接、重命名逻辑也难以介入,灵活性差
结论:多文件上传必须绕过 SaveFile,手动用 MultipartForm() + Open() + io.Copy 控制全流程。
常见错误:400 Bad Request 或空 form.File
现象:ctx.MultipartForm() 返回 nil 或 form.File 是空 map,但前端确认已选择文件并提交。
- 检查请求 Content-Type 是否为
multipart/form-data,且包含正确的boundary;若用 fetch,不能手动设 header,必须让浏览器自动生成:new FormData()直接传给fetch() - Fiber 默认禁用自动解析 multipart(因性能考虑),但只要调用
ctx.MultipartForm()或ctx.FormFile()就会触发解析——无需额外中间件 - 如果启用了
fiber.New(fiber.Config{DisableKeepalive: true})等非常规配置,可能干扰 multipart 解析,先还原默认配置排查 - 日志加一行
log.Println("Content-Type:", ctx.Request().Header.Peek("Content-Type"))看是否被代理或 Nginx 清洗掉
边界情况比想象中多:Nginx 默认限制 client_max_body_size 1m,Kubernetes Ingress 可能截断大文件,这些都不是 Fiber 层能捕获的错误。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










