Go中常用go-playground/validator对结构体字段校验,需加validate tag且字段导出;Gin中可用ShouldBind自动校验;支持嵌套结构体和自定义函数,但后端校验是安全兜底而非前端备份。

用 validator 包做结构体字段级校验
Go 本身没有内置表单校验机制,最常用且稳定的方式是借助第三方包 go-playground/validator 对接收的结构体做字段级验证。它不依赖 HTTP 框架,可直接作用于 struct 实例,适合搭配 json.Decode 或表单解析(如 r.ParseForm())后赋值使用。
关键点:必须为结构体字段添加 validate tag,且字段需为导出字段(首字母大写);校验失败时返回 validator.ValidationErrors 类型错误,需手动转换为可读提示。
type LoginForm struct {
Username string `json:"username" validate:"required,min=3,max=20"`
Password string `json:"password" validate:"required,min=6"`
Email string `json:"email" validate:"required,email"`
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var form LoginForm
if err := json.NewDecoder(r.Body).Decode(&form); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
validate := validator.New()
if err := validate.Struct(form); err != nil {
if errs, ok := err.(validator.ValidationErrors); ok {
for _, e := range errs {
log.Printf("Field: %s, Tag: %s, Value: %v", e.Field(), e.Tag(), e.Value())
}
}
http.Error(w, "Validation failed", http.StatusBadRequest)
return
}
// 校验通过,继续处理登录逻辑
}
在 Gin 中用 ShouldBind 自动触发校验
Gin 内置了对 validator 的集成,调用 c.ShouldBind(&obj) 会自动执行结构体校验,并将错误写入上下文。比手写 validate.Struct() 更简洁,但要注意它默认只校验 json、form、query 等常见来源,且要求结构体 tag 同步支持对应格式(如 form:"username")。
-
ShouldBind会根据 Content-Type 自动选择绑定方式:JSON 请求走BindJSON,application/x-www-form-urlencoded走BindForm - 若需统一处理所有校验错误,建议用 Gin 的
CustomValidator接口替换默认校验器,例如添加自定义错误消息映射 - 别在
ShouldBind后再调用validate.Struct()—— 重复校验且可能掩盖原始错误位置
type RegisterForm struct {
Name string `form:"name" validate:"required,len=2|len=3"` // 注意:len=2|len=3 是非法写法,应写成 len=2 或 len=3,或用 oneof
Email string `form:"email" validate:"required,email"`
}
func registerHandler(c *gin.Context) {
var form RegisterForm
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "success"})
}
处理嵌套结构体与自定义校验函数
表单常含嵌套数据(如地址对象、多组标签),validator 支持递归校验嵌套结构体,只需确保内层结构体也带 validate tag。但遇到业务规则(如“密码和确认密码必须一致”“开始时间不能晚于结束时间”),就得注册自定义函数。
立即学习“go语言免费学习笔记(深入)”;
注册自定义校验函数后,必须用 RegisterValidation 绑定名称,并在 tag 中引用该名称。注意:自定义函数签名固定为 func(fl validator.FieldLevel) bool,且无法直接访问整个结构体实例 —— 如需跨字段比较,得把相关字段合并到同一结构体中,或改用 StructLevel 校验。
- 嵌套结构体校验默认启用,无需额外配置
- 自定义函数名在 tag 中不加括号,如
validate:"eqfield=Password",不是eqfield(Password) -
EqField等内置跨字段校验已存在,优先复用,避免重复造轮子
type SignupRequest struct {
User User `json:"user" validate:"required"`
Password string `json:"password"`
Confirm string `json:"confirm" validate:"eqfield=Password"`
}
type User struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
// 注册自定义校验:手机号格式
func isMobile(fl validator.FieldLevel) bool {
s := fl.Field().String()
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, s)
return matched
}
// 在初始化时注册
validate.RegisterValidation("mobile", isMobile)
前端传参与后端校验的边界要分清
后端校验不是前端校验的备份,而是最后一道防线。很多开发者误以为 “前端做了 required 和长度限制,后端就不用校验了”,这是危险的。HTTP 请求可被任意构造,validator 的作用是防御性兜底,不是增强体验。
容易忽略的点:
- 空字符串
""、零值数字0、nullJSON 字段都可能绕过前端 JS 判断,但后端结构体字段若为非指针类型(如int、string),解码后就是零值,requiredtag 仍会生效 - 前端用
FormData提交时,若字段名拼错或未设置name,后端结构体对应字段会是零值,required依然捕获 - 不要在
validatetag 里写过于复杂的正则(如校验身份证),既难维护又影响性能;复杂逻辑应放在业务 handler 中单独判断
真正难处理的是动态字段(如用户自定义表单)、字段名运行时才确定、或需要查数据库才能判断是否唯一 —— 这些无法靠结构体 tag 解决,得在绑定后手动补校验。










