需求:
后台管理系统,用户继续操作时一定是登录的状态,每一次接口调用我们都需要对其权限进行校验
我们需要一个类似于拦截器的功能
概述:
与java中的拦截器类似, 我们可以在请求的过程中假如我们字节的钩子函数,这个钩子函数叫做中间件,中间件可以做一些公共的业务逻辑:登录认证,权限校验…
// 中间件都是 HandlerFunc 类型的
HandlerFunc func(*Context)
说明:
请求 /index 会先执行 globalM1,globalM1,m1 三个中间件之后再执行 /index 对应的处理函数
请求 /user/info会先执行 globalM1,globalM1两个中间件之后再执行 /user/info 对应的处理函数
请求 /shop/info会先执行 globalM1,globalM1,m1 , m2 四个中间件之后再执行 /shop/info对应的处理函数
请求 /shop/home会先执行 globalM1,globalM1,m2 三个中间件之后再执行 /shop/home对应的处理函数
注
: 中间件的执行顺序和添加时的顺序一致
代码表示如下:
// globalM1 全局中间件1
func globalM1(ctx *gin.Context) {...}
// globalM2 全局中间件2
func globalM2(ctx *gin.Context) {...}
// m1 路由中间件1
func m1(ctx *gin.Context) {...}
// m2 路由中间件2
func m2(ctx *gin.Context) {...}
func main() {
// 创建路由
r := gin.Default()
// 绑定全局中间件
r.Use(globalM1, globalM2)
// 绑定路由规则
// 先执行 中间件 再执行 处理函数
r.GET("/index", m1, func(ctx *gin.Context){.....})
r.GET("/user/info", func(ctx *gin.Context){.....})
r.GET("/shop/info", m1, m2, func(ctx *gin.Context){.....})
r.GET("/shop/home", m2, func(ctx *gin.Context){.....})
// 监听端口
r.Run(":8080")
}
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
/* *
- 中间件类似于java web中的过滤器, 至少这里比较灵活
- 可以定义全局的中间件,也可以为每一个路由添加任意数量的中间件
- 无论是全局中间件还是路由的中间件,执行顺序都是按照注册的顺序执行的
*/
// indexHandler 处理器
func indexHandler(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "hello index",
})
}
// homeHandler 处理器
func homeHandler(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message": "hello home",
})
}
// 中间件 统计某一次请求的处理时间
func statisticalRequestTime(ctx *gin.Context) {
fmt.Printf("开始统计请求 %s %s 处理时间\n", ctx.Request.Method, ctx.Request.URL)
start := time.Now()
// 调用后续处理函数
ctx.Next()
// 阻止调用后续处理函数
// ctx.Abort()
// 计算请求执行时间
timeCost := time.Since(start)
fmt.Printf("请求耗费时间:%v\n", timeCost)
}
func main() {
// 创建路由
r := gin.Default()
// 绑定路由规则
// 先执行 statisticalRequestTime 再执行 处理函数
r.GET("/index", statisticalRequestTime, indexHandler)
r.GET("/home", statisticalRequestTime, homeHandler)
// 监听端口
r.Run(":9000")
}
问题:
如果多个请求都需要用到同一个中间件,则中间需要在每一次的绑定路由的同时绑定中间件, 如上述案例中, 多个请求都需要绑定statisticalRequestTime这个中间件
--> 解决方案,定义一个全局的中间件,所有请求处理之前,先处执行全局中间件
r.Use(middleware ...HandlerFunc)
// 源码
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
改进上一个案例(只改变 main函数)
func main() {
// 创建路由
r := gin.Default()
// 绑定中间件
r.Use(statisticalRequestTime)
// 绑定路由规则
// 先执行 statisticalRequestTime 再执行 处理函数
/* r.GET("/index", statisticalRequestTime, indexHandler)
r.GET("/home", statisticalRequestTime, homeHandler)*/
r.GET("/index", indexHandler)
r.GET("/home", homeHandler)
// 监听端口
r.Run(":9000")
}
按照注册的顺序执行
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
/**
全局中间件执行顺序,按照注册的顺序执行
*/
//
func m1(ctx *gin.Context) {
ctx.Writer.Write([]byte("中间件1 "))
fmt.Println("中间件1")
}
func m2(ctx *gin.Context) {
ctx.Writer.Write([]byte("中间件2 "))
fmt.Println("中间件2")
}
func m3(ctx *gin.Context) {
ctx.Writer.Write([]byte("中间件3 "))
fmt.Println("中间件3")
}
func main() {
// 创建路由
r := gin.Default()
// 注册中间件
r.Use(m1, m2, m3)
// 绑定路由规则
r.GET("/index", func(ctx *gin.Context) {
ctx.Writer.Write([]byte("hello index"))
})
// 监听端口
r.Run(":9000")
}
为路由组注册中间件有以下两种写法。
写法1:
// 创建路由组的同时绑定中间件
group := r.Group(路由, 中间件)
写法2:
// 先创建路由组,在绑定中间件
group := r.Group(路由)
r.Use(中间件)
eg:
/**
为路由组注册中间件
*/
func userGroupMiddleWare(ctx *gin.Context) {
ctx.JSON(http.StatusOK, "路由组中间件")
}
func main() {
// 创建路由
r := gin.Default()
// 绑定路由规则
// 创建user路由组时绑定中间件
userGroup := r.Group("/user",userGroupMiddleWare)
// userGroup := r.Group("/user")
// userGroup.Use(userGroupMiddleWare)
{
userGroup.GET("/index", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, "hello /user/index")
})
}
// 监听端口
r.Run(":9000")
}
// 向 context中放入值
// Set is used to store a new key/value pair exclusively for this context.
// It also lazy initializes c.Keys if it was not used previously.
func (c *Context) Set(key string, value interface{})
// 从context中取出值
// If the value does not exists it returns (nil, false)
func (c *Context) Get(key string) (value interface{}, exists bool) {
eg:
import (
"github.com/gin-gonic/gin"
"net/http"
)
/**
中间件之间传递数据
*/
// 向 context中放入值
func testMiddleware1(ctx *gin.Context) {
// 向context中存储一个值
ctx.Set("name", "lisi")
}
// 从context中取出值
func testMiddleware2(ctx *gin.Context) {
// 从context中取出值
value, ok := ctx.Get("name")
if ok {
ctx.JSON(http.StatusOK, gin.H{
"name":value,
})
}
}
func main() {
// 创建路由
r := gin.Default()
// 绑定中间件
r.Use(testMiddleware1, testMiddleware2)
// 绑定路由规则
r.GET("/index", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"message":"hello index",
})
})
// 监听端口
r.Run(":9000")
}
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
// 源码
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
// 创建一个纯净的路由
engine := New()
// 默认使用了 Logger 和 Recover 两个中间件
engine.Use(Logger(), Recovery())
return engine
}
当在中间件或handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy()
)。
func testMiddleware(ctx *gin.Context) {
// goroutine 中不允许修改 context的值,只能读取context,所有要传递副本
go func(ctx.Copy()) {
}()
}