1. 什么是中间件

  1. Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数,这个钩子函数就叫中间件。当一个路由使用了某一个(或几个)中间件时,它将会在调用后续 HandlerFunc 前先调用中间件。其实,广义上来看,路由中的任意 HandlerFunc 都可以称作是中间件。不过通常,我们会给多个不同路由使用同一个HandlerFunc,且都在其他HandlerFunc之前,则此时一般将该中间件独立出来。<br />中间件最常见的一种功能就是权限验证,即:当路由调用 func1 时,系统需要知道该用户有没有权限使用该路由,因此需要在进入func1之前进行验证,这就需要中间件的使用。
  1. r := gin.Default()
  2. r.POST("/index",validate,func1)

上述代码中,validate 就是我们用于验证权限的中间件。不过,当该中间件被多个路由使用时,会造成代码冗余,因此我们就需要用到 Hook,使某一个路由组都默认使用该中间件。这里要用到 r.User(func) 方法

  1. router := gin.Default()
  2. r := router.Group("/home")
  3. r.User(validate) // validate为用于验证的中间件
  4. {
  5. r.GET("/index",func1)
  6. r.POST("/login",func2)
  7. ...
  8. // 这些路由在调用funcx之前,都会先进入validate中间件
  9. }

除此之外,中间件还常用于 登录认证、数据分页、记录日志、统计耗时 等业务


2. 定义中间件

Gin中的中间件必须是一个 gin.HandlerFunc 类型。最基本的,它可以是一个 HandlerFunc,即以 *gin.Context 为入参的函数。不过绝大多数我们都是使用闭包作为中间件,返回一个HandlerFunc。如:

  1. // StatCost 是一个统计耗时请求耗时的中间件
  2. func StatCost() gin.HandlerFunc {
  3. return func(c *gin.Context) {
  4. start := time.Now()
  5. c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
  6. // 调用该请求的剩余处理程序
  7. c.Next()
  8. // 不调用该请求的剩余处理程序
  9. // c.Abort()
  10. // 计算耗时
  11. cost := time.Since(start)
  12. log.Println(cost)
  13. }
  14. }

但是,要注意的是,当使用闭包作为中间件式,引用该中间件的语法格式和引用HandlerFunc的语法格式有些不同:

  1. func StatCost() gin.HandlerFunc {
  2. return func(c *gin.Context) {
  3. ...
  4. }
  5. }
  6. func Test(c *gin.Context){
  7. ...
  8. }
  9. func main() {
  10. r := gin.New()
  11. r.Use(StatCost()) // 闭包作为中间件时要加()
  12. r.Usr(Test) // 函数不能加
  13. }

3. 跨中间件操作

Gin的中间件中有四个方法用于跨中间件操作,分别为:c.Next()、c.Abort()、c.Set()、c.Get()

3.1 c.Next()

当一个中间件执行到 c.Next() 时,它会先停止执行自身接下来的代码,而是转到下一个中间件中执行。当下一个中间件执行完后,将返回自身执行未执行的代码。执行完后,下一个中间件不再执行。
举个例子,该操作可以用来获取某一个中间件的运行实践:

  1. func func1(c *gin.Context) {
  2. startTime := time.Now()
  3. fmt.Println(startTime)
  4. c.Next()
  5. spendTime := time.Since(startTime)
  6. fmt.Println(spendTime)
  7. }
  8. func func2(c *gin.Context) {
  9. fmt.Println("func2 start")
  10. var str = "have a nice day!"
  11. var test []byte = []byte(str)
  12. for index,_ := range test {
  13. index ++
  14. }
  15. }
  16. func main() {
  17. r:= gin.Default()
  18. r.GET("/time",func1,func2)
  19. r.Run(":8080")
  20. }
  1. 访问 localhost:8080/time 测试结果如下所示<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2643809/1619698220111-61833d9d-ffb2-4be5-a3e0-17efb1af2c09.png#clientId=u291dc5d9-cb38-4&from=paste&height=59&id=u2909f359&margin=%5Bobject%20Object%5D&name=image.png&originHeight=96&originWidth=778&originalType=binary&size=10487&status=done&style=none&taskId=ud96f690f-427e-4787-92b1-7c8423f314d&width=480)<br />其内部机制,和递归很像,总结一下就是下图这样的原理:<br />![](https://cdn.nlark.com/yuque/0/2021/png/2643809/1619698273482-419fa888-8000-4162-8aa7-d4c211b063be.png#clientId=u291dc5d9-cb38-4&from=paste&height=244&id=u5b508a3a&margin=%5Bobject%20Object%5D&originHeight=280&originWidth=711&originalType=url&status=done&style=none&taskId=u337971bd-ff18-405d-844b-c844bb89cb8&width=619.5)

3.2 c.Abort()

顾名思义,该操作将停止执行后续的中间件。

  1. func func1(c *gin.Context) {
  2. fmt.Println("func1 start")
  3. c.Abort()
  4. fmt.Println("func1 continue")
  5. }
  6. func func2(c *gin.Context) {
  7. fmt.Println("func2 start")
  8. }
  9. func main() {
  10. r:= gin.Default()
  11. r.GET("/test",func1,func2)
  12. r.Run(":8080")
  13. }

测试结果为:
image.png

3.3 c. Set() 和 c.Get()

这两个方法一般配套使用,c.Set 用于在上一个中间件定义一个字段(key-value),c.Get 用于在接下来的中间件里调用该字段。
c.Set 有两个入参,一个为string型的key,一个为作为value的空接口
image.png
c.Get 只用一个入参,string型的key,但又两个返回值,第二个为判断该key是否存在的bool值
image.png
举个例子:

  1. func func1(c *gin.Context) {
  2. c.Set("username","小王子")
  3. }
  4. func func2(c *gin.Context) {
  5. username,exist := c.Get("username")
  6. if exist {
  7. log.Println(username)
  8. }
  9. }
  10. func main() {
  11. r:= gin.Default()
  12. r.GET("/test",func1,func2)
  13. r.Run(":8080")
  14. }
  1. 测试结果(发了3次请求)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2643809/1619698965422-53d6a3d2-ef70-40fc-8460-b53d1b6f77b4.png#clientId=u291dc5d9-cb38-4&from=paste&height=122&id=ucf2eea9f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=243&originWidth=1410&originalType=binary&size=46157&status=done&style=none&taskId=u7ef5b79d-f848-4072-8a11-1b54c7753a2&width=705)