不能直接用c.DefaultQuery转int或bool,因其仅返回string且不校验格式,遇空值、非法字符或超大数会panic;应先c.Query取值再显式转换校验,或优选c.ShouldBindQuery绑定带validation规则的结构体。

c.Query 和 c.ShouldBindQuery 是 Gin 解析 Query 参数最常用也最推荐的两种方式。直接用 c.DefaultQuery 做类型转换极易 panic,不建议在生产环境单独使用。
为什么不能直接用 c.DefaultQuery 转 int 或 bool
因为 c.DefaultQuery("page", "1") 返回的是 string,不是 int;如果前端传了 ?page=(空值)、?page=abc 或 ?page=99999999999999999999,后续用 strconv.Atoi 会直接 panic 或溢出。
-
c.Query("page")返回空字符串(参数未传时),c.DefaultQuery("page", "1")返回默认字符串(参数未传时),但两者都**不做类型校验** - 所有数字、布尔、时间类参数,必须显式解析 + error 检查,不能跳过
- 若 handler 中多次调用
c.Query,会重复解析url.Values,轻微性能浪费
c.ShouldBindQuery 绑定结构体是首选方案
它把参数解析、类型转换、校验、默认值填充全包了,且字段名、参数名、验证规则一目了然。
- 结构体字段必须首字母大写(exported),否则绑定静默失败
-
form:"page"tag 明确指定 URL 参数名,和 JSON 的json:分开管理 -
binding:"min=1,default=1"中的default仅在参数完全未传时生效;若传了?page=(空字符串),则触发min=1校验失败 - 支持
oneof=active,inactive这类白名单校验,比手写if status != "active" && status != "inactive"更可靠
type ListOptions struct {
Page int `form:"page" binding:"required,min=1,default=1"`
PageSize int `form:"page_size" binding:"required,min=1,max=100,default=20"`
Status string `form:"status" binding:"omitempty,oneof=active inactive pending"`
Keyword string `form:"keyword" binding:"max=50"`
}
func handler(c *gin.Context) {
var opts ListOptions
if err := c.ShouldBindQuery(&opts); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// opts.Page 等字段已安全可用
}
什么时候该用 c.QueryArray 或 c.GetQueryArray
当 URL 中出现重复 key,比如 /search?tag=go&tag=web&tag=api,需要用数组接收。
-
c.QueryArray("tag")返回[]string,参数不存在时返回空切片[]string{} -
c.GetQueryArray("tag")返回([]string, bool),第二个返回值表示参数是否存在(可用于区分“没传”和“传了空数组”) - 结构体绑定也支持数组:
Tags []string `form:"tag"`,但注意它无法区分?tag=和未传参数
真正容易被忽略的是:Query 参数的空字符串行为和结构体 binding 中 omitempty 的配合逻辑——它不等于“忽略校验”,而是“跳过该字段的非空校验”。实际业务中,状态过滤、关键词搜索、分页控制这些字段,绝大多数时候都需要明确的“存在性+合法性”双重判断,而不是靠默认值兜底。


















