go 中验证需绑定结构体、收口错误、兜住零值;validator.v10 的 structlevel 需手动注册跨字段校验,required 对 time.time/int/[]string 无效,自定义函数须符合 fieldlevel 签名,validator 应启动时单例初始化且仅校验导出字段。

Go 里没有“校验函数封装”这回事——你真正要做的,是把验证逻辑和结构体绑定、把错误处理路径收口、把零值陷阱显式兜住。
validator.v10 的 StructLevel 验证必须手动注册
跨字段校验(比如 Password 和 ConfirmPassword 是否一致)不会自动触发,validate:"eqfield=ConfirmPassword" 只适用于同类型、可直接比较的字段;若涉及加密、脱敏或业务逻辑(如密码需满足复杂度再比对),就得用 RegisterStructValidation。
- 注册回调只在该 struct 被
validate.Struct()调用时执行,不递归进嵌套 struct - 子结构体要单独注册,或在其内部也加
RegisterStructValidation -
sl.Current().Interface().(User)强转前务必确认类型,否则 panic;建议加类型断言检查 - 报错用
sl.ReportError(u.Password, "Password", "Password", "password_mismatch", ""),第四个参数是自定义错误码,后续可做 i18n 映射
time.Time、int、[]string 的 required 标签根本没用
required 判断的是“是否为零值”,而这些类型的零值在业务中往往合法甚至常见:
-
time.Time{}是0001-01-01,validate:"required"不会失败;得用validate:"required,datetime=2006-01-02"或自定义函数判非零时间 -
int零值是0,若业务中年龄不能为 0,得写validate:"gt=0"或validate:"ne=0" -
[]string{}是空切片,required不报错;要确保至少一个元素,必须写validate:"min=1,dive,required"——注意dive必须紧跟在切片 tag 后,validate:"required,dive"会被忽略
自定义验证函数必须带 FieldLevel 参数且返回 bool
用 RegisterValidation 添加规则时,签名固定为 func(fl validator.FieldLevel) bool,不是任意函数都能塞进去:
- 不能捕获外部变量做状态判断(比如累计错误数),因为每次校验都是新调用
- 字段值通过
fl.Field().Interface()获取,类型断言需谨慎;字符串用fl.Field().String()更安全 - 若需访问同结构体其他字段,只能走
fl.Parent().Interface()再强转,不能直接用fl.Top().Interface() - 错误提示靠
fl.Error()返回,但更推荐统一用fl.ReportError(...)保持风格一致
validator 实例不该全局复用,但也不必每次 new
validator.New() 开销不大,但它的配置(如注册的自定义规则、翻译器)是实例级的。常见误用:
- 在 HTTP handler 里每次请求都
validator.New()——没必要,浪费内存 - 全局单例但没注册自定义规则,结果 struct 验证时规则不生效
- 并发调用时修改 validator 实例(如动态 RegisterValidation)——非线程安全,会导致 panic 或规则丢失
- 正确做法:应用启动时初始化一个实例,注册所有规则,之后所有地方复用它
最易被忽略的一点:validator 只校验它能通过反射安全读取的字段——首字母小写的字段、未导出字段、嵌套 struct 中未导出的字段,一律跳过。别指望 tag 写了它就认得。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











