自定义校验函数必须使用 validator.fieldlevel 参数,通过 fl.field().interface() 安全取值并断言,指针需 elem(),复合类型须判空;gin 中须在 gin.default() 后立即通过 binding.validator.engine() 断言注册,且不可重复;跨字段校验须用 structlevel;错误提示需手动 registertranslation。

自定义校验函数必须用 validator.FieldLevel 参数
不是 interface{},也不是 reflect.Value,更不能直接传字段值。所有校验逻辑必须从 fl.Field().Interface() 取值,再做安全断言。常见错误是写成 func(v interface{}) bool,结果 panic 或静默跳过。
断言失败时要返回 false(表示校验失败),而不是 panic 或忽略。比如校验字符串长度:
v.RegisterValidation("minlen2", func(fl validator.FieldLevel) bool {
s, ok := fl.Field().Interface().(string)
if !ok {
return false // 类型不符,视为不通过
}
return len(s) >= 2
})
- 必须用
fl.Field().Interface(),不能用fl.Field().String()(对非字符串类型会 panic) - 指针字段需先
fl.Field().Elem().Interface(),否则取到的是*string而非string - 切片、map、结构体等复合类型,务必先判空再操作,避免 nil dereference
Gin 中注册必须走 binding.Validator.Engine()
自己 new 一个 validator.New() 然后注册,Gin 完全不会用——它只认自己绑定的那个实例。注册时机也关键:必须在第一次 c.ShouldBind() 之前,且不能早于 gin.Default()。
稳妥写法是 main 函数里立即获取并注册:
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("chinese_mobile", mobileValidator)
}
// ...
}
-
binding.Validator.Engine()返回interface{},必须类型断言为*validator.Validate - 注册名(如
"chinese_mobile")必须和 struct tag 中的validate:"chinese_mobile"完全一致,大小写敏感 - 重复注册会 panic,可用
v.HasRegisteredNamespace("xxx")防御
跨字段校验不能用 FieldLevel,得换 StructLevel
比如 “密码” 和 “确认密码” 一致性校验,FieldLevel 函数只能看到当前字段,拿不到另一个字段值。这时候必须用 RegisterStructValidation。
签名是 func(sl validator.StructLevel),通过 sl.CurrentStruct.Interface() 拿到整个结构体,再用反射或类型断言访问各字段:
v.RegisterStructValidation(func(sl validator.StructLevel) {
u := sl.CurrentStruct.Interface().(User)
if u.Password != u.ConfirmPassword {
sl.ReportError(reflect.ValueOf(u.Password), "ConfirmPassword", "confirm_password", "eqfield", "")
}
}, User{})
- 第二个参数传结构体类型字面量(
User{}),不是*User或reflect.TypeOf(User{}) - 报错要用
sl.ReportError(),传入目标字段的reflect.Value,否则错误不归到对应字段 - 字段名拼错、类型断言失败会导致校验逻辑被跳过,无任何提示
错误提示中文化必须配 RegisterTranslation
默认错误全是英文,像 Key: 'User.Name' Error:Field validation for 'Name' failed on the 'required' tag。不手动注册翻译,msg tag 也只覆盖内置规则,自定义规则的提示仍为空。
注册翻译模板时,key 是规则名(如 "chinese_mobile"),value 是带占位符的模板:
v.RegisterTranslation("chinese_mobile", trans, registerCustomTranslation("chinese_mobile", "{0} 不是合法的大陆手机号"))
- 必须在注册校验函数之后、首次校验之前调用,顺序错就失效
- 模板里的
{0}是字段名,{1}是 struct tag 中的label值(如有),别漏掉 - 没配翻译时,
err.(validator.ValidationErrors)里.Tag()返回规则名,但.Error()还是英文;想日志可读,这步绕不开
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











