gin对同名query或form参数默认只取第一个值,需用queryarray()、postformarray()或shouldbindquery()获取全部值,文件上传则须通过multipartform()遍历处理。

Gin 默认对同名 query 或 form 参数只取第一个值,遇到重名或需要多值的场景(比如 filter=tag1&filter=tag2),必须显式调用对应方法,否则会丢数据。
query 和 form 中重名参数默认只取第一个
Gin 的 c.Query()、c.DefaultQuery()、c.PostForm() 等函数底层都调用 url.Values.Get(),该方法对重复 key 只返回首个 value。这不是 bug,是标准行为,但容易被误认为“没收到”。
- 错误现象:
?q=a&q=b&q=c时c.Query("q")返回"a",后两个被静默丢弃 - 适用场景:绝大多数单选、单输入表单,无需改动
- 风险点:前端传了多个同名 checkbox 或 tag,后端却只处理了一个
用 c.QueryArray() 和 c.PostFormArray() 获取全部值
这两个函数返回 []string,直接拿到所有同名参数值,是处理多选、标签过滤等场景的标准方式。
-
c.QueryArray("filter")解析?filter=go&filter=gin&filter=gorm→["go", "gin", "gorm"] -
c.PostFormArray("role")解析 multipart 表单中多个<input name="role" value="admin"><input name="role" value="editor">→["admin", "editor"] - 注意:如果参数根本不存在,返回空切片
[]string{},不是nil;需用len(vals) == 0判断有无值 - 不支持嵌套结构(如
user[roles][]=a&user[roles][]=b),Gin 不解析这种格式,得靠前端扁平化或手动解析
c.GetQueryArray() 和 c.ShouldBindQuery() 的区别
c.GetQueryArray() 是低层 API,只读 query string;c.ShouldBindQuery() 是结构体绑定,走完整校验流程,适合已知固定字段结构的场景。
- 示例结构体:
type SearchReq struct { Filters []string `form:"filter"` } -
c.ShouldBindQuery(&req)会自动把?filter=a&filter=b绑定到req.Filters,且支持binding:"required"等校验 - 但
ShouldBindQuery不支持混合类型(如同时有filter[]=a和filter=b),也不处理非标准键名(如含点号、中括号) - 若 query 字段动态、数量不定(如自定义筛选器),优先用
QueryArray()手动处理,更可控
文件上传中同名 file 字段的特殊处理
HTML 表单里多个 <input type="file" name="file"> 是合法的,但 Gin 的 c.FormFile("file") 仍只取第一个;必须用 c.MultipartForm() 手动遍历。
-
form, _ := c.MultipartForm()→ 得到*multipart.Form -
form.File["file"]是[]*multipart.FileHeader,可遍历每个文件头再c.SaveUploadedFile() - 别漏掉错误检查:
if len(form.File["file"]) == 0表示没传任何文件 - 注意内存:Gin 默认限制单次请求最大 32MB,大文件或多文件需提前调
gin.SetMode(gin.ReleaseMode); r.MaxMultipartMemory = 1024 * 1024 * 100
重名参数不是边缘情况——搜索过滤、权限角色、多图上传都是高频需求。关键不是“能不能取到”,而是明确区分“单值语义”和“多值语义”,并始终用匹配的 API:要全部值,就不用 Query(),而用 QueryArray() 或结构体绑定;文件同理,别被 FormFile() 的名字带偏。











