自定义验证 #
一、自定义验证概述 #
1.1 为什么需要自定义验证 #
内置验证规则无法满足所有业务需求:
go
type User struct {
Phone string `binding:"required,???"` // 需要验证手机号格式
IDCard string `binding:"required,???"` // 需要验证身份证号
Username string `binding:"required,???"` // 需要验证用户名规则
}
1.2 自定义验证流程 #
text
定义验证函数 → 注册验证器 → 使用验证标签
二、注册自定义验证器 #
2.1 基本注册 #
go
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
type User struct {
Phone string `binding:"required,phone"` // 使用自定义验证器
}
func main() {
r := gin.Default()
// 获取验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册自定义验证器
v.RegisterValidation("phone", validatePhone)
}
r.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run()
}
// 验证函数
func validatePhone(fl validator.FieldLevel) bool {
phone := fl.Field().String()
// 中国大陆手机号验证
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
}
2.2 验证函数签名 #
go
// validator.FieldLevel 接口
type FieldLevel interface {
Field() reflect.Value // 获取当前字段值
FieldName() string // 获取字段名
StructFieldName() string // 获取结构体字段名
Param() string // 获取验证参数
GetStructFieldOK() (reflect.Value, bool) // 获取其他字段
Top() reflect.Value // 获取顶层结构体
Parent() reflect.Value // 获取父结构体
}
三、常用自定义验证器 #
3.1 手机号验证 #
go
func validatePhone(fl validator.FieldLevel) bool {
phone := fl.Field().String()
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("phone", validatePhone)
}
r.Run()
}
3.2 身份证号验证 #
go
func validateIDCard(fl validator.FieldLevel) bool {
idCard := fl.Field().String()
// 18位身份证号验证
if len(idCard) != 18 {
return false
}
// 验证格式
matched, _ := regexp.MatchString(`^\d{17}[\dXx]$`, idCard)
if !matched {
return false
}
// 验证校验码
weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
checkCodes := "10X98765432"
sum := 0
for i := 0; i < 17; i++ {
sum += int(idCard[i]-'0') * weights[i]
}
checkCode := checkCodes[sum%11]
return strings.ToUpper(string(idCard[17])) == string(checkCode)
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("idcard", validateIDCard)
}
r.Run()
}
3.3 用户名验证 #
go
func validateUsername(fl validator.FieldLevel) bool {
username := fl.Field().String()
// 4-20位,字母开头,只含字母数字下划线
matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{3,19}$`, username)
return matched
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("username", validateUsername)
}
r.Run()
}
3.4 密码强度验证 #
go
func validatePassword(fl validator.FieldLevel) bool {
password := fl.Field().String()
// 至少8位,包含大小写字母和数字
if len(password) < 8 {
return false
}
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasDigit := regexp.MustCompile(`[0-9]`).MatchString(password)
return hasUpper && hasLower && hasDigit
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("strong_password", validatePassword)
}
r.Run()
}
3.5 日期范围验证 #
go
func validateDateRange(fl validator.FieldLevel) bool {
dateStr := fl.Field().String()
// 解析日期
date, err := time.Parse("2006-01-02", dateStr)
if err != nil {
return false
}
// 获取参数
param := fl.Param()
if param == "" {
return true
}
// 检查是否在指定范围内
switch param {
case "past":
return date.Before(time.Now())
case "future":
return date.After(time.Now())
default:
return true
}
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("date_range", validateDateRange)
}
r.Run()
}
3.6 枚举验证 #
go
func validateEnum(fl validator.FieldLevel) bool {
value := fl.Field().String()
param := fl.Param()
// 参数格式: "value1,value2,value3"
allowedValues := strings.Split(param, ",")
for _, v := range allowedValues {
if value == v {
return true
}
}
return false
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("enum", validateEnum)
}
r.Run()
}
// 使用
type Request struct {
Status string `binding:"required,enum=active,inactive,pending"`
}
四、跨字段验证 #
4.1 字段比较验证 #
go
func validateFieldEq(fl validator.FieldLevel) bool {
// 获取当前字段
field := fl.Field()
// 获取参数(要比较的字段名)
otherField := fl.Param()
// 获取其他字段
otherValue, _, ok := fl.GetStructFieldOK(otherField)
if !ok {
return false
}
// 比较
switch field.Kind() {
case reflect.String:
return field.String() == otherValue.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() == otherValue.Int()
default:
return false
}
}
4.2 条件验证 #
go
func validateRequiredIf(fl validator.FieldLevel) bool {
// 获取参数:field=value
param := fl.Param()
parts := strings.Split(param, "=")
if len(parts) != 2 {
return false
}
otherField := parts[0]
expectedValue := parts[1]
// 获取其他字段值
otherValue, _, ok := fl.GetStructFieldOK(otherField)
if !ok {
return true // 其他字段不存在,跳过验证
}
// 如果其他字段等于期望值,当前字段必填
if otherValue.String() == expectedValue {
return !fl.Field().IsZero()
}
return true
}
五、验证器管理 #
5.1 集中注册 #
go
package validator
import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
func InitValidators() {
v, ok := binding.Validator.Engine().(*validator.Validate)
if !ok {
return
}
// 注册所有自定义验证器
v.RegisterValidation("phone", validatePhone)
v.RegisterValidation("idcard", validateIDCard)
v.RegisterValidation("username", validateUsername)
v.RegisterValidation("strong_password", validatePassword)
v.RegisterValidation("date_range", validateDateRange)
v.RegisterValidation("enum", validateEnum)
// 注册结构体级别验证
v.RegisterStructValidation(validateStruct, User{})
}
// main.go
func main() {
validator.InitValidators()
r := gin.Default()
r.Run()
}
5.2 验证器配置 #
go
type ValidatorConfig struct {
validators map[string]validator.Func
}
func NewValidatorConfig() *ValidatorConfig {
return &ValidatorConfig{
validators: make(map[string]validator.Func),
}
}
func (c *ValidatorConfig) Register(name string, fn validator.Func) {
c.validators[name] = fn
}
func (c *ValidatorConfig) Apply() {
v, ok := binding.Validator.Engine().(*validator.Validate)
if !ok {
return
}
for name, fn := range c.validators {
v.RegisterValidation(name, fn)
}
}
func main() {
config := NewValidatorConfig()
config.Register("phone", validatePhone)
config.Register("idcard", validateIDCard)
config.Apply()
r := gin.Default()
r.Run()
}
六、验证错误处理 #
6.1 自定义错误消息 #
go
var errorMessages = map[string]string{
"phone": "手机号格式不正确",
"idcard": "身份证号格式不正确",
"username": "用户名必须是4-20位字母开头,只含字母数字下划线",
"strong_password": "密码至少8位,包含大小写字母和数字",
}
func getCustomErrorMsg(fe validator.FieldError) string {
if msg, ok := errorMessages[fe.Tag()]; ok {
return msg
}
return fe.Error()
}
func main() {
r := gin.Default()
r.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
var verr validator.ValidationErrors
if errors.As(err, &verr) {
errors := make(map[string]string)
for _, fe := range verr {
errors[fe.Field()] = getCustomErrorMsg(fe)
}
c.JSON(400, gin.H{"errors": errors})
return
}
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
})
r.Run()
}
七、完整示例 #
7.1 用户注册验证 #
go
package main
import (
"errors"
"regexp"
"strings"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
type RegisterRequest struct {
Username string `json:"username" binding:"required,username"`
Password string `json:"password" binding:"required,strong_password"`
Phone string `json:"phone" binding:"required,phone"`
Email string `json:"email" binding:"required,email"`
IDCard string `json:"idCard" binding:"omitempty,idcard"`
}
func main() {
r := gin.Default()
// 注册验证器
initValidators()
r.POST("/register", func(c *gin.Context) {
var req RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
handleValidationError(c, err)
return
}
c.JSON(200, gin.H{"message": "注册成功"})
})
r.Run()
}
func initValidators() {
v, ok := binding.Validator.Engine().(*validator.Validate)
if !ok {
return
}
v.RegisterValidation("phone", validatePhone)
v.RegisterValidation("idcard", validateIDCard)
v.RegisterValidation("username", validateUsername)
v.RegisterValidation("strong_password", validatePassword)
}
func handleValidationError(c *gin.Context, err error) {
var verr validator.ValidationErrors
if errors.As(err, &verr) {
errors := make(map[string]string)
for _, fe := range verr {
errors[fe.Field()] = getErrorMsg(fe)
}
c.JSON(400, gin.H{"errors": errors})
return
}
c.JSON(400, gin.H{"error": err.Error()})
}
func getErrorMsg(fe validator.FieldError) string {
fieldNames := map[string]string{
"Username": "用户名",
"Password": "密码",
"Phone": "手机号",
"Email": "邮箱",
"IDCard": "身份证号",
}
field := fieldNames[fe.Field()]
switch fe.Tag() {
case "required":
return field + "不能为空"
case "phone":
return "手机号格式不正确"
case "idcard":
return "身份证号格式不正确"
case "username":
return "用户名必须是4-20位字母开头"
case "strong_password":
return "密码至少8位,包含大小写字母和数字"
case "email":
return "邮箱格式不正确"
default:
return field + "验证失败"
}
}
八、总结 #
8.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 注册验证器 | RegisterValidation |
| 验证函数 | func(FieldLevel) bool |
| 获取字段值 | fl.Field() |
| 获取参数 | fl.Param() |
| 跨字段 | fl.GetStructFieldOK() |
8.2 最佳实践 #
| 实践 | 说明 |
|---|---|
| 集中管理 | 统一注册验证器 |
| 错误消息 | 提供友好的错误提示 |
| 复用验证 | 封装常用验证规则 |
| 测试覆盖 | 编写单元测试 |
8.3 下一步 #
现在你已经掌握了自定义验证,接下来让我们学习 错误处理,了解Gin的错误处理机制!
最后更新:2026-03-28