[ Gin框架使用 ]


目录:

基本使用

安装(提前配置好gopath环境变量, mod包管理)

go get -u "github.com/gin-gonic/gin"

运行

go run main.go

一.个人项目展示

  • 使用github.com/gin-gonic/gin轻量级web框架
  • 使用gorm.io/gorm数据库第三方包
  • 使用gopkg.in/yaml.v3 读取yaml配置文件
  • 使用github.com/sirupsen/logrus 日志配置
  • 使用github.com/satori/go.uuid 生成uuid
  • ...等待补充

二.响应

  • 使用"-"来隐藏此属性的响应, 防止响应重要数据
type UserInfo struct {
    Name string `json:"user"`
    Password string `json:"-"`
}

func main(){
    router := gin.Default("templates/*")
    router.StaticFile("/static/xxx.png","static/xxx.png")
    router.GET("/", func(ctx *gin.Context){
        ctx.JSON(200, ")
    })
    router.GET("/", func(ctx *gin.Context){
        ctx.String(200, "你好")
    })
    router.GET("/", func(ctx *gin.Context){
        ctx.XML(200, gin.H{"name": "陈海峰"})
    })
    router.GET("/", func(ctx *gin.Context){
        ctx.YAML(200, gin.H{"name": "陈海峰"})
    })
    router.GET("/", func(ctx *gin.Context){
        ctx.HTML(200, "index.html", {"name": "陈海峰"})
    })
}

1.设置响应头

func TestResponseHeader(ctx *gin.Context) {
    ctx.Header("Token", "chenhaifeng11")
    ctx.Header("Content-Type", "application/text;charset=utf-8")
    ctx.JSON(200, gin.H{"data": "看看响应头"})
}

2.重定向

func TestApi(ctx *gin.Context) {
    // ctx.JSON(200, gin.H{"msg": "测试api"})
    // 重定向
    ctx.Redirect(301, "https://www.baidu.com")
}

三.请求

请求头

func TestRequestHeader(ctx *gin.Context) {
    // 1. 不区分大小写
    fmt.Println(ctx.GetHeader("User-Agent"))

    // 2. 获取整个请求头, 再从map中get
    fmt.Println(ctx.Request.Header.Get("User-Agent"))
}

路径url参数params获取

func TestQueryApi(ctx *gin.Context) {
    // url http://127.0.0.1:9090/api/test?user=10&name=chf&em=10&em=20
    // 有可能空
    fmt.Println(ctx.Query("user"))
    // name := ctx.Query("name")
    fmt.Println(ctx.QueryArray("em")) // 列表

    fmt.Println(ctx.GetQuery("user")) // 已经判断是否传了
    ctx.JSON(200, gin.H{"msg": "获取params数据"})
}

url占位参数

func TestParamsApi(ctx *gin.Context) {
    // 路由地址 /api/test/:user_id
    fmt.Println(ctx.Param("user_id"))

    // 路由地址 /api/test/:user_id/:info_id
    fmt.Println(ctx.Param("user_id"))
    fmt.Println(ctx.Param("info_id"))
}

form表单获取

func TestFormApi(ctx *gin.Context) {
    fmt.Println(ctx.PostForm("user"))
    fmt.Println(ctx.DefaultPostForm("user", "chf"))
    fmt.Println(ctx.DefaultPostForm("user", "chf"))
    forms, err := ctx.MultipartForm()
    fmt.Println(forms, err) // 接受所有的form参数, 放到map里
    ctx.JSON(200, gin.H{"msg": "获取form表单数据"})
}

raw数据获取

func TestRawDataApi(ctx *gin.Context) {
    type User struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }
    var user User
    body, _ := ctx.GetRawData()
    contentType := ctx.GetHeader("Content-Type")
    switch contentType {
    case "application/json":
        // json解析

        err := json.Unmarshal(body, &user)
        if err != nil {
            fmt.Println(err.Error())
        }
    }
    fmt.Println(user)
    ctx.JSON(200, user)
}

四.请求参数绑定bind

  • 可以不传, 传了就必须正确

结构体

type UserInfo struct {
    Name string `json:"name" form:"name" uri:"name"`
    Age  int    `json:"age" form:"age" uri:"age"`
    Sex  string `json:"sex" form:"sex" uri:"sex"`
}

JSON数据绑定

func TestBindJson(ctx *gin.Context) {
    // 结构体定义json标签
    var userInfo UserInfo
    err := ctx.ShouldBindJSON(&userInfo)
    if err != nil {
        ctx.JSON(200, gin.H{"msg": "你错了"})
    }
    ctx.JSON(200, userInfo)
}

url路径参数绑定

func TestBindQuery(ctx *gin.Context) {
    // 路由 /api/test/?name=陈海峰&age=19&sex=男
    // 结构体定义form标签
    var userInfo UserInfo
    err := ctx.ShouldBindQuery(&userInfo)
    if err != nil {
        ctx.JSON(200, gin.H{"msg": "你错了"})
    }
    ctx.JSON(200, userInfo)
}

url占位符绑定

func TestBindUri(ctx *gin.Context) {
    // 路由 /api/test/:name/:age/:sex
    // 结构体定义uri标签
    var userInfo UserInfo
    err := ctx.ShouldBindUri(&userInfo)
    if err != nil {
        ctx.JSON(200, gin.H{"msg": "你错了"})
    }
    ctx.JSON(200, userInfo)
}

form表单绑定

func TestBindForm(ctx *gin.Context) {
    // 结构体定义form标签
    var userInfo UserInfo
    err := ctx.ShouldBind(&userInfo) // 根据congent-type来判断自动绑定, 默认就是form
    if err != nil {
        ctx.JSON(200, gin.H{"msg": "错误了"})
    }
    ctx.JSON(200, userInfo)
}

bing数据验证

  • required 必填
  • min, max 最大最小
  • eq, ne, gt , gte, lt, lte, len 等于, 不等于, 大于, 大于等于, 小于, 小于等于, 长度
  • eqfield 等于某字段的值, 例如 binging:"Password=RePassword"
  • nefield 不等于某字段的值
  • dive 数组验证,卸载dive后面的每一个都会进行验证 binding:"required,dive,startswitch=like"
  • ip 地址验证
  • url 地址
  • uri 地址,地址的子集
  • datetime 时间
type SignUserInfo struct {
    Name       string   `json:"name" binding:"min=4,max=6,required"`
    Age        int      `json:"age"`
    Password   string   `json:"password"`
    RePassword string   `json:"re_password"`
    Sex        string   `json:"sex" binding:"oneof=man woman"`
    LikeList   []string `json:"like_list" binding:"required,dive,startswitch=like"`
    Ip         string   `json:"ip" binding:"ip"`
    Url        string   `json:"url" binding:"url"`
    Uri        string   `json:"uri" binding:"uri"`
    Date       string   `json:"data" binding:"datatime=2006-01-02 15:04:05"`
}

func TestVerifyData(ctx *gin.Context) {
    // 数据验证
    // required 必填
    // min, max 最大最小
    // eq, ne, gt , gte, lt, lte, len 等于, 不等于, 大于, 大于等于, 小于, 小于等于, 长度
    // eqfield 等于某字段的值, 例如 binging:"Password=RePassword"
    // nefield 不等于某字段的值
    // oneof 枚举 binding:"oneof=man woman"
    // contains 包含 binding:"contains=f"
    // excludes 不包含
    // startswitch 前缀
    // endswitch 后缀

    // 数组验证
    // dive 数组验证,卸载dive后面的每一个都会进行验证 binding:"required,dive,startswitch=like"

    // ip 地址验证
    // url 地址
    // uri 地址,地址的子集
    // datetime 时间

    var signUserInfo SignUserInfo
    err := ctx.ShouldBindJSON(&signUserInfo)
    if err != nil {
        ctx.JSON(200, gin.H{"msg": "错了"})
    }
    ctx.JSON(200, signUserInfo)
}

自定义验证器

func SignFunc(fl validator.FieldLevel) bool {
    var nameList []string = []string{"陈海峰", "张三", "欧zz"}
    for _, nameStr := range nameList {
        name := fl.Field().Interface().(string)
        if name == nameStr {
            return false
        }
    }
    return true
}

// 在主路由下方注册自定义验证
func SignValid() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("sign", SignFunc)
        v.RegisterValidation("sign1", SignFunc1)
        v.RegisterValidation("sign2", SignFunc2)
        ...

    }
}

五.文件上传

前端使用form-data 选择 file上传

// 文件上传
func TestUpload(ctx *gin.Context) {
    file, _ := ctx.FormFile("file")

    // 方法1 保存文件到本地
    fmt.Println(file.Filename)
    fmt.Println(file.Size / 1024) // 默认是字节
    ctx.SaveUploadedFile(file, "/media/1.png")

    // 方法2 读取上传的文件
    readerFile, _ := file.Open()
    data, _ := io.ReadAll(readerFile)
    fmt.Println(string(data))

    // 方法3 读取文件写入到本地
    readerFile1, _ := file.Open()
    writeFile, _ := os.Create("./media/2.png")
    defer writeFile.Close()
    n, _ := io.Copy(writeFile, readerFile1)
    fmt.Println(n)

    ctx.JSON(200, gin.H{"msg": "上传成功"})
}

// 上传多个
func TestUploads(ctx *gin.Context) {
    form, _ := ctx.MultipartForm()
    fiels := form.File["uploads"]
    for _, file := range fiels {
        ctx.SaveUploadedFile(file, "/media/"+file.Filename)
    }
    ctx.JSON(200, gin.H{"msg": fmt.Sprintf("成功上传%d个文件", len(fiels))})
}

六.文件下载

// 下载
func TestDownload(ctx *gin.Context) {
    // 方式1
    ctx.Header("Content-Type", "application/octet-stream")              // 数据流
    ctx.Header("Content-Disposition", "attachment; filename="+"牛逼.png") // 文件名
    ctx.Header("Content-Transfer-Encoding", "binary")                   // 传输二进制编码
    ctx.File("media/1.png")

    // 方式2 交给前端处理
    ctx.Header("fileName", "xxxpng")
    ctx.File("media/2.png")
}

中间件

单个路由中间件

  • 如果有多个响应, 响应会拼接起来
  • 就是连续使用多个视图函数就可以
  • 由前往后执行
  • Abort跳出, 后面不执行
func TestRouter(router *gin.RouterGroup) {
    router.GET("/test", func(ctx *gin.Context) {
        fmt.Println("执行中间件1")
        ctx.JSON(200, gin.H{"msg": "响应1"})
    }, func(ctx *gin.Context) {
        fmt.Println("执行中间件2")
        ctx.JSON(200, gin.H{"msg": "响应2"})
        ctx.Abort() // 直接响应, 后面不走了
    }, func(ctx *gin.Context) {
        fmt.Println("执行中间件3")
        ctx.JSON(200, gin.H{"msg": "响应3"})
    })

请求前执行的中间价

  • 遇到Next就会跳到下一个视图
执行顺序 1, 2, 3, 4
func TestRouter(router *gin.RouterGroup) {
    router.GET("/test", func(ctx *gin.Context) {
        fmt.Println("1")
        ctx.Next()
        fmt.Println("4")
    }, func(ctx *gin.Context) {
        fmt.Println("2")
        ctx.Next()
        fmt.Println("3")
    })

执行顺序 1, 4
func TestRouter(router *gin.RouterGroup) {
    router.GET("/test", func(ctx *gin.Context) {
        fmt.Println("1")
        ctx.About()
        ctx.Next()
        fmt.Println("4")
    }, func(ctx *gin.Context) {
        fmt.Println("2")
        ctx.Next()
        fmt.Println("3")
    })

全局中间价

  • 使用use注册
func M1(ctx *gin.Context) {
    fmt.Println("执行全局中间价1")
}
func M2(ctx *gin.Context) {
    fmt.Println("执行全局中间价2")
}

func TestRouter(router *gin.RouterGroup) {
    router.Use(M1, M2)
    router.GET("/test", func(ctx *gin.Context) {
        fmt.Println("执行视图")
    })
}

中间价数值传递

  • 上下文参数, 使用Set设置, 使用Get接受
  • 由于接受的参数是any, 如果传的是结构体的话, 类型也会变成any, 需要取值的话使用断言 xxx := xxx.(xxx)转换成结构体类型
func M1(ctx *gin.Context) {
    fmt.Println("执行全局中间价1")
    name, _ := ctx.Set("name", "陈海峰")
}

func TestRouter(router *gin.RouterGroup) {
    router.Use(M1)
    router.GET("/test", func(ctx *gin.Context) {
        fmt.Println("执行视图")
        ctx.Get("name")
    })
}

企.路由分组

func main() {
    router := gin.Default()
    authRouter := router.Group("auth") // 组1
    mainRouter := router.Group("main") // 组2

    authRouter.GET("/xxx", func(ctx *gin.Context) {})
    ...
}

分组中间价

func MyMiddle(name string) gin.HandlerFunc {
    return func(ctx *gin.Context) {
        ctx.JSON(200, gin.H{"msg": "权限验证失败", "name": name})
    }
}

func InitRouter(app *gin.Engine) {
    app.Use(MyMiddle("陈海峰"))
}

八.内置的日志

  • os.Stdout 终端输出打印
func main() {
    file, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(file. os.Stdout)
    router := gin.Default()
    router.run()
}

```

自定义路由格式

func main() { gin.DebugPrintRouteFunc = func(xxx){ log.Printf( "[ chen ] %s %s %s %d \n, xxx, xxx, xxx, ) } ... }

打印所有路由信息

func main() { router := gin.Default() router.GET("/test", func(xx){xxx}) for _, info := range router.Routers() { fmt.Println(info.Path, info.Method, info.Handler) } }

配置打印信息 隐藏启动项目时的信息

func main() { gin.SetMode(gin.REleaseMode) }