应直接使用 github.com/go-playground/validator/v10,因其更稳、更快、生态更全;手写 tag 解析+反射校验易出错、类型不安全、嵌套切片支持难、错误上下文丢失。

直接用 github.com/go-playground/validator/v10,别自己解析 tag 写反射校验——99% 的场景它更稳、更快、生态支持更全。
为什么不该手写 tag 解析 + 反射校验
自己 parse validate:"required,min=3" 字符串、再用 reflect 逐字段取值、再 dispatch 到不同校验函数,看似“可控”,实际踩坑密集:
- 字符串解析易出错:空格、等号位置、逗号分隔逻辑要反复测试(
"required, min=3"会失效) - 类型安全难保障:
fl.Field().Interface()后需手动断言,int64和int混用、指针解引用 panic 频发 - 嵌套和切片支持成本高:要自己实现
dive逻辑、处理omitempty跳过规则、区分*T和T的零值语义 - 错误上下文丢失:手写容易只返回
"invalid",无法带出字段名、失败规则、原始值,前端根本没法做精准提示
validator.New() 必须在首次校验前完成注册
自定义规则(如 validate:"mobile")或中文翻译不生效?大概率是 RegisterValidation 或 RegisterTranslation 调用太晚。
- 注册必须在第一次调用
v.Struct()之前——放在init()最稳妥 - 自定义函数签名严格为
func(fl validator.FieldLevel) bool,返回false表示失败 - 若用
validator.Default()(v10 已弃用),注册会被忽略;必须用新实例:v := validator.New() - 跨字段校验(如密码一致性)不能用
FieldLevel,得改用RegisterStructValidation,否则拿不到其他字段值
required 对 string/int/time 的“零值”陷阱
required 不等于“非空字符串”或“非零数字”,它只判断 Go 的零值:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
-
string:仅""触发失败," "(空格)、"\t\n"全部放过 -
int:零值是0,required永远不报错;业务中 0 非法?改用gt=0或ne=0 -
time.Time:零值是0001-01-01,required不触发;需配合datetime规则或自定义函数判非零 -
[]string:空切片[]是合法零值,required不报错;要求数组非空?加min=1
嵌套结构体 + omitempty 的真实行为
很多人以为 omitempty 是“有值才校验”,其实它只控制**是否跳过整个子结构体校验**,且只对特定类型有效:
- ✅ 有效类型:
*T、map、slice、chan、func、interface{} - ❌ 无效类型:
T(普通 struct),哪怕所有字段都是零值,也会递归进子字段校验 -
Child *Address `validate:"omitempty"`:当Child == nil时,完全跳过Address校验 -
Child Address `validate:"omitempty"`:omitempty被忽略,Address{}仍会校验其内部required字段 - 想让普通 struct 也“可选”?只能改字段为指针,或在校验前手动判空:
if req.Child != (Address{}) { ... }
真正麻烦的不是写对那几行 validate tag,而是理解 validator 只按反射能安全读取的方式工作——它不会帮你 trim 空格、不会自动 parse 时间字符串、也不会猜你“其实想校验的是非空白”。这些都得靠前置清洗或自定义规则兜底。

















