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 mainimport ("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 routersimport ("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 routersimport ("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 routersimport ("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 webimport ("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 apiimport ("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 adminimport ("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 adminimport ("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 adminimport ("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 mainimport ("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 mainimport ("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 mainimport ("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 middlewaresimport ("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 routersimport ("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 adminimport ("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())
相关文章:
Gin框架: 控制器, 中间件的分层设计案例
对控制器的分组与继承 1 )设计项目目录结构 yourGinProject/ 根目录├── go.mod go mod 文件├── go.sum go sum 文件├── main.go main 文件└── tpls html模板目录│ └── web│ │ └── index.html├── routers 路由目录│ …...
日常遇到Maven出现依赖版本/缓存问题通用思路。
Maven依赖错误联想 明明自己的工程是直接从大佬哪里拉下来的,并且自己的setting文件也是没有问题,可是自己偏偏编译有问题。这里介绍一种通用解决方案,仅供参考。 前置排查确认 我遇到原因是在JDK升级过程中遇到的: java.lang.…...
安卓11-HDMI插拔检测流程
hdmi从插入到拔出经过底层一系列检测到应用层,应用层获取hdmi插入状态后又会做出一系列相应的动作,下面梳理了从应用层到底层一步步追踪到芯片的hpd-pin的检测过程。 frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.…...
OkHttp Retrofit HttpClient之间的区别
OkHttp、Retrofit 和 HttpClient 是三个不同的 HTTP 客户端库,它们各自有不同的特点和用途。下面是它们之间的主要区别: 1. **OkHttp**: - OkHttp 是一个高性能的 HTTP 和 HTTP/2 客户端,由 Square 公司开发。 - 它…...
Paddlepaddle使用自己的VOC数据集训练目标检测(0废话简易教程)
一 安装paddlepaddle和paddledection(略) 笔者使用的是自己的数据集 二 在dataset目录下新建自己的数据集文件,如下: 其中 xml文件内容如下: 另外新建一个createList.py文件: # -- coding: UTF-8 -- imp…...
【解析】C语言两个实例
例一: 下面程序输出什么? int main() { int i 43; int n printf("%d\n",i); printf("%d\n",n); return 0; } 大家深入考虑一下为什么返回是3这背后有什么鲜为人知的秘密到底是C语言离奇的规定还是深思熟…...
阅读笔记(Multimedia Systems2020)Review on image-stitching techniques
Wang Z, Yang Z. Review on image-stitching techniques[J]. Multimedia Systems, 2020, 26: 413-430. DOI https://doi.org/10.1007/s00530-020-00651-y...
【Java程序员面试专栏 数据结构】三 高频面试算法题:栈和队列
一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,因为栈和队列这两哥们结构特性比较向对应,所以放到一篇Blog中集中练习 题目题干直接给出对应博客链接,这里只给出简单思路、代码实现、复杂度分析 题目关键字…...
Python | Conda常用命令
一、介绍 1、Anaconda工具 Anaconda是一个用于数据科学和机器学习的开源软件包管理器和环境管理器。它包含了许多流行的数据科学工具和库,如Python、Jupyter Notebook、numpy、pandas、scikit-learn等,可以帮助用户轻松地管理和安装这些工具和库。Anaco…...
Linux 驱动开发基础知识——APP 怎么读取按键值(十二)
个人名片: 🦁作者简介:学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755qq.com 🦉个人WeChat:Vir2021GKBS 🐼本文由…...
【FastAPI】P3 请求与响应
目录 请求路径参数查询参数 响应JSON 响应文本响应返回 Pydantic 模型 在网络通讯中,请求(Request) 与 响应(Response) 扮演着至关重要的角色,它们构成了客户端与服务器间互动的根本理念。 请求࿰…...
Python学习-流程图、分支与循环(branch and loop)
十、流程图 1、流程图(Flowchart) 流程图是一种用于表示算法或代码流程的框图组合,它以不同类型的框框代表不同种类的程序步骤,每两个步骤之间以箭头连接起来。 好处: 1)代码的指导文档 2)有助…...
Python Flask Web 框架学习笔记+完整项目
Flask是一个轻量级的基于Python的web框架。 我们建议使用最新版本的 Python。Flask 支持 Python 3.8 及更高版本。 官网:欢迎使用 Flask — Flask 文档 (3.0.x) (palletsprojects.com) RESTFul API:Python Flask高级编程之REST…...
XML Map 端口进阶篇——常用关键字和格式化器详解
XML Map 端口是用于在不同XML之间建立关系映射的工具,允许通过拖拽操作实现源XML和目标 XML之间的数据字段映射,除此之外,XML Map 端口还提供了其它丰富多彩的功能,使用户能够更加灵活和高效的处理XML 数据映射任务,让…...
排序算法之——直接插入排序
直接插入排序——以升序排列为例 1.1基本思想1.2动态图示感知1.3静态图示详解1.4代码实现1.5时间复杂度1.5.1最好情况1.5.2最差情况 1.6空间复杂度1.7稳定性1.7.1一个小问题 1.1基本思想 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直…...
突出最强算法模型——回归算法 !!
文章目录 1、特征工程的重要性 2、缺失值和异常值的处理 (1)处理缺失值 (2)处理异常值 3、回归模型的诊断 (1)残差分析 (2)检查回归假设 (3)Cooks 距离 4、学…...
云数据库 Redis 性能深度评测(阿里云、华为云、腾讯云、百度智能云)
在当今的云服务市场中,阿里云、腾讯云、华为云和百度智能云都是领先的云服务提供商,他们都提供了全套的云数据库服务,其中 Redis属于RDS 之后第二被广泛应用的服务,本次测试旨在深入比较这四家云服务巨头在Redis云数据库性能方面的…...
Android---Retrofit实现网络请求:Java 版
简介 在 Android 开发中,网络请求是一个极为关键的部分。Retrofit 作为一个强大的网络请求库,能够简化开发流程,提供高效的网络请求能力。 Retrofit 是一个建立在 OkHttp 基础之上的网络请求库,能够将我们定义的 Java 接口转化为…...
使用静态CRLSP配置MPLS TE隧道
正文共:1591 字 13 图,预估阅读时间:4 分钟 静态CRLSP(Constraint-based Routed Label Switched Paths,基于约束路由的LSP)是指在报文经过的每一跳设备上(包括Ingress、Transit和Egress…...
gentoo安装笔记
最近比较闲,所以挑战一下自己,在自己的台式电脑上安装gentoo 下面记录了我亲自安装的步骤,作为以后我再次安装时参考所用。 整体步骤 一般来将一个linux发行版的安装步骤其实大体上都差不多,基本分为一下几步: 1. …...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...
密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...
STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...
初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)
零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...
Shell 解释器 bash 和 dash 区别
bash 和 dash 都是 Unix/Linux 系统中的 Shell 解释器,但它们在功能、语法和性能上有显著区别。以下是它们的详细对比: 1. 基本区别 特性bash (Bourne-Again SHell)dash (Debian Almquist SHell)来源G…...
