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

对控制器的分组与继承

1 )设计项目目录结构

tree 复制代码
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

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

html 复制代码
{{ define "web/index.html" }}

  <h1>web index 页面</h1>

  {{.msg}}

{{ end }}

4 ) routers 配置

4.1 webRouters.go

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

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

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

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

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

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

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

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 ) 基础用法, 单一中间件

go 复制代码
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 )多个路由中间件

go 复制代码
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 )全局中间件

go 复制代码
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 )中间件的拆分

tree 复制代码
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

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 文件

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 文件

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())
相关推荐
互联网中的一颗神经元5 小时前
10. Large 对象分配:直接路径
开发语言·golang
microrain8 小时前
从关系库硬扛到专业时序引擎:SagooIoT时序数据存储的架构取舍
物联网·golang·开源·sagooiot
Vect__10 小时前
channel 底层剖析
golang
李燚13 小时前
数据库迁移不翻车:golang-migrate 实战,143 个 DDL 有序执行(第97篇-E83)
golang·agent·multiagent·migrate·eino·subagent·deepflux
念何架构之路14 小时前
Gin内置中间件详解
中间件·gin
运维开发笔记15 小时前
7.1 Go Map 基础
golang
运维开发笔记15 小时前
7.2 Go Map 进阶
golang
西瓜很甜哟1 天前
golang每日十题第2期
golang
程序员小八7771 天前
Go 语言特性
开发语言·后端·golang
X1A0RAN1 天前
pytest 动态报告:数据可视化篇 —— 用 Golang + Echo 打造自动化测试任务可视化平台
信息可视化·golang·pytest