函数式验证的核心在于校验逻辑的组合方式:每个规则是纯函数、无副作用、可组合、可复用;正确签名应为func(ctx context.Context, req interface{}) error,返回具体error或nil,校验层只判错不响应。

Validate 函数本身不是“函数式验证”的核心,真正体现函数式的是校验逻辑的组合方式:每个规则是纯函数、无副作用、可组合、可复用。
校验函数必须返回 error,不能用 bool 或结构体
Go 里写 func(req *User) bool 看似简洁,但一出错就只能 log.Fatal 或硬编码错误字符串,无法传递上下文。真实业务中你得知道是哪个字段、什么规则、为什么失败。
- 正确签名:
func(ctx context.Context, req interface{}) error—— 失败时 return 具体 error(如errors.New("email: invalid format")),成功时 returnnil - 避免在函数内调用
http.Error或panic:校验层只负责“判错”,不负责“响应” - 若需访问 DB 或缓存,用闭包注入:
func(db *sql.DB) ValidatorFunc { return func(ctx context.Context, req interface{}) error { ... } }
用切片组装验证链,别写嵌套 if
把多个校验函数塞进 []ValidatorFunc,按序执行,任一返回非 nil error 就中断。这不是语法糖,是控制流收口的关键。
- 注册顺序 = 执行顺序:
chain.Use(RequireToken).Use(ValidateEmail).Use(CheckQuota) - 跳过某环节?直接从切片里删掉对应函数,不用改逻辑分支
- 调试时加一行
log.Printf("→ %s", reflect.TypeOf(h).Name())就能看清哪步挂了 - 别在结构体里存
next ValidatorFunc:每次请求新建链,避免状态污染
StructLevel 校验必须手动注册,且只对顶层 struct 生效
跨字段规则(比如密码和确认密码一致)不会自动触发。validator 默认不看结构体整体,只扫单个字段。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 必须在初始化
v := validator.New()后、首次v.Struct()前调用v.RegisterStructValidation(..., User{}) - 回调函数签名固定:
func(fl validator.StructLevel),通过fl.CurrentStruct.Interface().(*User)拿值 - 报错必须用
fl.ReportError(u.Password, "Password", "Password", "password_mismatch"),否则前端收不到字段级定位 - 嵌套结构体(如
User.Profile)要单独注册,或把字段拉平到同一级,否则eqfield=Password找不到外层字段
动态规则别硬编码在 tag 里,用 VarWithValue 替代
validate:"max=50" 写死在结构体上,上线后改需求就得发版。真正的动态校验发生在运行时,靠的是值+规则字符串的组合。
立即学习“go语言免费学习笔记(深入)”;
- 用
v.VarWithValue(value, fieldName, ruleString):比如v.VarWithValue(req.Desc, "description", "required,max="+strconv.Itoa(limit)) - ruleString 必须是 validator 支持的语法(
gt=0、email),不能含空格或变量插值 - 别反复拼接字符串:提前缓存常用规则,或用 map[string]string 预置不同角色的规则模板
- 错误信息里
Key为空?那是用了Var而非VarWithValue—— 生产环境必须带字段名

















