需调用v.RegisterValidation注册自定义校验函数,函数签名必须为func(fl validator.FieldLevel) bool,通过fl.Field().Interface()获取值并安全断言,返回true表示通过。

如何用 validator 包注册自定义校验函数
Go 生态中最常用的结构体校验库是 go-playground/validator,它不支持直接在 struct tag 里写逻辑,但允许通过 RegisterValidation 注册命名规则。关键点在于:函数签名必须是 func(fl validator.FieldLevel) bool,且返回 true 表示校验通过。
常见错误是把参数类型写成 interface{} 或漏掉 FieldLevel 的调用链,导致 panic 或静默失效。
- 注册前确保已初始化
validator.New()实例(不要复用全局未配置的默认实例) - 函数体内用
fl.Field().Interface()取值,注意类型断言安全,比如字符串要先判空再调len() - 错误信息靠 struct tag 中的
msg参数或单独调SetError控制,不建议在函数里panic
v := validator.New()
v.RegisterValidation("ltehalf", func(fl validator.FieldLevel) bool {
if s, ok := fl.Field().Interface().(string); ok {
return len(s) <= len(s)/2 // 显然不合理,仅示意结构
}
return false
})
struct tag 中怎么引用自定义规则名
注册时传入的第一个参数就是 tag 里写的规则名,大小写敏感,且不能含空格或特殊符号。它和内置规则(如 required、email)语法完全一致,可与其他规则用 , 连接。
容易踩的坑是规则名拼错、没注册就使用(此时校验直接跳过,不报错也不生效),或者误以为能传参数——tag 里不能带括号或值,所有参数必须从字段值或上下文推导。
- 正确:
json:"name" validate:"required,ltehalf" - 错误:
validate:"required,ltehalf(10)"(语法非法) - 若需参数化,得用
StructLevel或封装新 tag(如maxlen=10),再在自定义函数里解析fl.Param()
如何在校验失败时返回具体字段名和原因
validator 默认返回的 error 是泛型 ValidationErrors,需类型断言后遍历。每个 FieldError 提供 Field()、Tag()、Value() 和 Param(),但不直接暴露自定义函数里的提示文本。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
真正可控的错误文案,得靠 RegisterTranslation 绑定翻译模板,否则 fallback 到默认英文。中文项目务必配这一步,否则日志或 API 返回全是英文 Key: 'User.Name' Error:Field validation for 'Name' failed on the 'ltehalf' tag。
- 翻译模板中用
{0}表示字段名,{1}是参数(即fl.Param()返回值),{2}是 tag 名 - 注册翻译必须在注册校验函数之后,否则无效
- 如果只是调试,可用
err.(validator.ValidationErrors).Translate(trans),其中trans是调用ut.AddTranslator(...)后得到的实例
为什么自定义规则里不能直接访问其他字段或上下文数据
FieldLevel 函数只提供当前字段值和少量元信息,无法拿到整个 struct 实例或相邻字段。这是设计使然:单字段校验保证可组合性与性能,避免反射开销和循环依赖。
若业务需要跨字段判断(例如 end_time 必须大于 start_time),必须改用 StructLevel 注册函数,它接收 StructLevel 接口,可通过 sl.CurrentStruct.Interface() 拿到完整对象。
-
StructLevel函数粒度更粗,适合一致性校验,但不能用于单字段复用场景 - 别试图在
FieldLevel里用reflect.ValueOf(sl.CurrentStruct).FieldByName("X")——sl在FieldLevel回调里不可用 - 混合使用时注意执行顺序:struct 级先于字段级,且任一失败都会中断后续校验(除非显式设置
Validate.StructPartial)
最常被忽略的是规则作用域边界:自定义函数不是“方法”,没有 receiver,也没有隐式上下文。所有依赖都得显式传入或从字段值反推。

















