关于go:Go框架深入解析gin中使用validator包对请求体进行验证

3次阅读

共计 3926 个字符,预计需要花费 10 分钟才能阅读完成。

大家好,我是渔夫子。

明天给大家聊一聊 gin 框架中是如何解析申请中的 json 并对其进行验证的。

从一个示例开始

在上面这个示例中,定义了一个 User 构造体,该构造体中有 3 个字段:FirstNameLastNameEmail。同时定义了一个校验函数 UserStructLevelValidation ,该函数对 User 构造体中的字段进行了校验。如下:

  • 校验 FirstName 和 LastName 是否为空
  • 对 Email 字段值进行正则校验,同时校验是否是空。

代码如下:

// User contains user information.
type User struct {
    FirstName string `json:"fname"`
    LastName  string `json:"lname"`
    Email     string `binding:"required,email"`
}

func UserStructLevelValidation(sl validator.StructLevel) {user := sl.Current().Interface().(User)

    if len(user.FirstName) == 0 && len(user.LastName) == 0 {sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "")
        sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "")
    }

    // plus can to more, even with different tag than "fnameorlname"
}

那么问题来了:

第一:校验函数 UserStructLevelValidation 是如何和指标构造体 User 进行关联的?

第二:UserStructLevelValidation 是在哪里被调用的?

第三:UserStructLevelValidation 函数的入参为什么是 validator.StructLevel 类型?

第四:User 构造体中的 Email 字段是如何被校验的?

第五:bingding tag 都有哪些属性以及对应的含意?

接下来,咱们就一一解答上述所有问题,以便对构造体的验证有一个全面的理解。

校验函数和指标构造体是如何关联的

当咱们自定义了校验函数 UserStructLevelValidation 之后,在 main 函数中就能够通过以下代码和指标构造体 User 进行关联了:

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    validator "github.com/go-playground/validator/v10"
)

func main() {route := gin.Default()

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterStructValidation(UserStructLevelValidation, User{})
    }

    route.POST("/user", validateUser)
    route.Run(":8085")
}

通过代码咱们能够看到,校验函数和指标构造体是通过 **v.RegisterStructValidation** 函数进行关联的。该实例是将 **UserStructLevelValidation****User**构造体进行了关联

那么 v 又是什么对象呢?首先咱们晓得 v 是通过断言,转换成了 *validator.Validate类型。validator.Validate 是应用的第三方包github.com/go-playground/validator/v10

那么 binding.Validator 对象又是什么呢?通过源码可知,binding.Validator 是 gin 框架中定义的一个全局的 StructValidator 类型的变量,其默认值是 defaultValidator 类型对象,defaultValidator中又定义了一个 validate *validator.Validate 字段。如下:

var Validator StructValidator = &defaultValidator{}

type defaultValidator struct {
    once     sync.Once
    validate *validator.Validate
}

所以,binding.Validator.Engine()函数本质上返回的是 defaultValidator 中的 validate 字段,也就是说 代码中的 v 变量 是 validator.Validate 类型的对象。

校验函数是在何处被调用的?

校验函数和要校验的指标构造体关联后,校验函数是在哪里被调用的呢?答案是 在绑定申请参数中:ShouldBindJSON 函数或其余 ShouldBindXXX 函数

在下面示例中,注册了 /user 到 validateUser 的路由。在 validateUser 中,将申请参数和 User 类型的变量 u 进行了绑定,在绑定过程中,实际上是调用了 UserStructLevelValidation 函数的。咱们看下具体的示例代码:

func validateUser(c *gin.Context) {
    var u User
    if err := c.ShouldBindJSON(&u); err == nil {c.JSON(http.StatusOK, gin.H{"message": "User validation successful."})
    } else {
        c.JSON(http.StatusBadRequest, gin.H{
            "message": "User validation failed!",
            "error":   err.Error(),})
    }
}

ShouldBindJSON 函数底层实际上是调用的 jsonBinding 类型的 Bind 函数,而 Bind 函数调用 decodeJSON 函数,在 decodeJSON 函数中调用了 validate,如下:

再看下 validate 函数,实际上是调用了 Validator.ValidateStruct 函数,如下:

Validator 就是在问题 1 中咱们说的 binding.Validator 的变量,其值是 defaultValidator 类型,也就是说要调用该对象的 ValidateStruct 函数,如下:

咱们看到,当 obj 是构造体时就调用 validateStruct 函数,留神和 ValidateStruct 函数命名上的区别。在 validateStruct 函数中,又调用了 defaultValidator 中 validate 变量的 Struct 函数,也就是 validator.Validate 类型。
这里的 validate 变量就又和问题 1 中注册校验函数和指标构造体中的对象关联起来了

校验函数的入参为什么是 validator.StructLevel 类型

在定义校验函数 UserStructLevelValidation 时,咱们看到该函数的入参是一个 validator.StructLevel 类型的变量,是为什么呢?

这个还是要从关联校验函数和指标构造体的 RegisterStructValidation 函数说起。进入 RegisterStructValidation 函数的源代码,会看到将校验函数 UserStructLevelValidation 进行了包装,如下:

通过 wrapStructLevelFunc 函数包装后,转换成了如下函数:

到这里 就看到了 fn(sl)函数的调用了,fn 就是注册的校验函数 UserStructLevelValidation,参数 sl 就是 StructLevel 类型的变量。依据该类型的参数能够取得被校验的构造体对象。

所以,在校验函数中传入 validator.StructLevel 类型的变量是 github.com/go-playground/validator/v10 这个校验包的个性

User 构造体中的 Email 字段是如何被校验的?

在校验函数 UserStructLevelValidation 中,咱们并没有看到对 User.Email 字段的校验,但实际上又校验了,这是为什么呢?答案就在于该字段设置了 binding 的 tag:binding:”required,email”。binding 的 tag 是 gin 框架在初始化 **github.com/go-playground/validator/v10** 包的对象时设置的。如下代码:

Engine 函数 是不是相熟,在注册校验函数时首先就调用了binding.Validator.Engine()

binding tag 都有哪些属性以及对应的含意?

binding 标签是 github.com/go-playground/validator/v10 包中设置的 tag。其属性天然是和 validator 包有关系。validator 反对的校验属性在 baked_in.go 文件中定义的,以下是反对的局部属性及对应的校验函数,若想理解更多 可间接拜访校验规定:

总结

本文通过一个示例介绍了在 gin 框架中如何解析申请并校验对应的构造体字段。gin 实际上是调用了第三方包 validator,并自定义了具体构造体的校验函数。

— 特地举荐 —

特地举荐:一个专一 go 我的项目实战、我的项目中踩坑教训及避坑指南、各种好玩的 go 工具的公众号,Go 学堂,专一实用性,十分值得大家关注。

正文完
 0