基于Gin框架的web后端开发(十): Gin框架-中间件(定义、使用、通信与实例)详解

LiberHome -
基于Gin框架的web后端开发(十): Gin框架-中间件(定义、使用、通信与实例)详解

Gin-中间件:Gin框架的作者为开发者们提供的一种机制,可以让开发者自定义请求执行的Hook函数,中间件函数(或者叫Hook函数、钩子函数)适合处理很多重复的操作的场景(比如登录认证,权限校验,数据分页,耗时统计,记录日志等) ,如果仅仅是一个页面的独有的逻辑直接放到对应的路由下的Handler函数处理即可,

定义中间件

Gin的中间件必须是gin.HandlerFunc类型(就是包含请求上下文参数*gin.Context的函数)

例如:下面的indexHandler就是一个中间件:

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func indexHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}
func main() {
    r := gin.Default()
    r.GET("/index", indexHandler)
    r.Run(":9090")
}

再比如,我们还可以这样定义一个中间件:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定义一个中间件m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
}
func main() {
    r := gin.Default()
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", m1, indexHandler)
    r.Run(":9090")
}
c.Next()

但上面这样写中间件并没有什么实际意义,我们可以尝试写一个程序计时的功能,这里介绍一下,c.Next()可以调用后续的处理函数:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定义一个中间件m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //调用后续的处理函数
    //c.Abort() //组织调用后续的处理函数
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}
func main() {
    r := gin.Default()
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", m1, indexHandler)
    r.Run(":9090")
}

运行结果如下:

r.Use()

当然,中间件的设计初衷是为了更多的复用,比如使用Use(middle_ware...)函数进行全局注册,比如,我有几个路由,想在访问这几个路由的时候都调用m1计时器的功能:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定义一个中间件m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //调用后续的处理函数
    //c.Abort() //组织调用后续的处理函数
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}
func main() {
    r := gin.Default()
    //全局注册中间件
    r.Use(m1)
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", indexHandler)
    r.GET("/game", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "shop",
        })
    })
    r.GET("/food", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "food",
        })
    })
    r.GET("/ad", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "ad",
        })
    })
    r.Run(":9090")
}

运行结果如下:

c.Abort()

这里介绍一下,c.Abort()可以剥夺所有后续的处理函数运行的权利,直接跳过去,例子如下:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定义一个中间件m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //调用后续的处理函数
    //c.Abort() //组织调用后续的处理函数
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}

//定义一个中间件m2
func m2(c *gin.Context) {
    fmt.Println("this is middle-ware m2~")
    c.Abort() //组织调用后续的处理函数
    fmt.Println("m2 is done")
}

func main() {
    r := gin.Default()
    //全局注册中间件
    r.Use(m1, m2)
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", indexHandler)
    r.GET("/game", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "shop",
        })
    })
    r.GET("/food", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "food",
        })
    })
    r.GET("/ad", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "ad",
        })
    })
    r.Run(":9090")
}

运行结果如下:

登录鉴权的中间件

再比如,我们想写一个登录鉴权的中间件,伪代码就可以写成:

//定义一个登录中间件
func authMiddleware(c *gin.Context) {
    //if 是登录用户
    //  c.Next()
    //else
    //  c.Abort()
}

不过,为了更方便进行一些准备工作(比如连接数据库等),通常我们更加推荐将中间件写成闭包的形式:

//定义一个登录中间件
func authMiddleware(doChck bool) gin.HandlerFunc {
    //写成闭包的形式,就可以在这里进行数据库的连接,或其他的准备工作
    return func(c *gin.Context) { //这里存放校验参数的逻辑
        if doChck{
            //if 是登录用户
            //  c.Next()
            //else
            //  c.Abort()
        } else {
            c.Next()
        }
    }
}

将这部分代码纳入整体代码如下:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    c.JSON(http.StatusOK, gin.H{
        "msg": "index",
    })
}

//定义一个中间件m1
func m1(c *gin.Context) {
    fmt.Println("this is middle-ware m1~")
    start := time.Now()
    c.Next() //调用后续的处理函数
    //c.Abort() //组织调用后续的处理函数
    cost := time.Since(start)
    fmt.Printf("cost:  %v\n ", cost)
    fmt.Println("m1 is done")
}

//定义一个中间件m2
func m2(c *gin.Context) {
    fmt.Println("this is middle-ware m2~")
    c.Abort() //组织调用后续的处理函数
    fmt.Println("m2 is done")
}

//定义一个登录中间件
func authMiddleware(doChck bool) gin.HandlerFunc {
    //写成闭包的形式,就可以在这里进行数据库的连接,或其他的准备工作
    return func(c *gin.Context) { //这里存放校验参数的逻辑
        if doChck {
            //if 是登录用户
            //  c.Next()
            //else
            //  c.Abort()
        } else {
            c.Next()
        }
    }
}

func main() {
    r := gin.Default()
    //全局注册中间件
    r.Use(m1, m2, authMiddleware(true))
    //GET(relativePath string, handlers ...HandlerFunc)
    r.GET("/index", indexHandler)
    r.GET("/game", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "shop",
        })
    })
    r.GET("/food", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "food",
        })
    })
    r.GET("/ad", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "ad",
        })
    })
    r.Run(":9090")
}

当然,路由组也可以进行全局注册,例如:

    billGroup := r.Group("/bill")
    billGroup.Use(authMiddleware(true))
    {
        billGroup.GET("/index1", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "lokaloka1"})
        })
        billGroup.GET("/index2", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "lokaloka2"})
        })
        billGroup.GET("/index3", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "lokaloka3"})
        })
    }
中间件之间通信 c.Set()与c.Get()

比如在中间件m2中设置一个键值对,想在后面的indexHandler中间件中根据key取得value,可以这样写:

//定义一个中间件m2
func m2(c *gin.Context) {
    fmt.Println("this is middle-ware m2~")
    //c.Abort() //组织调用后续的处理函数
    c.Set("name", "lokays")
    fmt.Println("m2 is done")
}

func indexHandler(c *gin.Context) {
    fmt.Println("this is index")
    name, ok := c.Get("name")
    if !ok {
        name = "匿名用户"
    }
    c.JSON(http.StatusOK, gin.H{
        "msg": name,
    })
}
注意

? 注意:gin.Default()会自动加载Logger中间件和Recovery中间件:

Logger中间件会将日志写入gin.DefaultWriter,无论是否配置了GIN_ModeRecovery中间件会Recover所有的panic,如果有panic会写入500响应码

所以,如果不想使用任何中间件, 可以用gin.New()新建一个路由。

在中间件或者Handler中启动新的goroutine的时候,不能使用上下文的c *gin.Context,只能使用只读副本c.Copy()参考:bilibili
特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

gitgo中间件

扩展阅读

加个好友,技术交流

1628738909466805.jpg