当前位置: 首页 > news >正文

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 &#xff09;设计项目目录结构 yourGinProject/ 根目录├── go.mod go mod 文件├── go.sum go sum 文件├── main.go main 文件└── tpls html模板目录│ └── web│ │ └── index.html├── routers 路由目录│ …...

日常遇到Maven出现依赖版本/缓存问题通用思路。

Maven依赖错误联想 明明自己的工程是直接从大佬哪里拉下来的&#xff0c;并且自己的setting文件也是没有问题&#xff0c;可是自己偏偏编译有问题。这里介绍一种通用解决方案&#xff0c;仅供参考。 前置排查确认 我遇到原因是在JDK升级过程中遇到的&#xff1a; java.lang.…...

安卓11-HDMI插拔检测流程

hdmi从插入到拔出经过底层一系列检测到应用层&#xff0c;应用层获取hdmi插入状态后又会做出一系列相应的动作&#xff0c;下面梳理了从应用层到底层一步步追踪到芯片的hpd-pin的检测过程。 frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.…...

OkHttp Retrofit HttpClient之间的区别

OkHttp、Retrofit 和 HttpClient 是三个不同的 HTTP 客户端库&#xff0c;它们各自有不同的特点和用途。下面是它们之间的主要区别&#xff1a; 1. **OkHttp**&#xff1a; - OkHttp 是一个高性能的 HTTP 和 HTTP/2 客户端&#xff0c;由 Square 公司开发。 - 它…...

Paddlepaddle使用自己的VOC数据集训练目标检测(0废话简易教程)

一 安装paddlepaddle和paddledection&#xff08;略&#xff09; 笔者使用的是自己的数据集 二 在dataset目录下新建自己的数据集文件&#xff0c;如下&#xff1a; 其中 xml文件内容如下&#xff1a; 另外新建一个createList.py文件&#xff1a; # -- coding: UTF-8 -- imp…...

【解析】C语言两个实例

例一&#xff1a; 下面程序输出什么&#xff1f; 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是一个用于数据科学和机器学习的开源软件包管理器和环境管理器。它包含了许多流行的数据科学工具和库&#xff0c;如Python、Jupyter Notebook、numpy、pandas、scikit-learn等&#xff0c;可以帮助用户轻松地管理和安装这些工具和库。Anaco…...

Linux 驱动开发基础知识——APP 怎么读取按键值(十二)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…...

【FastAPI】P3 请求与响应

目录 请求路径参数查询参数 响应JSON 响应文本响应返回 Pydantic 模型 在网络通讯中&#xff0c;请求&#xff08;Request&#xff09; 与 响应&#xff08;Response&#xff09; 扮演着至关重要的角色&#xff0c;它们构成了客户端与服务器间互动的根本理念。 请求&#xff0…...

Python学习-流程图、分支与循环(branch and loop)

十、流程图 1、流程图&#xff08;Flowchart&#xff09; 流程图是一种用于表示算法或代码流程的框图组合&#xff0c;它以不同类型的框框代表不同种类的程序步骤&#xff0c;每两个步骤之间以箭头连接起来。 好处&#xff1a; 1&#xff09;代码的指导文档 2&#xff09;有助…...

Python Flask Web 框架学习笔记+完整项目

Flask是一个轻量级的基于Python的web框架。 我们建议使用最新版本的 Python。Flask 支持 Python 3.8 及更高版本。 官网&#xff1a;欢迎使用 Flask — Flask 文档 &#xff08;3.0.x&#xff09; (palletsprojects.com) RESTFul API&#xff1a;Python Flask高级编程之REST…...

XML Map 端口进阶篇——常用关键字和格式化器详解

XML Map 端口是用于在不同XML之间建立关系映射的工具&#xff0c;允许通过拖拽操作实现源XML和目标 XML之间的数据字段映射&#xff0c;除此之外&#xff0c;XML Map 端口还提供了其它丰富多彩的功能&#xff0c;使用户能够更加灵活和高效的处理XML 数据映射任务&#xff0c;让…...

排序算法之——直接插入排序

直接插入排序——以升序排列为例 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基本思想 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直…...

突出最强算法模型——回归算法 !!

文章目录 1、特征工程的重要性 2、缺失值和异常值的处理 &#xff08;1&#xff09;处理缺失值 &#xff08;2&#xff09;处理异常值 3、回归模型的诊断 &#xff08;1&#xff09;残差分析 &#xff08;2&#xff09;检查回归假设 &#xff08;3&#xff09;Cooks 距离 4、学…...

云数据库 Redis 性能深度评测(阿里云、华为云、腾讯云、百度智能云)

在当今的云服务市场中&#xff0c;阿里云、腾讯云、华为云和百度智能云都是领先的云服务提供商&#xff0c;他们都提供了全套的云数据库服务&#xff0c;其中 Redis属于RDS 之后第二被广泛应用的服务&#xff0c;本次测试旨在深入比较这四家云服务巨头在Redis云数据库性能方面的…...

Android---Retrofit实现网络请求:Java 版

简介 在 Android 开发中&#xff0c;网络请求是一个极为关键的部分。Retrofit 作为一个强大的网络请求库&#xff0c;能够简化开发流程&#xff0c;提供高效的网络请求能力。 Retrofit 是一个建立在 OkHttp 基础之上的网络请求库&#xff0c;能够将我们定义的 Java 接口转化为…...

使用静态CRLSP配置MPLS TE隧道

正文共&#xff1a;1591 字 13 图&#xff0c;预估阅读时间&#xff1a;4 分钟 静态CRLSP&#xff08;Constraint-based Routed Label Switched Paths&#xff0c;基于约束路由的LSP&#xff09;是指在报文经过的每一跳设备上&#xff08;包括Ingress、Transit和Egress&#xf…...

gentoo安装笔记

最近比较闲&#xff0c;所以挑战一下自己&#xff0c;在自己的台式电脑上安装gentoo 下面记录了我亲自安装的步骤&#xff0c;作为以后我再次安装时参考所用。 整体步骤 一般来将一个linux发行版的安装步骤其实大体上都差不多&#xff0c;基本分为一下几步&#xff1a; 1. …...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景

Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知&#xff0c;帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量&#xff0c;能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度&#xff0c;还为机器人、医疗设备和制造业的智…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...