parsemultipartform仅解析表单结构而不保存文件,必须手动调用r.formfile或遍历r.multipartform.file读取part并写入目标路径;其参数32mb为内存缓存阈值,超限部分自动落盘,但文件本体始终需显式处理。

Go 的 http.Request.ParseMultipartForm 本身不保存文件,只解析到内存或临时磁盘;真正保存文件需手动读取 multipart.Part 并写入目标路径——这是最容易卡住的地方。
为什么 ParseMultipartForm 不会自动存文件
它只做两件事:解析表单边界、把字段和文件头加载进 r.MultipartForm(字段存 Value,文件元信息存 File),但文件本体仍留在请求流里。调用 r.FormFile 或遍历 r.MultipartForm.File 才真正触发读取。
ParseMultipartForm(32 中的参数是内存阈值,超限后自动用临时文件缓存,但不会帮你挪到业务目录- 若没调用
FormFile或没遍历File,文件数据根本没被读走,后续Read会报http: request body closed - 临时文件默认在
/tmp(Linux/macOS)或C:\Temp(Windows),且 Go 不自动清理——你得自己删或依赖系统轮转
用 r.FormFile 快速保存单个文件
适合表单中只有一个 <input type="file"> 且名字确定的场景,比如头像上传。
func saveSingleFile(r *http.Request, fieldName, destPath string) error {
f, fh, err := r.FormFile(fieldName)
if err != nil {
return err
}
defer f.Close()
out, err := os.Create(destPath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, f)
return err
}
-
fieldName必须和 HTML 中input name="xxx"完全一致,大小写敏感 -
fh.Header.Get("Content-Type")可获取 MIME 类型,用于校验(如拒绝application/octet-stream) -
fh.Size是文件大小(字节),可在io.Copy前做限制,避免写满磁盘
遍历所有文件并按名保存(支持多文件上传)
当表单含多个同名 input(name="files")或不同名字段时,必须用 r.MultipartForm.File 遍历。
func saveAllFiles(r *http.Request, baseDir string) error {
err := r.ParseMultipartForm(32
- 上面代码有严重 bug:
defer src.Close()在循环中不会按预期执行——最后一个src关闭前,前面的句柄已泄漏。正确做法是把保存逻辑抽成函数,或在每次迭代末尾显式src.Close() -
f.Filename来自客户端,不可信,务必 sanitize(例如用path.Base(f.Filename)去掉路径遍历字符) - 如果表单同时含文本字段,它们在
r.MultipartForm.Value[name]里,可一并处理
真正麻烦的不是读文件,而是错误处理粒度、文件名净化、磁盘空间预检、并发安全(多个请求同时写同一目录)、以及临时文件残留——这些不写进函数里,上线后就会变成凌晨三点的告警。











