c.FormFile() 只返回 *multipart.FileHeader 元数据,需显式调 file.Open() 获取 io.ReadCloser 才能读内容;漏调则 file 为空或 panic,且必须 defer f.Close() 防句柄泄漏。

c.FormFile() 只拿元信息,不读文件内容,必须显式调 file.Open() 才能读;漏掉这步,file 是空的,或直接 panic。
为什么 c.FormFile("key") 拿不到文件内容?
它返回的是 *multipart.FileHeader,只含 Filename、Size、Header 等元数据,不是可读流。常见错误是拿到 file 就直接传给 io.Copy 或解析,结果读不到字节。
- 必须调用
file.Open()获取io.ReadCloser - 务必
defer f.Close(),否则文件句柄泄漏,后续上传会卡住或失败 - 如果已调过
c.Body()或c.BodyBytes(),再调c.FormFile()会静默返回nil—— fasthttp 底层缓冲已被消耗
多文件上传必须用 c.MultipartForm(),不是 c.FormFile()
c.FormFile("images") 永远只取第一个同名文件;真正批量处理得靠 c.MultipartForm() 返回的 *multipart.Form,其中 form.File["key"] 是 []*multipart.FileHeader 切片。
- 每个
FileHeader都要单独调file.Open() - 别漏掉
defer f.Close(),多个文件就多个defer - 若表单 enctype 不是
multipart/form-data,c.MultipartForm()会返回 error,且form.File为nil
保存前必须校验:大小、类型、文件名
Fiber 不做任何校验,全靠你自己加,否则前端伪造 Content-Length 或传恶意 .php 文件就能打穿服务。
- 大小以
file.Size为准(不是请求头里的Content-Length) - 类型检测不能信
file.Header.Get("Content-Type"),要用f, _ := file.Open(); defer f.Close()后读前 512 字节,再传给http.DetectContentType() - 白名单控制 MIME 类型,比如只允许
"image/jpeg"、"image/png",拒绝"application/octet-stream" - 保存时重命名文件名:去掉路径(防目录穿越)、过滤特殊字符、加随机前缀
c.SaveFile() 适合本地保存,但不适用于 MinIO / GCS 等远程存储
c.SaveFile(file, "./uploads/"+newName) 是最简方式,但它只是调 os.Create() + io.Copy(),没法加进度、限速、断点续传。
- 上传到 MinIO、Google Cloud Storage 等,得自己用
file.Open()拿到io.Reader,再喂给对应 SDK 的PutObject() - 大文件建议加
io.LimitReader(f, maxFileSize)防爆内存 - 记得提前设
app.Config().MultipartForm.MaxMemory = 32 << 20(32MB),不然默认值可能触发 413 错误
最容易被忽略的是:所有校验和打开操作,都必须在同一个 c.MultipartForm() 解析上下文里完成。跳过解析、混用 c.FormValue() 和 c.FormFile()、或在中间件里提前读了 body,都会让整个流程静默失效。


















