字段必须首字母大写,否则validator完全跳过校验;小写字段即使带完整validate标签也不生效;嵌套字段、time.time(需转string)、query与json结构体分离、自定义规则注册到gin实例,均须严格满足导出前提。

字段首字母小写导致 validator 完全校验失效
validator 包根本看不到小写字段,不是“校验失败”,而是直接跳过——不报错、不提示、不校验。这是最常被忽略的静默陷阱。
- 结构体字段必须首字母大写,例如
Namestring,不能是namestring - 即使 tag 写得再全:
`validate:"required,email"`,小写字段也完全无效 - 嵌套结构体同理:内层字段也必须导出,否则
validate:"required,structonly"或dive都不生效 - 若需兼容 JSON key(如
json:"user_name"),结构体字段仍用大写(UserName),再加json:"user_name"映射,别为迁就 JSON 而牺牲可导出性
time.Time 字段加 datetime 标签毫无作用
validate:"datetime=2006-01-02" 只对 string 类型生效。给 time.Time 字段加这个 tag,校验永远通过,形同虚设。
- 正确做法:把时间字段声明为
string,例如CreatedAt string `validate:"required,datetime=2006-01-02T15:04:05Z"` - 校验通过后,再调用
time.Parse(time.RFC3339, req.CreatedAt)转换;err != nil时单独返回格式错误 - 避免依赖 Gin 的
binding:"2006-01-02",它可能使用本地时区或掩盖解析失败细节 - 零值
time.Time{}是0001-01-01,required不会触发,必须靠自定义函数判断是否为零时间
query 和 JSON 必须分用不同结构体
URL 查询参数(?page=abc&limit=10)和 JSON Body({"page":"abc","limit":10})在类型转换、容错行为、错误时机上完全不同,混用同一结构体会导致校验逻辑错乱或用户得不到明确反馈。
-
BindQuery遇到page=abc通常静默转为零值0,后续gte=1才失败;而BindJSON直接 decode 失败返回 400 - 正确做法:为 query 单独建
UserListQuery结构体,手动用strconv.ParseInt(r.URL.Query().Get("page"), 10, 64)并检查 err - Body 则走标准
BindJSON+validate标签组合,二者不可复用 - 指针字段(如
Page *int)在 query 中为 nil 时会被跳过,不是 bug,是设计行为;需额外做空值判断
自定义规则注册后仍不触发?检查注册目标实例
写了校验函数、调用了 RegisterValidation,但 tag 就是不生效——大概率是注册到了错误的 validator 实例上。
- Gin 内部持有自己的 validator 实例,
binding.Validator.Engine().(*validator.Validate).RegisterValidation(...)才能生效 - 注册必须在
gin.Default()之后、任何路由注册之前完成,顺序错则无效 - 别在 handler 里临时注册,也不要用新
validator.New()实例去注册,那跟 Gin 无关 - 常见错误:在中间件或某个 handler 里重复注册,或注册到全局未导出的变量中
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











