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
性能优化建议
- 复用 validator 实例
- 对于复杂结构,预编译验证规则
- 避免在热路径上创建新的 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开发中使用。通过合理利用其泛型特性,可以大幅减少重复的验证代码,提高开发效率。