gin学习记录 - 4 | 青训营;

116 阅读5分钟

4. bind 绑定参数

gin 中的 bind 可以很方便的将前端传递来的数据与结构体进行参数绑定,以及参数校验

4.1 参数绑定

在使用这个功能的时候,需要给结构体加上Tag :json,form,url

两种绑定方式:

  • MustBind 实际开发不用,校验失败会改状态码
  • ShouldBind 可以绑定json, query, param, yaml, xml,如果校验不通过会返回错误

绑定post参数 ShouldBindJSON

tag 为 : json

type UserInfo struct {  
    Name string `json:"name"`  
    Age int `json:"age"`  
    Gender string `json:"gender"`  
}  
  
func main() {  
    router := gin.Default()  

    // 绑定post参数  
    /* 发送到数据:  
    {  
        "name": "aaa",  
        "age": 33,  
        "gender": "male"  
    }  
    */  
    router.POST("/bind-post", func(arg *gin.Context) {  
        var userInfo UserInfo  

        // 将请求中体json数据绑定到userInfo 上  
        err := arg.ShouldBindJSON(&userInfo)  

        if err != nil {  
            arg.JSON(400, gin.H{"error": err, "msg": "绑定失败"})  
            fmt.Println(err)  
            return  
        }  
        arg.JSON(200, gin.H{"data": userInfo, "msg": "绑定成功"})  
    })  
    /* 返回结果:  
    {  
    "data": {  
            "name": "aaa",  
            "age": 33,  
            "gender": "male"  
        },  
        "msg": "绑定成功"  
    }  
    */  

    err := router.Run(":80")  
    if err != nil {  
        return  
    }  
}  

4.2 绑定query参数 ShouldBindQuery

tag 为 : form

form是用来绑定 POST 请求体中的查询参数,这里不能用query,query是用来绑定 GET 请求中的查询参数

type UserInfo struct {  
    Name string `json:"name" form:"name"`  
    Age int `json:"age" form:"age"`  
    Gender string `json:"gender" form:"gender"`  
}  
  
func main() {  
    router := gin.Default()  

    // 绑定query参数  
    // 输入的请求为: http://127.0.0.1/bind-query?name=kjasn&age=54&gender=male  
    router.POST("/bind-query", func(arg *gin.Context) {  
        var userInfo UserInfo  

        // 将请求中的json数据绑定到userInfo 上  
        err := arg.ShouldBindQuery(&userInfo)  

        if err != nil {  
            arg.JSON(400, gin.H{"error": err, "msg": "绑定失败"})  
            fmt.Println(err)  
            return  
        }  
        arg.JSON(200, gin.H{"type": "bind-query", "data": userInfo, "msg": "绑定成功"})  
    })  
    /* 结果如下:  
    {  
        "data": {  
            "name": "kjasn",  
            "age": 54,  
            "gender": "male"  
        },  
        "msg": "绑定成功",  
        "type": "bind-query"  
    } */

    err := router.Run(":80")  
    if err != nil {  
        return  
    }  
}  
  

4.3 绑定动态参数 ShouldBindUri

tag 为 : uri

type UserInfo struct {  
    // 后面加的是tag uri是用来绑定POST 请求中的url的参数  
    Name string `json:"name" form:"name" uri:"name" `  
    Age int `json:"age" form:"age" uri:"age"`  
    Gender string `json:"gender" form:"gender" uri:"gender"`  
}  
  
func main() {  
router := gin.Default()  
  
// 绑定动态参数 从url中获取参数  
// 输入的请求为:http://127.0.0.1/bind-uri/qqq/12/female  
router.POST("/bind-uri/:name/:age/:gender", func(arg *gin.Context) {  
    var userInfo UserInfo  

    // 将请求中的url的数据绑定到userInfo 上  
    err := arg.ShouldBindUri(&userInfo)  

    if err != nil {  
        arg.JSON(400, gin.H{"error": err, "msg": "绑定失败"})  
        fmt.Println(err)  
            return  
        }  
        arg.JSON(200, gin.H{"data": userInfo, "msg": "绑定成功"})  
    })  
    /*  
    {  
        "data": {  
            "name": "qqq",  
            "age": 12,  
            "gender": "female"  
        },  
        "msg": "绑定成功"  
    }  
    */  

    err := router.Run(":80")  
    if err != nil {  
        return  
    }  
}  
  

4.4 ShouldBind

会根据请求头中的 content-type 去自动绑定
默认tag 为 form

router.POST("/bind-form", func(arg *gin.Context) {  
    var userInfo UserInfo  

    // 请求1: http://127.0.0.1/bind-form?name=lkh&age=77&gender=secret (虽然是post,但也能绑定get请求)  
    /* 请求2: (json格式) 也可以请求form-data等类型  
    {  
        "name": "aaa",  
        "age": 33,  
        "gender": "男"  
    }  
    */  

    err := arg.ShouldBind(&userInfo)  

    if err != nil {  
        arg.JSON(400, gin.H{"error": err, "msg": "绑定失败"})  
        fmt.Println(err)  
            return  
    }  
    arg.JSON(200, gin.H{"data": userInfo, "msg": "绑定成功"})  
    /*  
    // 请求1的结果  
    {  
        "data": {  
            "name": "lkh",  
            "age": 77,  
            "gender": "secret"  
        },  
        "msg": "绑定成功",  
        "type": "" // get请求没有的请求体 Content-Type字段  
    }  

    // 请求2的结果  
    {  
    "data": {  
            "name": "aaa",  
            "age": 33,  
            "gender": "男"  
        },  
        "msg": "绑定成功",  
        "type": "application/json" // 类型  
    }  
    */  
})  
  

4.5 bind 绑定器

需要使用参数验证功能,需要加 binding tag

常用验证器

验证器和值之间不能有空格!!!,同一个字段多个验证规则用','隔开
eg: binding:"min = 5" // error!!!

  • 不能为空,并且不能没有这个字段
    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 等于其他字段的值,如:binding:"eqfield=ConfirmPassword"
    nefield 不等于其他字段的值
    忽略字段,如:binding:"-"

gin 内置验证器

枚举验证器 oneof

要求当前字段的值必须是 oneof 中的一个,oneof的值用空格隔开,常用与性别检验

// 以下是结构体中的一个字段  
Gender string `json:"gender" binding:"oneof=男 女"` // 性别  

字符串相关

contains=arg 包含arg的字符
excludes=arg 不包含arg
startswith=arg 以arg开头
endswith=arg 以arg结尾
dive 用来对数组或切片中的每个元素进行单独的验证

// name字段  
Name string `json:"name" binding:"required,endswith=f"` // 用户名  
  
// 1. 验证切片中的元素不能为空:  
Data []string `binding:"dive,required"`  
  
// 2. 验证切片中的元素的长度不能超过10:  
Data []string `binding:"dive,max=10"`  
  
// 3. 验证切片中的元素必须为数字:  
Data []string `binding:"dive,number"`  

自定义验证器

内置的验证器往往不够用,需要自定义验证器,如下自定义一个校验姓名是否重复的验证器

package main  
  
import (  
    "fmt"  
    "github.com/gin-gonic/gin"  
    "github.com/gin-gonic/gin/binding"  
    "github.com/go-playground/validator/v10"  
    "net/http"  
    "reflect"  
)  
  
type SignUserInfo struct {  
    // 添加验证器 常用的 此处 uniqueName是一个自定义的验证器  
    Name string `json:"name" binding:"required,uniqueName" msg:"昵称不能为空也不能重复"` // 用户名  
    Age int `json:"age" binding:"lt=100,gt=0" msg:"年龄不符合要求"` // 年龄  
}  
  
// 简单写,直接用一个字符串数组 实际开发需要存储用户信息的数据库然后到数据库中查找昵称是否重复  
var nameList = []string{"张三", "李四", "王五"}  
  
func _GetValidMsg(err error, user SignUserInfo) string {  
    // 将err接口断言为具体类型  
    if errs, ok := err.(validator.ValidationErrors); ok {  
        getObj := reflect.TypeOf(&user)  
        fmt.Println("类型为:", getObj)  

        // 断言成功  
        for _, e := range errs { // 遍历每一个错误信息 报错信息可能有多个  
            // 根据报错字段名,获取结构体的具体字段  
            if f, exits := getObj.Elem().FieldByName(e.Field()); exits {  
                msg := f.Tag.Get("msg")  
                fmt.Println(msg)  
                    return msg  
                }  
            }  
        }  
    return "" // 没有检查到验证器错误则返回空  
}  
  
// 函数原型为 func(fl FieldLevel) bool  
func _nameCheck(fl validator.FieldLevel) bool {  
    // 遍历用户信息列表 实际应到数据库中查询,这里只是简单模拟  
    for _, nameStr := range nameList {  
        // 断言  
        name := fl.Field().Interface().(string)  

        if nameStr == name {  
            return false  
        }  
    }  
    return true  
}  
  
func main() {  
    router := gin.Default()  

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {  
        // 注册uniqueName验证器,其自定义验证功能通过_nameCheck函数来实现  
        err := v.RegisterValidation("uniqueName", _nameCheck)  
        if err != nil {  
            return  
        }  
    }  
  
    router.POST("/custom-check", func(c *gin.Context) {  
        fmt.Println("访问...")  
        var user SignUserInfo  
        err := c.ShouldBindJSON(&user) // 返回error信息,不报错则返回空  
        if err != nil {  
            customErr := _GetValidMsg(err, user)  
            c.JSON(http.StatusBadRequest, gin.H{"msg": customErr})  
            return  
        }  

        c.JSON(http.StatusOK, gin.H{"data": user})  
    })  

    err := router.Run(":80")  
    if err != nil {  
        return  
    }  
}