c.formfile 仅支持单文件上传,适用于 name 无数组语法的场景;多文件必须用 c.multipartform() 解析,且需预先调用 parsemultipartform 设置内存限制。

如何用 c.FormFile 获取单个上传文件
在 Echo 中,c.FormFile 是最常用、最直接的文件获取方式,但它**每次只返回一个 *multipart.FileHeader 实例**,不支持直接取多文件。如果你的 HTML 表单中 name="file"(无数组语法),那它就只能拿到第一个选中的文件,其余被忽略。
常见错误现象:c.FormFile("files[]") 返回 nil 或 panic —— 因为表单字段名是 files[],但 c.FormFile 不解析方括号,它只按字面名匹配。
- 正确做法:前端
<input type="file" name="files" multiple>,后端用c.FormFile("files")会失败(浏览器实际仍发多个同名字段,但FormFile只取首个) - 真正能用
c.FormFile的场景,仅限明确单文件上传,比如头像、封面图等 - 若坚持用
c.FormFile处理多文件,必须配合 JS 分多次提交,或服务端手动调用多次(不推荐)
为什么必须用 c.MultipartForm() 处理多文件
c.MultipartForm() 才是 Echo 解析多文件上传的正解。它底层调用 ParseMultipartForm,把整个 multipart/form-data 请求体解析成结构化数据,其中 form.File 方法可按 name 获取全部同名文件列表(返回 []*multipart.FileHeader)。
容易踩的坑:c.MultipartForm() 默认只解析前 32MB 数据,且要求先调用 c.Request().ParseMultipartForm() 或显式设置最大内存限制,否则可能报错 http: multipart: NextPart: bufio: buffer full。
- 务必在调用前设置:
c.Request().MultipartReader()不够,得用c.Request().ParseMultipartForm(32 (32MB) - 或者更稳妥地,在路由处理函数开头加:
if err := c.Request().ParseMultipartForm(64 -
form.File("files")返回的是切片,不是单个值;如果表单 name 是files[],这里仍填"files",方括号由浏览器编码决定,服务端不认
完整多文件上传处理流程示例
以下是一个生产可用的最小闭环逻辑,包含验证、保存、错误聚合:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
func uploadFiles(c echo.Context) error {
// 1. 解析 multipart 表单(关键!)
if err := c.Request().ParseMultipartForm(64 // 2. 获取所有同名文件(假设前端 input name="files")
form, _ := c.MultipartForm()
files := form.File["files"]
// 3. 遍历处理每个文件
results := make([]map[string]string, 0, len(files))
for _, file := range files {
src, err := file.Open()
if err != nil {
results = append(results, map[string]string{"name": file.Filename, "status": "open failed", "error": err.Error()})
continue
}
defer src.Close()
// 示例:保存到 ./uploads/ 目录(需确保目录存在且可写)
dst, err := os.Create("./uploads/" + file.Filename)
if err != nil {
results = append(results, map[string]string{"name": file.Filename, "status": "create failed", "error": err.Error()})
continue
}
if _, err := io.Copy(dst, src); err != nil {
results = append(results, map[string]string{"name": file.Filename, "status": "save failed", "error": err.Error()})
dst.Close()
continue
}
dst.Close()
results = append(results, map[string]string{"name": file.Filename, "status": "success"})
}
return c.JSON(http.StatusOK, results)
}
注意:io.Copy 比 file.Open().ReadAll() 更省内存,尤其对视频、大压缩包等文件;defer src.Close() 放在循环内即可,不用怕重复 defer。
前端表单 name 和后端解析的对应关系
浏览器对 multiple 的实现,和 PHP 的 name="files[]" 习惯不同:现代浏览器(Chrome/Firefox/Safari)在 <input multiple name="files"> 下,会把每个选中文件作为独立的同名字段发送,所以服务端用 form.File["files"] 就能拿到全部;而 name="files[]" 是历史遗留写法,部分旧环境可能兼容,但 Echo 不依赖它。
- ✅ 推荐前端写法:
<input type="file" name="files" multiple> - ❌ 不要写
name="files[]"并指望 Echo 自动识别数组 —— 它不会,form.File["files[]"]会是空切片 - ⚠️ 若必须兼容老系统(如某些 PHP 后端共存),可在 JS 层拆分上传,或用中间件重写表单字段名
最易被忽略的一点:没有显式调用 ParseMultipartForm 就直接调 c.MultipartForm(),Echo 内部会尝试默认解析,但一旦文件总大小超默认阈值(通常是 32MB),就会静默失败或截断 —— 看似没报错,实则漏文件。










