gofiber多文件上传需用c.multipartform()而非c.formfile(),因后者仅返回首个文件;须校验大小/类型/扩展名,生成唯一安全路径并确保目录存在。

GoFiber 的文件上传不是“调个函数就完事”,它把解析、校验、保存拆得很开,不手动处理每一步,轻则丢文件,重则被路径遍历或内存打爆。
为什么 c.FormFile() 不能用于多文件上传
c.FormFile("files") 只返回第一个匹配 name 的 *multipart.FileHeader,其余全部丢弃——前端哪怕传了 10 个文件,后端只拿到 1 个。真正批量入口是 c.MultipartForm(),它返回 *multipart.Form,其 File 字段是 map[string][]*multipart.FileHeader,注意那个切片 [] 才是关键。
常见错误现象:
- 前端用
<input type="file" name="files" multiple>,后端写c.FormFile("files")→ 永远只存第一个 - 没检查
c.MultipartForm()的 error → 请求体超限、编码错误时直接 panic 或 500
如何安全保存多个文件并避免覆盖和路径遍历
每个文件必须生成唯一路径,且不能信任原始 file.Filename。直接拼接 "./uploads/" + file.Filename 会触发路径遍历(如 ../../etc/passwd)或同名覆盖。
实操建议:
- 用
uuid.New().String() + filepath.Ext(file.Filename)生成新文件名,例如fmt.Sprintf("./uploads/%s%s", uuid.New(), filepath.Ext(file.Filename)) - 保存前必须确保目录存在:
os.MkdirAll("./uploads", 0755),否则首次上传必报no such file or directory -
c.SaveFile()是单文件函数,需遍历切片逐个调用,不能传整个切片
后端必须做的三重校验:大小、类型、扩展名
前端的 accept="image/*" 和 JS 校验完全不可信,攻击者可构造任意 multipart 请求绕过。
实操要点:
- 全局内存限制:启动时设
fiber.Config{ServerHeader: "Fiber", Prefork: false, ...}不够,得用app.Use(func(c *fiber.Ctx) error { c.Locals("maxMultipartMemory", 32 或更推荐在 <code>fiber.New()里配BodyLimit和ReadBufferSize - 单文件大小:用
if file.Size > 10 拦截,单位是字节 - 类型校验:别信
file.Header.Get("Content-Type"),它可伪造;应打开文件读前 512 字节,用http.DetectContentType()或比对 magic number - 扩展名白名单:用
strings.HasSuffix(strings.ToLower(filepath.Base(file.Filename)), ".jpg"),禁用.php、.sh等可执行后缀
大文件或生产环境该用 MinIO 而不是本地磁盘
本地 c.SaveFile() 适合开发或小流量原型,但存在硬伤:无法断点续传、无分片支持、无权限隔离、磁盘 IO 成瓶颈、备份恢复困难。
真实生产中,你应该:
- 用
minio-go/v7客户端替代c.SaveFile(),把文件流直接 PutObject 到 MinIO - 前端改用切片上传(
file.slice()+ hash 校验),后端配合 ETag 合并分片,避免单次请求过大 - MinIO 的 presigned URL 可实现直传,彻底卸载服务端文件接收压力
最易被忽略的一点:无论用磁盘还是 MinIO,c.MultipartForm() 解析出的 *multipart.FileHeader 只是元数据,真正读取内容(比如做 magic number 检查)必须调 file.Open(),而这个操作会实际加载文件到内存——所以校验顺序必须是「先大小、再扩展名、最后才打开读内容」,否则恶意用户传个 2GB 假 JPG 就能卡死你的服务。











