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. …...

Git如何使用 五分钟快速入门
Git如何使用 五分钟快速入门 Git是一个分布式版本控制系统,它可以帮助开发人员跟踪和管理项目的代码变更。与传统的集中式版本控制系统(如SVN)不同,Git允许开发人员在本地存储完整的代码仓库,并且可以独立地进行代码修…...

FreeRTOS学习笔记——(FreeRTOS临界段代码保护及调度器挂起与恢复)
这里写目录标题 1,临界段代码保护简介(熟悉)2,临界段代码保护函数介绍(掌握)3,任务调度器的挂起和恢复(熟悉) 1,临界段代码保护简介(熟悉…...

箱形理论在交易策略中的实战应用与优化
箱形理论,简单来说,就是将价格波动分成一段一段的方框,研究这些方框的高点和低点,来推测价格的趋势。 在上升行情中,价格每突破新高价后,由于群众惧高心理,可能会回跌一段,然后再上升…...

MinIO 和 Apache Tika:文本提取模式
Tl;dr: 在这篇文章中,我们将使用 MinIO Bucket Notifications 和 Apache Tika 进行文档文本提取,这是大型语言模型训练和检索增强生成 LLM和RAG 等关键下游任务的核心。 前提 假设我想构建一个文本数据集,然后我可以用它来微调 LLM.为了做…...

c编译器学习05:与chibicc类似的minilisp编译器(待续)
minilisp项目介绍 项目地址:https://github.com/rui314/minilisp 作者也是rui314,commits也是按照模块开发提交的。 minilisp只有一个代码文件:https://github.com/rui314/minilisp/blob/master/minilisp.c 加注释也只有996行。 代码结构&a…...

手撕qsort函数
前言 本篇主要讲解的是qsort函数细节以及运用实例。 紧跟我的脚步一起手撕qsort函数吧~ 欢迎关注个人主页:逸狼 更多优质内容: 拿捏c语言指针(上) 拿捏c语言指针(中) 拿捏c语言指针(下&…...

项目在linux上的简单部署
本文章只介绍项目的简单部署,暂时没有Docker部署。 项目部署有两种方式,一种是直接命令部署,第二种是用脚本,脚本本身也是将命令进行封装来执行。 命令 项目通过maven打包,启动命令: # 启动命令 nohup …...

MySQL安装教程(详细版)
今天分享的是Win10系统下MySQL的安装教程,打开MySQL官网,按步骤走呀~ 宝们安装MySQL后,需要简单回顾一下关系型数据库的介绍与历史(History of DataBase) 和 常见关系型数据库产品介绍 呀,后面就会进入正式…...

Linux platform tree下的单总线驱动程序设计(DHT11)
目录 概述 1 认识DHT11 1.1 DHT11特性 1.2 DHT11数据格式 1.3 DHT11与MCU通信 1.4 DHT11信号解析 1.4.1 起始信号 1.4.2 解析信号0 1.4.3 解析信号1 2 驱动开发 2.1 硬件接口 2.2 更新设备树 2.2.1 添加驱动节点 2.2.2 编译.dts 2.2.3 更新板卡中的.dtb 2.3 驱…...

自研爬虫框架的经验总结(理论及方法)
背景: 由于业务需要,承接一部分的数据采集工作。目前市场内的一些通用框架不太适合。故而进行了自研。 对比自研和目前成熟的框架,自研更灵活适配,可以自己组装核心方法;后者对于新场景的适配需要对框架本身有较高的理…...