数据验证 #

一、验证概述 #

1.1 为什么需要验证 #

数据验证确保用户输入的数据符合预期格式和规则,防止无效数据进入系统。

1.2 验证方式 #

方式 说明
手动验证 代码中手动检查
结构体标签 使用validator库
自定义验证器 编写验证函数

二、使用Validator库 #

2.1 安装 #

bash
go get github.com/go-playground/validator/v10

2.2 基本使用 #

go
package main

import (
    "github.com/go-playground/validator/v10"
    "github.com/gofiber/fiber/v2"
)

type User struct {
    Name  string `validate:"required,min=2,max=50"`
    Email string `validate:"required,email"`
    Age   int    `validate:"required,min=0,max=150"`
    Phone string `validate:"omitempty,e164"`
}

var validate = validator.New()

func main() {
    app := fiber.New()
    
    app.Post("/users", func(c *fiber.Ctx) error {
        var user User
        if err := c.BodyParser(&user); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid request body",
            })
        }
        
        // 验证
        if err := validate.Struct(user); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error":  "Validation failed",
                "fields": formatErrors(err),
            })
        }
        
        return c.JSON(user)
    })
    
    app.Listen(":3000")
}

func formatErrors(err error) map[string]string {
    errors := make(map[string]string)
    
    for _, err := range err.(validator.ValidationErrors) {
        field := err.Field()
        tag := err.Tag()
        
        switch tag {
        case "required":
            errors[field] = field + " is required"
        case "email":
            errors[field] = field + " must be a valid email"
        case "min":
            errors[field] = field + " must be at least " + err.Param()
        case "max":
            errors[field] = field + " must be at most " + err.Param()
        default:
            errors[field] = field + " validation failed"
        }
    }
    
    return errors
}

三、常用验证规则 #

3.1 字符串验证 #

go
type StringValidation struct {
    Required  string `validate:"required"`                    // 必需
    Min       string `validate:"min=5"`                       // 最小长度
    Max       string `validate:"max=100"`                     // 最大长度
    MinMax    string `validate:"min=5,max=100"`               // 长度范围
    Email     string `validate:"email"`                       // 邮箱格式
    URL       string `validate:"url"`                         // URL格式
    Alpha     string `validate:"alpha"`                       // 仅字母
    Alphanum  string `validate:"alphanum"`                    // 字母和数字
    Numeric   string `validate:"numeric"`                     // 数字
    Lowercase string `validate:"lowercase"`                   // 小写
    Uppercase string `validate:"uppercase"`                   // 大写
    Contains  string `validate:"contains=foo"`                // 包含
    Excludes  string `validate:"excludes=bar"`                // 不包含
    StartsWith string `validate:"startswith=prefix"`          // 以...开始
    EndsWith   string `validate:"endswith=suffix"`            // 以...结束
    Regex     string `validate:"regex=^[a-z]+$"`              // 正则匹配
}

3.2 数字验证 #

go
type NumberValidation struct {
    Required int `validate:"required"`           // 必需
    Min      int `validate:"min=0"`              // 最小值
    Max      int `validate:"max=100"`            // 最大值
    MinMax   int `validate:"min=0,max=100"`      // 范围
    Positive int `validate:"gt=0"`               // 大于0
    Negative int `validate:"lt=0"`               // 小于0
    Range    int `validate:"gte=0,lte=100"`      // 0-100
    OneOf    int `validate:"oneof=1 2 3"`        // 枚举值
}

3.3 其他验证 #

go
type OtherValidation struct {
    EqField   string `validate:"eqfield=ConfirmPassword"`  // 等于其他字段
    NeField   string `validate:"nefield=OldPassword"`      // 不等于其他字段
    GtField   int    `validate:"gtfield=MinAge"`           // 大于其他字段
    GteField  int    `validate:"gtefield=MinAge"`          // 大于等于
    LtField   int    `validate:"ltfield=MaxAge"`           // 小于其他字段
    LteField  int    `validate:"ltefield=MaxAge"`          // 小于等于
    Unique    []string `validate:"unique"`                 // 唯一值
    Dive      []string `validate:"dive,min=3"`             // 数组元素验证
    OmitEmpty string `validate:"omitempty,email"`          // 空值跳过
}

四、验证中间件 #

4.1 通用验证中间件 #

go
func ValidateBody(s interface{}) fiber.Handler {
    validate := validator.New()
    
    return func(c *fiber.Ctx) error {
        // 创建结构体副本
        value := reflect.New(reflect.TypeOf(s).Elem()).Interface()
        
        if err := c.BodyParser(value); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid request body",
            })
        }
        
        if err := validate.Struct(value); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error":  "Validation failed",
                "fields": formatErrors(err),
            })
        }
        
        c.Locals("validated", value)
        return c.Next()
    }
}

// 使用
app.Post("/users", ValidateBody(&CreateUserRequest{}), createUser)

4.2 查询参数验证 #

go
func ValidateQuery(s interface{}) fiber.Handler {
    validate := validator.New()
    
    return func(c *fiber.Ctx) error {
        value := reflect.New(reflect.TypeOf(s).Elem()).Interface()
        
        if err := c.QueryParser(value); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid query parameters",
            })
        }
        
        if err := validate.Struct(value); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error":  "Validation failed",
                "fields": formatErrors(err),
            })
        }
        
        c.Locals("validated", value)
        return c.Next()
    }
}

五、自定义验证 #

5.1 注册自定义验证器 #

go
validate := validator.New()

// 注册自定义验证
validate.RegisterValidation("phone", func(fl validator.FieldLevel) bool {
    phone := fl.Field().String()
    matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
    return matched
})

// 使用
type User struct {
    Phone string `validate:"required,phone"`
}

5.2 验证函数示例 #

go
// 验证中国手机号
validate.RegisterValidation("cn_phone", func(fl validator.FieldLevel) bool {
    phone := fl.Field().String()
    matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
    return matched
})

// 验证身份证号
validate.RegisterValidation("id_card", func(fl validator.FieldLevel) bool {
    idCard := fl.Field().String()
    matched, _ := regexp.MatchString(`^\d{17}[\dXx]$`, idCard)
    return matched
})

// 验证密码强度
validate.RegisterValidation("strong_password", func(fl validator.FieldLevel) bool {
    password := fl.Field().String()
    hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
    hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
    hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
    hasSpecial := regexp.MustCompile(`[!@#$%^&*]`).MatchString(password)
    return hasUpper && hasLower && hasNumber && hasSpecial
})

六、错误处理 #

6.1 格式化错误信息 #

go
func formatErrors(err error) map[string]string {
    errors := make(map[string]string)
    
    for _, err := range err.(validator.ValidationErrors) {
        field := err.Field()
        tag := err.Tag()
        param := err.Param()
        
        var message string
        
        switch tag {
        case "required":
            message = fmt.Sprintf("%s is required", field)
        case "email":
            message = fmt.Sprintf("%s must be a valid email address", field)
        case "min":
            message = fmt.Sprintf("%s must be at least %s characters", field, param)
        case "max":
            message = fmt.Sprintf("%s must be at most %s characters", field, param)
        case "gte":
            message = fmt.Sprintf("%s must be greater than or equal to %s", field, param)
        case "lte":
            message = fmt.Sprintf("%s must be less than or equal to %s", field, param)
        case "oneof":
            message = fmt.Sprintf("%s must be one of: %s", field, param)
        default:
            message = fmt.Sprintf("%s validation failed on %s", field, tag)
        }
        
        errors[field] = message
    }
    
    return errors
}

6.2 中文错误信息 #

go
var errorMessages = map[string]string{
    "required": "字段不能为空",
    "email":    "必须是有效的邮箱地址",
    "min":      "长度不能小于%s",
    "max":      "长度不能大于%s",
    "gte":      "必须大于或等于%s",
    "lte":      "必须小于或等于%s",
    "gt":       "必须大于%s",
    "lt":       "必须小于%s",
    "oneof":    "必须是以下值之一: %s",
    "url":      "必须是有效的URL",
    "alpha":    "只能包含字母",
    "alphanum": "只能包含字母和数字",
    "numeric":  "必须是数字",
}

func formatErrorsCN(err error) map[string]string {
    errors := make(map[string]string)
    
    for _, err := range err.(validator.ValidationErrors) {
        field := err.Field()
        tag := err.Tag()
        param := err.Param()
        
        if msg, ok := errorMessages[tag]; ok {
            if param != "" {
                errors[field] = fmt.Sprintf(msg, param)
            } else {
                errors[field] = msg
            }
        } else {
            errors[field] = fmt.Sprintf("%s validation failed", field)
        }
    }
    
    return errors
}

七、完整示例 #

7.1 用户注册验证 #

go
package main

import (
    "github.com/go-playground/validator/v10"
    "github.com/gofiber/fiber/v2"
)

type RegisterRequest struct {
    Username string `json:"username" validate:"required,min=3,max=20,alphanum"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8,max=32,strong_password"`
    ConfirmPassword string `json:"confirm_password" validate:"required,eqfield=Password"`
    Phone    string `json:"phone" validate:"required,cn_phone"`
    Age      int    `json:"age" validate:"required,gte=18,lte=120"`
    Terms    bool   `json:"terms" validate:"required,eq=true"`
}

var validate *validator.Validate

func init() {
    validate = validator.New()
    
    // 注册自定义验证
    validate.RegisterValidation("cn_phone", validateCNPhone)
    validate.RegisterValidation("strong_password", validateStrongPassword)
}

func main() {
    app := fiber.New()
    
    app.Post("/register", func(c *fiber.Ctx) error {
        var req RegisterRequest
        if err := c.BodyParser(&req); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error": "Invalid request body",
            })
        }
        
        if err := validate.Struct(req); err != nil {
            return c.Status(400).JSON(fiber.Map{
                "error":  "Validation failed",
                "fields": formatErrors(err),
            })
        }
        
        // 注册逻辑...
        
        return c.JSON(fiber.Map{
            "message": "Registration successful",
        })
    })
    
    app.Listen(":3000")
}

八、总结 #

8.1 验证规则汇总 #

规则 说明
required 必需字段
email 邮箱格式
min 最小值/长度
max 最大值/长度
gte 大于等于
lte 小于等于
oneof 枚举值
url URL格式
alpha 仅字母
alphanum 字母数字

8.2 下一步 #

现在你已经掌握了数据验证,接下来让我们学习 自定义验证,编写更复杂的验证逻辑!

最后更新:2026-03-28