使用validator校验结构体前,字段必须首字母大写(导出)、非nil指针;嵌套结构体默认递归校验;query与JSON应分用不同结构体;time.Time不可直接加datetime标签;自定义规则须通过RegisterValidation注册。

用 validator 校验结构体字段前,必须确保字段可导出且已解引用
Go 的 validator 库(github.com/go-playground/validator/v10)靠反射读取字段和标签,它**完全忽略小写字段**。如果你写 type UserReq struct { name string `validate:"required"` },校验会静默跳过 name,不报错也不检查。
- 所有需校验的字段必须首字母大写(即导出字段),如
Name、Email - 传给
Validate.Struct()的不能是nil指针,否则 panic;要先判空:if req == nil { return errors.New("request is nil") } - 若结构体字段本身是指针(如
Child *Child),validator遇到nil会跳过其内部校验——这是默认行为,不是 bug,但容易误以为“没生效” - 嵌套结构体字段(如
Address Address)即使全零值也会被递归校验,除非你显式加omitempty标签并配合required
query 和 json 参数别混用同一结构体自动绑定
URL 查询参数(?page=1&limit=abc)和 JSON Body({"name":"a","age":-5})语义不同、来源不同、解析方式也不同。很多开发者图省事,定义一个 UserReq 结构体,既用在 BindQuery 又用在 BindJSON,结果出问题:
-
BindQuery不会把limit=abc转成 int 并报错,而是让limit字段保持零值(0),后续校验gte=1才失败——但用户根本不知道自己输错了类型 -
BindJSON遇到"age": "25"(字符串)会直接 decode 失败,返回 400,而BindQuery却能容忍字符串转数字(依赖框架实现),行为不一致 - 更稳妥的做法:为 query 单独建
UserListQuery结构体,手动用strconv.ParseInt(r.URL.Query().Get("page"), 10, 64)转换并检查 error,再塞进结构体;body 则用标准BindJSON + validator
time.Time 字段校验不能直接放结构体里用 validate tag
validator 对 time.Time 类型没有原生支持,validate:"datetime" 这类规则只对 string 字段有效。如果你写:
type EventReq struct {
At time.Time `validate:"datetime"`
}
校验永远通过,因为 time.Time 不是 string,datetime 规则压根不触发。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:把时间字段声明为
string,用validate:"datetime=2006-01-02T15:04:05Z"或datetime=RFC3339 - 接收后手动调用
time.Parse转成time.Time,并在 error 分支返回明确提示 - 不要依赖框架自动转换(如 Gin 的
binding:"time_format"),它可能掩盖格式错误或使用本地时区,导致逻辑偏差
自定义校验规则别绕开 RegisterValidation 直接手写 if 判断
遇到手机号、身份证、IP 段等业务规则,有人习惯在校验函数里写一堆 if strings.HasPrefix...,这会导致校验逻辑散落、无法复用、难测试。
- 必须用
v.RegisterValidation("chinese_mobile", validateChineseMobile)注册,然后在 struct tag 里写validate:"chinese_mobile" - 自定义函数签名固定为
func(fl validator.FieldLevel) bool,里面用fl.Field().String()取值,fl.Param()取参数(如chinese_mobile=11) - 注册后,该规则就和内置规则一样参与嵌套校验、错误聚合、翻译流程——这才是可维护的关键
- 别在 handler 里重复写
if !isValidMobile(req.Phone) { ... },那等于放弃validator的整套错误处理链路


















