跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Golang中如何构建一个文件上传接口

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

相关文章