Go mod 好菜系列 - 0x07 validator 别再手搓参数校验了
详细讲 validator 在 Go 项目里为什么高频、标签校验怎么写、适合放在请求层还是业务层,以及什么时候别迷信自动校验。
接口一多以后,你很快就会发现一件事:参数校验这种活,写起来没有技术含量,但量特别大,而且特别容易重复。用户名不能为空、邮箱格式要对、年龄不能小于 0、分页参数得有上限……这些东西如果每个接口都手写 if,很快就会让人产生一种“我今天是不是在做表单保安”的感觉。
validator 是来解决什么的
它最核心做的事,就是把一部分通用的参数合法性检查,从一堆手写判断里抽出来,让你能用结构体 tag 的方式声明校验规则。
这类规则特别适合它:
- 必填
- 长度限制
- 邮箱格式
- 数值区间
- 枚举值约束
也就是说,它不是替你做所有业务校验,而是先帮你把“参数长得像不像话”这层事接过去。
为什么它在 Go 项目里这么高频
因为很多 Go Web 项目都会和 gin、echo、fiber 之类框架一起用,而这些框架在绑定请求结构体以后,天然就会碰到“下一步我要不要校验”的问题。validator 正好卡在这里。
最常见的写法
type CreateUserReq struct {
Name string `json:"name" validate:"required,min=2,max=32"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=0,lte=150"`
}validate := validator.New()
req := CreateUserReq{
Name: "Raymond",
Email: "ray@example.com",
Age: 18,
}
if err := validate.Struct(req); err != nil {
return err
}这套写法的好处特别直观:字段定义和字段约束放在一起,结构是收拢的,不用跳来跳去找。
常见 tag 很值得记住
requiredmin/maxgte/lteoneofemailomitempty
比如状态字段:
Status string `json:"status" validate:"oneof=draft published archived"`这类声明式约束很适合把一些常规输入边界固定下来。
和 gin 搭在一起为什么很常见
因为 gin 的请求绑定本来就是按结构体走的,所以很多项目会这样用:
func createUser(c *gin.Context) {
var req CreateUserReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if err := validate.Struct(req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"ok": true})
}这一层做的其实是“输入校验”,不是完整业务规则判断。
别把所有校验都往 validator 里塞
这是很重要的一点。validator 特别擅长的是字段级、结构级的格式和约束检查,但它并不适合承接复杂业务逻辑。
比如这些就不太适合全塞进去:
- 用户名是否已存在
- 当前用户有没有权限这么做
- 文章发布时间是否满足业务流程
这些本质上不是“参数格式问题”,而是业务规则问题,应该更多待在 service 层。
自定义校验也能做
validate.RegisterValidation("slug", func(fl validator.FieldLevel) bool {
value := fl.Field().String()
return regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(value)
})这样你就能补自己的规则。但还是那句话,别因为它支持扩展,就把整套业务判断都写成 tag 驱动迷宫。
错误信息别直接裸奔给用户
validator 返回的错误对开发者还行,对用户就未必友好了。真实项目里更常见的做法,是把这些错误整理成稳定、清楚的字段提示,而不是直接把原始报错抛前端。
小结
validator 这道菜特别像一个“省重复劳动”的工具:
- 它适合做输入参数的通用合法性校验
- 和 gin 这类框架结合很顺
- 字段格式约束适合它,复杂业务判断不适合它
- 能省很多重复
if,但别把它当业务引擎
下一篇我们讲 jwt。虽然前面在另一套系列里聊过鉴权思路,但这次会更聚焦到模块层面:项目里到底怎么把 JWT 用得像样。