bigywater.tech
Gin · 32

自定义校验器 RegisterValidation

内置规则不够用时怎么加自己的?注册要放在哪里、什么时候生效?规则名写错会在什么时候暴露?这篇从 Engine() 开始,把自定义规则、别名、结构体级校验、TagNameFunc 的坑和整体替换校验器的做法一一试出来。

*validator.Validate
binding.Validator.Engine() 返回的引擎类型
第一个请求才 panic
规则名没注册时,程序启动正常,第一次校验时 panic: Undefined validation function
nil
binding.Validator 设为 nil 之后,required 的空 JSON 也能通过

注册规则与别名

实测 · 本机执行输出
    binding.Validator.Engine() 的类型是 *validator.Validate: true
     nil(校验通过)
     Req.Day[tag=weekday param=""] Req.End[tag=gtfield param="Start"] Req.Code[tag=code8 param="8"]

binding.Validator.Engine().(*validator.Validate) 就是原生的 validator 引擎。RegisterValidation 注册一个 func(fl FieldLevel) bool,RegisterAlias 把一串规则起个名字;跨字段的规则(gtfield=Start)是内置的,可以直接用在 time.Time 上。别名失败时 Tag() 是别名本身。

参数、结构体级与 TagNameFunc

实测 · 本机执行输出
     nil(校验通过)
     Range.Tag[tag=startswithx param="ID-"] Range.Max[tag=gtefield_min param=""]
    --- 之后才注册 RegisterTagNameFunc:已经校验过的类型 Range 的名字不变,第一次校验的新类型 Range2 用 json 名
     Range  → Range.Tag[tag=startswithx param="ID-"] Range.Max[tag=gtefield_min param=""]
     Range2 → Range2.tag[tag=startswithx param="ID-"]

规则可以带参数,在 fl.Param() 里取。需要同时看多个字段时,用 RegisterStructValidation 在 StructLevel 里 ReportError。RegisterTagNameFunc 能让错误里的字段名换成 json 名,但 validator 会缓存每个类型的字段信息:必须在这个类型第一次被校验之前注册,否则对它不生效。

规则名写错

实测 · 本机执行输出
    路由注册完成、程序已启动,到这里没有任何报错
    第一个请求 → panic: Undefined validation function 'nosuchrule' on field 'Name'
    第二个请求 → panic: Undefined validation function 'nosuchrule' on field 'Name'

validator 在第一次校验某个结构体时才解析它的 tag。规则名拼错或忘了注册,启动时一切正常,第一个走到这个结构体的请求才会 panic(被 Recovery 捕获成 500)。给每个绑定用的结构体写一个最简单的校验单测,就能在 CI 里提前发现。

整体替换

实测 · 本机执行输出
     自定义校验器第 1 次被调用,拿到 *main.Req
     自定义校验器第 2 次被调用,拿到 *main.Req
     Validator=nil 时 err=<nil> q={Name:}

binding.Validator 是个接口变量,实现 ValidateStruct 与 Engine 两个方法就能替换。设成 nil 则所有绑定都不再校验(binding 标签完全失效),这在测试里有用,在线上是危险的。

分步演示

自定义校验
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew)/ Gin v1.12.0(go.mod 里 require,源码在模块缓存里);用 go run / go test 直接编译运行,需要库时经代理从 proxy.golang.org 下载;HTTP 实验用 httptest 或本机回环端口(9700 以上);计时用 time.Now / testing.B,数字每次运行会有小幅抖动。校验器是 Gin 内置的 validator v10.30.1;每个实验是一个独立的进程(全局的 binding.Validator 互不影响);请求用 httptest 直接调用 ServeHTTP;每种情形各一次
    • 没有验证:并发注册规则的安全性(validator 文档说明不是线程安全的,没有测);RegisterCustomTypeFunc(自定义类型的取值);RegisterTranslation 的错误翻译;替换成其它校验库(如 ozzo-validation)的完整集成
    京ICP备2026067502号-1京公网安备11010502063487号