爱摸鱼的Demon
首页
前端知识
后端技术
工程实践
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

爱摸鱼的Demon

我的地盘,欢迎光临。
首页
前端知识
后端技术
工程实践
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • C#

  • Golang

    • Go

    • Gin

      • Gin介绍
      • Gin中的身份认证
      • Gin中间件
        • 分类
          • 1)路由中间件
          • 2)Gin中间件
          • 3)全局中间件
          • 4)路由分组
          • 5)中间件-控制器、中间件-中间件数据共享
          • 6)gin.Default()中的默认中间件
        • AOP(Aspect-Oriented Programming)解决方案
          • 核心概念
        • 基于Redis实现中间件
      • Gin之K8s
    • GORM

  • 后端技术
  • Golang
  • Gin
DemonW-X
2025-09-29
目录

Gin中间件

Gin允许开发者在处理请求过程中,加入用户自己的钩子(Hook)函数,即中间件(拦截器)。中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作。

Gin中间件:https://github.com/gin-gonic/contrib (opens new window)

package main

import (
	"github.com/DemonW-X/WeDemo/internal/web"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"strings"
	"time"
)

func main() {
	server := gin.Default()
	//注册中间件,作用于定义在server上的全部路由
	server.Use(cors.New(cors.Config{
		//AllowOrigins: []string{"http://localhost:3000"}, //允许通过路由
		AllowMethods: []string{"POST"},                          //允许通过的方法,若不配置,默认方法都通过
		AllowHeaders: []string{"Content-Type", "Authorization"}, //允许通过的header
		//ExposeHeaders:    []string{"Content-Length"},//指定允许公开的头部信息
		AllowCredentials: true, //是否允许带cookie之类的东西
		AllowOriginFunc: func(origin string) bool { //允许多个指定的路由
			if strings.HasPrefix(origin, "http://localhost") {
				return true //本机开发环境通过
			}
			return strings.Contains(origin, "yourcompany.com") //仅公司域名可通过
		},
		MaxAge: 12 * time.Hour,
	}))

	u := web.InitUserHandler()
	u.RegisterRoutes(server)
	server.Run(":2222")
}
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

# 分类

# 1)路由中间件

Gin中的中间件必须是一个gin.HandlerFunc类型,配置路由的时候可以传递多个func回调函数,最后一个func回调函数前面触发的方法都可以称为中间件。

// 路由中间件
func initMiddleware(c *gin.Context) {
    fmt.Println("aaaaa")
}
func main() {
    r := gin.Default()
    //自定义模板函数,函数只能放在加载模板前
    r.SetFuncMap(template.FuncMap{ //"html/template"包
       "UnixToTime": UnixToTime,
    })
    //加载模板 放在配置路由上面
    r.LoadHTMLGlob("templates/**/*")
    //配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
    r.Static("/static", "./static")
    //前台路由
    //routers.InitDefaultRouters(r)
    //routers.InitApiRouters(r)
    //routers.InitUserRouters(r)
    r.GET("/", initMiddleware, func(c *gin.Context) {
       c.String(http.StatusOK, "gin首页")
    })
    r.GET("/news", initMiddleware, func(c *gin.Context) {
       c.String(http.StatusOK, "新闻首页")
    })
    r.GET("/login", initMiddleware, func(c *gin.Context) {
       c.String(http.StatusOK, "login")
    })
    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
24
25
26
27
28
29

(以http://localhost:8080/为例)
终端:app | aaaaa
接口:gin首页

# 2)Gin中间件

1、ctx.Next() :调用请求该请求的剩余处理程序。

func initMiddleware(c *gin.Context) {
    start := time.Now().UnixNano()
    fmt.Println("1-I'm initMiddleware")
    //调用该请求的剩余处理程序
    c.Next()

    fmt.Println("2-I'm initMiddleware")
    end := time.Now().UnixNano()
    fmt.Println("3-time:", end-start)
}
func main() {
    r := gin.Default()
    //自定义模板函数,函数只能放在加载模板前
    r.SetFuncMap(template.FuncMap{ //"html/template"包
       "UnixToTime": UnixToTime,
    })
    //加载模板 放在配置路由上面
    r.LoadHTMLGlob("templates/**/*")
    //配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
    r.Static("/static", "./static")
    //前台路由
    //routers.InitDefaultRouters(r)
    //routers.InitApiRouters(r)
    //routers.InitUserRouters(r)
    r.GET("/", initMiddleware, func(c *gin.Context) {
       fmt.Println("I'm index")
       //time.Sleep(time.Second)
       c.String(http.StatusOK, "gin首页")
    })
    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
24
25
26
27
28
29
30
31

终端:3-time: 0
接口:gin首页

多个中间件调用方式:

func initMiddlewareOne(c *gin.Context) {
    fmt.Println("1-I'm initMiddlewareOne")
    //调用该请求的剩余处理程序
    c.Next()
    fmt.Println("2-I'm initMiddlewareOne")
}
func initMiddlewareTwo(c *gin.Context) {
    fmt.Println("1-I'm initMiddlewareTwo")
    //调用该请求的剩余处理程序
    c.Next()
    fmt.Println("2-I'm initMiddlewareTwo")
}
func main() {
    r := gin.Default()
    //自定义模板函数,函数只能放在加载模板前
    r.SetFuncMap(template.FuncMap{ //"html/template"包
       "UnixToTime": UnixToTime,
    })
    //加载模板 放在配置路由上面
    r.LoadHTMLGlob("templates/**/*")
    //配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
    r.Static("/static", "./static")
    //前台路由
    //routers.InitDefaultRouters(r)
    //routers.InitApiRouters(r)
    //routers.InitUserRouters(r)
    r.GET("/admin", initMiddlewareOne, initMiddlewareTwo, func(c *gin.Context) {
       fmt.Println("I‘m admin index")
       c.String(http.StatusOK, "admin首页")
    })
    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
24
25
26
27
28
29
30
31
32

终端:
1-I'm initMiddlewareOne
1-I'm initMiddlewareTwo
I‘m admin index
2-I'm initMiddlewareTwo
2-I'm initMiddlewareOne

接口:admin首页

2、ctx.Abort() :终止调用请求该请求的剩余处理程序。

func initMiddlewareAbort(c *gin.Context) {
    start := time.Now().UnixNano()
    fmt.Println("1-I'm initMiddleware")
    //调用该请求的剩余处理程序
    c.Abort()

    fmt.Println("2-I'm initMiddleware")
    end := time.Now().UnixNano()
    fmt.Println("3-time:", end-start)
}

func main() {
    r := gin.Default()
    //自定义模板函数,函数只能放在加载模板前
    r.SetFuncMap(template.FuncMap{ //"html/template"包
       "UnixToTime": UnixToTime,
    })
    //加载模板 放在配置路由上面
    r.LoadHTMLGlob("templates/**/*")
    //配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
    r.Static("/static", "./static")
    //前台路由
    //routers.InitDefaultRouters(r)
    //routers.InitApiRouters(r)
    //routers.InitUserRouters(r)
    r.GET("/news", initMiddlewareAbort, func(c *gin.Context) {
       c.String(http.StatusOK, "新闻首页")
    })
    r.Run()
}
/*输出结果
    终端:3-time: 0
    接口:
*/
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

终端:3-time: 0
接口:

# 3)全局中间件

func initMiddleware(c *gin.Context) {
    start := time.Now().UnixNano()
    fmt.Println("1-I'm initMiddleware")
    //调用该请求的剩余处理程序
    c.Next()

    fmt.Println("2-I'm initMiddleware")
    end := time.Now().UnixNano()
    fmt.Println("3-time:", end-start)
}
func initMiddlewareOne(c *gin.Context) {
    fmt.Println("1-I'm initMiddlewareOne")
    //调用该请求的剩余处理程序
    c.Next()
    fmt.Println("2-I'm initMiddlewareOne")
}
func initMiddlewareTwo(c *gin.Context) {
    fmt.Println("1-I'm initMiddlewareTwo")
    //调用该请求的剩余处理程序
    c.Next()
    fmt.Println("2-I'm initMiddlewareTwo")
}
func main() {
    r := gin.Default()
    //自定义模板函数,函数只能放在加载模板前
    r.SetFuncMap(template.FuncMap{ //"html/template"包
       "UnixToTime": UnixToTime,
    })
    //加载模板 放在配置路由上面
    r.LoadHTMLGlob("templates/**/*")
    //配置静态web目录 第一个参数表示路由 第二个参数表示映射目录
    r.Static("/static", "./static")
    //全局中间件
    r.Use(initMiddleware, initMiddlewareOne, initMiddlewareTwo)
    r.GET("/", func(c *gin.Context) {
       fmt.Println("I'm index")
       //time.Sleep(time.Second)
       c.String(http.StatusOK, "gin首页")
    })
    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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

终端:
1-I'm initMiddleware
1-I'm initMiddlewareOne
1-I'm initMiddlewareTwo
I'm index
2-I'm initMiddlewareTwo
2-I'm initMiddlewareOne
2-I'm initMiddleware

接口:gin首页

# 4)路由分组

func InitMiddleware(c *gin.Context) {
    //判断用户是否登录
    fmt.Println(time.Now())
    fmt.Println(c.Request.URL)
}

func InitApiRouters(r *gin.Engine) {
    apiRouters := r.Group("/api", middlewares.InitMiddleware)
    {
       apiRouters.GET("/", api.ApiController{}.Index)
       apiRouters.GET("/add", api.ApiController{}.Add)
       apiRouters.GET("/update", api.ApiController{}.Update)
       apiRouters.GET("/delete", api.ApiController{}.Delete)
       apiRouters.GET("/select", api.ApiController{}.Select)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

终端:
2025-06-09 19:01:33.0349342 +0800 CST m=+47.332205901
/api/

接口:api接口

# 5)中间件-控制器、中间件-中间件数据共享

func UserMiddleware(c *gin.Context) {
    //判断用户是否登录
    c.Set("username", "zhangsan")
}
func InitUserRouters(r *gin.Engine) {
    userRouters := r.Group("/user", middlewares.UserMiddleware)
    {
       userRouters.GET("/", user.UserController{}.Index)
       userRouters.GET("/add", user.UserController{}.Add)
       userRouters.GET("/update", user.UserController{}.Update)
       userRouters.GET("/delete", user.UserController{}.Delete)
       userRouters.GET("/select", user.UserController{}.Select)
    }
}
func (con UserController) Index(c *gin.Context) {
    //c.String(http.StatusOK, "user接口")
    con.Success(c)
}
func (con UserController) Add(c *gin.Context) {
    c.String(http.StatusOK, "user接口--增加 -- 继承")
}

func (con UserController) Update(c *gin.Context) {
    c.String(http.StatusOK, "user接口--修改 -- 继承")
}
func (con UserController) Delete(c *gin.Context) {
    c.String(http.StatusOK, "user接口--删除 -- 继承")
}
func (con UserController) Select(c *gin.Context) {
    username, _ := c.Get("username") //空接口类型
    fmt.Println(username)
    v, ok := username.(string)
    if ok == true {
       c.String(http.StatusOK, "user接口--查询 -- 继承--"+v)

    } else {
       c.String(http.StatusBadRequest, "获取用户数据失败!")
    }
}
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

user接口--查询 -- 继承--zhangsan

# 6)gin.Default()中的默认中间件

gin.Default()中含有默认中间件Logger()和Recovery()两个中间件。若不想使用的话可以修改为gin.New()默认不创建路由。

Logger()中间件将日志写入gin.DefaultWriter,即配置了GIN_MODE=release

Recovery()中间件回recover任何panic。如果有panic,会写入500响应码

注意:

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

原因:

1)c.Copy() 的作用

调用 c.Copy() 会创建一个 只读、安全的副本,其中包含:

  • 请求的必要数据(如 URL、Header、Method、Path 等)。

  • 与并发无关的数据(不包括响应写入器、Body、上下文变量等)。

副本不会被回收,可以安全地在 goroutine 中使用。

2)*gin.Context 是复用的

Gin 为了提升性能,会通过对象池(sync.Pool)来复用 *gin.Context 对象。也就是说,请求处理完毕后,这个 c 会被回收并复用用于下一次请求。

如果你在 goroutine 中异步地访问 c,而此时主请求已返回,c 可能已经被其他请求复用了,导致读取到的是错误或被修改的数据,甚至引发数据竞态(data race)。

3)goroutine 是异步执行的

当你在 handler 中启动一个 goroutine 时,该 goroutine 的执行时间是不确定的,可能晚于主 handler 的返回时间。

一旦主 handler 返回,c 可能就失效了。

此时 goroutine 中访问 c.Request.URL.Path 等字段,就有可能访问到已经被回收或复用的内存。

# AOP(Aspect-Oriented Programming)解决方案

AOP(Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点从核心业务逻辑中分离出来,帮助我们更好地组织代码。横切关注点是指那些在程序多个部分都需要关注的功能,如日志记录、事务管理、性能监控等,这些功能并不是直接影响业务逻辑,但却需要在多个地方重复出现。通过AOP,我们可以避免这些代码的重复,减少冗余并提高代码的可维护性。

# 核心概念

  • 切面(Aspect):切面是AOP的核心,代表了横切关注点的模块化,包含了跨越多个功能模块的代码逻辑。例如,日志切面、事务切面、权限验证切面等。
  • 通知(Advice):通知是AOP中定义的操作,描述了“什么时候”以及“如何”去执行切面代码。常见的通知类型包括:
    • 前置通知(Before):在方法执行之前执行某些操作。
    • 后置通知(After):在方法执行之后执行某些操作。
    • 环绕通知(Around):在方法执行之前和之后都执行操作,甚至可以决定是否执行目标方法。

Q:为什么使用到AOP?

A:在没有AOP的情况下,功能模块间的横切关注点(如日志记录、事务管理)会反复出现在代码的不同地方。每当业务逻辑改变时,我们可能需要修改多个位置的代码,导致代码难以维护。AOP通过将这些横切关注点提取到切面中,避免了代码重复,简化了维护和扩展。

# 基于Redis实现中间件

//main.go
func initWebServer() *gin.Engine {
	server := gin.Default()
	//注册中间件,作用于定义在server上的全部路由
	//...
    //Redis注册
    //参数标注
    //1:最大空闲连接数量 2:连接方式,tcp/udp 3:连接地址 4、5:连接账号密码 6、7:身份认证key和数据加密key
  	store, err := redis.NewStore(16, "tcp", "localhost:6379", "root", "", []byte("SPJbeQTIXdpJ7lSzidrOVoWsaEbLdZFB"), []byte("ueFnfGb0JFsvdzeH6ZIu6Oip2cEXVhIR"))
	if err != nil {
		panic(err)
	}
	server.Use(sessions.Sessions("weDemo", store))
	return server
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
编辑 (opens new window)
上次更新: 2026/04/08, 15:06:55
Gin中的身份认证
Gin之K8s

← Gin中的身份认证 Gin之K8s→

最近更新
01
Gin之K8s
02-02
02
Gorm之事务
11-13
03
Gorm之关联进阶版
11-13
更多文章>
Theme by Vdoing | Copyright © 2022-2026 爱摸鱼的Demon | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式