导读
本套笔记是为了学习过其他语言框架,想要快速掌握gin框架推行的一套笔记。 虽然为了青训营而制作的一套笔记,但其他想要学go的程序员也可以通过这个上手go世界,后续会带你快速上手gorm,学完这两个之后,简体版抖音基本上就可以独立完成了,后续还会进行大项目的讲解开发,制作不易,喜欢的就点个关注吧。
注意
代码详解大部分是注释的形式给出,请留意代码注释。
Gin框架介绍
导读:Gin是一个非常受欢迎的Golang Web框架,它旨在提供高性能、易用和轻量级的解决方案。
bind绑定器
Gin 框架提供了许多便捷的功能和工具,其中包含了一种基于 HTTP 请求的绑定器,用于将请求中的数据绑定到结构体或参数中。
Gin 框架使用了反射机制,可以根据数据类型自动将请求中的数据绑定到指定的结构体或参数字段上,以实现参数验证和解析功能。绑定器可以方便地处理各种数据格式,如 JSON、XML、表单数据等。
当我们学习这些知识的时候会大量用上标签
ShouldBindJSON
ShouldBindJSON 专门用于处理请求中的 JSON 数据。它会根据请求的 Content-Type 头部自动选择适合的绑定方法,并将 JSON 数据解析为对应的结构体。
package main
import (
"github.com/gin-gonic/gin" //引入框架
"net/http"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Gender int `json:"gender"`
} //一个user简单的user结构体
func get(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) //绑定失败,返回错误信息
return
}
c.JSON(http.StatusOK, gin.H{"message": "响应成功", "user": user}) //绑定成功,响应user
}
func main() {
router := gin.Default() //设置路由
router.GET("/user", get)
router.Run() //设置运行接口
}
我们使用postman发送数据
可以看到成功绑定
ShouldBindQuery
与json类似,但是我们要用form标签。
通过为结构体字段添加 form 标签,你可以指定该字段应该从哪个表单字段中获取值。
当使用框架的参数绑定方法ShouldBindQuery时就会解析请求中的表单数据,并将对应的字段值绑定到 User 结构体的字段上。
package main
import (
"github.com/gin-gonic/gin" //引入框架
"net/http"
)
type User struct {
Name string `json:"name" form:"name"`
Age int `json:"age" form:"age"`
Gender int `json:"gender" form:"gender"`
} //一个user简单的user结构体
func get(c *gin.Context) {
var user User
if err := c.ShouldBindQuery(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) //绑定失败,返回错误信息
return
}
c.JSON(http.StatusOK, gin.H{"message": "响应成功", "user": user}) //绑定成功,响应user
}
func main() {
router := gin.Default() //设置路由
router.GET("/user", get)
router.Run() //设置运行接口
}
postman发送
响应结果如下
ShouldBindUri
绑定动态参数
uri 标签用于标识结构体字段与URI路径中的参数之间的映射关系。通过为结构体字段添加 uri 标签,我们可以指定该字段应该从URI路径中的哪个参数中获取值。
package main
import (
"github.com/gin-gonic/gin" //引入框架
"net/http"
)
type User struct {
Name string `uri:"name"`
Age int `uri:"age"`
Gender int `uri:"gender"`
} //一个user简单的user结构体
func get(c *gin.Context) {
var user User
if err := c.ShouldBindUri(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) //绑定失败,返回错误信息
return
}
c.JSON(http.StatusOK, gin.H{"message": "响应成功", "user": user}) //绑定成功,响应user
}
func main() {
router := gin.Default() //设置路由
router.GET("user/:name/:age/:gender", get)
router.Run() //设置运行接口
}
注意要修改路径
postman发送信息和接受数据如下
绑定form-data、x-www-form-urlencode
这个和前面类似
package main
import (
"github.com/gin-gonic/gin" //引入框架
"net/http"
)
type User struct {
Name string `form:"name"`
Age int `form:"age"`
Gender int `form:"gender"`
} //一个user简单的user结构体
func get(c *gin.Context) {
var user User
if err := c.ShouldBind(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) //绑定失败,返回错误信息
return
}
c.JSON(http.StatusOK, gin.H{"message": "响应成功", "user": user}) //绑定成功,响应user
}
func main() {
router := gin.Default() //设置路由
router.GET("/form", get)
router.Run() //设置运行接口
}
postman运行结果
常用验证器
// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"
// 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
len 长度,如:binding:"len=6"
// 针对数字的大小
eq 等于,如:binding:"eq=3"
ne 不等于,如:binding:"ne=12"
gt 大于,如:binding:"gt=10"
gte 大于等于,如:binding:"gte=10"
lt 小于,如:binding:"lt=10"
lte 小于等于,如:binding:"lte=10"
// 针对同级字段的
eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
nefield 不等于其他字段的值
- 忽略字段,如:binding:"-"
我们重新改变一下结构体的代码
type User struct {
Name string `form:"name" binding:"required"`
Age int `form:"age" binding:"min=18"`
Gender int `form:"gender" binding:"len=1"`
} //一个user简单的user结构体
运行之后就会按照上面的验证。 postman运行结果如下
1:全部正常
2: name不写
3:age<18
4:genderlen>1
当有多个不符合时,只会报道第一个发现的error
Gin自带的验证器
// 枚举 只能是red 或green
oneof=red green
// 字符串
contains=fengfeng // 包含fengfeng的字符串
excludes // 不包含
startswith // 字符串前缀
endswith // 字符串后缀
// 数组
dive // dive后面的验证就是针对数组中的每一个元素
// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径
// 日期验证 1月2号下午3点4分5秒在2006年
datetime=2006-01-02
与常用验证器使用方法一致,鼓励读者自己去运行测试,其实非常简单,只能说一模一样。
自定义验证的错误信息
我们上面的代码运行过后,当测试失败案例时,返回的meg往往不够明显,所以我们可以自定义验证错误信息,也就是当出现我们预料到的情况时返回给前端。
只需要给结构体加一个msg 的tag。
type User struct {
Name string `form:"name" binding:"required" msg:"name没有填写"`
Age int `form:"age" binding:"min=18"`
Gender int `form:"gender" binding:"len=1"`
} //一个user简单的user结构体
之后我们需要获取msg中的信息,这里我们自定义一个方法来获得 当元素不符合需求时,返回标签中的信息
// GetValidMsg 返回结构体中的msg参数
func GetValidMsg(err error, obj any) string {
// 使用的时候,需要传obj的指针
getObj := reflect.TypeOf(obj)
// 将err接口断言为具体类型
if errs, ok := err.(validator.ValidationErrors); ok {
// 断言成功
for _, e := range errs {
// 循环每一个错误信息
// 根据报错字段名,获取结构体的具体字段
if f, exits := getObj.Elem().FieldByName(e.Field()); exits {
msg := f.Tag.Get("msg")
return msg
}
}
}
return err.Error()
}
GetValidMsg 函数的作用是从结构体的字段中获取 msg 参数,并返回错误信息。
函数签名为:
func GetValidMsg(err error, obj any) string
参数说明:
err是一个错误类型,用于接收验证错误信息。obj是一个任意类型,需要传入结构体的指针。
函数内部实现的逻辑如下:
-
通过
reflect.TypeOf(obj)获取传入对象的具体类型。 -
将
err断言为validator.ValidationErrors类型,用于验证是否为验证错误。 -
如果断言成功,则表示错误是由验证器返回的。
-
遍历每一个错误信息,通过报错字段名获取结构体中对应的字段。
-
如果找到了对应的字段,则通过
Tag.Get("msg")获取字段的msg参数。 -
返回
msg参数作为错误提示信息。 -
如果没有找到对应的字段,则返回原始错误信息
err.Error()。
内部方法解答:
1:
validator.ValidationErrors 是一个类型,它表示验证器返回的错误信息。
如果 err 是 validator.ValidationErrors 类型或其子类型,则转换成功, ok 的值将为 true,同时将转换后的结果赋值给 errs。
getObj.Elem().FieldByName(e.Field()) 这段代码用于根据报错字段名获取结构体的具体字段。
2:
getObj是一个reflect.Type类型的变量,表示传入对象的类型。getObj.Elem()方法返回getObj的指针所指向的具体类型。FieldByName(name string) reflect.StructField方法会返回一个reflect.StructField类型的值,该值描述了结构体的一个字段。参数name表示字段的名称,该方法会根据名称在结构体中查找并返回对应的字段。e.Field()返回验证错误的字段名。
因此,getObj.Elem().FieldByName(e.Field()) 的作用是根据报错字段名,在结构体中查找并返回对应的字段的描述信息(reflect.StructField 类型)。
完整代码
package main
import (
"github.com/gin-gonic/gin" //引入框架
"github.com/go-playground/validator/v10"
"net/http"
"reflect"
)
type User struct {
Name string `form:"name" binding:"required" msg:"name没有填写"`
Age int `form:"age" binding:"min=18"`
Gender int `form:"gender" binding:"len=1"`
} //一个user简单的user结构体
// GetValidMsg 返回结构体中的msg参数
func GetValidMsg(err error, obj any) string {
// 使用的时候,需要传obj的指针
getObj := reflect.TypeOf(obj)
// 将err接口断言为具体类型
if errs, ok := err.(validator.ValidationErrors); ok {
// 断言成功
for _, e := range errs {
// 循环每一个错误信息
// 根据报错字段名,获取结构体的具体字段
if f, exits := getObj.Elem().FieldByName(e.Field()); exits {
msg := f.Tag.Get("msg")
return msg
}
}
}
return err.Error()
}
func get(c *gin.Context) {
var user User
if err := c.ShouldBind(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": GetValidMsg(err, &user)}) //绑定失败,返回错误信息
return
}
c.JSON(http.StatusOK, gin.H{"message": "响应成功", "user": user}) //绑定成功,响应user
}
func main() {
router := gin.Default() //设置路由
router.GET("/form", get)
router.Run() //设置运行接口
}
之后我们在postman里故意不写name,将会给出我们的msg: