必须使用 validate 作为 struct tag 键名,因 go-playground/validator 仅识别该键;需全局复用 validator 实例、字段必须导出、json 与 validate tag 要对齐,错误需类型断言为 ValidationErrors 提取字段级信息。

struct tag 里用 validate 而不是 valid 或自定义字段名
Go 标准库不解析校验标签,必须依赖第三方库(如 go-playground/validator),而它约定使用 validate 这个 key。写成 valid、validation 或 check 都不会被识别——连 warning 都没有,静默失效。
常见错误现象:结构体字段明明写了 `json:"name" valid:"required"`,但调用 Validate.Struct() 却始终返回 nil 错误。
- 必须统一用
validate作为 struct tag 的键名 - 值部分支持链式规则,比如
validate:"required,email,max=100" - 嵌套结构体默认不递归校验,需显式加
validate:"dive" - 指针字段要小心空指针 panic,建议配合
omitempty或前置判空
初始化 validator 实例时启用 ValidateStruct 的 struct cache
每次调用 validate.Struct() 若没复用 validator 实例,会反复反射解析 struct tag,性能损耗明显。尤其在 HTTP handler 中高频调用时,QPS 可能掉 30%+。
正确做法是全局复用一个 *validator.Validate 实例,并在初始化时注册自定义函数(如手机号、身份证号校验):
立即学习“go语言免费学习笔记(深入)”;
var validate *validator.Validate
func init() {
validate = validator.New()
validate.RegisterValidation("chinese_mobile", validateChineseMobile)
}
- 不要在 handler 里每次 new 一个
validator.New() - 注册自定义校验函数必须在首次调用
Struct()前完成 - 若用
SetTagName("validate")改了 tag 名,所有 struct 都得同步改,不推荐
绑定 JSON 请求体时,json tag 和 validate tag 要对齐字段可导出性
如果结构体字段首字母小写(未导出),json 解码会失败,validate 校验也根本跑不到——因为字段压根没被反序列化进来。
典型错误场景:前端传了 {"user_name":"foo"},后端定义 UserName string `json:"user_name" validate:"required"` 是对的;但若写成 userName string(小写 u),则解码后字段为空,校验跳过,且无任何提示。
- 所有参与绑定和校验的字段必须是导出字段(首字母大写)
-
jsontag 的 value 和实际传入 key 必须完全一致(区分大小写、下划线) - 可加
omitempty避免零值干扰,但validate:"required"与之不冲突——required 检查的是字段是否被设置,不是是否为零值 - 时间字段建议用
time.Time+time.UnmarshalJSON,别用字符串手动 parse
错误信息提取别直接用 err.Error(),要用 validator.ValidationErrors 类型断言
Validate.Struct() 返回的 error 是个 interface,直接 err.Error() 得到的是全量堆栈式字符串,没法做前端友好的字段级提示(比如“邮箱格式错误”对应到 email 字段)。
必须类型断言为 validator.ValidationErrors,再遍历每个 FieldError 提取 Field()、Tag()、Value():
if err != nil {
if errs, ok := err.(validator.ValidationErrors); ok {
for _, e := range errs {
log.Printf("field %s failed %s, value: %v",
e.Field(), e.Tag(), e.Value())
}
}
}
- 不同语言提示需靠
e.Tag()匹配规则(如"required"→ “不能为空”) -
e.Param()可取 max=100 里的"100",用于动态拼提示 - 注意:嵌套结构体的
Field()返回的是带点路径的字符串,如"User.Address.ZipCode"


















