• GO请求参数规则校验(自定义校验规则、规则中文化)



    ​ 参数校验是开发中不可或缺的重要组成部分,本章节我们将介绍参数的校验,采用的是 go-playground/validator。对应的github地址: https://github.com/go-playground/validator

    1 简介

    ​ 验证器是基于标签实现结构和单个字段的值验证。它具有以下独特功能:

    ​ 1,使用验证标签或自定义验证器进行跨字段和跨结构验证。

    ​ 2,切片、数组和映射潜水,允许验证多维字段的任何或所有级别。

    ​ 3,能够深入研究地图键和值以进行验证

    ​ 4,通过在验证之前确定它的基础类型来处理类型接口。

    ​ 5,处理自定义字段类型,例如 sql driver Valuer 请参阅Valuer

    ​ 6,别名验证标签,允许将多个验证映射到单个标签,以便更轻松地定义结构上的验证

    ​ 7,提取自定义字段名称,例如可以指定在验证时提取 JSON 名称,并使其在生成的 FieldError 中可用

    ​ 8,可定制的 i18n 感知错误消息。

    ​ 安装此验证器只需要下面几个步骤,第一步在项目模块获取包,第二步将验证器引入到代码中:

    go get github.com/go-playground/validator/v10
    
    • 1
    import "github.com/go-playground/validator/v10"
    
    • 1

    ​ 返回校验错误值,Validator 仅返回错误验证输入的 InvalidValidationError、nil 或 ValidationErrors 作为类型错误;因此,在您的代码中,您需要做的就是检查返回的错误是否不为零,如果不是,则检查错误是否为 InvalidValidationError (如果需要,大多数情况下不是)类型将其转换为类型 ValidationErrors 像这样:

    err := validate.Struct(mystruct)
    validationErrors := err.(validator.ValidationErrors)
    
    • 1
    • 2

    2 简单校验

    ​ 这部分我们通过一个简单的例子来入门学习一下数据的校验。

    package main
    
    import (
    	"fmt"
    
    	"github.com/go-playground/validator/v10"
    )
    
    // User contains user information
    type User struct {
    	FirstName      string     `validate:"required"`               //不能为空
    	LastName       string     `validate:"required"`               //不能为空
    	Age            uint8      `validate:"gte=0,lte=130"`          //值大于等于0 小于等于100
    	Email          string     `validate:"required,email"`         //不能为空 满足邮箱类型
    	FavouriteColor string     `validate:"iscolor"`                //是颜色数据          // alias for 'hexcolor|rgb|rgba|hsl|hsla'
    	Addresses      []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
    }
    
    // Address houses a users address information
    type Address struct {
    	Street string `validate:"required"` //不能为空
    	City   string `validate:"required"` //不能为空
    	Planet string `validate:"required"` //不能为空
    	Phone  string `validate:"required"` //不能为空
    }
    
    // use a single instance of Validate, it caches struct info
    var validate *validator.Validate
    
    func main() {
    
    	validate = validator.New()
    
    	validateStruct()
    	validateVariable()
    }
    
    func validateStruct() {
    
    	address := &Address{
    		Street: "Eavesdown Docks",
    		Planet: "Persphone",
    		Phone:  "none",
    	}
    
    	user := &User{
    		FirstName:      "Badger",
    		LastName:       "Smith",
    		Age:            135,
    		Email:          "Badger.Smith@gmail.com",
    		FavouriteColor: "#000-",
    		Addresses:      []*Address{address},
    	}
    
    	// returns nil or ValidationErrors ( []FieldError )
    	err := validate.Struct(user)
    	if err != nil {
    
    		// this check is only needed when your code could produce
    		// an invalid value for validation such as interface with nil
    		// value most including myself do not usually have code like this.
    		if _, ok := err.(*validator.InvalidValidationError); ok {
    			fmt.Println(err)
    			return
    		}
    
    		for _, err := range err.(validator.ValidationErrors) {
    
    			fmt.Println("命名空间:", err.Namespace())
    			fmt.Println("字段:", err.Field())
    			fmt.Println("结构命名空间:", err.StructNamespace())
    			fmt.Println("结构字段:", err.StructField())
    			fmt.Println("标签:", err.Tag())
    			fmt.Println("实际标签:", err.ActualTag())
    			fmt.Println("字段种类:", err.Kind())
    			fmt.Println("字段类型:", err.Type())
    			fmt.Println("字段值:", err.Value())
    			fmt.Println("字段校验值:", err.Param())
    			fmt.Printf("错误提示信息:%v\n", err)
    			fmt.Println()
    		}
    
    		// from here you can create your own error messages in whatever language you wish
    		return
    	}
    
    	// save user to database
    }
    
    func validateVariable() {
    
    	myEmail := "joeybloggs.gmail.com"
    
    	errs := validate.Var(myEmail, "required,email")
    
    	if errs != nil {
    		fmt.Println(errs) // output: Key: "" Error:Field validation for "" failed on the "email" tag
    		return
    	}
    
    	// email ok, move on
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    ​ 运行validateStruct方法,可以看到控制台输出下面错误信息:

    命名空间: User.Age
    字段: Age
    结构命名空间: User.Age
    结构字段: Age
    标签: lte
    实际标签: lte
    字段种类: uint8
    字段类型: uint8
    字段值: 135
    字段校验值: 130
    错误提示信息:Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
    
    命名空间: User.FavouriteColor
    字段: FavouriteColor
    结构命名空间: User.FavouriteColor
    结构字段: FavouriteColor
    标签: iscolor
    实际标签: hexcolor|rgb|rgba|hsl|hsla
    字段种类: string
    字段类型: string
    字段值: #000-
    字段校验值: 
    错误提示信息:Key: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag
    
    命名空间: User.Addresses[0].City
    字段: City
    结构命名空间: User.Addresses[0].City
    结构字段: City
    标签: required
    实际标签: required
    字段种类: string
    字段类型: string
    字段值: 
    字段校验值: 
    错误提示信息:Key: 'User.Addresses[0].City' Error:Field validation for 'City' failed on the 'required' tag
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    ​ 可以看出触发了三条规则,Age不在指定范围,City字段为空,FavouriteColor字段不属于颜色范围。

    ​ 运行validateVariable方法,可以看到控制台输出下面错误信息:

    Key: '' Error:Field validation for '' failed on the 'email' tag
    
    • 1

    ​ 这是因为输出参数不满足email校验的格式。

    3 自定义字段类型

    ​ 本部分学一下自定义字段类型的校验,编写如下代码:

    package main
    
    import (
    	"database/sql"
    	"database/sql/driver"
    	"fmt"
    	"reflect"
    	"time"
    
    	"github.com/go-playground/validator/v10"
    )
    
    type NullTime struct {
    	Time  time.Time
    	Valid bool // Valid is true if String is not NULL
    }
    
    func (n NullTime) Value() (driver.Value, error) {
    	if !n.Valid {
    		return nil, nil
    	}
    	return n.Time, nil
    }
    
    // DbBackedUser User struct
    type DbBackedUser struct {
    	Name sql.NullString `validate:"required"`
    	Age  sql.NullInt64  `validate:"required"`
    	Time NullTime       `validate:"required,gte='2022-09-09 09:09:09'"`
    }
    
    // use a single instance of Validate, it caches struct info
    var validate *validator.Validate
    
    func main() {
    
    	validate = validator.New()
    
    	// register all sql.Null* types to use the ValidateValuer CustomTypeFunc
    	validate.RegisterCustomTypeFunc(ValidateValuer, sql.NullString{}, sql.NullInt64{}, sql.NullBool{}, sql.NullFloat64{}, NullTime{})
    
    	// build object for validation
    	x := DbBackedUser{Name: sql.NullString{String: "100", Valid: true}, Age: sql.NullInt64{Int64: 10, Valid: true}, Time: NullTime{Time: time.Now(), Valid: true}}
    
    	err := validate.Struct(x)
    
    	if err != nil {
    		fmt.Printf("Err(s):\n%+v\n", err)
    	}
    }
    
    // ValidateValuer implements validator.CustomTypeFunc
    func ValidateValuer(field reflect.Value) interface{} {
    
    	if valuer, ok := field.Interface().(driver.Valuer); ok {
    
    		val, err := valuer.Value()
    		if err == nil {
    			return val
    		}
    		// handle the error how you want
    	}
    
    	return nil
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    ​ 本例子主要校验sql.NullString、sql.NullInt64 、NullTime这三种字段类型,其中NullTime是我们自定义的。运行上面代码,可以在控制台看到下面输出结果:

    Key: 'DbBackedUser.Time' Error:Field validation for 'Time' failed on the 'gte' tag
    
    • 1

    ​ 如果我们修改参数,将数据修改成下面:

    x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: true}, Time: NullTime{Valid: true}}
    
    • 1

    ​ 运行可以看到控制台输出下面三条规则:

    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
    Key: 'DbBackedUser.Time' Error:Field validation for 'Time' failed on the 'required' tag
    
    • 1
    • 2
    • 3

    4 结构层校验

    ​ 这部分通过实现一个结构层的规则校验,编写下面代码:

    package main
    
    import (
    	"fmt"
    	"reflect"
    	"strings"
    
    	"github.com/go-playground/validator/v10"
    )
    
    /**
    * 定义一个User的结构体
    * FirstName 和 LastName 这两个字段在结构体中没有定义校验规则
     */
    type User struct {
    	FirstName      string     `json:"fname"`
    	LastName       string     `json:"lname"`
    	Age            uint8      `validate:"gte=0,lte=130"`
    	Email          string     `json:"e-mail" validate:"required,email"`
    	FavouriteColor string     `validate:"hexcolor|rgb|rgba"`
    	Addresses      []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
    }
    
    /**
    * 定义一个Address的结构体
     */
    type Address struct {
    	Street string `validate:"required"`
    	City   string `validate:"required"`
    	Planet string `validate:"required"`
    	Phone  string `validate:"required"`
    }
    
    /**
    * 定义一个validate这样赋值以后可以全局使用
     */
    var validate *validator.Validate
    
    func main() {
    
    	validate = validator.New()
    
    	/**
    	 * register function to get tag name from json tags.
    	 * 获取tag标签中json标明的字符串
    	 */
    	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
    		name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
    		if name == "-" {
    			return ""
    		}
    		return name
    	})
    
    	// register validation for 'User'  为User注册校验器
    	// NOTE: only have to register a non-pointer type for 'User', validator  注册的User是一个非指针
    	// internally dereferences during it's type checks. 在类型检查期间进行内部引用。
    	validate.RegisterStructValidation(UserStructLevelValidation, User{})
    
    	// 赋值
    	address := &Address{
    		Street: "Eavesdown Docks",
    		Planet: "Persphone",
    		Phone:  "none",
    		City:   "Unknown",
    	}
    
    	user := &User{
    		FirstName:      "zhangsan",
    		LastName:       "",
    		Age:            45,
    		Email:          "Badger.Smith@gmail",
    		FavouriteColor: "#000",
    		Addresses:      []*Address{address},
    	}
    
    	// returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )
    	// 返回校验不通过的信息
    	err := validate.Struct(user)
    	if err != nil {
    
    		// this check is only needed when your code could produce
    		// an invalid value for validation such as interface with nil
    		// value most including myself do not usually have code like this.
    		if _, ok := err.(*validator.InvalidValidationError); ok {
    			fmt.Println(err)
    			return
    		}
    
    		for _, err := range err.(validator.ValidationErrors) {
    
    			fmt.Println(err.Namespace()) // can differ when a custom TagNameFunc is registered or
    			fmt.Println(err.Field())     // by passing alt name to ReportError like below
    			fmt.Println(err.StructNamespace())
    			fmt.Println(err.StructField())
    			fmt.Println(err.Tag())
    			fmt.Println(err.ActualTag())
    			fmt.Println(err.Kind())
    			fmt.Println(err.Type())
    			fmt.Println(err.Value())
    			fmt.Println(err.Param())
    			fmt.Printf("错误信息:%v\n", err)
    			fmt.Println()
    		}
    
    		// from here you can create your own error messages in whatever language you wish
    		return
    	}
    
    	// save user to database
    }
    
    // UserStructLevelValidation contains custom struct level validations that don't always
    // make sense at the field validation level. For Example this function validates that either
    // FirstName or LastName exist; could have done that with a custom field validation but then
    // would have had to add it to both fields duplicating the logic + overhead, this way it's
    // only validated once.
    //UserStructLevelValidation包含自定义结构级验证,这些验证并不总是有效 在现场验证级别有意义。
    // 例如,此函数验证 名或姓存在;可以通过自定义字段验证完成,但是 必须将其添加到两个字段中,复制逻辑+开销,这样
    //仅验证一次。
    // NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
    // hooks right into validator and you can combine with validation tags and still have a
    // common error output format.
    //注意:您可能会问,为什么我不在验证器外部执行此操作,因为这样做会直接连接到验证器中,您可以与验证标记组合,并且仍然有一个
    //输出格式的常见错误。
    
    func UserStructLevelValidation(sl validator.StructLevel) {
    
    	user := sl.Current().Interface().(User)
    
    	//这里自定义了一种组合校验规则,只有当FirstName和LastName都为空的时候才会触发
    	if len(user.FirstName) == 0 && len(user.LastName) == 0 {
    		sl.ReportError(user.FirstName, "fname", "FirstName", "fnameorlname", "")
    		sl.ReportError(user.LastName, "lname", "LastName", "fnameorlname", "")
    	}
    
    	// 这里可以做一些其他的事情
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139

    ​ 在这个例子中,我们定义了一个结构体User,它里面的FirstName和LastName字段后面并没有加tag标签进行校验,而是通过UserStructLevelValidation这个校验,我们可以在这个里面灵活写一些校验规则,并把规则注册到此结构体。

    validate.RegisterStructValidation(UserStructLevelValidation, User{})
    
    • 1

    ​ 运行上面代码,发现只有当FirstName和LastName两个字段都为空的时候才会触发单独注册到结构体里面的规则。

    5 翻译和自定义错误

    ​ 通过前面的几个例子发现,触发校验规则之后输出的原因是一个很长的字符串,看起来不是很直观,这里我们可以映射和自定义错误,编写下面代码:

    package main
    
    import (
    	"fmt"
    
    	"github.com/go-playground/locales/en"
    	ut "github.com/go-playground/universal-translator"
    	"github.com/go-playground/validator/v10"
    	en_translations "github.com/go-playground/validator/v10/translations/en"
    )
    
    // User contains user information
    type User struct {
    	FirstName      string     `validate:"required"`
    	LastName       string     `validate:"required"`
    	Age            uint8      `validate:"gte=0,lte=130"`
    	Email          string     `validate:"required,email"`
    	FavouriteColor string     `validate:"iscolor"`                // alias for 'hexcolor|rgb|rgba|hsl|hsla'
    	Addresses      []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
    }
    
    // Address houses a users address information
    type Address struct {
    	Street string `validate:"required"`
    	City   string `validate:"required"`
    	Planet string `validate:"required"`
    	Phone  string `validate:"required"`
    }
    
    // use a single instance , it caches struct info
    var (
    	uni      *ut.UniversalTranslator
    	validate *validator.Validate
    )
    
    func main() {
    
    	// NOTE: ommitting allot of error checking for brevity
    
    	en := en.New()
    	uni = ut.New(en, en)
    
    	// this is usually know or extracted from http 'Accept-Language' header
    	// also see uni.FindTranslator(...)
    	trans, _ := uni.GetTranslator("en")
    
    	validate = validator.New()
    	en_translations.RegisterDefaultTranslations(validate, trans)
    
    	translateAll(trans)
    	translateIndividual(trans)
    	translateOverride(trans) // yep you can specify your own in whatever locale you want!
    }
    
    func translateAll(trans ut.Translator) {
    
    	type User struct {
    		Username string `validate:"required"`
    		Tagline  string `validate:"required,lt=10"`
    		Tagline2 string `validate:"required,gt=1"`
    	}
    
    	user := User{
    		Username: "",
    		Tagline:  "This tagline is way too long.",
    		Tagline2: "1",
    	}
    
    	err := validate.Struct(user)
    	if err != nil {
    
    		// translate all error at once
    		errs := err.(validator.ValidationErrors)
    
    		// returns a map with key = namespace & value = translated error
    		// NOTICE: 2 errors are returned and you'll see something surprising
    		// translations are i18n aware!!!!
    		// eg. '10 characters' vs '1 character'
    		fmt.Println(errs.Translate(trans))
    	}
    }
    
    func translateIndividual(trans ut.Translator) {
    
    	type User struct {
    		Username string `validate:"required"`
    	}
    
    	var user User
    
    	err := validate.Struct(user)
    	if err != nil {
    
    		errs := err.(validator.ValidationErrors)
    
    		for _, e := range errs {
    			// can translate each error one at a time.
    			fmt.Println(e.Translate(trans))
    		}
    	}
    }
    
    /**
    * 重写required输出
    */
    func translateOverride(trans ut.Translator) {
    
    	validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
    		return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
    	}, func(ut ut.Translator, fe validator.FieldError) string {
    		t, _ := ut.T("required", fe.Field())
    
    		return t
    	})
    
    	type User struct {
    		Username string `validate:"required"`
    	}
    
    	var user User
    
    	err := validate.Struct(user)
    	if err != nil {
    
    		errs := err.(validator.ValidationErrors)
    
    		for _, e := range errs {
    			// can translate each error one at a time.
    			fmt.Println(e.Translate(trans))
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133

    ​ 运行上面代码,可以在控制台看到,前两个案例是按照翻译后输出的,第三个测试案例的规则是按照自定义规则输出来的:

    map[User.Tagline:Tagline must be less than 10 characters in length User.Tagline2:Tagline2 must be greater than 1 character in length User.Username:Username is a required field]
    Username is a required field
    Username must have a value!
    
    • 1
    • 2
    • 3

    6 国际化成中文

    ​ 通过上部分的学习,我们已经可以翻译和自定义错误,如果觉得英文看起来还是不直观,可以国际化成中文,编写下面代码:

    package main
    
    import (
    	"fmt"
    	"net/http"
    	"reflect"
    	"strings"
    
    	"github.com/gin-gonic/gin"
    	"github.com/gin-gonic/gin/binding"
    	"github.com/go-playground/locales/en"
    	"github.com/go-playground/locales/zh"
    	ut "github.com/go-playground/universal-translator"
    	"github.com/go-playground/validator/v10"
    	en_translations "github.com/go-playground/validator/v10/translations/en"
    	zh_translations "github.com/go-playground/validator/v10/translations/zh"
    )
    
    var trans ut.Translator
    
    type LoginForm struct {
    	User     string `json:"user" binding:"required,min=3,max=10"`
    	Password string `json:"password" binding:"required"`
    }
    
    type SignUpForm struct {
    	Age        uint8  `json:"age" binding:"gte=1,lte=130"`
    	Name       string `json:"name" binding:"required,min=3"`
    	Email      string `json:"email" binding:"required,email"`
    	Password   string `json:"password" binding:"required"`
    	RePassword string `json:"re_password" binding:"required,eqfield=Password"` //跨字段
    }
    
    func removeTopStruct(fileds map[string]string) map[string]string {
    	rsp := map[string]string{}
    	for field, err := range fileds {
    		rsp[field[strings.Index(field, ".")+1:]] = err
    	}
    	return rsp
    }
    
    func InitTrans(locale string) (err error) {
    	//修改gin框架中的validator引擎属性, 实现定制
    	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    		//注册一个获取json的tag的自定义方法
    		v.RegisterTagNameFunc(func(fld reflect.StructField) string {
    			name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
    			if name == "-" {
    				return ""
    			}
    			return name
    		})
    
    		zhT := zh.New() //中文翻译器
    		enT := en.New() //英文翻译器
    		//第一个参数是备用的语言环境,后面的参数是应该支持的语言环境
    		uni := ut.New(enT, zhT, enT)
    		trans, ok = uni.GetTranslator(locale)
    		if !ok {
    			return fmt.Errorf("uni.GetTranslator(%s)", locale)
    		}
    
    		switch locale {
    		case "en":
    			en_translations.RegisterDefaultTranslations(v, trans)
    		case "zh":
    			zh_translations.RegisterDefaultTranslations(v, trans)
    		default:
    			en_translations.RegisterDefaultTranslations(v, trans)
    		}
    		return
    	}
    
    	return
    }
    
    func main() {
    	//代码侵入性很强 中间件
    	if err := InitTrans("zh"); err != nil {
    		fmt.Println("初始化翻译器错误")
    		return
    	}
    	router := gin.Default()
    	router.POST("/loginJSON", func(c *gin.Context) {
    
    		var loginForm LoginForm
    		if err := c.ShouldBind(&loginForm); err != nil {
    			errs, ok := err.(validator.ValidationErrors)
    			if !ok {
    				c.JSON(http.StatusOK, gin.H{
    					"msg": err.Error(),
    				})
    				return
    			}
    			c.JSON(http.StatusBadRequest, gin.H{
    				"error": removeTopStruct(errs.Translate(trans)),
    			})
    			return
    		}
    
    		c.JSON(http.StatusOK, gin.H{
    			"msg": "登录成功",
    		})
    	})
    
    	router.POST("/signup", func(c *gin.Context) {
    		var signUpFrom SignUpForm
    		if err := c.ShouldBind(&signUpFrom); err != nil {
    			errs, ok := err.(validator.ValidationErrors)
    			if !ok {
    				c.JSON(http.StatusOK, gin.H{
    					"msg": err.Error(),
    				})
    				return
    			}
    			c.JSON(http.StatusBadRequest, gin.H{
    				"error": removeTopStruct(errs.Translate(trans)),
    			})
    			return
    		}
    
    		c.JSON(http.StatusOK, gin.H{
    			"msg": "注册成功",
    		})
    	})
    
    	_ = router.Run(":8080")
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    ​ 在上面的代码中,我们加上了中文部分。启动上面实例,分别调用两个接口,可以看到异常原因已经变成中文了:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U489KRgL-1660124878923)(D:\developsoftware\mayun\note\study-note\go\images\image-20220810174338790.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ddujJ0PZ-1660124878933)(D:\developsoftware\mayun\note\study-note\go\images\image-20220810174517215.png)]

  • 相关阅读:
    Java开源专业计算引擎:跑批真的这么难吗?
    彻底理解Java并发:ThreadLocal详解
    [附源码]java毕业设计商城管理系统
    Android Material Design之ShapeableImageView(十三)
    辅助驾驶功能开发-控制篇(03)-基于PID的请求角度转扭矩算法
    数据(单行)处理函数和分组函数
    Python之写文件操作(二十九)
    StringBuffer类 和StringBuilder类
    Servlet介绍与配置
    开发上门送桶装水小程序要考虑哪些业务场景
  • 原文地址:https://blog.csdn.net/qq_36305027/article/details/126271410