初识中间件
从最简单的模板开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) { c.String(200, "测试页面") })
r.Run()
}
|
在这个例子中,只有一个函数处理根目录下的路由
然而其实也可以传递多个函数,它们将被依次执行,最后一个函数前面触发的方法都可以称为中间件
中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、 记录日志、耗时统计等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt"
"github.com/gin-gonic/gin" )
func initMiddleware(ctx *gin.Context) { fmt.Println("我是一个中间件") }
func main() {
r := gin.Default()
r.GET("/", initMiddleware, func(c *gin.Context) { c.String(200, "测试页面") })
r.Run()
}
|
可以看见,在处理路由时,先执行了中间件,再响应了页面

.Next()
方法
在中间件中调用.Next()
方法可以递归调用下一个中间件或最终函数
例1:统计一个请求的执行时间
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
| package main
import ( "fmt" "time"
"github.com/gin-gonic/gin" )
func initMiddleware(ctx *gin.Context) { start := time.Now().UnixNano() ctx.Next() end := time.Now().UnixNano() fmt.Println(end - start) }
func main() {
r := gin.Default()
r.GET("/", initMiddleware, func(c *gin.Context) { time.Sleep(time.Second) c.String(200, "测试页面") })
r.Run()
}
|

例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 31
| package main
import ( "fmt"
"github.com/gin-gonic/gin" )
func MiddlewareOne(ctx *gin.Context) { fmt.Println("第一个中间件开始") ctx.Next() fmt.Println("第一个中间件结束") }
func MiddlewareTwo(ctx *gin.Context) { fmt.Println("第二个中间件开始") ctx.Next() fmt.Println("第二个中间件结束") }
func main() {
r := gin.Default()
r.GET("/", MiddlewareOne, MiddlewareTwo, func(c *gin.Context) { c.String(200, "测试页面") })
r.Run()
}
|

.Abort()
方法
在中间件中调用.Next()
方法可以终止该请求的剩余处理程序
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
| package main
import ( "fmt"
"github.com/gin-gonic/gin" )
func MiddlewareOne(ctx *gin.Context) { fmt.Println("第一个中间件开始") ctx.Abort() fmt.Println("第一个中间件结束") }
func MiddlewareTwo(ctx *gin.Context) { fmt.Println("第二个中间件开始") ctx.Next() fmt.Println("第二个中间件结束") }
func main() {
r := gin.Default()
r.GET("/", MiddlewareOne, MiddlewareTwo, func(c *gin.Context) { c.String(200, "测试页面") })
r.Run()
}
|

全局中间件
在引擎上调用.Use()
方法可以配置全局中间件
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
| package main
import ( "fmt"
"github.com/gin-gonic/gin" )
func initMiddleware(ctx *gin.Context) { fmt.Println("我是一个中间件") }
func main() {
r := gin.Default()
r.Use(initMiddleware)
r.GET("/test1", func(c *gin.Context) { c.String(200, "测试页面1") })
r.GET("/test2", func(c *gin.Context) { c.String(200, "测试页面2") }) r.Run()
}
|

在路由分组中配置中间件
还记得之前的路由分组吗?
在\routers\adminRouters.go
中,我们之前是这样写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package routers
import ( "test/controllers/admin"
"github.com/gin-gonic/gin" )
func AdminRoutersInit(r *gin.Engine) { //后台路由 adminRouters := r.Group("/admin") { adminRouters.GET("/", admin.AdminController{}.Index) adminRouters.GET("/user", admin.AdminController{}.User) adminRouters.GET("/article", admin.AdminController{}.Article) } }
|
现在,需要为adminRouters
这个路由组配置中间件,一共有两种写法
写法1:
1 2 3 4 5 6 7 8 9 10 11 12 13
| func initMiddleware(ctx *gin.Context) { fmt.Println("我是一个中间件") }
func AdminRoutersInit(r *gin.Engine) { adminRouters := r.Group("/admin", initMiddleware) { adminRouters.GET("/", admin.AdminController{}.Index) adminRouters.GET("/user", admin.AdminController{}.User) adminRouters.GET("/article", admin.AdminController{}.Article) } }
|
写法2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func initMiddleware(ctx *gin.Context) { fmt.Println("我是一个中间件") }
func AdminRoutersInit(r *gin.Engine) { adminRouters := r.Group("/admin") adminRouters.Use(initMiddleware) { adminRouters.GET("/", admin.AdminController{}.Index) adminRouters.GET("/user", admin.AdminController{}.User) adminRouters.GET("/article", admin.AdminController{}.Article) } }
|
中间件和对应控制器之间共享数据
在中间件中可以设置键值对(.Set()
方法),供其他中间件或控制器读取(.Get()
方法)
\routers\adminRouters.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
| package routers
import ( "fmt" "test/controllers/admin"
"github.com/gin-gonic/gin" )
func initMiddleware(ctx *gin.Context) { fmt.Println("我是一个中间件") ctx.Set("username", "张三") }
func AdminRoutersInit(r *gin.Engine) { adminRouters := r.Group("/admin") adminRouters.Use(initMiddleware) { adminRouters.GET("/", admin.AdminController{}.Index) adminRouters.GET("/user", admin.AdminController{}.User) adminRouters.GET("/article", admin.AdminController{}.Article) } }
|
\controllers\admin\adminController.go
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package admin
import "github.com/gin-gonic/gin"
type AdminController struct { BaseController }
func (c AdminController) Index(con *gin.Context) { con.String(200, "后台首页") }
func (c AdminController) User(con *gin.Context) { username, _ := con.Get("username") con.String(200, username.(string)) }
func (c AdminController) Article(con *gin.Context) { con.String(200, "新闻列表") }
|

中间件注意事项
默认中间件
gin.Default()
默认使用了 Logger
和 Recovery
中间件
Logger
中间件将日志写入 gin.DefaultWriter
,即使配置了 GIN_MODE=release
Recovery
中间件会 recover
任何 panic
,如果有 panic
的话,会写入 500
响应码
如果不想使用上面两个默认的中间件,可以使用 gin.New()
新建一个没有任何默认中间件的路由
中间件中使用 goroutine
当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c *gin.Context), 必须使用其只读副本(c.Copy())
1 2 3 4 5 6
| func initMiddleware(ctx *gin.Context) { ctxCp:=ctx.Copy() go func () { ... } }
|