c.Query("tag")只返回第一个值,正确获取多值需用c.QueryArray("tag")返回字符串切片;c.Param仅适用于路由路径占位符,与query参数无关;Header多值必须用c.Request.Header["Key"]获取全部。

直接用 c.Query 拿不到重复 key 的 query 值,c.QueryArray 才是正确姿势;c.Param 只能用于路由路径中定义的占位符(如 /user/:id),不能用来取 query 字符串里的 id;Header 多值场景下 c.GetHeader 会静默丢弃后续值,必须用 c.Request.Header["Key"] 才能拿到全部。
Query 参数里有多个同名 key 怎么办
比如请求 URL 是 /search?tag=go&tag=web&tag=gin,用 c.Query("tag") 只返回第一个 "go",这不是 bug,是设计如此。
-
c.QueryArray("tag")返回[]string{"go", "web", "gin"},适用于标签、ID 列表等场景 -
c.GetQueryArray("tag")返回([]string, bool),第二个布尔值表示该 key 是否在 URL 中出现过(注意:哪怕值为空,只要 key 存在就为true) - 如果前端传的是
?tag[]=go&tag[]=web这种形式,Gin 默认不识别——它只认原生 URL 编码格式,不是 PHP 风格数组语法
Param 和 Query 容易混淆的边界
c.Param("id") 和 c.Query("id") 看起来一样,但来源完全不同,混用会导致参数始终为空。
-
c.Param只响应路由定义中的占位符,例如r.GET("/user/:id", handler)→ 请求/user/123才能取到"123" -
c.Query只读?后面的内容,/user/123?id=456中的456必须用c.Query("id") - 如果路由写成
/user/:id?name=xxx,:id是 path param,name是 query param,两者互不影响,也不能互相替代
Header 多值时 c.GetHeader 为什么总少数据
HTTP 规范允许同一个 Header 名出现多次(如 Roles: admin、Roles: user),但 Gin 的 c.GetHeader 和 c.Request.Header.Get 都只返回第一个值。
- 要获取全部值,得直接访问
c.Request.Header["Roles"],它返回[]string - 注意:map key 是规范大写形式(
"Roles"),不是原始小写或驼峰("roles"或"user-name"不行) - 如果要用结构体绑定 Header,必须用
ShouldBindHeader并配合 struct tag,c.ShouldBind(&s)默认不处理 Header
Body 解析失败时别急着改 Content-Type
常见错误是 POST JSON 请求返回 400 Bad Request 或空结构体,第一反应常是“是不是没设 Content-Type: application/json”,但还有更隐蔽的原因。
- 确保客户端真正发了 JSON(不是字符串、不是 form-data),可用
c.GetRawData()打印原始 body 确认 -
c.ShouldBindJSON要求 struct field tag 显式写json:"field_name",漏写或拼错就会静默跳过赋值 - 如果同时支持 JSON 和 form 表单,别用
ShouldBind——它会根据Content-Type自动选解析器,但对 multipart/form-data + JSON 混合体不可靠,应显式分路径处理
最常被忽略的是:Gin 的所有 Query/Param/PostForm 类方法都**不校验类型**,返回全是 string,业务层必须自己做 strconv.Atoi 或 time.Parse;而绑定结构体的方式(ShouldBind)才带类型转换和校验能力——选哪种方式,本质是在「手动控制」和「自动约束」之间做权衡。











