必须在gin.default()后立即调用binding.validator.engine().(*validator.validate).registervalidation()注册自定义验证器,传入唯一tag名和func(fl validator.fieldlevel) bool函数,且须在首次shouldbind前完成。

怎么注册一个自定义验证标签
必须先拿到 Gin 内置的 validator 实例,再调用 RegisterValidation 注册。Gin 默认用的是 github.com/go-playground/validator/v10,但不暴露实例,得从 binding 里取:
-
binding.Validator.Engine()返回的是interface{},需断言为*validator.Validate - 注册时第一个参数是 tag 名(比如
"admin_only"),第二个是函数值,类型必须是validator.Func - 如果同名 tag 已存在(比如内置的
"required"),新注册的会直接覆盖——不是报错,而是静默替换
示例注册代码片段:
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("no_admin", func(fl validator.FieldLevel) bool {
s, ok := fl.Field().Interface().(string)
return ok && s != "admin" && s != "Admin"
})
}
字段级验证器里能访问哪些上下文信息
validator.FieldLevel 提供的接口有限,只能拿到当前字段的值、结构体字段名、所属结构体类型等基础信息,拿不到其他字段值或请求上下文。
-
fl.Field()获取反射值,需手动类型断言(如.Interface().(string)) -
fl.FieldName()返回字段名(如"Name"),不是 tag 值 -
fl.Top().Interface()可取到整个结构体实例,但需二次断言,且不能保证安全(可能 panic) - 想跨字段比较(比如密码和确认密码),必须用结构体级别验证器,不能靠字段级
为什么 c.ShouldBind 没触发自定义 tag
常见原因不是代码写错,而是绑定方式和 tag 不匹配:
- GET 请求带 query 参数,结构体字段必须有
form:"xxx"tag,且bindingtag 才生效;若只写了json:"xxx",验证直接跳过 - POST 提交 JSON,但用了
c.ShouldBind()而非c.ShouldBindJSON(),Gin 会按默认规则(content-type 判定) fallback,可能误走 form 解析路径 - 自定义 tag 名拼写错误,比如注册时写
"no_admin",结构体里却写binding:"no_admim"(少个 n) - 没在
main()初始化阶段注册,而是在 handler 里注册——注册必须在路由启动前完成
结构体级验证器怎么写才安全
结构体级验证器函数签名是 func(sl validator.StructLevel),它能访问整个结构体,但容易踩空指针或类型断言失败:
- 用
sl.Current().Interface()拿结构体实例后,务必先判断是否为 nil,再断言具体类型 - 访问嵌套字段时,推荐用
sl.Current().FieldByName("XXX"),而不是硬解引用(.(*MyStruct).Field) - 跨字段逻辑(如
Password和ConfirmPassword比较)必须在这里做,字段级验证器看不到对方 - 注意执行顺序:字段级验证先跑,全部通过后才进结构体级;任一字段级失败,结构体级不会执行
真正麻烦的不是写法,而是验证失败时错误信息默认只显示字段名("User"),不带结构体名("User.Password")。要精准定位,得自己封装错误格式化逻辑。











