前言
这是我参与更文挑战的第4天,大家好,我是作曲家种太阳
上一篇讲到了Zap日志管理和路由初始化,相信你对gin已近有了初步的认知.
这一篇我们讲下如何校验前端发送过来的数据,校验器的中文翻译,并编写一个自定义的校验器
1. 介绍
字段校验是后端比较重要的环节,虽然前端的form表单做了校验,但是后端为了安全性和避免数据库的
脏数据,我们必须要做字段校验
字段校验我们虽然可以编写函数,自己封装校验逻辑,但是结构体的tag已近有一些基础校验功能,再加上可以自定义校验器,代码优雅效率十分高!
为了让你较为轻松的理解请求流程,画了一张数据流向图,了解下整个数据流向走势
2. 快速实现最基础的字段校验
(1).定义校验结构体
在 forms/user.go 编写
package forms
type PasswordLoginForm struct {
// 密码 binding:"required"为必填字段,长度大于3小于20
PassWord string `form:"password" json:"password" binding:"required,min=3,max=20"`
//用户名
Username string `form:"name" json:"name" binding:"required"`
}
结构体中的tag是自带一些校验的参数的,但是都是比较基础的,如果做一些严格的校验,需要做自定义校验器,后面我们会讲到.
(2).编写controller
在 controller/user.go 编写
package controller
import (
"github.com/fatih/color"
"github.com/gin-gonic/gin"
"go_gin/forms"
"net/http"
)
// PasswordLogin 登录
func PasswordLogin(c *gin.Context) {
PasswordLoginForm := forms.PasswordLoginForm{
}
if err := c.ShouldBind(&PasswordLoginForm); err != nil {
color.Blue(err.Error())
c.JSON(http.StatusInternalServerError, gin.H{
"err": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "sussess",
})
}
(3).添加router路由
在 router/user.go中加一段路由设置
UserRouter.POST("login", controller.PasswordLogin)
(4).验证校验有效性
参数正确验证:
参数错误时验证:
name字段没有输入情况下
ps:能验证到这里,说明你以上的步骤都成功了,但是这里留了一个坑,就是验证的错误返回字段是英文的,我们下一步需要给验证器加上中文翻译转换器
3. 添加字段校验的中文转换器
1).安装validator,翻译器,中文
// validator校验器
go get github.com/go-playground/validator/v10
// 翻译器
go get github.com/go-playground/universal-translator
// 中文包
go get github.com/go-playground/locales/zh
// 英文包
go get github.com/go-playground/locales/en
(2).定义全局翻译器实例变量
在 global/globalVar中加上 变量
Trans ut.Translator
(3).编写翻译器
在 initialize/validator中
package initialize
import (
"fmt"
"github.com/fatih/color"
"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"
"go_gin/global"
"reflect"
"strings"
)
// InitTrans validator信息翻译
func InitTrans(locale string) (err error) {
color.Red("test111111111111")
//修改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)
global.Trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s)", locale)
}
switch locale {
case "en":
_ = en_translations.RegisterDefaultTranslations(v, global.Trans)
case "zh":
_ = zh_translations.RegisterDefaultTranslations(v, global.Trans)
default:
_ = en_translations.RegisterDefaultTranslations(v, global.Trans)
}
return
}
return
}
ps:大致流程是给InitTrans传递一个参数,判断加载什么语言包,然后获取到语言包赋值给全局翻译器
(3).编写字段校验异常函数
封装一个统一处理字段异常的函数
在 utils/validator 编写
package utils
import (
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"go_gin/global"
"net/http"
"strings"
)
// HandleValidatorError 处理字段校验异常
func HandleValidatorError(c *gin.Context, err error) {
//如何返回错误信息
errs, ok := err.(validator.ValidationErrors)
if !ok {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
}
c.JSON(http.StatusBadRequest, gin.H{
"error": removeTopStruct(errs.Translate(global.Trans)),
})
return
}
// removeTopStruct 定义一个去掉结构体名称前缀的自定义方法:
func removeTopStruct(fileds map[string]string) map[string]string {
rsp := map[string]string{}
for field, err := range fileds {
// 从文本的逗号开始切分 处理后"mobile": "mobile为必填字段" 处理前: "PasswordLoginForm.mobile": "mobile为必填字段"
rsp[field[strings.Index(field, ".")+1:]] = err
}
return rsp
}
ps:removeTopStruct主要用作字符串的切分,应为用翻译成中文,返回的key里面前半段还是有英文,做切分处理
(4).在controller中使用HandleValidatorError函数
package controller
import (
"github.com/gin-gonic/gin"
"go_gin/utils"
"go_gin/forms"
"net/http"
)
// PasswordLogin 登录
func PasswordLogin(c *gin.Context) {
PasswordLoginForm := forms.PasswordLoginForm{
}
if err := c.ShouldBind(&PasswordLoginForm); err != nil {
// 统一处理异常
utils.HandleValidatorError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"msg": "sussess",
})
}
ps:ShouldBind()函数里面传入PasswordLoginForm实例,会根据结构体的tag校验字段
(5).在main.go中使用初始化字段校验器
在 main.go 中写入
//4. 初始化翻译
if err := initialize.InitTrans("zh"); err != nil {
panic(err)
}
(6).测试以上步骤
一个简单的字段校验器就完成了,我们现在验证下 \
<1>.参数错误时:
<1>.参数正确时:
中文的错误校验就显示出来了,如果你做到这一步,并且测试通过 就可以继续往下做~
3.自定义校验
结构体的tag都校验功能比较基础,但是有些复杂的校验需求,我们必须得自定义校验器才能做到
现在假设一个需求,前端传递的用户名必须得是手机号格式:
- 连续数字11位
- 数字1开头 了解了需求,我们大概知道校验函数用正则匹配好做一些
(1).定义校验函数
在 utils/valicator 中 编写
// ValidateMobile 校验手机号
func ValidateMobile(fl validator.FieldLevel) bool {
// 利用反射拿到结构体tag含有mobile的key字段
mobile := fl.Field().String()
//使用正则表达式判断是否合法
ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$`, mobile)
if !ok{
return false
}
return true
}
(2).定义注册自定义校验tag函数
在 initialize/validator中加入
// Func myvalidator.ValidateMobile
type Func func(fl validator.FieldLevel) bool
// RegisterValidatorFunc 注册自定义校验tag
func RegisterValidatorFunc(v *validator.Validate, tag string, msgStr string, fn Func) {
// 注册tag自定义校验
_ = v.RegisterValidation(tag, validator.Func(fn))
//自定义错误内容
_ = v.RegisterTranslation(tag, global.Trans, func(ut ut.Translator) error {
return ut.Add(tag, "{0}"+msgStr, true) // see universal-translator for details
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T(tag, fe.Field())
return t
})
return
}
(3).添加到InitTrans函数中去
我们写好了一个校验函数和一个注册自定义校验tag函数 ,
这时候我们把他们加入到InitTrans函数中就可以实现自定义校验了~
在 initialize/validator 的中InitTrans函数中加入
最后--测试环节
(1). PasswordLoginForm上加上mobile的tag
在 forms/user.go中 编写
(2).测试
name字段不符合自定义字段的校验逻辑,这时候就会报错
当你完成到这一步的时候说明你已经掌握了自定义校验器的使用
下一篇章介绍统一的response返回处理
如果这系列的文章对你有有用,请点赞和留言吧~