需在gin.Default()后立即调用binding.Validator.Engine().(*validator.Validate).RegisterValidation注册自定义校验函数,函数签名必须为func(fl validator.FieldLevel) bool,且结构体字段binding标签中使用对应名称(如binding:"required,not_contain_username")才能生效。

如何在 Gin 中注册自定义 validator 函数
Gin 默认使用 go-playground/validator/v10,但它的 tag 规则(如 required、email)不支持动态逻辑或业务强相关的校验(比如“密码不能包含用户名”)。必须通过 engine.Validator.RegisterValidation 注册自定义函数。
关键点:注册必须在初始化 gin.Engine 之后、启动服务器之前完成;且函数签名必须严格匹配 func(fl validator.FieldLevel) bool。
- 注册示例:
if v, ok := gin.Default().Validator.(*validator.Validate); ok { v.RegisterValidation("not_contain_username", func(fl validator.FieldLevel) bool { field := fl.Field().String() username := fl.Parent().FieldByName("Username").String() return !strings.Contains(field, username) }) } - 注意
fl.Parent()才能拿到整个结构体,否则只能访问当前字段值 - 若用
gin.New(),需先调用gin.New().Validator并断言为*validator.Validate
struct tag 怎么写才能触发自定义规则
自定义规则名就是 RegisterValidation 的第一个参数,直接写进 struct tag 即可,和其他内置规则语法一致,支持组合与参数(但 Gin 默认 validator 不支持参数传递,需自行解析)。
- 基础写法:
type User struct { Password string `json:"password" binding:"required,not_contain_username"` } - 不能写成
not_contain_username=Username—— 默认 validator 不解析等号右侧,要实现参数需改用RegisterStructValidation或在函数内硬编码字段名 - 多个规则用英文逗号分隔,顺序影响执行:前面的失败会短路后续校验
- tag 中的空格、换行会被忽略,但建议保持紧凑(如不用
not_contain_username带尾部空格)
为什么自定义校验器总不生效
最常见原因是没正确绑定结构体,或校验函数返回了错误结果。Gin 的 binding 机制只对 c.ShouldBind / c.ShouldBindJSON 等显式调用生效,不会自动校验裸指针或 map。
- 必须用结构体指针接收数据:
c.ShouldBind(&user),而不是c.ShouldBind(user)(后者传值,无法写回) - 结构体字段必须是导出的(首字母大写),否则
validator无法反射访问 - 校验函数里调用
fl.Field().Interface()比.String()更安全,避免空指针 panic(如字段是*string) - 如果用了
c.Bind而非c.ShouldBind,校验失败会直接返回 400,不进 handler,容易误判为“没走到校验逻辑”
怎么让自定义规则支持跨字段和上下文参数
默认 RegisterValidation 只能访问当前字段和所属结构体,没法传额外参数(如当前登录用户 ID、请求头语言)。这时得用 RegisterStructValidation 或改用中间件预处理。
- 推荐方案:用
RegisterStructValidation对整个结构体做校验,例如检查Password和ConfirmPassword是否一致:v.RegisterStructValidation(func(sl validator.StructLevel) { user := sl.Current().Interface().(User) if user.Password != user.ConfirmPassword { sl.ReportError(reflect.ValueOf(user.Password), "Password", "Password", "match", "") } }, User{}) - 若需 HTTP 上下文(如 header、cookie),不要塞在校验器里 —— 改用中间件提前提取并存入
c.Set(),再在 struct tag 校验函数中通过c.MustGet()获取(需把 validator 封装进 handler 内,放弃纯 binding) - 注意
sl.ReportError的第四个参数是错误码(用于 i18n),第五个是实际错误信息,别填反
fl.Field().Interface() 确认值是否符合预期。











