如何在Golang中上传图片_Golang net/http 图片上传方法

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

Go 的 http.Request.ParseMultipartForm 必须提前设置 MaxMemory

不调用或忘记设置 ParseMultipartForm,会导致 r.MultipartReader() 返回 nilr.FormFilehttp: no such file 错误。这不是文件名写错,而是 Go 默认禁用 multipart 解析。

  • ParseMultipartForm 参数是最大内存缓存字节数,比如 32 (32MB),超过部分自动流式写入临时磁盘文件
  • 必须在读取 FormFile 或访问 r.MultipartReader() 前调用,顺序错误会静默失败
  • 若只上传小图(如头像),设为 10 足够;大文件上传需配合 io.Copy 流式处理,避免全量加载进内存

r.FormFile 返回的 *multipart.FileHeader 需校验 SizeHeader

前端可能伪造 Content-Type,仅靠 file.Header.Get("Content-Type") 不可靠;真实 MIME 类型需用 net/http.DetectContentType 检查前 512 字节。

  • 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 崩溃。