应使用 github.com/go-playground/validator/v10 而非手写反射校验,但需确保字段导出、结构体非 nil、query 与 json 分用结构体、time.time 不直接用 datetime 标签、自定义规则正确注册至 gin 的 validator 实例。

直接用 github.com/go-playground/validator/v10,别自己手写反射校验逻辑——它已覆盖 95% 的参数校验场景,但必须做对几件事,否则字段静默跳过、时间总通过、自定义规则不触发。
结构体字段必须首字母大写且非 nil
validator 完全依赖反射读取字段,小写字母开头的字段(如 name string)会被忽略,不报错也不校验。传入 Validate.Struct() 的结构体指针也不能是 nil,否则 panic。
- 所有需校验字段必须导出:写成
Name string,不是name string - 嵌套字段(如
Address Address)默认递归校验;若字段是指针类型(如Child *Child),值为nil时内层直接跳过——这不是 bug,是设计行为 - 校验前加空值判断:
if req == nil { return errors.New("request is nil") }
query 和 JSON 必须分用不同结构体
URL 查询参数(?page=1&limit=abc)和 JSON Body({"name":"a","age":-5})解析机制、类型容错、错误时机完全不同,混用同一结构体会导致校验失效或行为不一致。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
BindQuery遇到limit=abc不会报错,而是让Limit int保持零值(0),后续gte=1才失败,用户得不到明确提示 -
BindJSON遇到"age": "25"直接 decode 失败返回 400,而BindQuery却可能容忍字符串转数字 - 正确做法:为 query 单独建
UserListQuery结构体,手动用strconv.ParseInt(r.URL.Query().Get("page"), 10, 64)转换并检查err;body 则走标准BindJSON + validator
time.Time 字段不能直接加 datetime 标签
validate:"datetime" 规则只对 string 类型生效。time.Time 字段加该标签毫无作用,校验永远通过。
- 把时间字段声明为
string,例如At string `validate:"datetime=2006-01-02T15:04:05Z"` - 校验通过后,再调用
time.Parse(time.RFC3339, req.At)转成time.Time,并在err != nil分支返回明确错误 - 避免依赖 Gin 的
binding:"time_format",它可能使用本地时区或掩盖格式错误
自定义规则必须向 Gin 的 validator 实例注册
写好函数还不够,validator.RegisterValidation("chinese_mobile", fn) 必须作用于 Gin 内部持有的那个 validator 实例,否则 tag 完全不触发。
- 注册时机:在
gin.Default()之后、任何路由注册之前,调用binding.Validator.Engine().(*validator.Validate).RegisterValidation(...) - 不能
v := validator.New()然后注册——Gin 根本不用你这个新实例 - tag 名大小写必须严格匹配,
binding:"chinese_mobile"和注册名"chinese_mobile"不一致就失效 - 跨字段校验(如 ConfirmPassword == Password)要用
RegisterStructValidation,FieldLevel拿不到其他字段
最常被忽略的是:校验错误返回前没做类型断言提取字段名和规则,直接 c.JSON(400, err) 会让前端拿到一串难以解析的原始结构;还有人把 omitempty 当成校验开关,其实它只影响 JSON 序列化,跟 validator 无关。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










