gin框架使用系列之五——表单校验

一 、表单验证的基本理论

在第三篇中,我们介绍了如何将form表单和json等数据转成结构体对象中的方法,当时在绑定的结构体中,其tag中就有"binding"的信息,这就是gin中表单验证的基础。为了详细了解表单验证,我们进一步了解以下表单绑定的知识。

1.1、若要将请求体绑定到结构体中,需要使用模型绑定,支持JSON、XML、YAML和标准表单的绑定,设置时需要在绑定的字段上设置tag,其只要有两套绑定方法

  • Must bind

    • 方法: Bind 、BindJSON、BindXML、BindQuery、BindYAML
    • 行为:这些方法底层使用MustBindWith方法,如果存在绑定错误,请求将被终止,响应代码会被设置成400
  • Should bind

    • 方法: ShouldBind、ShouldBindJSON、ShouldBindXML、ShouldBindQuery、ShouldBindYAML
    • 行为:底层使用ShouldBindWith方法,如果存在绑定错误,则返回go语言的错误形式,开发人员可以处理错误,请求不会被终止

1.2、Gin中使用 go-playground/validator来验证表单,详细文档

二、表单验证示例

我们以一个注册的接口的表单验证为例,示例表单验证的写法如下:

go 复制代码
type SignUpParam struct {
	//  1<= age <= 130
	Age uint8 `json:"age" binding:"gte=1,lte=130"`
	// name,必须
	Name string `json:"name" binding:"required"`
	// email,必须且满足email格式
	Email string `json:"email" binding:"required,email"`
	// password,必须
	Password string `json:"password" binding:"required"`
	// re_password,必须,且要和password字段相同
	RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

func main() {
	router := gin.Default()
	router.POST("/sign-up", func(c *gin.Context) {
		var param SignUpParam
		if err := c.ShouldBind(&param); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"msg": err.Error(),
			})
			return
		}
		// TODO 业务逻辑
		c.JSON(http.StatusOK, "success")
	})

	router.Run() // listen and serve on 0.0.0.0:8080
}

在postman中调用接口,报错信息如下:

三、表单验证信息国际化

上面示例中我们可以看到表单验证信息的报错中,显示的不是很详细,而且暴露了go后台代码的数据,我们可以为其添加国际化信息。

下面是增加翻译器的方法

go 复制代码
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
}

整体代码如下:

go 复制代码
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"
)

type SignUpParam struct {
	//  1<= age <= 130
	Age uint8 `json:"age" binding:"gte=1,lte=130"`
	// name,必须
	Name string `json:"name" binding:"required"`
	// email,必须且满足email格式
	Email string `json:"email" binding:"required,email"`
	// password,必须
	Password string `json:"password" binding:"required"`
	// re_password,必须,且要和password字段相同
	RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

// 定义一个全局的翻译器
var trans ut.Translator

func main() {

	//代码侵入性很强 中间件
	if err := InitTrans("zh"); err != nil {
		fmt.Println("初始化翻译器错误")
		return
	}

	router := gin.Default()
	router.POST("/sign-up", func(c *gin.Context) {
		var param SignUpParam
		if err := c.Bind(&param); err != nil {
			errs, ok := err.(validator.ValidationErrors)
			if !ok {
				c.JSON(http.StatusBadRequest, gin.H{
					"msg": err.Error(),
				})
				return
			}
			// 检查使用自定义的转换器
			c.JSON(http.StatusBadRequest, gin.H{
				"msg": errs.Translate(trans),
			})
			return
		}
		// TODO 业务逻辑
		c.JSON(http.StatusOK, "success")
	})

	router.Run() // listen and serve on 0.0.0.0:8080
}
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
}

用postman输入错误信息如下:

输入全部正确信息如下:


后记

个人总结,欢迎转载、评论、批评指正

相关推荐
幼儿园老大*10 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
童先生17 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*19 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
架构师那点事儿1 天前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
RationalDysaniaer2 天前
Gin入门笔记
笔记·gin
于顾而言2 天前
【笔记】Go Coding In Go Way
后端·go
qq_172805592 天前
GIN 反向代理功能
后端·golang·go
千年死缓2 天前
gin中间件
中间件·gin
follycat2 天前
2024强网杯Proxy
网络·学习·网络安全·go
OT.Ter2 天前
【力扣打卡系列】单调栈
算法·leetcode·职场和发展·go·单调栈