0


Go 验证器 validator 详解

开发过程中,我们往往需要对用户提交的数据进行验证,以保证数据的合法性和完整性。今天我们就来介绍 Go 语言社区推出的一个验证包 —— validator。

validator 包根据 tags 对结构体和单个字段的值进行验证。它具备以下优秀的功能:

  1. 提供了一系列验证规则用于验证,并且支持自定义验证规则;
  2. 支持跨字段、跨结构体进行验证;
  3. 支持多维字段如 array、slice、map 等;
  4. 在验证接口类型前会先确定它的底层数据类型;
  5. 支持自定义字段类型比如 sql 驱动程序 Valuer;
  6. 可以自定义并支持国际化(i18n)的错误信息;
  7. 是 gin 框架的默认验证器。

1.安装

使用

go get

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

然后在你的源码中引入 validator 包:

import"github.com/go-playground/validator/v10"

2.使用

2.1 基本使用

package main

import("fmt""github.com/go-playground/validator/v10")type User struct{
    FirstName string`validate:"required"`
    LastName  string`validate:"required"`
    Age       uint8`validate:"gte=0,lte=130"`}var validate *validator.Validate

funcmain(){
    validate = validator.New()validateStruct()}funcvalidateStruct(){
    user :=&User{
        FirstName:"Badger",
        LastName:"Smith",
        Age:135,}

    err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){
            fmt.Println(err.Namespace())// User.Age
            fmt.Println(err.Field())// Age
            fmt.Println(err.StructNamespace())// User.Age
            fmt.Println(err.StructField())// Age
            fmt.Println(err.Tag())// lte
            fmt.Println(err.ActualTag())// lte
            fmt.Println(err.Kind())// uint8
            fmt.Println(err.Type())// uint8
            fmt.Println(err.Value())// 135
            fmt.Println(err.Param())// 130
            fmt.Println(err.Error())// Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
            fmt.Println()}}}

2.2 跨字段验证

跨字段的验证规则有很多,具体可以查看文末规则表。下面的示例使用了

eqfield

规则,它表示当前字段必须等于指定的字段。

package main

import("fmt""github.com/go-playground/validator/v10")type User struct{
    Password        string`validate:"required"`
    ConfirmPassword string`validate:"required,eqfield=Password"`}var validate *validator.Validate

funcmain(){
    validate = validator.New()validateStruct()}funcvalidateStruct(){
    user := User{
        Password:"password",
        ConfirmPassword:"pass",}

    err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){
            fmt.Println(err.Error())// Key: 'User.ConfirmPassword' Error:Field validation for 'ConfirmPassword' failed on the 'eqfield' tag}}}

2.3 跨结构体验证

除了跨字段验证,validator 还支持跨结构体验证,在规则表中你会发现,跨结构体的验证规则通常是在跨字段的验证规则的

field

前加上

cs

,可以理解为

cross-struct

。所以下面示例的

eacsfield

就是用来验证当前字段是否等于制定结构体的字段:

package main

import("fmt""github.com/go-playground/validator/v10")type User struct{
    Uid     string`validate:"required,eqcsfield=Account.PayUid"`
    Account Account
}type Account struct{
    PayUid string`validate:"required"`}var validate *validator.Validate

funcmain(){
    validate = validator.New()validateStruct()}funcvalidateStruct(){
    account := Account{
        PayUid:"uid-1025",}

    user := User{
        Uid:"uid-1024",
        Account: account,}

    err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){
            fmt.Println(err.Error())// Key: 'User.Uid' Error:Field validation for 'Uid' failed on the 'eqcsfield' tag}}}

2.4 验证 slice、map

验证 slice 和 map 等复合数据类型时,通常需要加上

dive

表示递归验证,否则将不会深入验证数据类型内部元素。

package main

import("fmt""github.com/go-playground/validator/v10")type User struct{// 第一个 required 规则用于确保切片本身不为零值(即不为 nil)。// dive 规则指定了对切片元素进行递归验证。// 第二个 required 规则用于验证切片中的每个元素是否不为空。
    Accounts []string`validate:"required,dive,required"`// 第一个 required 规则用于确保 map 本身不为零值(即不为 nil)。// dive 规则指定了对 map 值进行递归验证。// gt=0 规则用于验证 map 中的每个值是否大于 0。// 第二个 required 规则用于验证 map 中的每个值是否不为空。
    Balance map[string]int`validate:"required,dive,gt=0,required"`}var validate *validator.Validate

funcmain(){
    validate = validator.New()validateStruct()}funcvalidateStruct(){
    user := User{
        Accounts:[]string{"account-1","","account-3"},
        Balance:map[string]int{"key1":1,"key2":-1,},}

    err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){// Key: 'User.Accounts[1]' Error:Field validation for 'Accounts[1]' failed on the 'required' tag// Key: 'User.Balance[key2]' Error:Field validation for 'Balance[key2]' failed on the 'gt' tag
            fmt.Println(err.Error())}}}

2.5 自定义类型验证器

package main

import("database/sql""database/sql/driver""fmt""reflect""github.com/go-playground/validator/v10")type DbBackedUser struct{
    Name sql.NullString `validate:"required"`
    Age  sql.NullInt64  `validate:"required"`}var validate *validator.Validate

funcmain(){

    validate = validator.New()// 注册所有的 sql.Null* 类型,使用 ValidateValuer 自定义类型函数进行验证
    validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{})

    x := DbBackedUser{
        Name: sql.NullString{String:"", Valid:true},
        Age:  sql.NullInt64{Int64:0, Valid:false},}

    err := validate.Struct(x)if err !=nil{
        fmt.Printf("Err(s):\n%+v\n", err)}}// ValidateValuer 实现了 validator.CustomTypeFunc 接口funcValidateValuer(field reflect.Value)interface{}{// 如果字段的类型实现了 driver.Valuer 接口(即支持向数据库写入值),则进行验证if valuer, ok := field.Interface().(driver.Valuer); ok {

        val, err := valuer.Value()// 获取字段的值if err ==nil{return val
        }// 处理错误}returnnil}

最终输出:

Err(s):
Key: 'DbBackedUser.Name' Error:Field validation for 'Name' failed on the 'required' tag
Key: 'DbBackedUser.Age' Error:Field validation for 'Age' failed on the 'required' tag

2.6 国际化/翻译

在前面的示例中,我们可以看到返回的信息都是英文的,如果需要翻译成中文信息,首先需要安装翻译包:

go get -u github.com/go-playground/locales
go get -u github.com/go-playground/universal-translator

接着我们修改 2.1 的代码,实现翻译:

package main

import("fmt""github.com/go-playground/locales/zh"
    ut "github.com/go-playground/universal-translator""github.com/go-playground/validator/v10"
    zhTrans "github.com/go-playground/validator/v10/translations/zh")type User struct{
    FirstName string`validate:"required"`
    LastName  string`validate:"required"`
    Age       uint8`validate:"gte=0,lte=130"`}var validate *validator.Validate
var trans ut.Translator

funcmain(){
    validate = validator.New()// 中文翻译器
    uniTrans := ut.New(zh.New())
    trans,_= uniTrans.GetTranslator("zh")// 注册翻译器到验证器
    err := zhTrans.RegisterDefaultTranslations(validate, trans)if err !=nil{panic(fmt.Sprintf("registerDefaultTranslations fail: %s\n", err.Error()))}validateStruct()}funcvalidateStruct(){
    user :=&User{
        FirstName:"Badger",
        LastName:"Smith",
        Age:135,}

    err := validate.Struct(user)if err !=nil{for_, err :=range err.(validator.ValidationErrors){// 翻译
            fmt.Println(err.Translate(trans))// Age必须小于或等于130}}}

3.错误处理

通过源码,我们可以发现

validator

返回的错误类型有两种,分别是参数错误

InvalidValidationError

和验证错误

ValidationErrors

// $GOPATH/pkg/mod/github.com/go-playground/validator/v10/errors.go// InvalidValidationError describes an invalid argument passed to// `Struct`, `StructExcept`, StructPartial` or `Field`type InvalidValidationError struct{
    Type reflect.Type
}// Error returns InvalidValidationError messagefunc(e *InvalidValidationError)Error()string{if e.Type ==nil{return"validator: (nil)"}return"validator: (nil "+ e.Type.String()+")"}// ValidationErrors is an array of FieldError's// for use in custom error messages post validation.type ValidationErrors []FieldError

// Error is intended for use in development + debugging and not intended to be a production error message.// It allows ValidationErrors to subscribe to the Error interface.// All information to create an error message specific to your application is contained within// the FieldError found within the ValidationErrors arrayfunc(ve ValidationErrors)Error()string{

    buff := bytes.NewBufferString("")for i :=0; i <len(ve); i++{

        buff.WriteString(ve[i].Error())
        buff.WriteString("\n")}return strings.TrimSpace(buff.String())}

通常情况下一般会忽略对

InvalidValidationError

的判断/处理。

4.常用验证规则

完整的验证规则表可以参阅这里。

详细的验证规则介绍可以参阅这里

4.1 比较

规则描述eq等于eq_ignore_case等于,忽略大小写ne不等于ne_ignore_case不等于,忽略大小写gt大于gte大于等于lt小于lte小于等于

4.2 跨字段校验

TagDescriptioneqfield当前字段必须等于指定字段nefield当前字段必须不等于指定字段gtfield当前字段必须大于指定字段gtefield当前字段必须大于等于指定字段ltfield当前字段必须小于指定字段ltefield当前字段必须小于等于指定字段fieldcontains当前字段必须包含指定字段,只能用于字符串类型fieldexcludes当前字段必须不包含指定字段,只能用于字符串类型

4.3 跨结构体跨字段校验

「跨结构体跨字段校验」规则与「跨字段校验」规则类似,一般是在「跨字段校验」规则的

field

前加上

cs

字符。
TagDescriptioneqcsfield当前字段必须等于指定结构体的字段necsfield当前字段必须不等于指定结构体的字段gtcsfield当前字段必须大于指定结构体的字段gtecsfield当前字段必须大于等于指定结构体的字段ltcsfield当前字段必须小于指定结构体的字段ltecsfield当前字段必须小于等于指定结构体的字段

4.4 字符串相关验证

TagDescriptionalpha仅限字母alphanum仅限字母、数字alphanumunicode仅限字母、数字和 Unicodealphaunicode字母和 UnicodeasciiASCIIboolean当前字段必须是能够被

strconv.ParseBool

解析为字符串的值contains当前字段必须包含指定字符串startswith当前字段必须以指定字符串开头startsnotwith当前字段必须不是以指定字符串开头endswith当前字段必须以指定字符串结尾endsnotwith当前字段必须不是以指定字符串结尾uppercase当前字段的字母必须是大写,可包含数字,不能为空lowercase当前字段的字母必须是小写,可包含数字,不能为空

4.5 格式化验证

TagDescriptionbase64当前字段必须是有效的 Base64 值,不能为空base64url当前字段必须是包含根据 RFC4648 规范的有效 base64 URL 安全值。json正确的 JSON 串rgb正确的 RGB 字符串rgba正确的 RGBA 字符串

4.6 其他验证

TagDescriptiondir指定字段的值必须是已存在的目录dirpath指定字段的值必须是合法的目录路径file指定字段的值必须是已存在的文件filepath指定字段的值必须是合法的文件路径len当前字段的长度必须指定值,可用于 string、slice 等max当前字段的最大值必须是指定值min当前字段的最小值必须是指定值required当前字段为必填项,且不能为零值required_if当指定字段等于给定值时,当前字段使用 required 验证。如

required_if=Field1 foo Field2 bar

required_unless当指定字段不等于给定值时,当前字段使用 required 验证。如

required_unless=Field1 foo Field2 bar

required_with当任一指定字段不为零值时,当前字段使用 required 验证。如

required_with=Field1 Field2

required_with_all当所有指定字段不为零值时,当前字段使用 required 验证。

标签: golang

本文转载自: https://blog.csdn.net/weixin_42427946/article/details/131465613
版权归原作者 OliverZ7 所有, 如有侵权,请联系我们删除。

“Go 验证器 validator 详解”的评论:

还没有评论