大家好,我是渔夫子。
今天给大家聊一聊gin框架中是如何解析请求中的json并对其进行验证的。
从一个示例开始
在下面这个示例中,定义了一个User
结构体,该结构体中有3个字段:FirstName
、LastName
和Email
。同时定义了一个校验函数 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学堂,专注实用性,非常值得大家关注。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
2-27 在命令行窗口中启动的Python解释器中实现 在Python自带的IDLE中实现 print(“Hello world”) 编码规范 每个import语句只导入一个模块,尽量避免一次导入多个模块 不要在行尾添加分号“:”,也不要用分号将两条命令放在同…