必须提前调用 http.Request.ParseMultipartForm 并设置 MaxMemory,否则 r.MultipartReader() 返回 nil、r.FormFile 报“http: no such file”错误;默认禁用 multipart 解析,参数为内存缓存上限(如 32

Go 的 http.Request.ParseMultipartForm 必须提前设置 MaxMemory
不调用或忘记设置 ParseMultipartForm,会导致 r.MultipartReader() 返回 nil,r.FormFile 报 http: no such file 错误。这不是文件名写错,而是 Go 默认禁用 multipart 解析。
-
ParseMultipartForm参数是最大内存缓存字节数,比如32 (32MB),超过部分自动流式写入临时磁盘文件 - 必须在读取
FormFile或访问r.MultipartReader()前调用,顺序错误会静默失败 - 若只上传小图(如头像),设为
10 足够;大文件上传需配合io.Copy流式处理,避免全量加载进内存
r.FormFile 返回的 *multipart.FileHeader 需校验 Size 和 Header
前端可能伪造 Content-Type,仅靠 file.Header.Get("Content-Type") 不可靠;真实 MIME 类型需用 net/http.DetectContentType 检查前 512 字节。
网络工作室源码基于热腾CMS(RTCMS)定制,栏目全站自动调用,可设置生成为html静态文件。网站分类适合网络公司和工作室使用。程序中带有演示数据,如果全新安装,可将根目录下的/uploads 文件夹中的演示图片文件删掉。安装方式:上传upload_install中的文件上传到虚拟主机或服务器网站根目录下;访问 http://域名/ 即可安装,安装时可以选取“演示数据&
-
file.Size是客户端声明大小,可能被篡改,需与实际读取字节数比对 - 检查扩展名要从
file.Filename提取(注意路径遍历:用filepath.Base清洗) - 推荐组合判断:
strings.HasSuffix(strings.ToLower(filepath.Base(file.Filename)), ".jpg")+DetectContentType读取开头字节
保存文件时别直接用 file.Filename 当本地路径
用户上传的 Filename 可能含 ../、空字节、Unicode 控制符,直接拼接 os.OpenFile 路径会导致目录穿越或 open 失败。
- 始终用
filepath.Base截取纯文件名,再生成唯一前缀(如uuid.New().String()) - 保存路径应固定在服务可写目录下,例如
./uploads/,禁止拼接用户输入的任意路径段 - 写入前用
os.Stat确认父目录存在,不存在则os.MkdirAll
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
err := r.ParseMultipartForm(32 << 20)
if err != nil {
http.Error(w, "Parse form error: "+err.Error(), http.StatusBadRequest)
return
}
file, header, err := r.FormFile("image")
if err != nil {
http.Error(w, "No image field or parse error", http.StatusBadRequest)
return
}
defer file.Close()
// 校验文件名和类型
filename := filepath.Base(header.Filename)
if filename == "." || filename == "" {
http.Error(w, "Invalid filename", http.StatusBadRequest)
return
}
buf := make([]byte, 512)
_, _ = file.Read(buf)
filetype := http.DetectContentType(buf)
if !strings.HasPrefix(filetype, "image/") {
http.Error(w, "Not an image", http.StatusBadRequest)
return
}
// 重置 reader 到开头(Read 已消耗 buf)
file.Seek(0, 0)
// 生成安全路径
uploadDir := "./uploads"
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
os.MkdirAll(uploadDir, 0755)
}
safePath := filepath.Join(uploadDir, uuid.New().String()+"_"+filename)
out, err := os.Create(safePath)
if err != nil {
http.Error(w, "Save failed", http.StatusInternalServerError)
return
}
defer out.Close()
if _, err := io.Copy(out, file); err != nil {
http.Error(w, "Write failed", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"path": safePath})
}
Golang 上传图片真正的难点不在接收,而在边界控制:内存阈值、类型探测、路径净化、流式写入——这些环节漏掉任何一个,上线后都可能变成安全漏洞或 OOM 崩溃。









