MENU

Gin框架笔记

June 16, 2022 • Read: 1465 • 编码,Go

安装

通过此命令下载安装,需要设置代理后下载否则会失败。

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

Gin 示例

package main

import "github.com/gin-gonic/gin"

func sayhello(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "success",
    })
}

func main() {

    r := gin.Default()
    
// 指定路由
    r.GET("/hello", sayhello)
    
    // 启动服务 默认 8080 端口
    r.Run()
}

RESTful API

Gin 支持 RESTful API 风格的API

func main() {
    r := gin.Default()
    // 指定路由
    r.GET("/demo", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "method": "Get Request",
        })
    })
    r.POST("/demo", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "method": "POST Request",
        })
    })
    r.PUT("/demo", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "method": "PUT Request",
        })
    })
    r.DELETE("/demo", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "method": "DELETE Request",
        })
    })
    // 启动服务
    r.Run()
}

模板渲染

Go 语言自带了两个模板库,用于文本渲染的 text/template 和用于 HTML渲染的 http/template。他们的规则如下:

  • 模板文件通常为 .tmpl 和 .tpl 为后缀(也可以使用其他后缀),必须为 UTF-8 编码。
  • 模板文件使用 {{}} 标识需要传入的数据。
  • 传给模板的数据可以使用 . 来访问,如果数据是复杂类型,还可以使用 {{.FieldName}} 来访问。
  • 除了模板标签包裹的内容外,其他内容均原样输出。

mian 代码如下:

func sayHello(w http.ResponseWriter, r *http.Request) {
    t, err := template.ParseFiles("./hello.tmpl")
    if err != nil {
        fmt.Println("template Err:%v\n", err)
    }

    err = t.Execute(w, "张三")
    if err != nil {
        fmt.Println("render Err:%v\n", err)
    }
}

func main() {
    http.HandleFunc("/", sayHello)
    err := http.ListenAndServe(":9000", nil)
    if err != nil {
        fmt.Println("HTTP server start Err:%v\n", err)
    }
}

hello.tmpl 代码如下:

<h1><p>Hello {{ . }}</p></h1>

请求 Request

请求参数

GET 请求

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

    r.Any("/", func(c *gin.Context) {
        // 获取 kay 为 name 的 Get 参数
        name := c.Query("name")
        // 获取 kay 为 name 的 Get 参数,如果 name 不存在则使用默认值 lisi
        name = c.DefaultQuery("name", "lisi")
        // 获取 Kay 为 name 的 Get 参数,如果不存在 第二个返回值为 false;上面两个是基于此方法封装的
        name, ok := c.GetQuery("name")
        if !ok {
            name = "lisi"
        }
        c.JSON(200, gin.H{"name": name})
    })

    r.Run()
}

POST 请求

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

    r.Any("/", func(c *gin.Context) {
        // 获取 Kay 为 name 的 POST 请求参数
        name := c.PostForm("name")
        // 获取 Kay 为 name 的 POST 请求参数,如果不传则使用第二个参数作为默认值
        name = c.DefaultPostForm("name", "lisi")
        // 获取 Kay 为 name 的 POST 请求参数,如未传递 name 则第二个返回值为 false
        name, ok := c.GetPostForm("name")
        if !ok {
            name = "wangwu"
        }
        c.JSON(200, gin.H{"name": name})
    })

    r.Run()
}

JSON 请求

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

    r.Any("/", func(c *gin.Context) {
        // 获取 JSON 请求参数,只能获取所有然后自行解析
        b, _ := c.GetRawData()
        var m map[string]interface{}
        // b 是一个 base64 编码,需要进行转码
        _ = json.Unmarshal(b, &m)
        c.JSON(200, m)
    })

    r.Run()
}

PATH 参数

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

    r.Any("/user/:name", func(c *gin.Context) {
        // 获取 URL 中的 PATH 参数
        name := c.Param("name")
        c.JSON(200, gin.H{"name": name})
    })

    r.Run()
}

参数绑定

使用参数绑定,可以将用户提交的参数快速映射到结构体而不需要单独赋值。

// 定义结构体
type UserInfo struct {
    Username string `form:"username"`
    Password string `form:"password"`
    Age      uint8
}

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

    r.Any("/login", func(context *gin.Context) {
        var u UserInfo
        // 绑定参数 支持 Get、Post Json 等多种格式
        _ = context.ShouldBind(&u)
        // {"status":"OK","userInfo":{"Username":"zhangsan","Password":"123456","Age":0}}
        context.JSON(200, gin.H{"status": "OK", "userInfo": u})
    })

    r.Run()
}

ShouldBind 按照下面的顺序解析请求中的数据:

  • 如果是 GET 请求,只使用 Form 绑定引擎(query)。
  • 如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)

文件上传

单文件上传

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

    router.POST("/upload", func(c *gin.Context) {
        // 读取上传文件
        f, err := c.FormFile("file")
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
            return
        }

        // 拼接服务器保存目录
        dst := path.Join("./", f.Filename)
        // 保存上传文件到指定目录
        _ = c.SaveUploadedFile(f, dst)
        // 返回成功
        c.JSON(http.StatusBadRequest, gin.H{
            "status": "OK",
        })
    })
    router.Run()
}

多文件上传

func main() {
    router := gin.Default()
    router.POST("/upload", func(c *gin.Context) {
        // 读取多个上传文件
        form, _ := c.MultipartForm()
        files := form.File["files"]
        // 遍历多个文件
        for _, file := range files {
            // 拼接服务器保存目录
            dst := path.Join("./", file.Filename)
            // 保存上传文件到指定目录
            _ = c.SaveUploadedFile(file, dst)
        }
        // 返回成功
        c.JSON(200, gin.H{
            "status": "OK",
        })
    })
    router.Run()
}

:::alert-info
通过 router.MaxMultipartMemory = 8 << 20 可以将提交文件的内存限制设置为 8 MiB,默认为 32 MiB。
注意:这不是上传文件的大小限制,而是上传文件后会先暂存到内存中,超过内存限制后,会将内存里的数据先写入磁盘,然后后续上传文件继续暂存到内存中
:::

重定向

HTTP 重定向

func main() {
    router := gin.Default()
    router.GET("/index", func(context *gin.Context) {
        // 301 重定向到某个网页
        context.Redirect(http.StatusMovedPermanently, "https://www.dbkuaizi.com")
    })
    router.Run()
}

路由重定向

与 HTTP 重定向不同的是,走 "/b" 的路由,但客户端不会发生跳转行为,同时也只有一次请求行为。

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

    router.GET("/a", func(context *gin.Context) {
        context.Request.URL.Path = "/b"
        router.HandleContext(context)
    })

    router.GET("/b", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{"hello": "world"})
    })
    router.Run()
}

路由

普通路由

func main() {
    router := gin.Default()
    
    // 匹配 Get 请求
    router.GET("/user", func(context *gin.Context) {...})
    // 匹配 Post 请求
    router.POST("/user", func(context *gin.Context) {...})
    // 匹配 PUT 请求
    router.PUT("/user", func(context *gin.Context) {...})
    // 匹配 Delete 请求
    router.DELETE("/user", func(context *gin.Context) {...})
    // 匹配所有请求类型
    router.Any("/user", func(context *gin.Context) {...})
    
    // 当所有路由都未命中,则触发此方法 也就是 404
    router.NoRoute(func(context *gin.Context) {
        context.JSON(404, gin.H{"message": "404 Not FounD"})
    })

    router.Run()
}

路由组

可以将共同前缀的URL划分为一个路由组。
子路由使用 {} 包裹只是为了看着清晰,不使用 {} 也不会有任何区别。

func main() {
    r := gin.Default()
    userGroup := r.Group("/user")
    {
        userGroup.GET("/index", func(c *gin.Context) {...})
        userGroup.GET("/login", func(c *gin.Context) {...})
        userGroup.POST("/login", func(c *gin.Context) {...})

    }
    shopGroup := r.Group("/shop")
    {
        shopGroup.GET("/index", func(c *gin.Context) {...})
        shopGroup.GET("/cart", func(c *gin.Context) {...})
        shopGroup.POST("/checkout", func(c *gin.Context) {...})
    }
    r.Run()
}

当然路由组也能嵌套:

shopGroup := r.Group("/shop")
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    shopGroup.GET("/cart", func(c *gin.Context) {...})
    shopGroup.POST("/checkout", func(c *gin.Context) {...})
    // 嵌套路由组
    xx := shopGroup.Group("xx")
    xx.GET("/oo", func(c *gin.Context) {...})
}

中间件

Gin 允许开发者在处理请求的过程中,加入用户自己的钩子(Hook) 函数。这个钩子函数就叫做中间件,可以用来处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin 的中间件必须是一个 gin.HandlerFunc 类型(既函数必须接收一个 *gin.Context 类型的参数)。

func middleware(c *gin.Context) {
    name, _ := c.GetQuery("name")
    start := time.Now()
    // 只有管理员才可以访问
    if name != "admin" {
        c.JSON(403, gin.H{"msg": "权限不足"})
        // 拦截,不继续执行
        c.Abort()
    } else {
        // 执行后面的内容
        c.Next()
    }
    cost := time.Since(start)
    fmt.Printf("耗时:%v\n", cost)
}

注册中间件

Gin 框架中可以注册任意数量的中间件。

func main() {
    router := gin.Default()
    // 全局中间件
    router.Use(middleware)

    // 单个中间件
    router.GET("/index", middleware, func(context *gin.Context) {
        context.JSON(200, gin.H{"msg": "index"})
    })
    // 路由组中间件
    userGroup := router.Group("/user",middleware)
    {
        userGroup.GET("/index", func(c *gin.Context) {...})
        userGroup.GET("/login", func(c *gin.Context) {...})
        userGroup.POST("/login", func(c *gin.Context) {...})
    }

    // 路由组中间件 另一种方式
    userGroup.Use(middleware)
    router.Run()
}

:::alert-warning

通过 gin.Default() 创建的路由,默认使用了 LoggerRecovery 中间件,如果不想使用这两个默认的中间件,可以使用 gin.New() 创建一个没有任何默认中间件的路由。
:::

中间件传参

上面的认证举例,管理员认证通过后,需要将管理员id 传递给后续的逻辑使用,我们可以通过 context.Set(key,value) 写入,然后在后续的逻辑中通过 context.Get(key) 等方式获取。

func middleware(c *gin.Context) {
    name, _ := c.GetQuery("name")
    // 只有管理员才可以访问
    if name != "admin" {
        c.JSON(403, gin.H{"msg": "权限不足"})
        // 拦截,不继续执行
        c.Abort()
    } else {
        // 设置登录用户名称
        c.Set("name", name)
        // 执行后面的内容
        c.Next()
    }

}

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

    router.GET("/index", middleware, func(context *gin.Context) {

        // 获取登录用户名称,返回两个参数用于判断获取是否成功
        name, ok := context.Get("name")
        if !ok {
            name = "匿名用户"
        }

        // 如果不想判断直接获取
        name = context.MustGet("name")
        // 还可以获取不同的内建类型,在返回时会自动转一下
        name = context.GetString("name")

        context.JSON(200, gin.H{"msg": "index", "username": name})
    })

    router.Run()
}
Last Modified: July 2, 2022