一、为什么要数据校验
- 数据校验有点类似中间件的作用,使前端传递过来的数据更加合法的到达控制器,以减少数据在控制层中一个一个的校验。
- 如果不使用数据校验,要不就自己手动的在控制器中一个一个的数据校验
- 在
go
语言中是没有类似java
一样的注解的方式来校验数据,但是可以在结构体中使用校验方法来校验(本人不是特别的喜欢这种方式,但是对于这门语言也只能这样)
二、在go语言中使用数据校验
-
1、官网地址
https://github.com/go-playground/validator
-
2、安装依赖包
go get github.com/go-playground/validator
-
3、基础的使用参考官网
三、自定义校验器
-
1、定义接收参数的结构体
type UserInfo struct { Id string `validate:"uuid" json:"id"` //UUID 类型 Name string `validate:"checkName,required" json:"name"` // 自定义校验 Age uint8 `validate:"min=0,max=130,required" json:"age"` // 年龄 }
-
2、自定义方法
func checkNameFunc(f validator.FieldLevel) bool { count := utf8.RuneCountInString(f.Field().String()) if count >= 2 && count <= 12 { return true } else { return false } }
-
3、在
init
函数中注册方法var valildate *validator.Validate func init() { valildate = validator.New() valildate.RegisterValidation("checkName", checkNameFunc) }
-
4、在
main
函数中使用func main() { router := gin.Default() router.POST("/register", func(c *gin.Context) { user := UserInfo{} //或者var user = UserInfo err := c.Bind(&user) if err != nil { c.JSON(http.StatusOK, gin.H{ "code": 1, "message": "请求参数错误", }) return } err = valildate.Struct(user) if err != nil { // 输出校验错误 .(validator.ValidationErrors)是断言 for _, e := range err.(validator.ValidationErrors) { fmt.Println("错误字段:", e.Field()) fmt.Println("错误的值:", e.Value()) fmt.Println("错误的tag:", e.Tag()) } c.JSON(http.StatusOK, gin.H{ "code": 1, "message": "数据校验错误", }) return } fmt.Println(user, "接收的数据") c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "请求成功", }) }) router.Run(":8000") }
四、对于多层嵌套数据的校验
-
1、对于多层结构体的数据校验使用
dive
-
2、入参的结构体
type UserInfo struct { Id string `validate:"uuid" json:"id"` //UUID 类型 Name string `validate:"checkName,required" json:"name"` // 自定义校验 Age uint8 `validate:"min=0,max=130,required" json:"age"` // 年龄 Address []Address `validate:"dive" json:"address"` // 收货地址 } type Address struct { Province string `json:"province" validate:"required"` // 省份 City string `json:"city" validate:"required"` // 市 County string `json:"county" validate:"required"` //县 Mobile string `json:"mobile" validate:"numeric,len=11"` // 手机号码 }
-
3、使用
postman
提交数据{ "name": "admin", "age": 20, "id": "3f7a783f-f9dc-4db8-9c6b-afe1d3f9b3f7", "address": [ { "province": "广东省", "city":"深圳市", "county": "宝安区", "mobile": "18112345678" } ] }
-
4、别的和上面一样的结构
五、翻译成中文错误提示
-
1、定义简单的注册结构体
type RegisterForm struct { UserName string `json:"username" binding:"required,min=3,max=10"` Password string `json:"password" binding:"required,min=3"` RePassword string `json:"rePassword" binding:"required,eqfield=Password"` }
-
2、常规的使用提示错误
{ "error": "Key: 'RegisterForm.RePassword' Error:Field validation for 'RePassword' failed on the 'eqfield' tag" }
-
3、使用翻译成中文,可读性更加好,定义一个翻译器的方法
import ( "fmt" "net/http" "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 //定义翻译的方法 func InitTrans(locale string) (err error) { //修改gin框架中的validator引擎属性, 实现定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 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 }
-
4、使用翻译器
func main() { if err := InitTrans("zh"); err != nil { fmt.Println("初始化翻译器错误") return } router := gin.Default() router.POST("/register", func(c *gin.Context) { var registerForm RegisterForm if err := c.ShouldBind(®isterForm); err != nil { fmt.Println(err.Error()) // 翻译错误 errs, ok := err.(validator.ValidationErrors) if !ok { c.JSON(http.StatusOK, gin.H{ "message": err.Error(), }) } // 将错误信息返回给前端 c.JSON(http.StatusOK, gin.H{ "error": errs.Translate(trans), }) return } fmt.Println("注册的参数:", registerForm) c.JSON(http.StatusOK, gin.H{ "code": 1, "message": "成功", "data": gin.H{ "username": registerForm.UserName, "password": registerForm.Password, }, }) }) router.Run(":8080") }
{ "error": { "RegisterForm.RePassword": "RePassword必须等于Password", "RegisterForm.UserName": "UserName长度必须至少为3个字符" } }
-
5、移除多余的标签,让提示更加友好
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 }
if err := c.ShouldBind(®isterForm); err != nil { fmt.Println(err.Error()) // 翻译错误 errs, ok := err.(validator.ValidationErrors) if !ok { c.JSON(http.StatusOK, gin.H{ "message": err.Error(), }) } c.JSON(http.StatusOK, gin.H{ "error": removeTopStruct(errs.Translate(trans)), }) return }
-
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 RegisterForm struct { UserName string `json:"username" binding:"required,min=3,max=10"` Password string `json:"password" binding:"required,min=3"` RePassword string `json:"rePassword" 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("/register", func(c *gin.Context) { var registerForm RegisterForm if err := c.ShouldBind(®isterForm); err != nil { fmt.Println(err.Error()) // 翻译错误 errs, ok := err.(validator.ValidationErrors) if !ok { c.JSON(http.StatusOK, gin.H{ "message": err.Error(), }) } c.JSON(http.StatusOK, gin.H{ "error": removeTopStruct(errs.Translate(trans)), }) return } fmt.Println("注册的参数:", registerForm) c.JSON(http.StatusOK, gin.H{ "code": 1, "message": "成功", "data": gin.H{ "username": registerForm.UserName, "password": registerForm.Password, }, }) }) router.Run(":8080") }
-
7、可以将上面这块封装成一个公共方法,以便在别的地方使用
package utils import ( "fmt" "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" enTranslations "github.com/go-playground/validator/v10/translations/en" zhTranslations "github.com/go-playground/validator/v10/translations/zh" "reflect" "strings" ) var Trans ut.Translator func init() { if err := InitTrans("zh"); err != nil { fmt.Println("初始化翻译器错误") panic("初始化翻译器错误") } } // RemoveTopStruct 移除多余的标签 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 } // InitTrans 定义翻译的方法 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": enTranslations.RegisterDefaultTranslations(v, Trans) case "zh": zhTranslations.RegisterDefaultTranslations(v, Trans) default: enTranslations.RegisterDefaultTranslations(v, Trans) } return } return }
-
8、外部使用
package main import ( "fmt" "gin_stuty/01/utils" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "net/http" ) type RegisterForm struct { UserName string `json:"username" binding:"required,min=3,max=10"` Password string `json:"password" binding:"required,min=3"` RePassword string `json:"rePassword" binding:"required,eqfield=Password"` } func main() { router := gin.Default() router.POST("/register", func(ctx *gin.Context) { var registerForm RegisterForm if err := ctx.ShouldBind(®isterForm); err != nil { fmt.Println(err.Error()) // 翻译错误 errs, ok := err.(validator.ValidationErrors) if !ok { ctx.JSON(http.StatusOK, gin.H{ "message": err.Error(), }) } // 使用封装的翻译方法 ctx.JSON(http.StatusOK, gin.H{ "error": utils.RemoveTopStruct(errs.Translate(utils.Trans)), }) return } ctx.JSON(http.StatusOK, gin.H{ "code": 0, "message": "成功", }) }) fmt.Println(fmt.Sprintf("服务已经启动:localhost:9000")) router.Run(":9000") }
六、针对自定义错误翻译成中文
-
1、回顾上面的自定义翻译器
type RegisterForm1 struct { //手机号码格式有规范可寻, 自定义validator,其中mobile是我们自定义的,官方是不带的 Mobile string `form:"mobile" json:"mobile" binding:"required,mobile"` } // ValidateMobile 自定义校验器 func ValidateMobile(f validator.FieldLevel) bool { // 获取字段的值 mobile := f.Field().String() // 使用正则来校验 if ok, _ := regexp.MatchString(`^1[3,5,7,8]\d{9}$`, mobile); ok { return true } else { return false } }
-
2、注册自定义校验器来验证
func main() { // 注册校验器 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { _ = v.RegisterValidation("mobile", ValidateMobile) _ = v.RegisterTranslation("mobile", utils.Trans, func(ut ut.Translator) error { return ut.Add("mobile", "{0}手机号码不符合规则", true) }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("mobile", fe.Field()) return t }) } router := gin.Default() router.POST("/register", func(ctx *gin.Context) { var registerForm RegisterForm1 if err := ctx.ShouldBindJSON(®isterForm); err != nil { fmt.Println(err.Error()) // 翻译错误 errs, ok := err.(validator.ValidationErrors) if !ok { ctx.JSON(http.StatusOK, gin.H{ "message": err.Error(), }) } // 使用封装的翻译方法 ctx.JSON(http.StatusOK, gin.H{ "error": utils.RemoveTopStruct(errs.Translate(utils.Trans)), }) return } ctx.JSON(http.StatusOK, gin.H{ "code": 0, "message": "成功", }) }) router.Run(":9000") }
-
3、一般自定义校验器会统一存放到项目的
validator
文件夹下,这里就不抽取出去