必须调用 r.ParseMultipartForm() 才能解析 multipart 上传数据,仅 r.ParseForm() 无效;需指定最大内存缓存值(如 32<<20),否则会 panic。
用
注册上传
路由
时必须调用
Go 的 HTTP
处理器
不会自动解析 multipart 数据,不手动调用
就直接读
或
,会返回
或
。常见错误是只写
—— 它对文件上传完全无效。
建议在 handler 开头统一处理:
值设太小(如
)会导致大文件上传失败;设太大可能耗尽内存
该限制只影响本次请求,不同请求可设不同值
用
获取单个文件时注意字段名和错误分支
中的
必须与 HTML 表单中
的
属性严格一致。大小写、空格、下划线都敏感。后端取不到文件,90% 是字段名不匹配。
它返回三个值:
、
、
。不能忽略
,也不能只检查
—— 错误时
可能非 nil 但内容不可读。
立即学习
“
go语言免费学习笔记(深入)
”;
vue.js + axios.js图片压缩处理并上传到服务器
vue.js + axios.js图片压缩处理并上传到服务器
下载
典型错误:用
判断上传失败,实际应
是客户端原始文件名,可能含路径(如
),需清洗
是文件
字节
数,可用于快速拦截超大文件(比如 >100MB)
保存文件前必须校验
和
仅靠前端
属性或 JS 检查毫无意义,攻击者可绕过。服务端必须做两件事:限制总大小、验证 MIME 类型。
大小校验应在
之后、
之前做,避免已触发磁盘写入再拒绝
由客户端提供,不可信;应通过读取文件前几个字节(magic bytes)识别真实类型,例如用
或第三方库如
不要只依赖扩展名判断类型(
文件可能是恶意脚本)
保存路径必须用
,禁用用户输入的完整路径
并发上传多个文件要用
而不是多次调用
内部调用的是
,但它每次都会重新定位文件指针。如果重复调用,第二次会读到空数据或报错
。
正确做法是先一次性提取所有文件:
HTML 表单中多个文件需写成
,而不是
—— Go 不支持 PHP 风格的数组字段名解析。
文件上传看似简单,但
的参数含义、
的错误判断方式、以及多文件时的资源释放顺序,三处最容易出线上问题。尤其别在循环里漏掉
或
,会导致句柄泄漏。
http.HandleFuncr.ParseMultipartFormr.ParseMultipartFormr.MultipartFormr.FormFilenilhttp.ErrNotMultipartr.ParseForm()func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 必须指定最大内存缓存(单位字节),否则 ParseMultipartForm 会 panic
if err := r.ParseMultipartForm(32 << 20); err != nil {
http.Error(w, "invalid form data", http.StatusBadRequest)
return
}
// 后续才能安全调用 r.FormFile
}32 表示 32MB,这是内存中最多缓存的文件大小;超过的部分会暂存到磁盘临时文件0r.FormFiler.FormFile("file")"file"namefile *os.Fileheader *multipart.FileHeadererr errorerrfile == nilfileif file == nilif err != nilheader.FilenameC:\foo\bar.jpgheader.Sizeheader.Sizeheader.Header.Get("Content-Type")acceptr.ParseMultipartFormr.FormFileContent-Typenet/http.DetectContentTypegabriel-vasile/mimetype.jpgfilepath.Join(uploadDir, secureFilename)r.MultipartForm.Filer.FormFiler.FormFiler.MultipartForm.File[key]http: invalid Read on closed Bodyif err := r.ParseMultipartForm(32 << 20); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
files := r.MultipartForm.File["files"] // 注意:HTML 中 name="files[]" 不起作用,必须是 name="files"
for _, fhdr := range files {
file, err := fhdr.Open()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close() // 注意:每个 file 都要单独 defer,或用显式 close
dst, err := os.Create(filepath.Join("./uploads", fhdr.Filename))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err := io.Copy(dst, file); err != nil {
dst.Close()
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
dst.Close()
}name="files[]"ParseMultipartFormFormFilefile.Close()dst.Close()