c.formfile 返回的是元数据,需调用 file.open() 才能读取文件内容,否则返回空或 panic;且必须先调用 c.parsemultipartform(32

c.FormFile 返回的是元数据,不是文件内容本身,必须调用 file.Open() 才能读取;漏掉这步会返回空或 panic。
必须先调 c.ParseMultipartForm(32
FastHTTP 底层不自动解析 multipart 请求体,Fiber 也不会帮你隐式触发。没调这句,c.FormFile 和 c.FormValue 全部返回 nil 或空字符串,且无错误提示,极难定位。
- 推荐在 handler 开头统一加:
if err := c.ParseMultipartForm(32 32 是 32MB 内存缓冲上限,超出部分写磁盘;设太小(如 4MB)会导致大文件上传失败,设太大(如 512MB)浪费内存- 一旦调过
c.Body()或c.BodyBytes(),再调ParseMultipartForm会静默失败——底层缓冲已消耗
c.FormFile 只拿第一个同名文件,多文件得用 c.MultipartForm()
前端传多个 <input type="file" name="images" multiple>,后端用 c.FormFile("images") 只拿到第一个;真正要遍历全部,得走 c.MultipartForm()。
-
form, err := c.MultipartForm()返回*multipart.Form,其中form.File["images"]是[]*multipart.FileHeader切片 - 每个
*multipart.FileHeader都需单独调file.Open()获取io.ReadCloser - 记得对每个打开的文件句柄
defer f.Close(),否则 fd 泄漏,后续请求可能卡住或报 "too many open files"
文件校验不能只信前端传的 Content-Type
攻击者可伪造 Content-Type: image/jpeg,但实际传个 WebShell。真正可靠的检测方式是读文件头 512 字节 + http.DetectContentType(),再比对白名单。
- 大小校验必须用
file.Size(解析后的真实值),别信Content-Length(可篡改) - 重命名文件名:去掉路径(防目录穿越)、过滤特殊字符、加随机前缀,例如
uuid.New().String() + "_" + sanitize(file.Filename) - 类型白名单示例:
map[string]bool{"image/jpeg": true, "image/png": true, "application/pdf": true},拒绝application/octet-stream等泛类型
c.SaveFile 仅适合本地快速验证,生产环境慎用
c.SaveFile(file, "./uploads/"+filename) 是 Fiber 封装的快捷方法,内部调 file.Open() + io.Copy(),但它不支持进度反馈、流式处理或自定义存储目标(如 MinIO、七牛云)。
- 上传到对象存储时,必须自己
file.Open()→ 读取io.ReadCloser→ 传给 SDK 的PutObject方法 - 若需限速、断点续传、分块上传,
c.SaveFile完全不适用,得手写流式转发逻辑 - 本地保存时注意目录权限和磁盘空间监控,避免写满根分区
ParseMultipartForm 必须在任何 Body 相关操作之前调用,且只能调一次**。这个顺序约束没有 runtime 报错,只有行为静默失效,排查成本极高。











