Gin参数验证-Validator
在进行api开发的时候,参数验证是极其频繁的一步,选择一个验证轮子很重要。validator是一个开源的验证器包,可以快速校验输入信息是否符合自定规则。
访问地址:https://github.com/go-playground/validator
一:安装到我们项目中:
go get github.com/go-playground/validator/v10然后在合适的位置导入包:
import "github.com/go-playground/validator/v10"
1:单个变量验证
package main import ( "fmt" "github.com/go-playground/validator/v10" //导入包 ) func main() { validate := validator.New() //创建一个实例 email := "" //验证这个字段 err := validate.Var(email, "required") // required 判断是否为空 if err != nil { fmt.Println(err.Error()) //如果为空,返回错误提示 } }
这里返回:Key: '' Error:Field validation for '' failed on the 'required' tag
下面增加判断条件,在不为空的基础上,增加一个是否是合法的email
email := "addd#eeee" //验证这个字段 err := validate.Var(email, "required,email") // required 判断是否为空,email判断是否是一个合法的email返回:Key: '' Error:Field validation for '' failed on the 'email' tag
细心的同学可能会发现一个问题,返回的提示信息是哪里来的?其实这个信息是validator默认的,而且这个提示很不友好,那么能不能修改一下?如果单个验证的话,可以直接把err.Error()换成你需要的提示语即可。还有个方法,单个变量可以通过注册规则和替换来实现,然后通过翻译来实现,这里有翻译包,一会儿再介绍
2:结构体验证
package main import ( "fmt" "github.com/go-playground/validator/v10" //导入包 ) func main() { validate := validator.New() //创建一个实例 // 结构体定义验证规则 type User struct { ID int64 `json:"id" validate:"gt=0"` //大于 0 Name string `json:"name" validate:"required"` // 不能为空 Gender string `json:"gender" validate:"required,oneof=man woman"` // 不能为空,只能为man woman Age uint8 `json:"age" validate:"required,gte=0,lte=130"` // 大于等于0,小于等于130 Email string `json:"email" validate:"required,email"` // 不能为空,检测邮箱是否合法 } user := &User{ ID: 12, Name: "jecken", Gender: "boy", Age: 190, Email: "abc@88.com", } err := validate.Struct(user) if err != nil { fmt.Println(err.Error()) //如果为空,返回错误提示 } }返回:
Key: 'User.Gender' Error:Field validation for 'Gender' failed on the 'oneof' tag
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
这个返回的还是不太好看,我们美化一下。
我们先下载2个东西:
go get github.com/go-playground/universal-translator //翻译器 go get github.com/go-playground/locales //本地化多语言库
然后代码是这样的:
package main import ( "fmt" "reflect" "strings" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" "github.com/go-playground/validator/v10" ut "github.com/go-playground/universal-translator" zh_translations "github.com/go-playground/validator/v10/translations/zh" ) func main() { // 创建一个新的翻译器和翻译映射表 enTranslator := en.New() zhTranslator := zh.New() trans := ut.New(enTranslator, zhTranslator) // 设置翻译语言 uniTranslator, _ := trans.GetTranslator("zh") validate := validator.New() //创建一个验证实例 //验证器注册翻译器 errs := zh_translations.RegisterDefaultTranslations(validate, uniTranslator) if errs != nil { fmt.Println(errs) } //提取结构体中的tag标签 lable validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := fld.Tag.Get("label") return name }) // 结构体定义验证规则 type User struct { ID int64 `json:"id" validate:"gt=0" label:"id"` //大于 0 Name string `json:"name" validate:"required" label:"姓名"` // 不能为空 Gender string `json:"gender" validate:"required,oneof=man woman" label:"性别"` // 不能为空,只能为man woman Age uint8 `json:"age" validate:"required,gte=0,lte=130" label:"年龄"` // 大于等于0,小于等于130 Email string `json:"email" validate:"required,email" label:"邮箱"` // 不能为空,检测邮箱是否合法 } user := &User{ ID: 12, Name: "", Gender: "boy", Age: 190, Email: "abc@88.com", } err := validate.Struct(user) if err != nil { errs, ok := err.(validator.ValidationErrors) if !ok { fmt.Println(err.Error()) //非validator.ValidationErrors类型错误直接返回 } fmt.Println(removeTopStruct(errs.Translate(uniTranslator))) // validator.ValidationErrors类型错误则进行翻译 } } //移除其 key 中包含“.”的键,并将其余部分作为新的键 func removeTopStruct(fields map[string]string) map[string]string { res := map[string]string{} for field, err := range fields { res[field[strings.Index(field, ".")+1:]] = err } return res }返回:map[姓名:姓名为必填字段 年龄:年龄必须小于或等于130 性别:性别必须是[man woman]中的一个]
3:我们把这玩意儿弄到GIN中去,搞一个模块单独调用,这里新建一个validator.go
package validator import ( "fmt" "reflect" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" "github.com/go-playground/validator/v10" ut "github.com/go-playground/universal-translator" zh_translations "github.com/go-playground/validator/v10/translations/zh" ) var Trans ut.Translator //全局翻译器 // 设置,init方法在golang会自动执行 func init() { uni := ut.New(en.New(), zh.New()) // 设置翻译语言 t, _ := uni.GetTranslator("zh") Trans = t //赋给全局变量 //获取gin的校验器,这里就不用上面那个 validator.New()了,用GIN框架的,它继承了 validate := binding.Validator.Engine().(*validator.Validate) //注册翻译器 errs := zh_translations.RegisterDefaultTranslations(validate, Trans) if errs != nil { fmt.Println(errs) } //提取结构体中的tag标签 lable validate.RegisterTagNameFunc(func(fld reflect.StructField) string { name := fld.Tag.Get("label") return name }) } //返回提示 func Fails(err error) string { errors := err.(validator.ValidationErrors) for _, err := range errors { return err.Translate(Trans) //返回第一个就行了 } return "" }
然后在我们所谓的控制器中调用,搞两个参数,page和category,GET form方式绑定这两个参数验证
搞一个结构体绑定
type CateRequest struct { //注意这里是form,单独使用用validate,这里用gin的binding Category string `form:"category" binding:"required" label:"类别"` Page string `form:"page" binding:"required" label:"分页"` }把我们上面这个validator.go import进来
func Cate(ctx *gin.Context) { var c CateRequest err := ctx.BindQuery(&c) if err != nil { ctx.JSON(200, gin.H{"message": validator.Fails(err)}) return } }执行一下:
{"message":"类别为必填字段"}
如果要返回全部提示,我们可以validator.go再加一个方法:
//返回全部提示 func FailsAll(err error) map[string][]string { var result = make(map[string][]string) errors := err.(validator.ValidationErrors) for _, err := range errors { result[err.Field()] = append(result[err.Field()], err.Translate(Trans)) } return result }执行:
{"message":{"分页":["分页为必填字段"],"类别":["类别为必填字段"]}}