自定义验证器

内置的验证规则不能满足所有需求,Gin 允许你注册自定义验证器。

注册验证器

使用 binding.Validator 注册自定义验证函数:

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
    "net/http"
    "regexp"
)

func main() {
    r := gin.Default()

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("phone", validatePhone)
        v.RegisterValidation("username", validateUsername)
    }

    r.POST("/users", createUser)

    r.Run(":8080")
}

func validatePhone(fl validator.FieldLevel) bool {
    phone := fl.Field().String()
    matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
    return matched
}

func validateUsername(fl validator.FieldLevel) bool {
    username := fl.Field().String()
    matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{2,19}$`, username)
    return matched
}

type CreateUserRequest struct {
    Username string `json:"username" binding:"required,username"`
    Phone    string `json:"phone" binding:"required,phone"`
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "username": req.Username,
        "phone":    req.Phone,
    })
}

常用自定义验证器

手机号验证

func validatePhone(fl validator.FieldLevel) bool {
    phone := fl.Field().String()
    matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
    return matched
}

使用:

type User struct {
    Phone string `binding:"required,phone"`
}

用户名验证

func validateUsername(fl validator.FieldLevel) bool {
    username := fl.Field().String()
    matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{2,19}$`, username)
    return matched
}

日期格式验证

import "time"

func validateDate(fl validator.FieldLevel) bool {
    date := fl.Field().String()
    _, err := time.Parse("2006-01-02", date)
    return err == nil
}

使用:

type Event struct {
    Date string `binding:"required,date"`
}

枚举验证

func validateStatus(fl validator.FieldLevel) bool {
    status := fl.Field().String()
    validStatuses := map[string]bool{
        "pending":   true,
        "approved":  true,
        "rejected":  true,
    }
    return validStatuses[status]
}

带参数的验证器

func validateMinAge(fl validator.FieldLevel) bool {
    param := fl.Param()
    minAge, _ := strconv.Atoi(param)
    age := fl.Field().Int()
    return age >= int64(minAge)
}

v.RegisterValidation("minage", validateMinAge)

使用:

type User struct {
    Age int `binding:"required,minage=18"`
}

跨字段验证

func validatePasswordConfirm(fl validator.FieldLevel) bool {
    password := fl.Field().String()
    confirmPassword := fl.Parent().FieldByName("ConfirmPassword").String()
    return password == confirmPassword
}

v.RegisterValidation("pwdconfirm", validatePasswordConfirm)

使用:

type RegisterRequest struct {
    Password        string `binding:"required,min=6"`
    ConfirmPassword string `binding:"required"`
}

完整示例

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
    "net/http"
    "regexp"
    "time"
)

type RegisterRequest struct {
    Username string `json:"username" binding:"required,username"`
    Email    string `json:"email" binding:"required,email"`
    Phone    string `json:"phone" binding:"required,phone"`
    Password string `json:"password" binding:"required,min=6"`
    Birthday string `json:"birthday" binding:"required,date"`
}

func main() {
    r := gin.Default()

    registerValidators()

    r.POST("/register", register)

    r.Run(":8080")
}

func registerValidators() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("username", validateUsername)
        v.RegisterValidation("phone", validatePhone)
        v.RegisterValidation("date", validateDate)
    }
}

func validateUsername(fl validator.FieldLevel) bool {
    username := fl.Field().String()
    matched, _ := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9_]{2,19}$`, username)
    return matched
}

func validatePhone(fl validator.FieldLevel) bool {
    phone := fl.Field().String()
    matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
    return matched
}

func validateDate(fl validator.FieldLevel) bool {
    date := fl.Field().String()
    _, err := time.Parse("2006-01-02", date)
    return err == nil
}

func register(c *gin.Context) {
    var req RegisterRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "message":  "注册成功",
        "username": req.Username,
    })
}

小结

这一章学习了:

  • 注册自定义验证器
  • 常用验证器实现
  • 带参数的验证器
  • 跨字段验证