• Gin框架: 控制器, 中间件的分层设计案例


    控制器的分组与继承


    1 )设计项目目录结构

    yourGinProject/ ·······························  根目录
      ├── go.mod ··································  go mod 文件
      ├── go.sum ··································  go sum 文件
      ├── main.go ·································  main 文件
      └── tpls ····································· html模板目录
      │     └── web
      │     │    └── index.html
      ├── routers ·································· 路由目录
      │     ├── webRouters.go
      │     ├── apiRouters.go
      │     └── adminRouters.go
      ├── controllers ······························ 控制器目录
      │     ├── web
      │     │     └── webCtrl.go
      │     ├── api
      │     │     └── apiCtrl.go
      │     └── admin
      │     │     ├── base.go
      │     │     ├── indexCtrl.go
      │     │     └── userCtrl.go
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2 )主程序 main.go

    package main
    
    import (
    	"gin-demo/routers" //gin-demo 是 go mod init 初始化的工程,下同
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    
    	//加载模板 放在配置路由前面
    	r.LoadHTMLGlob("tpls/**/*")
    
    	routers.WebRoutersInit(r)
    	routers.ApiRoutersInit(r)
    	routers.AdminRoutersInit(r)
    
    	r.Run()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3 ) HTML模板目录配置

    tpls/web/index.html

    {{ define "web/index.html" }}
    
      <h1>web index 页面h1>
    
      {{.msg}}
    
    {{ end }}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4 ) routers 配置

    4.1 webRouters.go

    package routers
    
    import (
    	"gin-demo/controllers/web"
    
    	"github.com/gin-gonic/gin"
    )
    
    func WebRoutersInit(r *gin.Engine) {
    	webRouters := r.Group("/")
    	{
    		webRouters.GET("/", web.WebCtrl{}.Index)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.2 apiRouters.go

    package routers
    
    import (
    	"gin-demo/controllers/api"
    	"github.com/gin-gonic/gin"
    )
    
    func ApiRoutersInit(r *gin.Engine) {
    	apiRouters := r.Group("/api")
    	{
    		apiRouters.GET("/", api.ApiCtrl{}.Index)
    		apiRouters.GET("/user", api.ApiCtrl{}.User)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.2 adminRouters.go

    package routers
    
    import (
    	"gin-demo/controllers/admin"
    	"github.com/gin-gonic/gin"
    )
    
    func AdminRoutersInit(r *gin.Engine) {
    	adminRouters := r.Group("/admin")
    	{
    		adminRouters.GET("/", admin.IndexCtrl{}.Index)
    		adminRouters.GET("/user", admin.UserCtrl{}.Index)
    		adminRouters.GET("/user/success", admin.UserCtrl{}.Success)
    		adminRouters.GET("/user/error", admin.UserCtrl{}.Error)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5 ) controller 配置

    5.1 web/webCtrl.go

    package web
    
    import (
    	"net/http"
    	"github.com/gin-gonic/gin"
    )
    
    type WebCtrl struct{}
    
    func (con WebCtrl) Index(c *gin.Context) {
    	c.HTML(http.StatusOK, "web/index.html", gin.H{
    		"msg": "我是一个msg",
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.2 api/apiCtrl.go

    package api
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    type ApiCtrl struct{}
    
    func (con ApiCtrl) Index(c *gin.Context) {
    	c.String(http.StatusOK, "api接口总承")
    }
    func (con ApiCtrl) User(c *gin.Context) {
    	c.String(http.StatusOK, "这是一个 user 接口")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.3 admin/indexCtrl.go

    package admin
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    type IndexCtrl struct {}
    
    func (con IndexCtrl) Index(c *gin.Context) {
    	c.String(http.StatusOK, "admin 页面")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5.4 admin/baseCtrl.go

    package admin
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    type BaseCtrl struct{}
    
    func (con BaseCtrl) success(c *gin.Context) {
    	c.String(http.StatusOK, "成功")
    }
    
    func (con BaseCtrl) error(c *gin.Context) {
    	c.String(http.StatusOK, "失败")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5.4 admin/userCtrl.go

    package admin
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    type UserCtrl struct {
    	BaseCtrl
    }
    
    func (con UserCtrl) Index(c *gin.Context) {
    	c.String(http.StatusOK, "user 页面")
    }
    func (con UserCtrl) Success(c *gin.Context) {
    	con.success(c)
    }
    func (con UserCtrl) Error(c *gin.Context) {
    	con.error(c)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    以上就是对控制器的一般文件拆分和继承关系的调用示例,验证如下

    / 访问首页
    /api
    /api/user
    /admin
    /admin/user
    /admin/user/success
    /admin/user/error

    以上均可正常访问,这样就可以最快完成一个项目的拆分

    中间件的处理


    1 ) 基础用法, 单一中间件

    package main
    
    import (
    	"fmt"
    	"time"
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func initMiddleware(c *gin.Context) {
    	// 记录开始时间
    	start := time.Now().UnixNano()
    	// 调用该请求的剩余处理程序
    	c.Next()
    	// 记录结束时间
    	end := time.Now().UnixNano()
    	// 输出当前渲染时间差
    	fmt.Println("时间:", end - start)
    }
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    
    	r.GET("/", initMiddleware, func(c *gin.Context) {
    		c.String(http.StatusOK, "首页")
    	})
    
    	r.GET("/news", initMiddleware, func(c *gin.Context) {
    		c.String(http.StatusOK, "新闻页面")
    	})
    
    	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
    • 中间件就是匹配路由前和匹配路由完成后执行的一系列操作
    • 中间件必须是一个 gin.HandlerFunc 类型

    2 )多个路由中间件

    package main
    
    import (
    	"fmt"
    	"time"
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func initMiddleware(c *gin.Context) {
    	fmt.Println("第1个中间件开始")
    	// 记录开始时间
    	start := time.Now().UnixNano()
    	// 调用该请求的剩余处理程序
    	c.Next()
    	// 记录结束时间
    	end := time.Now().UnixNano()
    	// 输出当前渲染时间差
    	fmt.Println("第1个中间件结束,并统计其处理时间:", end - start)
    }
    
    func initMiddleware2(c *gin.Context) {
    	fmt.Println("第2个中间件开始")
    	c.Next()
    	// 终止调用该请求的剩余处理程序
    	// c.Abort() // 注意,Next 和 Abort 只能二选一,可以控制在某些情况下,终止中间件
    	fmt.Println("第2个中间件结束")
    }
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    
    	r.GET("/", initMiddleware, initMiddleware2, func(c *gin.Context) {
    		c.String(http.StatusOK, "首页")
    	})
    
    	r.GET("/news", initMiddleware, initMiddleware2, func(c *gin.Context) {
    		c.String(http.StatusOK, "新闻页面")
    	})
    
    	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
    • 42
    • 43
    • 上述示例中,有两个中间件,就是 initMiddleware, initMiddleware2
    • 访问路由时的输出顺序
      第1个中间件开始
      第2个中间件开始
      第2个中间件结束
      第1个中间件结束,并统计其处理时间: 21000
      
      • 1
      • 2
      • 3
      • 4
    • 这种就是洋葱模型,基本上所有中间件都符合这一模型
    • 配置路由的时候可以传递多个 func 回调函数
    • 最后一个 func 回调函数前面触发的方法都可以称为中间件
    • 中间件里面加上 ctx.Next()可以让我们在路由匹配完成后执行一些操作
    • 如果想要终止中间件操作可以通过判断,添加 ctx.Abort() 来终止接下来的操作

    3 )全局中间件

    package main
    
    import (
    	"fmt"
    	"time"
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func initMiddleware(c *gin.Context) {
    	fmt.Println("第1个中间件开始")
    	// 记录开始时间
    	start := time.Now().UnixNano()
    	// 调用该请求的剩余处理程序
    	c.Next()
    	// 记录结束时间
    	end := time.Now().UnixNano()
    	// 输出当前渲染时间差
    	fmt.Println("第1个中间件结束,并统计其处理时间:", end - start)
    }
    
    func initMiddleware2(c *gin.Context) {
    	fmt.Println("第2个中间件开始")
    	c.Next()
    	fmt.Println("第2个中间件结束")
    }
    
    func main() {
    	// 创建一个默认的路由引擎
    	r := gin.Default()
    
    	// 全局中间件
    	r.Use(initMiddleware, initMiddleware2)
    
    	r.GET("/", func(c *gin.Context) {
    		c.String(http.StatusOK, "首页")
    	})
    
    	r.GET("/news", func(c *gin.Context) {
    		c.String(http.StatusOK, "新闻页面")
    	})
    
    	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
    • 42
    • 43
    • 44
    • 这种属于全局配置的中间件,不用在每个路由中书写,进行全局use
    • 这种写法和第2种效果一致

    4 )中间件的拆分

    yourGinProject/ ·······························  根目录
      ├── go.mod ··································  go mod 文件
      ├── go.sum ··································  go sum 文件
      ├── main.go ·································  main 文件
      └── tpls ····································· html模板目录
      │     └── web
      │     │    └── index.html
      ├── routers ·································· 路由目录
      │     ├── webRouters.go
      │     ├── apiRouters.go
      │     └── adminRouters.go
      ├── controllers ······························ 控制器目录
      │     ├── web
      │     │     └── webCtrl.go
      │     ├── api
      │     │     └── apiCtrl.go
      │     └── admin
      │     │     ├── base.go
      │     │     ├── indexCtrl.go
      │     │     └── userCtrl.go
      ├── middlewares ······························ 中间件目录
      │     └── init.go
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里使用最顶层控制器拆分时用的结构

    这里 middlewares/init.go

    package middlewares
    
    import (
    	"fmt"
    	"time"
    	"github.com/gin-gonic/gin"
    )
    
    func InitMiddleware(c *gin.Context) {
    	//判断用户是否登录
    
    	fmt.Println("当前时间:", time.Now())
    	fmt.Println("当前URL:", c.Request.URL)
    
    	c.Set("username", "Wang") // 在请求上下文中设置值,后续的处理函数能够取到该值
    
    	// 定义一个 goroutine 统计日志
    	// 当在中间件或 handler 中启动新的 goroutine 时
    	// 不能使用原始的上下文(c *gin.Context), 必须使用其只读副本(c.Copy())
    	cCp := c.Copy()
    	go func() {
    		time.Sleep(2 * time.Second)
    		fmt.Println("Done! in path " + cCp.Request.URL.Path)
    	}()
    }
    
    • 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

    改造 routers/adminRouters.go 文件

    package routers
    
    import (
    	"gin-demo/controllers/admin"
    	"github.com/gin-gonic/gin"
    	"gin-demo/middlewares" // 引入
    )
    
    func AdminRoutersInit(r *gin.Engine) {
    	adminRouters := r.Group("/admin", middlewares.InitMiddleware) // 注意这里
    	{
    		adminRouters.GET("/", admin.IndexCtrl{}.Index)
    		adminRouters.GET("/user", admin.UserCtrl{}.Index)
    		adminRouters.GET("/user/success", admin.UserCtrl{}.Success)
    		adminRouters.GET("/user/error", admin.UserCtrl{}.Error)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    /admin 及子路由被访问时都会经过这个中间件
    这里用了一个 goroutine 做数据统计,下面在 admin.userCtrl 中获取中间件中配置的值

    改造 controllers/admin/userCtrl.go 文件

    package admin
    
    import (
    	"net/http"
    	"github.com/gin-gonic/gin"
    )
    
    type UserCtrl struct {
    	BaseCtrl
    }
    
    func (con UserCtrl) Index(c *gin.Context) {
    	username, _ := c.Get("username") // 这里从中间件中读取数据
    	c.String(http.StatusOK, "user 页面: %v", username) // 响应出去
    }
    func (con UserCtrl) Success(c *gin.Context) {
    	con.success(c)
    }
    func (con UserCtrl) Error(c *gin.Context) {
    	con.error(c)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这样就可以获取到中间件中读取的数据了

    注意事项

    • gin 默认中间件
      • gin.Default()默认使用了 Logger 和 Recovery 中间件,其中:
      • Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN_MODE=release
      • Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500 响应码
      • 如果不想使用上面两个默认的中间件,可以使用 gin.New() 新建一个没有任何默认中间件的路由
    • gin 中间件中使用 goroutine
      • 当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c *gin.Context)
      • 必须使用其只读副本(c.Copy())
  • 相关阅读:
    20_数组的常见操作
    VBA:合并多行到同一列
    程序员这样提高英语,少走很多弯路
    (70)Verilog读文件函数【readmemb】
    香橙派OrangePi AIpro上手笔记——之USB摄像头目标检测方案测试(三)
    Git分支操作
    【Linux】自动化构建工具:make/Makefile
    Fouier Net和DeepOnet等求解器算法解读和代码
    C++ -------- 异常
    [HDLBits] Count15
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/136187762