上传路由必须用POST且 enctype 为 multipart/form-data;Echo 不自动解析,需在处理函数首行调用 c.Request().ParseMultipartForm(32 << 20)。

上传路由必须用 POST,且不能漏掉 multipart/form-data 表单编码
Echo 默认不自动解析 multipart 数据,如果前端用 <form enctype="multipart/form-data"> 提交但后端没显式调用解析方法,c.MultipartForm() 会返回空或 panic。必须在路由处理函数里第一件事调用 c.Request().ParseMultipartForm(32 (32MB 是推荐上限,防止内存爆掉),或者更稳妥地用 Echo 内置的 <code>c.FormFile("file") —— 它内部已触发解析,且直接返回 *multipart.FileHeader。
常见错误现象:http: no such file 或 nil pointer dereference,基本都是因为没 parse 就直接取文件。
- 表单字段名(如
file)要和c.FormFile("file")中字符串严格一致 - 不要在中间件里提前调用
ParseMultipartForm,Echo 的FormFile和SaveUploadedFile有自己的一套生命周期控制 - 如果同时需要读表单其他字段(如
user_id),务必在FormFile之后再用c.FormValue("user_id"),否则可能因 body 已被消费而读不到
c.SaveUploadedFile 是最简落地方式,但路径需手动确保可写
c.SaveUploadedFile 封装了打开、拷贝、关闭全过程,比手写 Open + io.Copy 更安全。但它不会自动创建父目录,如果目标路径如 ./uploads/2024/06/xxx.jpg 中 2024/06 不存在,会直接报错 no such file or directory。
实操建议:
- 用
os.MkdirAll(filepath.Dir(dstPath), 0755)在SaveUploadedFile前创建完整目录树 - 文件名别直接信任
fileHeader.Filename,它可能含路径遍历(如../../etc/passwd),要用filepath.Base(fileHeader.Filename)截取干净文件名 - 保存路径建议用 UUID 或时间戳重命名,避免冲突和恶意覆盖,例如:
dst := filepath.Join("./uploads", uuid.New().String()+".png")
大文件上传要调 e.Use(middleware.BodyLimit("200MiB")),否则默认 4MB 就 400
Echo 默认限制请求体为 4MB,超过直接返回 400 Bad Request,错误信息是 request body too large。这个限制发生在路由匹配前,所以即使你在 handler 里做了流式处理也拦不住。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
必须在 Echo 实例初始化后、启动前加中间件:
e := echo.New()
e.Use(middleware.BodyLimit("200MiB")) // 注意单位是字符串,支持 KiB/MiB/GiB
e.POST("/upload", uploadHandler)
注意点:
-
BodyLimit是字节级硬限制,不是流控;真要支持 GB 级上传,还得配合 Nginx 的client_max_body_size和超时配置 - 别把
BodyLimit设得过大(比如"1GiB"),容易被恶意请求拖垮内存 - 如果用了反向代理,这个中间件必须放在代理之后,否则代理可能先截断
返回 JSON 时别忘了设 Content-Type: application/json,否则前端可能解析失败
Echo 的 c.JSON 方法默认会设对 header,但如果你手动用 c.String 或 c.JSONBlob,就容易漏。尤其调试时用 curl 看响应,发现返回的是纯文本 JSON 字符串但没 Content-Type,前端 fetch().json() 就会报 Unexpected token。
正确做法始终优先用 c.JSON(http.StatusOK, map[string]string{"url": "/uploads/xxx.png"})。如果必须手拼 JSON:
- 先调
c.Response().Header().Set("Content-Type", "application/json") - 再写 body,顺序不能反
- 错误响应也一样,比如
c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid file type"})
文件类型校验这类逻辑常被跳过,但仅靠前端 accept="image/*" 完全不可信 —— 攻击者删掉 HTML 属性就能传任意内容。至少用 mimeType := fileHeader.Header.Get("Content-Type") 做基础过滤,或用 github.com/h2non/filetype 库做 magic number 检查。

















