c.FormFile() 仅获取同名字段首个文件,多文件必须用 c.MultipartForm() 解析完整表单;前端建议统一用 name="files",调用前需确保请求体未被提前读取,否则解析失败。

直接说结论:Echo 的 c.FormFile() 适合单文件,c.MultipartForm() 才是处理多文件或复杂表单的正确入口,别用错函数名,否则拿不到文件。
为什么 c.FormFile("xxx") 拿不到多文件?
因为 c.FormFile() 只解析第一个匹配字段名的 *multipart.FileHeader,即使前端传了多个同名 name="files[]",它也只返回一个。真正支持数组式上传的是 c.MultipartForm(),它返回完整的 *multipart.Form 结构,其中 Form.File 是 map[string][]*multipart.FileHeader。
- 前端必须用
name="files"(不带[])或name="files[]",但后端解析逻辑完全不同 -
c.FormFile("files")→ 只取第一个文件,其余丢弃 -
form, err := c.MultipartForm(); files := form.File["files"]→ 拿到全部文件切片 - 如果前端用了
name="files[]",部分浏览器会自动转义为files[],建议统一用name="files"避免歧义
c.MultipartForm() 的实际使用要点
调用前必须确保请求体未被提前读取(比如没被中间件或日志拦截),否则 ParseMultipartForm 会失败并报 http: multipart: NextPart: EOF。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 默认最大内存缓存是 32MB,超出会写入临时磁盘;可通过
c.Request().ParseMultipartForm(10 提前设置为 10MB - 务必检查
err,常见错误包括http: request body too large(需配middleware.BodyLimit("200M")) - 获取文件后,用
file.Open()得到io.ReadCloser,不要直接io.ReadAll()加载整文件到内存 - 示例关键片段:
form, err := c.MultipartForm() if err != nil { return c.JSON(http.StatusBadRequest, echo.Map{"error": "parse form failed"}) } for _, fh := range form.File["files"] { src, _ := fh.Open() defer src.Close() dst, _ := os.Create("upload/" + fh.Filename) defer dst.Close() io.Copy(dst, src) }
文件名、路径与安全校验不能跳过
直接拼接 fh.Filename 写入磁盘等于开放任意文件写入漏洞,尤其当用户传 ../../../etc/passwd 这类名字时。
- 永远用
path.Base(fh.Filename)截取纯文件名,丢弃所有路径信息 - 生成唯一文件名(如
uuid.NewString() + ".mp4"),避免覆盖和竞争条件 - 校验
fh.Size是否在合理范围(如视频不超过 500MB),防止 DoS - 用
mime.TypeByExtension()或http.DetectContentType()校验真实类型,不能只信fh.Header.Get("Content-Type")
大文件上传时容易卡住或中断
不是 Echo 本身的问题,而是 HTTP 层默认限制和客户端行为叠加导致的——上传中途断开、超时、连接重置都可能静默发生。
- 服务端必须加
middleware.BodyLimit("500M"),否则默认只允许 4MB - 启动服务器时设超时:
e.Server.ReadTimeout = 30 * time.Minute,否则默认 30 秒就断连 - 前端建议配合分片上传(
uploadId+index+fileMD5),Echo 路由可按uploadId归集临时块 - 别依赖
fh.Size做唯一校验,某些客户端(如 curl)可能不填,应以实际读取字节数为准
真正麻烦的从来不是怎么拿到文件,而是怎么确认「这个文件确实是用户本意上传的、没被篡改、不会撑爆磁盘、也不会被用来打穿你的服务器」——每一步校验都要落在实处,而不是写在注释里。

















