【注意】最后更新于 March 17, 2023,文中内容可能已过时,请谨慎使用。
     
   
    
      
中间件(英语:Middleware),又译中间件、中介层,是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件,应用软件可以借助中间件在不同的技术架构之间共享信息与资源。
1.介绍
在Gin框架中,中间件本质上是gin.HandlerFunc 函数,如果我们要自定义中间件,只需要返回类型是gin.HandlerFunc 即可。
在Gin框架中,使用中间件可分为以下几种场景:
2.使用
2.1 全局使用
在gin.Default()函数中,默认注册全局中间件Logger、Recovery,具体代码如下:
| 1
2
3
4
5
6
7
8
9
 | func Default() *Engine {
    // 打印debug信息
    debugPrintWARNINGDefault()
    // 创建引擎
    engine := New()
    // 注册全局中间件
    engine.Use(Logger(), Recovery())
    return engine
}
 | 
 
2.2 单个路由使用
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 | func main()  {
    engine := gin.New()
    // 添加一个中间件: Logger()
    engine.GET("/route",gin.Logger(), func(context *gin.Context) {
        context.JSON(200,gin.H{"msg":"针对单个路由"})
    })
    // 添加多个中间件: Logger(),Recovery()
    engine.GET("/route2",gin.Logger(),gin.Recovery(), func(context *gin.Context) {
        context.JSON(200,gin.H{"msg":"针对单个路由添加多个中间件"})
    })
    _ = engine.Run()
}
 | 
 
2.3 路由组使用
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 | func main()  {
    engine := gin.New()
    // 声明路由后,通过Use注册中间件
    v1Group := engine.Group("/v1").Use(gin.Logger())
    {
        v1Group.GET("/middleGroup", func(context *gin.Context) {
             context.JSON(200,gin.H{"msg":"succss"})
        })
    }
    _ = engine.Run()
}
 | 
 
3.自定义中间件
我们可以创建自己的中间件,在中间件中需要继续执行时,使用c.Next(),而要终止执行时则需调用c.Abort()终止请求。
3.1 语法结构
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 | func MiddleName() gin.HandlerFunc {
    return func(context *gin.Context) {
        // 请求前业务逻辑...
        // 继续往下执行
        context.Next()
        // context.Abort() 停止往下执行
        // 请求后业务逻辑...
        end := time.Now().Unix()
        fmt.Printf("接口耗时:%v 秒 \n", end - t)
    }
}
 | 
 
代码说明:
- 
MiddleName: 自定义中间件名称。
 
- 
gin.HandlerFunc: 中间件始终返回这个类型。
 
- 
func(context *gin.Context): 匿名函数,参数是上下文(context *gin.Context)
 
- 
context.Next(): 继续执行调用这个函数。
 
- 
context.Abort():终止执行调用这个函数。
 
3.2 示例
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 | // 声明自定义中间件
func MyMiddleware() gin.HandlerFunc {
    return func(context *gin.Context) {
        // 请求前
        fmt.Println("中间件-- 请求前")
        t := time.Now().Unix()
        // 继续往下执行
        context.Next()
        // context.Abort() 停止往下执行
        // 请求后
        end := time.Now().Unix()
        fmt.Printf("接口耗时:%v 秒 \n", end - t)
    }
}
// 自定义中间件使用
func RunWithMyMiddle()  {
    engine := gin.New()
    // 注册自定义中间件
    engine.GET("/route",MyMiddleware(), func(context *gin.Context) {
        time.Sleep(time.Second * 3)
        context.JSON(200,gin.H{"msg":"自定义路由"})
    })
    _ = engine.Run()
}
 | 
 
4.多个中间件
如果同时注册多个中间件,那么他们之间的执行顺序是什么样呢?
4.1 执行流程

4.2 代码示例
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 | // 自定义中间件A
func MyMiddleware() gin.HandlerFunc {
    return func(context *gin.Context) {
        // 请求前
        fmt.Println("中间件1-- 请求前")
        context.Next()
        // 请求后
        fmt.Println("中间件1-- 请求后")
    }
}
// 自定义中间件B
func MyMiddleware2() gin.HandlerFunc {
    return func(context *gin.Context) {
        // 请求前
        fmt.Println("中间件2-- 请求前")
        context.Next()
        // 请求后
        fmt.Println("中间件2-- 请求后")
    }
}
// 启动服务
func main()  {
    engine := gin.New()
    // 使用多个中间件
    engine.GET("/route",MyMiddleware(), MyMiddleware2(),func(context *gin.Context) {
        time.Sleep(time.Second * 3)
        context.JSON(200,gin.H{"msg":"自定义路由"})
    })
    _ = engine.Run()
}
 | 
 
请求控制台输出:
| 1
2
3
4
 | 中间件1-- 请求前
中间件2-- 请求前
中间件2-- 请求后
中间件1-- 请求后
 | 
 
5.实践
创建自定义中间件用来判断Token是否有效(是否是登录状态)。
5.1 源码
./main.go代码:
| 1
2
3
4
5
 | package main
import "go-use/practise"
func main() {
    practise.RunServeWithCheckToken()
}
 | 
 
./practise/check_token.go 代码:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 | package practise
import (
    "github.com/gin-gonic/gin"
)
// 检测token
func CheckTokenMiddle() gin.HandlerFunc {
    return func(context *gin.Context) {
        // 获取token
        token := context.DefaultQuery("token","")
        // 检测token
        if token != "abcd" {
            context.JSON(500,gin.H{"msg":"请先登录!"})
            // 终止请求
            context.Abort()
        }
        // token合法则继续往下执行
        context.Next()
    }
}
// 启动服务
func RunServeWithCheckToken()  {
    engine := gin.Default()
    // 登录接口不需要添加检测Token中间件
    engine.GET("/user/login", func(context *gin.Context) {
        context.JSON(200,gin.H{"msg":"登录成功!","token":"abcd"})
    })
    // 需要验证token的路由
    user := engine.Group("/user").Use(CheckTokenMiddle())
    {
        // 用户基本信息
        user.GET("/info", func(context *gin.Context) {
            data := map[string]interface{} {
                "name": "张三",
                "age": 18,
                "likes": []string{"打游戏","旅游"},
            }
            context.JSON(200,gin.H{"msg":"请求成功","data":data})
        })
        // 更新用户
        user.GET("/update", func(context *gin.Context) {
            context.JSON(200,gin.H{"msg":"请求成功"})
        })
    }
    // 启动服务
    _ = engine.Run()
}
 | 
 
5.2 请求
| 1
2
3
4
5
6
7
8
9
 | # 不需要验证token的接口
➜ curl -X GET http://127.0.0.1:8080/user/login
{"msg":"登录成功!","token":"abcd"}
# 需要验证Token,但是没传时
➜ curl -X GET http://127.0.0.1:8080/user/info
{"msg":"请先登录!"}
# 需要验证Token,并且已传时
➜  curl -X GET http://127.0.0.1:8080/user/info?token=abcd
{"data":{"age":18,"likes":["打游戏","旅游"],"name":"张三"},"msg":"请求成功"}
 | 
 
6.使用Goroutine
当在中间件或 handler 中启动新的 Goroutine 时,不能使用原始的上下文,必须使用只读副本。
6.1 代码示例
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
 | // 在中间件中使用Goroutine
func main()  {
    engine := gin.Default()
    engine.Use(MyMiddleWithGoRoutine())
    engine.GET("/useGo", func(context *gin.Context) {
        context.JSON(200,gin.H{"msg":"success"})
    })
    _ = engine.Run()
}
// 在中间件中使用Goroutine
func MyMiddleWithGoRoutine() gin.HandlerFunc {
    return func(context *gin.Context) {
        // 复制上下文
        cpContext := context.Copy()
        go func() {
            time.Sleep(3 * time.Second)
            fmt.Println(cpContext.Request.URL.Path)
        }()
        context.Next()
    }
}
 | 
 
7.流行的中间件
在gin-gonic/contrib(https://github.com/gin-gonic/contrib)库中整理很多常用的中间件,可以直接使用,具体中间件有以下:
