validator.struct() 必须在 json 解析后立即调用,即 json.unmarshal 或 c.shouldbind() 完成、业务处理前;需确保结构体字段导出且嵌套字段加 dive 标签,避免空格等边界校验遗漏。

validator.Struct() 必须在 JSON 解析后立即调用
很多开发者把 v.Struct() 放在业务逻辑中间,甚至塞进 if 分支里手动判断字段长度或正则——这既重复又容易漏掉空格、Unicode 控制字符等边界情况。校验必须发生在 json.Unmarshal 或框架绑定(如 c.ShouldBind())之后、任何业务处理之前。
- 正确时机:解析完请求体,拿到结构体实例的那一刻就调用
v.Struct() - 错误写法:
if req.Email == "" { ... }—— 这绕过了 validator 的统一规则,且无法捕获" "这类全空格字符串 - 注意:Gin 的
c.ShouldBind()默认只读binding标签;要启用validate标签,结构体需同时带json和validate,且确保全局 validator 实例已注册
required 不等于非空字符串,空格会通过校验
required 规则对 string 类型只检查是否为零值(即 ""),但用户提交的 " "、" "(全角空格)或 "\t\n" 都会被视为有效值。这类数据在表单场景中毫无意义,必须额外处理。
- 方案一:用
trim预处理 —— 在校验前对所有 string 字段调用strings.TrimSpace() - 方案二:自定义规则 —— 注册
required_without_whitespace,内部先 trim 再 IsZero - 方案三:组合使用
required,gt=0—— 但要注意gt=0对 string 是长度判断,仅适用于 ASCII 空格,对 Unicode 空格仍可能失效
GORM 钩子中验证不能替代请求层校验
BeforeCreate 或 BeforeSave 钩子适合做数据库约束级检查(如“邮箱不能超 100 字符”),但不适合替代 HTTP 层的语义校验(如“邮箱格式必须合法”)。两者职责不同,缺一不可。
- 请求层校验(validator):拦截非法输入,早失败、早返回,避免无效数据进入业务流程
- GORM 钩子校验:兜底防护,防止绕过 API 直接调用模型方法插入脏数据
- 陷阱:只在钩子里做
emailRegex.MatchString(u.Email),但前端传了个空字符串过来 —— 此时钩子不触发(因为u.Email == "",BeforeCreate仍会执行,但你没判空),而 validator 又没用,数据就直接入库了
嵌套结构体或切片校验必须显式加 dive
validator 默认不递归校验嵌套字段。比如 Users []User `validate:"dive"` 中的 dive 不是可选修饰,而是必需标记;否则 User 内部的 required、email 规则全部被忽略。
-
dive表示“进入当前字段的每个元素进行校验”,适用于 slice、array、map - 多层嵌套需叠加,例如
Orders []Order `validate:"dive"`+Order struct { Items []Item `validate:"dive"` } - 注意:如果嵌套结构体字段本身未导出(首字母小写),validator 读不到其标签,
dive也无效 —— 所有参与校验的字段都必须是导出字段
dive 漏写,整个校验链就静默失效,没有任何 panic 或 warning。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











