ctx.FormFile只获取第一个同名文件,因其实现为单次消费式读取;需循环调用并以file==nil&&err==nil为终止条件,配合file.Size>0和file.Open()双重校验确保安全上传。

为什么 ctx.FormFile 只拿到第一个文件
因为 ctx.FormFile 是单次调用、单次消费:它从 multipart reader 中读取并解析下一个同名字段,内部移动了读取偏移。只调一次 ctx.FormFile("files"),就只取走第一个;其余文件仍留在流里,但后续再调不会“重头开始”,而是继续往后——可如果没更多,就返回 nil, err。常见错误写法是:file, _ := ctx.FormFile("files"),这直接丢弃了所有非首项。
怎么安全循环获取全部同名文件
必须用循环 + 显式判空逻辑,不能依赖 err != nil 就退出。关键点在于:file == nil && err == nil 才表示已无更多文件,应终止循环。
- 每次调用
ctx.FormFile("files")后,先检查file == nil—— 若为真,说明没拿到文件头,可能字段不存在或已取完 - 再检查
err != nil—— 常见值如http.ErrMissingFile(整个字段缺失)、io.EOF(流结束),但这些不总代表“没文件了” - 只有
file == nil && err == nil时,才确认本次调用已无可用项,应break - 若
file != nil,才进入保存流程,并记得验证file.Size > 0
为什么别用 ctx.SaveUploadedFile
ctx.SaveUploadedFile 内部硬编码调用 os.Create,覆盖写入、无权限控制、无法校验内容(比如 Magic Bytes)、无法限大小、也无法复用句柄。它适合原型开发,不适合生产。
- 正确路径是:先
f, err := file.Open()得到multipart.File接口 - 再
dst, err := os.OpenFile(..., os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)控制打开方式 - 然后
io.Copy(dst, f),完成后分别defer f.Close()和dst.Close() - 注意顺序:必须先打开目标文件,再
f.Open(),否则io.Copy可能因源句柄提前关闭失败
前端 name 属性和 multiple 属性必须匹配
后端行为严重依赖前端写法。如果 HTML 是 <input type="file" name="files">(无 multiple),即使用户多次点击选择,浏览器也只发一个文件;而 <input type="file" name="files" multiple> 才保证多文件被合并进同一个字段。
- 不要指望后端能“兼容”无
multiple的重复提交——那是未定义行为,不同浏览器处理不一致 - 服务端不应假设字段一定存在多个项,始终按“可能 0 个、1 个、N 个”来健壮处理
- 若需支持拖拽多文件 + 点击添加,前端必须统一用
multiple并确保name一致
file == nil 不等于“没文件”,file != nil 也不等于“可安全读取”——必须配合 file.Size > 0 和 f, err := file.Open() 的双重校验,才能避免空文件落地或 panic。**


















