Gin(五) 中间件

中间件

需求:
 后台管理系统,用户继续操作时一定是登录的状态,每一次接口调用我们都需要对其权限进行校验
 我们需要一个类似于拦截器的功能

概述:
 与java中的拦截器类似, 我们可以在请求的过程中假如我们字节的钩子函数,这个钩子函数叫做中间件,中间件可以做一些公共的业务逻辑:登录认证,权限校验…

// 中间件都是 HandlerFunc 类型的
HandlerFunc func(*Context) 

如图(箭头代表执行顺序):
Gin(五) 中间件_第1张图片

说明:

请求 /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")
}

1. 为某个路由单独注册

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")

}

Gin(五) 中间件_第2张图片
Gin(五) 中间件_第3张图片

Gin(五) 中间件_第4张图片

问题:

如果多个请求都需要用到同一个中间件,则中间需要在每一次的绑定路由的同时绑定中间件, 如上述案例中, 多个请求都需要绑定statisticalRequestTime这个中间件

​ --> 解决方案,定义一个全局的中间件,所有请求处理之前,先处执行全局中间件

2. 为全局路由注册

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")

}

3. 全局中间件的执行顺序

按照注册的顺序执行

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")
}

在这里插入图片描述
Gin(五) 中间件_第5张图片

4. 为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法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")
}

在这里插入图片描述

5. 中间之间传递数据

// 向 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(五) 中间件_第6张图片

6. 中间件注意事项

1. gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • 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
}
2. gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

func testMiddleware(ctx *gin.Context) {
    // goroutine 中不允许修改 context的值,只能读取context,所有要传递副本
    go func(ctx.Copy()) {
        
    }()
}

你可能感兴趣的:(Golang,golang,gin,web,中间件)