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

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

3 ) HTML模板目录配置

tpls/web/index.html

{
  { define "web/index.html" }}

  <h1>web index 页面</h1>

  {
  {.msg}}

{
  { end }}

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

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

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

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

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

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

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, "失败")
}

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

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

/ 访问首页
/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()
}
  • 中间件就是匹配路由前和匹配路由完成后执行的一系列操作
  • 中间件必须是一个 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()
}
  • 上述示例中,有两个中间件,就是 initMiddleware, initMiddleware2
  • 访问路由时的输出顺序
    第1个中间件开始
    第2个中间件开始
    第2个中间件结束
    第1个中间件结束,并统计其处理时间: 21000
    
  • 这种就是洋葱模型,基本上所有中间件都符合这一模型
  • 配置路由的时候可以传递多个 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()
}
  • 这种属于全局配置的中间件,不用在每个路由中书写,进行全局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

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

这里 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)
	}()
}

改造 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)
	}
}

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

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

注意事项

  • 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())

相关推荐

  1. Gin框架: 控制器, 中间分层设计案例

    2024-02-21 07:42:03       24 阅读
  2. Vue中间讲解案例分析

    2024-02-21 07:42:03       25 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-21 07:42:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-21 07:42:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-21 07:42:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-21 07:42:03       20 阅读

热门阅读

  1. SpringBoot异步任务获取HttpServletRequest

    2024-02-21 07:42:03       24 阅读
  2. 分布式理论

    2024-02-21 07:42:03       28 阅读
  3. 【gpt】中文写作论文prompt

    2024-02-21 07:42:03       30 阅读
  4. 修改SAP默认编辑器(MS WORD改为SAP TEXTEDITOR)

    2024-02-21 07:42:03       23 阅读
  5. 大语言模型LLM中Transformer模型的调用过程与步骤

    2024-02-21 07:42:03       27 阅读