gin默认maxmultipartmemory为32mb,不足以支持ios端大文件上传,需用gin.new()创建引擎并显式设置router.maxmultipartmemory = 128。

移动端App上传文件时,Gin默认的MaxMultipartMemory经常不够用
App端(尤其是iOS)上传图片、视频时,单个文件很容易超过 32MB,而 Gin 默认的 MaxMultipartMemory 是 32MB —— 这意味着超限文件会直接被丢弃,c.Request.ParseMultipartForm 报错或静默失败,前端收不到明确错误,只看到连接中断或 400。
必须在路由注册前显式调大这个值,且不能只改 gin.Default() 的配置,得用 gin.New() 手动构造引擎:
router := gin.New() router.MaxMultipartMemory = 128
- 数值建议按业务最大文件尺寸 × 1.2 设置,比如支持上传 100MB 视频,就设为
105 - 该值只影响内存缓冲区大小;超出部分会自动写入临时磁盘(由
os.TempDir()决定),但磁盘空间和 IO 仍需你监控 - 别设成
0或极大值(如1 ),否则可能触发 OOM 或被系统 kill
App上传常带自定义 header,Gin中间件要提前读取Content-Type再解析 multipart
很多 App SDK(如 OkHttp、AFNetworking)会在 multipart 请求里加额外 header,比如 X-Upload-ID、X-File-Name,甚至把文件名、类型藏在 Content-Type 的 boundary 后面。但 Gin 的 c.MultipartForm() 会强制先解析整个 body,一旦解析失败,header 就没法再安全读取了。
正确做法是:在解析前,手动检查并记录关键 header,再调用 c.Request.ParseMultipartForm:
func uploadHandler(c *gin.Context) {
uploadID := c.GetHeader("X-Upload-ID")
fileName := c.GetHeader("X-File-Name")
if uploadID == "" || fileName == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing required headers"})
return
}
// 必须在 ParseMultipartForm 前设置 max memory,否则用默认值
if err := c.Request.ParseMultipartForm(128
- 不要依赖
c.DefaultPostForm或c.PostForm来取文件元信息——它们只读 form-data 字段,不读 header - Android/iOS 某些版本对
boundary编码不一致,如果发现ParseMultipartFormpanic,可在解析前加bytes.ReplaceAll清理掉非法字符(如 \r\n\r\n 后多出的空格)
App断点续传场景下,Gin需配合分片校验与状态存储
移动端网络不稳定,大文件上传极易中断。单纯靠 Gin 接收完整 multipart 并不可靠。真实项目中,应让 App 按固定大小(如 5MB/片)分片上传,并携带 X-Part-Index、X-Total-Parts、X-File-Hash 等 header。
Gin 层要做三件事:
- 收到分片后,校验
X-File-Hash是否与首片一致(防止客户端拼错) - 将分片存入临时目录(建议用
filepath.Join(os.TempDir(), uploadID)隔离),并记录已接收索引到 Redis 或本地 map(注意并发写) - 当
X-Part-Index == X-Total-Parts时,合并所有分片,计算最终 MD5,再触发业务逻辑(如保存到 OSS)
注意:c.FormFile("file") 在分片模式下不适用——App 通常把每个分片作为独立 multipart/form-data 发送,字段名固定为 file,但 Gin 不会自动拼接,必须手动读 c.Request.Body 并写入文件。
上传路径中含中文或特殊字符时,Gin需禁用自动 URL 解码
某些 Android WebView 或旧版 iOS SDK 在构建上传 URL 时,会把文件名做两次 encode(如 %E4%BD%A0%E5%A5%BD.txt 变成 %25E4%25BD%25A0%25E5%25A5%25BD.txt)。Gin 默认会对 path 参数自动 decode 一次,导致解出来仍是乱码路径,os.Open 失败。
解决方案是关闭自动解码,改用原始 path:
router.POST("/upload/:filename", func(c *gin.Context) {
rawPath := c.Request.URL.EscapedPath() // 获取未 decode 的原始路径
filename := strings.TrimPrefix(rawPath, "/upload/")
// 手动 decode 一次,或直接用 filename 作存储 key(推荐 base64 或 UUID)
})
- 更稳妥的做法是:App 上传时不带文件名在 URL 里,全部通过 header 或 form-data 字段传递
- 服务端生成唯一文件名(如
uuid.New().String() + ".jpg"),避免路径、编码、权限等一连串问题
移动端上传不是简单“接个 c.FormFile”就能跑通的事——header 解析时机、内存阈值、分片状态管理、路径编码这几个点,漏掉任何一个,上线后都会在凌晨三点弹出报警。











