根本原因是html表单未设enctype="multipart/form-data"或前端未用formdata;需检查enctype、name一致、content-type自动设置;file.open()必须调用,否则panic;文件名须净化或重命名防路径遍历;maxmultipartmemory需手动调大。

上传文件时 c.FormFile 返回 nil 怎么办
根本原因是没在 HTML 表单里设置 enctype="multipart/form-data",或者前端用 fetch / axios 时没构造正确的 FormData。Gin 不会自动解析普通 POST body 里的二进制数据。
检查点:
- HTML 表单必须带
enctype="multipart/form-data" - 用
axios时别手动设Content-Type,让它自动设置边界(否则c.FormFile拿不到) -
name属性值要和c.FormFile("xxx")里的字符串完全一致(区分大小写)
保存前必须调用 file.Open() 吗
是的。Gin 的 *multipart.FileHeader 只是元信息(文件名、大小、头信息),不是可读流。不调用 file.Open() 就直接 io.Copy 会 panic:「invalid argument」或空文件。
典型流程:
- 用
c.FormFile("file")获取*multipart.FileHeader - 立即调用
header.Open()得到multipart.File(本质是io.ReadCloser) - 用
os.Create()打开目标路径,再io.Copy(dst, src) - 务必
defer src.Close()和dst.Close()(后者尤其不能漏,否则文件可能写不全)
如何避免文件名被恶意覆盖或路径遍历
用户传来的 header.Filename 完全不可信。直接拼接会导致 ../../../etc/passwd 类攻击,或同名覆盖。
安全做法:
- 用
filepath.Base(header.Filename)剥离路径部分 - 用
strings.ReplaceAll清除控制字符、斜杠、点号等(如strings.Map(func(r rune) rune { if unicode.IsControl(r) || r == '/' || r == '\' { return -1 }; return r }, name)) - 更稳妥的是弃用原名,用
uuid.New().String() + filepath.Ext(name)生成新文件名 - 保存目录必须是预设的绝对路径(如
/var/uploads),禁止拼接用户输入的路径段
大文件上传时 Gin 报 http: request body too large
Gin 默认限制 32MB,超过就直接拒绝请求,连 c.FormFile 都不会执行。这不是业务逻辑问题,是 HTTP 层拦截。
解决方式是在初始化 gin.Engine 后立刻设置:
r := gin.Default() r.MaxMultipartMemory = 1024 <p>注意单位是字节,<code> 是 MB,<code> 是 GB;值设太大可能引发 OOM,建议按业务上限略放宽即可。</code></code></p> <p>另外,Nginx 或其他前置代理也可能有 <code>client_max_body_size</code> 限制,需同步调整。</p> 文件保存本身很简单,但绕不开权限、路径、大小、并发写入这些底层细节。特别是多实例部署时,别把文件写到本地磁盘还指望另一台机器能访问到——这时候该考虑对象存储了。











