10篇带你手摸手封装gin框架(4)-Validator字段校验

·  阅读 1361
10篇带你手摸手封装gin框架(4)-Validator字段校验

前言

这是我参与更文挑战的第4天,大家好,我是作曲家种太阳
上一篇讲到了Zap日志管理和路由初始化,相信你对gin已近有了初步的认知.
这一篇我们讲下如何校验前端发送过来的数据,校验器的中文翻译,并编写一个自定义的校验器

1. 介绍

字段校验是后端比较重要的环节,虽然前端的form表单做了校验,但是后端为了安全性和避免数据库的 脏数据,我们必须要做字段校验
字段校验我们虽然可以编写函数,自己封装校验逻辑,但是结构体的tag已近有一些基础校验功能,再加上可以自定义校验器,代码优雅效率十分高!
为了让你较为轻松的理解请求流程,画了一张数据流向图,了解下整个数据流向走势

image.png

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).验证校验有效性

参数正确验证:

image.png 参数错误时验证: name字段没有输入情况下 image.png

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>.参数错误时:

image.png

<1>.参数正确时:

image.png

中文的错误校验就显示出来了,如果你做到这一步,并且测试通过 就可以继续往下做~

3.自定义校验

结构体的tag都校验功能比较基础,但是有些复杂的校验需求,我们必须得自定义校验器才能做到
现在假设一个需求,前端传递的用户名必须得是手机号格式:

  1. 连续数字11位
  2. 数字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函数中加入 image.png

最后--测试环节

(1). PasswordLoginForm上加上mobile的tag 在 forms/user.go中 编写 image.png

(2).测试

image.png name字段不符合自定义字段的校验逻辑,这时候就会报错

当你完成到这一步的时候说明你已经掌握了自定义校验器的使用

下一篇章介绍统一的response返回处理

如果这系列的文章对你有有用,请点赞和留言吧~

分类:
后端
标签:
分类:
后端
标签: