golang基于泛型的通用数据验证插件库go-validator的使用

Golang基于泛型的通用数据验证插件库go-validator的使用

快速直观的Go验证库

这个库使用了govalidator项目中的Is...验证函数。

安装

go get github.com/tiendc/go-validator

使用

基本用法

import (
    vld "github.com/tiendc/go-validator"
)

type Person struct {
    FirstName string
    LastName  string
    Birthdate time.Time

    Unemployed bool
    Salary     uint
    Rank       string
    WorkEmail  string
    Projects   []string
    TaskMap    map[string]Task
}
var p Person

errs := vld.Validate(
    // 分别验证first和last name
    vld.StrLen(&p.FirstName, 3, 30).OnError(
        vld.SetField("first_name", nil),
        vld.SetCustomKey("ERR_VLD_PERSON_FIRST_NAME_INVALID"),
    ),
    vld.StrLen(&p.FirstName, 3, 30).OnError(
        vld.SetField("last_name", nil),
        vld.SetCustomKey("ERR_VLD_PERSON_LAST_NAME_INVALID"),
    ),

    // 或者使用Group来在其中一个失败时只产生一个错误
    vld.Group(
        vld.StrLen(&p.FirstName, 3, 30),
        vld.StrLen(&p.LastName, 3, 30),
    ).OnError(
        vld.SetField("name", nil),
        vld.SetCustomKey("ERR_VLD_PERSON_NAME_INVALID"),
    ),

    // 生日是可选的,但如果存在,必须在1950年和现在之间
    vld.When(!p.Birthdate.IsZero()).Then(
        vld.TimeRange(p.Birthdate, <1950-01-01>, time.Now()).OnError(...),
    )

    vld.When(!p.Unemployed).Then(
        vld.Required(&p.Salary),
        // 工作邮箱必须有效
        vld.StrIsEmail(&p.WorkEmail),

        // Rank必须是常量之一
        vld.StrIn(&p.Rank, "Employee", "Manager", "Director"),
        vld.Case(
            vld.When(p.Rank == "Manager").Then(vld.NumGT(&p.Salary, 10000)),
            vld.When(p.Rank == "Director").Then(vld.NumGT(&p.Salary, 30000)),
        ).Default(
            vld.NumLT(&p.Salary, 10000),
        ),

        // Projects是可选的,但如果存在,必须是唯一且排序的
        vld.When(len(p.Projects) > 0).Then(
            vld.SliceUnique(p.Projects).OnError(...),
            vld.SliceSorted(p.Projects).OnError(...),
        )
    ).Else(
        // 当人员失业时
        vld.NumEQ(&p.Salary, 0),
        vld.StrEQ(&p.WorkEmail, ""),
    ),

    // 验证切片元素
    vld.Slice(p.Projects).ForEach(func(elem int, index int, validator ItemValidator) {
        validator.Validate(
            vld.StrLen(&elem, 10, 30).OnError(
                vld.SetField(fmt.Sprintf("projects[%d]", index), nil),
                vld.SetCustomKey("ERR_VLD_PROJECT_NAME_INVALID"),
            ),
        )
    }),

    // 验证map条目
    vld.Map(p.TaskMap).ForEach(func(k string, v Task, validator ItemValidator) {
        validator.Validate(
            vld.StrLen(&v.Name, 10, 30).OnError(
                vld.SetField(fmt.Sprintf("taskMap[%s].name", k), nil),
                vld.SetCustomKey("ERR_VLD_TASK_NAME_INVALID"),
            ),
        )
    }),

    // 其他函数
    // 如果至少有一个验证通过则通过
    vld.OneOf(
        // 验证列表
    ),

    // 如果恰好有一个验证通过则通过
    vld.ExactOneOf(
        // 验证列表
    ),

    // 如果没有验证通过则通过
    vld.NotOf(
        // 验证列表
    ),
)

for _, e := range errs {
    detail, warnErr := e.BuildDetail()
    fmt.Printf("%+v\n", detail)
}

错误消息本地化

方法1:内联本地化(不推荐)

errs := Validate(
    NumLTE(&p.Age, 40).OnError(
        // 覆盖默认的英文模板
        SetTemplate("Tuổi nhân viên phải nhỏ hơn hoặc bằng {{.Max}}"),
    ),
)

for _, e := range errs {
    detail, warnErr := e.BuildDetail()
    fmt.Printf("%+v\n", detail)
}

方法2:使用其他本地化库(推荐)

// 假设你有2个定义错误消息的文件
// 在`error_messages.en`中:
// ERR_VLD_EMPLOYEE_AGE_TOO_BIG = "Employee {{.EmployeeName}} has age bigger than {{.Max}}"
// 在`error_messages.vi`中:
// ERR_VLD_EMPLOYEE_AGE_TOO_BIG = "Nhân viên {{.EmployeeName}} có tuổi lớn hơn {{.Max}}"

errs := Validate(
    NumLTE(&p.Age, 40).OnError(
        // 自定义参数(默认模板没有这个)
        SetParam("EmployeeName", p.Name),
        // 自定义键来定义要使用的自定义模板
        SetCustomKey("ERR_VLD_EMPLOYEE_AGE_TOO_BIG"),
    ),
)

for _, e := range errs {
    errKey := e.CustomKey()
    errParams := e.Params() // 或 e.ParamsWithFormatter()
    errorMsg := translationFunction(errKey, errParams) // 你需要提供这个函数
    fmt.Printf("%+v\n", errorMsg)
}

自定义错误参数格式化器

errs := Validate(
    NumLT(&budget, 1000000).OnError(
        SetField("Budget", nil),
    ),
)

// e.BuildDetail()可能产生消息`Budget must be less than 1000000`,
// 但你可能想要这样的消息: `Budget must be less than 1,000,000`.
// 让我们使用自定义格式化器

errs := Validate(
    NumLT(&budget, 1000000).OnError(
        SetField("Budget", nil),
        SetNumParamFormatter(NewDecimalFormatFunc('.', ',', "%f")),
    ),
)

贡献

  • 欢迎提交新功能和错误修复的拉取请求。

许可证

  • MIT License

更多关于golang基于泛型的通用数据验证插件库go-validator的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang基于泛型的通用数据验证插件库go-validator的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go-Validator: 基于泛型的通用数据验证库

go-validator 是一个利用 Go 1.18+ 泛型特性构建的轻量级数据验证库,它提供了简单而强大的方式来验证各种数据结构。下面我将详细介绍它的使用方法和示例代码。

基本安装

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

核心功能

1. 基本验证

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

type User struct {
	Name     string `validate:"required,min=3,max=32"`
	Email    string `validate:"required,email"`
	Age      int    `validate:"gte=18"`
	Password string `validate:"required,min=8"`
}

func main() {
	validate := validator.New()

	user := User{
		Name:     "John",
		Email:    "john@example.com",
		Age:      25,
		Password: "secure123",
	}

	err := validate.Struct(user)
	if err != nil {
		for _, err := range err.(validator.ValidationErrors) {
			fmt.Println(err.Field(), err.Tag())
		}
		return
	}

	fmt.Println("Validation passed!")
}

2. 自定义验证规则

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

// 自定义验证函数
func isCool(fl validator.FieldLevel) bool {
	return fl.Field().String() == "cool"
}

func main() {
	validate := validator.New()
	_ = validate.RegisterValidation("cool", isCool)

	type Data struct {
		Status string `validate:"cool"`
	}

	data := Data{Status: "cool"}
	err := validate.Struct(data)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Status is cool!")
}

3. 切片和Map验证

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

type Config struct {
	Servers []string `validate:"required,min=1,dive,ipv4"`
	Options map[string]int `validate:"dive,keys,required,endkeys,gt=0"`
}

func main() {
	validate := validator.New()

	config := Config{
		Servers: []string{"192.168.1.1", "10.0.0.1"},
		Options: map[string]int{"timeout": 30, "retries": 5},
	}

	err := validate.Struct(config)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Config is valid!")
}

4. 跨字段验证

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

type UserRegistration struct {
	Password        string `validate:"required,min=8"`
	PasswordConfirm string `validate:"required,eqfield=Password"`
}

func main() {
	validate := validator.New()

	reg := UserRegistration{
		Password:        "secure123",
		PasswordConfirm: "secure123",
	}

	err := validate.Struct(reg)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Passwords match!")
}

5. 使用泛型进行通用验证

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

// 泛型验证函数
func ValidateStruct[T any](v *validator.Validate, data T) error {
	return v.Struct(data)
}

type Product struct {
	ID    string `validate:"required,uuid"`
	Name  string `validate:"required,min=3"`
	Price float64 `validate:"gt=0"`
}

func main() {
	validate := validator.New()

	product := Product{
		ID:    "550e8400-e29b-41d4-a716-446655440000",
		Name:  "Laptop",
		Price: 999.99,
	}

	err := ValidateStruct(validate, product)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Product is valid!")
}

常用验证标签

  • 字符串: required, min, max, len, eq, ne, oneof, contains, excludes, startswith, endswith, email, url, uuid, alpha, alphanum, numeric, hexadecimal, lowercase, uppercase
  • 数字: gt, gte, lt, lte, eq, ne
  • 切片/数组: dive, min, max, len, unique
  • 时间: datetime, before, after
  • 其他: eqfield, nefield, required_with, required_without

性能优化建议

  1. 复用 validator 实例
  2. 对于复杂结构,预编译验证规则
  3. 避免在热路径上创建新的 validator 实例

错误处理最佳实践

if err != nil {
    if validationErrors, ok := err.(validator.ValidationErrors); ok {
        for _, fieldError := range validationErrors {
            fmt.Printf("Field %s failed validation with tag %s\n", 
                fieldError.Field(), 
                fieldError.Tag())
        }
    } else {
        fmt.Println("Non-validation error:", err)
    }
    return
}

go-validator 是一个功能强大且灵活的验证库,特别适合在微服务和API开发中使用。通过合理利用其泛型特性,可以大幅减少重复的验证代码,提高开发效率。

回到顶部