Go 结构体校验实战:用 validator 把参数合法性收拢到一处

test12026-10-051 次阅读

为什么不用手写 if 判断

接收外部请求时,参数校验最容易散落成一堆 if err != nil。go-playground/validator 用结构体 tag 统一描述规则,校验失败时一次性返回所有错误,配合自定义规则可以覆盖业务特有的约束(如密码强度、手机号格式),把"数据合法"这道关从业务代码里彻底抽离。

基础校验与错误收集

下面定义一个创建用户的入参,required、min、max、email、gte/lte 都是内置规则。校验失败时把错误转成字段级的 map,前端能精准提示。

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"unicode"

	"github.com/go-playground/validator/v10"
)

type CreateUser struct {
	Name     string `json:"name" validate:"required,min=2,max=20"`
	Email    string `json:"email" validate:"required,email"`
	Age      int    `json:"age" validate:"gte=0,lte=150"`
	Password string `json:"password" validate:"required,strongpwd"`
}

var validate = validator.New()

func handle(w http.ResponseWriter, r *http.Request) {
	var req CreateUser
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "bad json", http.StatusBadRequest)
		return
	}
	if err := validate.Struct(req); err != nil {
		verrs := make(map[string]string)
		for _, e := range err.(validator.ValidationErrors) {
			verrs[e.Field()] = fmt.Sprintf("字段 %s 不满足规则 %s", e.Field(), e.Tag())
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusUnprocessableEntity)
		json.NewEncoder(w).Encode(verrs)
		return
	}
	w.WriteHeader(http.StatusCreated)
}

注册自定义规则

内置规则覆盖不了密码强度这类业务约束时,用 RegisterValidation 注册一个函数即可。注意函数签名是 validator.FieldLevel。

func strongpwd(fl validator.FieldLevel) bool {
	pwd := fl.Field().String()
	var hasUpper, hasNum bool
	for _, r := range pwd {
		if unicode.IsUpper(r) {
			hasUpper = true
		}
		if unicode.IsDigit(r) {
			hasNum = true
		}
	}
	return len(pwd) >= 8 && hasUpper && hasNum
}

func init() {
	validate.RegisterValidation("strongpwd", strongpwd)
}

跨字段校验

需要两个字段联动判断时(如确认密码必须等于密码),用 register_validation 不方便,改用结构体验证函数:

var _ = validate.RegisterStructValidation(func(sl validator.StructLevel) {
	u := sl.Current().Interface().(CreateUser)
	if u.Password == "" {
		return
	}
	// 这里可以读取另一个字段做二次确认
}, CreateUser{})

三个实践要点

  • 校验器建议做成包级单例并在 init 里注册自定义规则,避免每次请求重复注册。
  • 给前端返回的错误用字段名做 key,不要直接抛英文 ValidationErrors,否则排查成本高。
  • 仅校验"进入系统的边界":内部函数之间传递的、已经可信的结构体不必再校验,省下性能。
T

test1

文章作者

为什么不用手写 if 判断 接收外部请求时,参数校验最容易散落成一堆 if err != nil。go-playgro...

分类
技术
发布时间
2026-10-05
字数
约 2105 字
阅读
1 次