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输入错误信息如下:

输入全部正确信息如下:


后记

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

相关推荐
煎鱼eddycjy11 小时前
新提案:由迭代器启发的 Go 错误函数处理
go
煎鱼eddycjy11 小时前
Go 语言十五周年!权力交接、回顾与展望
go
不爱说话郭德纲1 天前
聚焦 Go 语言框架,探索创新实践过程
go·编程语言
0x派大星2 天前
【Golang】——Gin 框架中的 API 请求处理与 JSON 数据绑定
开发语言·后端·golang·go·json·gin
get2002 天前
Gin 框架中间件详细介绍
中间件·gin
bigbig猩猩2 天前
Gin 框架中的表单处理与数据绑定
驱动开发·gin
IT书架2 天前
golang高频面试真题
面试·go
郝同学的测开笔记2 天前
云原生探索系列(十四):Go 语言panic、defer以及recover函数
后端·云原生·go
荣~博客3 天前
Golang语言整合jwt+gin框架实现token
开发语言·golang·gin
拧螺丝专业户3 天前
gin源码阅读(2)请求体中的JSON参数是如何解析的?
前端·json·gin