Go 结构体验证神器validator
文章目录
1: 概述
在 Web 服务、RPC 接口、配置加载这类场景里,我们经常要对输入数据做合法性校验;
如果完全手写
if/else或正则,一个字段一个字段判断,代码很快就会变得又长又散;github.com/go-playground/validator/v10是 Go 里非常经典的结构体校验库,支持通过 tag 对字段、结构体、切片、map 甚至跨字段关系做验证;gin 默认就集成了这套验证能力,所以很多 Go Web 项目其实早就在间接使用它;
原文参考:
https://mp.weixin.qq.com/s/Ycs22tC45BjrjnefRBtDnAvalidator的核心价值,不只是“少写几行判断”,而是把约束直接贴在数据结构上。这样一眼看结构体,就能知道这个模型的业务边界,也更容易避免脏数据一路流到服务内部。
2: 安装
| |
| |
- 新项目建议直接使用
v10版本; - 官方文档里也建议尽量复用同一个
Validate实例,因为内部会缓存结构体元信息,重复创建没有必要。
3: 最简单的例子
| |
- 上面这个例子里,
Name被要求长度在6~10之间,Age被要求在1~100之间; validate.Struct(u1)校验通过时返回nil;- 一旦校验失败,会返回包含字段名和 tag 信息的错误。
| |
- 这类错误信息比单纯返回
false实用得多,因为你能直接看出来到底是哪个字段违反了哪个规则; - 结构体越复杂、规则越多,
validator的收益就越明显。
4: 基本使用步骤
- 使用
validator做结构体校验,通常就 3 步:- 创建校验器:
validate := validator.New(); - 定义带
validatetag 的结构体; - 调用
validate.Struct(obj)执行校验;
- 创建校验器:
- 如果只想校验单个变量,也可以用
validate.Var(value, rule)。
| |
Var()比较适合校验单个字符串、单个参数、配置项;Struct()更适合接口入参、表单对象、配置对象这类完整模型;- 一般在业务里最常见的还是
Struct()。
5: 常见 tag 怎么看
validator的规则全部写在validate:"..."里;- 同一个字段可以组合多个规则:
,表示“并且”;|表示“或者”;
- 常见示例:
| |
这里面最常见的几个规则如下:
required:不能为空,也不能是类型默认值;min/max:字符串是长度限制,数字是范围限制;gte/lte:大于等于、小于等于;oneof:枚举值之一;email/url/ip:格式校验。
一个容易忽略的细节是:tag 之间不要插空格,例如
validate:"required, email"这种写法是有风险的,实际项目里应始终紧凑书写。
6: 结构体校验示例
| |
这个例子里故意留了几个错误:
Age=135超过了lte=130;FavouriteColor="#000-"不是合法颜色;Address.City没填,违反required。
这也是
validator很适合接口层的原因:一旦请求模型不合法,就能在很靠前的位置拦住,而不是等业务逻辑运行一半才报错。
7: dive 是什么
dive是validator非常好用的一个能力;- 它的意思是:当前字段如果是
slice、array、map这类容器,不要停在外层,继续往内部元素做校验。
| |
max=15作用在整个切片;dive后面的min=4作用在切片里的每一个元素;也就是说,
dive之后的规则,是“向里一层”再应用。如果是二维切片,也可以连续使用多个
dive:
| |
- 记忆方式很简单:每出现一次
dive,就继续往里走一层。
8: map 和跨字段校验
- 对
map做校验时,除了dive,还可以结合keys和endkeys校验 key; - 对注册表单这种场景,还经常会用到跨字段比较。
| |
eqfield=Password表示Password2必须等于Password;这个场景最典型的用途就是“重复输入密码”校验;
其他类似规则还有:
nefield:不能等于另一个字段;gtefield/ltefield:与同结构体字段比较大小;eqcsfield:跨嵌套结构体字段比较。
这些能力能让很多“字段之间有关系”的校验直接声明在 tag 里,减少散落在 handler 里的判断代码。
9: 自定义校验
- 内置 tag 虽然很多,但总有一些业务规则比较个性化;
- 这时可以通过
RegisterValidation()注册自定义规则。
| |
- 上面的逻辑表示
Name必须等于jimmy; - 当然,这只是为了演示自定义校验的写法,真实业务里你可以把它替换成手机号前缀、业务编号格式、日期区间等自己的规则;
- 如果业务逻辑已经复杂到“单个字段的 tag 表达不清”,也可以使用结构体级别校验。
10: 错误处理
- 很多人第一次用
validator时,都是直接fmt.Println(err); - 这在开发期够用,但线上一般还要把错误翻译成用户可读信息、日志字段或者错误码;
- 这时就需要把返回的
error断言成validator.ValidationErrors。
| |
- 常用的几个字段:
Field():字段名;Tag():失败的校验规则;ActualTag():真实规则,别名场景尤其有用;Value():当前值;Param():规则参数,例如130、20这种;
- 有了这些信息,就能自己拼装中文错误提示,或者对接国际化翻译器。
11: 在 Web 项目里的使用建议
validator最适合放在“请求进入业务之前”的那一层;- 常见落点包括:
- HTTP handler 参数绑定后;
- RPC 请求对象进入 service 前;
- 配置文件加载完成后;
- 定时任务参数解析后;
- 如果你在用 gin,很多场景下已经默认走了这套机制,只需要把规则写在结构体 tag 上。
| |
- 这种做法的好处是:
- 参数约束集中在模型定义上;
- handler 更干净;
- 错误处理更统一;
- 模型本身的可读性更强。
12: 小结
validator是 Go 项目里非常值得掌握的一类基础库;- 它最核心的能力是通过 tag 把约束贴到结构体上,让校验逻辑声明化;
- 对简单场景,
required、min/max、email这些内置 tag 已经足够实用; - 对复杂场景,还可以用
dive、跨字段比较、自定义校验、错误翻译来继续扩展; - 如果你的接口模型越来越多、规则越来越复杂,尽早把
validator用起来,通常比在业务代码里到处手写校验更稳、更清晰。
13: 参考
- 官方仓库:
https://github.com/go-playground/validator - 文档:
https://pkg.go.dev/github.com/go-playground/validator/v10 - 原文参考:
https://mp.weixin.qq.com/s/Ycs22tC45BjrjnefRBtDnA
文章作者 lucas(lpp)
上次更新 2026-03-26