c.FormFile() 报错通常因前端未正确发送 multipart/form-data 表单,name 属性缺失或 Content-Type 不匹配;需确认请求头为 multipart/form-data,前端用 enctype="multipart/form-data" 表单或 curl -F 上传,其他字段用 c.FormValue() 获取;文件过大时需调整 http.Server 的 MaxMultipartMemory。

c.FormFile() 获取上传文件时常见错误
调用 c.FormFile("video") 报 http: no such file 或返回 nil,通常不是代码写错了,而是前端没发对——FormFile 只认 multipart/form-data 表单里带 name="video" 的 <input type="file"> 字段。如果用 JSON body、application/x-www-form-urlencoded 或漏传 name,它直接失败,不抛具体错误,只返回 err != nil。
验证方式:在 handler 开头加一行日志:fmt.Printf("Content-Type: %s\n", c.Request().Header.Get("Content-Type")),确认是 multipart/form-data; boundary=...。
- 前端必须用
<form enctype="multipart/form-data"> -
curl测试务必用-F "video=@xxx.mp4",不能用-d或--data-binary - 若同时提交其他字段(如标题、分类),它们会出现在
c.FormValue()里,和文件无关
文件过大导致请求被截断或超时
Echo 默认不限制上传大小,但 Go 标准库的 http.Server 有默认限制:MaxMultipartMemory = 32 (32MB)。超过这个值,<code>c.FormFile() 会静默失败,err 是 http.ErrMissingFile 类似错误,实际是内存 buffer 不够,临时文件没生成成功。
解决方法是在启动 Echo 前配置底层 http.Server:
e := echo.New()
srv := &http.Server{
Addr: ":8080",
Handler: e,
MaxMultipartMemory: 100 << 20, // 100MB
}
e.StartServer(srv)
- 别改
e.MaxMultipartMemory—— Echo v4 没这个字段,那是旧版或误传 - 如果上传超 1GB,建议配合流式处理(见下一条),避免全量读进内存
- 反向代理(如 Nginx)也可能拦截大文件,需同步调大
client_max_body_size
安全保存上传文件:避免路径遍历与覆盖
file.Filename 是客户端可控字段,直接拼接 os.OpenFile(file.Filename, ...) 会导致路径遍历(如传 ../../etc/passwd)或同名覆盖。必须做两件事:清洗文件名 + 生成唯一路径。
推荐做法:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 用
path.Base(file.Filename)截掉路径部分,只留原始文件名 - 用
uuid.NewString() + filepath.Ext(name)重命名,杜绝冲突和注入 - 保存目录必须是绝对路径,且提前
os.MkdirAll("/uploads", 0755) - 不要用
file.Size做唯一判断——多个同大小同扩展名文件会撞
示例片段:
name := path.Base(file.Filename)
ext := filepath.Ext(name)
safeName := uuid.NewString() + ext
dst, err := os.OpenFile(filepath.Join("/uploads", safeName), os.O_WRONLY|os.O_CREATE, 0644)
流式保存大视频文件(不加载进内存)
调用 src.Read(buf) 把整个 MP4 读进 []byte 是最危险的操作:200MB 视频 → 200MB 内存占用,高并发下直接 OOM。正确方式是打开源文件后,用 io.Copy() 直接流式写入目标文件。
关键点:
-
file.Open()返回的是multipart.File,实现了io.Reader - 目标文件用
os.Create()或os.OpenFile(...os.O_CREATE)打开 -
io.Copy(dst, src)自动分块、无缓冲区爆炸风险 - 记得
defer src.Close()和dst.Close(),否则句柄泄漏
完整保存逻辑节选:
src, err := file.Open()
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "open failed"})
}
defer src.Close()
dst, err := os.Create(filepath.Join("/uploads", safeName))
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "create failed"})
}
defer dst.Close()
_, err = io.Copy(dst, src) // 这里不占额外内存
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "copy failed"})
}
真正容易被忽略的是:io.Copy 不校验文件完整性,上传中断时 dst 是残缺文件,生产环境应加 MD5/SHA256 校验或用分块上传方案。

















