ctx.formfile 仅获取文件引用,必须调用 ctx.saveformfile 才能真正落盘;后者不自动创建目录,需提前 os.mkdirall;文件大小限制须通过 iris.withpostmaxmemory 配置 listen 参数,且需预留 10% 余量。

ctx.FormFile 是 Iris 处理单文件上传最直接的入口,但光调用它远远不够——路径不存在、大小超限、权限错误、并发写入冲突,这些都会让上传在生产环境静默失败。
单文件上传:为什么 ctx.FormFile 之后必须配 ctx.SaveFormFile
很多人以为拿到 file 和 fileHeader 就完事了,其实 ctx.FormFile 只是把内存或临时磁盘中的 multipart 数据“引用”出来,没做任何落盘动作。真正写入文件系统靠的是 ctx.SaveFormFile。
-
ctx.FormFile("file")返回的是一个multipart.File接口和元信息,不保证可重复读,也不自动清理临时缓存 -
ctx.SaveFormFile(file, dest)内部会处理 open/create/write/close 全流程,并确保目标目录存在(如果父目录不存在,它不会自动创建) - 常见错误:
open ./uploads/test.jpg: no such file or directory—— 这不是SaveFormFile的 bug,是你没提前os.MkdirAll("./uploads", 0755)
文件大小限制:别只改 iris.WithPostMaxMemory
iris.WithPostMaxMemory(8 * iris.MB) 控制的是请求体整体内存上限,但 multipart 解析还受底层 http.Request.ParseMultipartForm 影响。Iris 默认用 32MB,如果你设了 8MB 却仍收到 http: request body too large,大概率是没传给 app.Listen。
- 正确写法:
app.Listen(":8080", iris.WithPostMaxMemory(8*iris.MB))—— 必须作为Listen的第二个参数 - 错误写法:
app.Use(iris.LimitRequestBodySize(...))或单独调ctx.SetMaxRequestBodySize(...),它们只影响当前请求上下文,不作用于 multipart 解析阶段 - 注意:这个值要略大于你预期的最大单文件体积(比如用户选了 7.9MB 文件,8MB 刚好够;但加上 form 字段、boundary 等开销,建议留 10% 余量)
多文件上传:ctx.UploadFormFiles 的坑比想象中多
ctx.UploadFormFiles("./uploads") 看似省事,但它默认按 HTML 表单字段名匹配(如 <input type="file" name="upload[]" multiple>),且对字段名有硬编码逻辑。一旦字段名不是 upload 或没带 [],它就返回空切片。
- 字段名必须严格匹配:前端用
upload[],后端才能识别为多文件;用files或document都不行 - 它不会校验每个文件大小,只校验总请求体大小 —— 所以 10 个 1MB 文件可能通过,但一个 9MB 文件 + 1 个 1MB 文件就可能被截断
- 返回的
files切片里每个元素是*multipart.FileHeader,不含原始multipart.File,所以无法手动重读内容(比如你想先验 md5 再保存)
真实路径与权限:本地存储时最容易忽略的两件事
Iris 不帮你创建目录,也不检查写权限。你在开发机上跑通了,上线就报 permission denied,往往是因为:
- 目标路径用了相对路径(如
"./uploads"),而进程工作目录不是你预期的(比如 systemd 启动时 cwd 是/)—— 改用绝对路径,或用filepath.Abs("./uploads")显式计算 - Linux 下运行用户(如
www-data)对目标目录没有w权限,os.MkdirAll成功但SaveFormFile失败 —— 用ls -ld ./uploads确认属主和权限位 - Windows 上路径分隔符混用(
"./uploads\test.jpg")导致SaveFormFile报错 —— 统一用filepath.Join
实际部署时,SaveFormFile 的原子性、并发安全、临时文件清理都不是默认保障的。如果你需要秒传、断点续传或去重,就得绕过它,自己用 io.Copy + os.OpenFile 控制整个流。











