Mygin实现分组路由Group
本篇是Mygin第五篇
目的
- 实现路由分组
 
为什么要分组
分组控制(Group Control)是 Web 框架应该提供的基础功能之一,对同一模块功能的开发,应该有相同的前缀。或者对一部分第三方接口,统一需要加解密等功能。分组后很方便。例如:
- 对于任务模块,统一前缀为/task
 - 除去/user/login接口,都需要鉴权
 - 以/openapi 开头的接口,需要对接第三方平台,需要三方平台鉴权
 
大多数分组都是统一前缀,正确的分组可以实现子分组,无限极往下分组,当然实际情况下也不会有太多层分组。每个分组有不同的中间件(middleware),分组与子分组就像洋葱一样,一层一层往内。要想往内,需要拨开最外层,也就是要执行外层的中间件才能往内。对于分组,也有后续扩展的好处,比如/task分组,现在要统一加上访问日志记录,有了分组后就可以在该分组上添加一个中间件就可以了。
分组嵌套
在实现分组嵌套之前,先看看gin中的分组嵌套是怎么实现的。gin中的分组中含有Engin的指针,其实很好理解,因为分组要有访问Router的能力。Engin中继承了RouterGroup,实例化Engin后就可以使用Group的功能。有了Engin的指针,整个框架的所有资源都是由Engine去统一调度的,因此通过Engine可以间接地拥有整个框架的功能。你中有我我中有你。
- gin/routergroup.go
 
package mygin
import (
	"net/http"
	"path"
	"regexp"
)
// IRoutes 定义了路由组的接口
type IRoutes interface {
	BasePath() string
	GET(string, ...HandlerFunc) IRoutes
	POST(string, ...HandlerFunc) IRoutes
	DELETE(string, ...HandlerFunc) IRoutes
	PATCH(string, ...HandlerFunc) IRoutes
	PUT(string, ...HandlerFunc) IRoutes
	OPTIONS(string, ...HandlerFunc) IRoutes
	HEAD(string, ...HandlerFunc) IRoutes
	Match([]string, string, ...HandlerFunc) IRoutes
}
// anyMethods 包含所有 HTTP 方法的字符串表示
var anyMethods = []string{
	http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
	http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
	http.MethodTrace,
}
// RouterGroup 定义了路由组的结构体
type RouterGroup struct {
	Handlers HandlersChain // 路由组的中间件处理函数链
	basePath string        // 路由组的基础路径
	engine   *Engine       // 路由组所属的引擎
	root     bool          // 是否是根路由组
}
// Group 创建一个新的路由组
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		Handlers: append(group.Handlers, handlers...),
		basePath: path.Join(group.basePath, relativePath),
		engine:   group.engine,
	}
}
// BasePath 返回路由组的基础路径
func (group *RouterGroup) BasePath() string {
	return group.basePath
}
// handle 处理路由,将路由信息添加到引擎中
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
	absolutePath := path.Join(group.basePath, relativePath)
    //处理函数,Group组定义的函数先执行,自定义的函数后执行
	handlers = append(group.Handlers, handlers...)
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	if group.root {
		return group.engine
	}
	return group
}
// Handle 校验 HTTP 方法的有效性,并处理路由
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
	// 检查 HTTP 方法的有效性
	if match := regexp.MustCompile("^[A-Z]+$").MatchString(httpMethod); !match {
		panic("http method " + httpMethod + " is not valid")
	}
	// 处理路由
	return group.handle(httpMethod, relativePath, handlers)
}
// GET 注册 GET 方法的路由
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}
// POST 注册 POST 方法的路由
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodPost, relativePath, handlers)
}
// DELETE 注册 DELETE 方法的路由
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodDelete, relativePath, handlers)
}
// PATCH 注册 PATCH 方法的路由
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodPatch, relativePath, handlers)
}
// PUT 注册 PUT 方法的路由
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodPut, relativePath, handlers)
}
// OPTIONS 注册 OPTIONS 方法的路由
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodOptions, relativePath, handlers)
}
// HEAD 注册 HEAD 方法的路由
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodHead, relativePath, handlers)
}
// Match 注册多个方法的路由
func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
	for _, method := range methods {
		group.handle(method, relativePath, handlers)
	}
	if group.root {
		return group.engine
	}
	return group
}
// Any 注册所有方法的路由
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
	for _, method := range anyMethods {
		group.handle(method, relativePath, handlers)
	}
	if group.root {
		return group.engine
	}
	return group
}
可以看到group.engine.addRoute方法,实际调用的仍然是mygin/router.go中的addRoute方法。当group.root为true,也就是为根时,相当于没有调用Group方法,返回group.engine,也就是相当于直接调用的engine,只有当调用Group方法后才会返回group。
接下来看engine的修改
- mygin/engine.go
 
package mygin
import (
	"net/http"
)
// HandlerFunc 定义处理函数类型
type HandlerFunc func(*Context)
// HandlersChain 定义处理函数链类型
type HandlersChain []HandlerFunc
// Engine 定义引擎结构,包含路由器
type Engine struct {
	Router
	RouterGroup
}
// ServeHTTP 实现http.Handler接口的方法,用于处理HTTP请求
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 获取对应HTTP方法的路由树的根节点
	root := e.trees.get(r.Method)
	// 解析请求路径
	parts := root.parseFullPath(r.URL.Path)
	// 查找符合条件的节点
	searchNode := make([]*node, 0)
	root.search(parts, &searchNode)
	// 没有匹配到路由
	if len(searchNode) == 0 {
		w.Write([]byte("404 Not found!\n"))
		return
	}
	// 参数赋值
	params := make([]Param, 0)
	searchPath := root.parseFullPath(searchNode[0].fullPath)
	for i, sp := range searchPath {
		if sp[0] == ':' {
			params = append(params, Param{
				Key:   sp[1:],
				Value: parts[i],
			})
		}
	}
	// 获取处理函数链
	handlers := searchNode[0].handlers
	if handlers == nil {
		w.Write([]byte("404 Not found!\n"))
		return
	}
	// 执行处理函数链
	for _, handler := range handlers {
		handler(&Context{
			Request: r,
			Writer:  w,
			Params:  params,
		})
	}
}
// Default 返回一个默认的引擎实例
func Default() *Engine {
	engine := &Engine{
		Router: Router{
			trees: make(methodTrees, 0, 9),
		},
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
	}
	// Group 保存 engine 的指针
	engine.RouterGroup.engine = engine
	return engine
}
// Run 启动HTTP服务器的方法
func (e *Engine) Run(addr string) error {
	return http.ListenAndServe(addr, e)
}
最后来测试一下
- main.go
 
package main
import (
	"gophp/mygin"
	"path"
)
func main() {
	// 创建一个默认的 mygin 实例
	r := mygin.Default()
	group := r.Group("/api", func(context *mygin.Context) {
		context.String("api Group ....\n")
	})
	group.GET("/hello/:name", func(context *mygin.Context) {
		name := context.Params.ByName("name")
		context.String(path.Join("hello ", name, "!"))
	})
	group.GET("/hello2/:name", func(context *mygin.Context) {
		name := context.Params.ByName("name")
		context.String(path.Join("hello2 ", name, "!\n"))
	})
	// 启动服务器并监听端口
	r.Run(":8088")
}
curl请求测试
 curl -i http://localhost:8088/api/hello/scott
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 24 Jan 2024 05:16:45 GMT
Content-Length: 29
api Group ....
hello /scott/!
~ curl -i http://localhost:8088/api/hello2/scott
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Wed, 24 Jan 2024 05:16:54 GMT
Content-Length: 31
api Group ....
hello2 /scott/!
可以看到两个路由都安装预定的返回了,即先返回Group定义的,再返回路由定义的。
Mygin实现分组路由Group的更多相关文章
- laravel路由之分组路由
		
laravel下的分组路由可以嵌套如下: Route::group(['prefix'=>'admin'],function(){ Route::group(['prefix'=>'dtk ...
 - Oracle和MySQL分组查询GROUP BY
		
Oracle和MySQL分组查询GROUP BY 真题1.Oracle和MySQL中的分组(GROUP BY)有什么区别? 答案:Oracle对于GROUP BY是严格的,所有要SELECT出来的字段 ...
 - C# WPF DataGrid 分组(Group)
		
原文:C# WPF DataGrid 分组(Group) 效果如图,每个列的名字可以自定义.我随便用了”File”和”Attachment Name”. 在Window的Resources里面设置S ...
 - MySQL之单表查询    一 单表查询的语法     二 关键字的执行优先级(重点)     三 简单查询     四 WHERE约束     五 分组查询:GROUP BY     六 HAVING过滤     七 查询排序:ORDER BY     八 限制查询的记录数:LIMIT     九 使用正则表达式查询
		
MySQL之单表查询 阅读目录 一 单表查询的语法 二 关键字的执行优先级(重点) 三 简单查询 四 WHERE约束 五 分组查询:GROUP BY 六 HAVING过滤 七 查询排序:ORDER B ...
 - 分组函数group by和Oracle中分析函数partition by的用法以及区别
		
1.分组函数group by和Oracle中分析函数partition by的用法以及区别 2.开窗函数.
 - MySQL进阶5--分组函数 / 分组排序和分组查询 group by(having) /order by
		
MySQL进阶--分组排序和分组查询 group by(having) /order by /* 介绍分组函数 功能:用做统计使用,又称为聚合函数或组函数 1.分类: sum, avg 求和 /平均数 ...
 - MongoDB学习笔记~管道中的分组实现group+distinct
		
回到目录 mongoDB的管道是个好东西,它可以将很多操作批处理实现,即将多个命令放入一个管道,然后去顺序的执行它们,今天我要说的是,利用管道中的分组来实现实现中的ditinct+group的效果,即 ...
 - MySql分组函数-Group by与having理解
		
注意:select 后的字段,必须要么包含在group by中,要么包含在having 后的聚合函数里. 1. GROUP BY 是分组查询, 一般 GROUP BY 是和聚合函数配合使用 group ...
 - SQL --分组【Group By】
		
Group By:对数据进行分组,分组之后的数据就是“分组信息”,和原来表的信息,就没有联系了, 分组之后,可以取到分组数据,就是根据什么字段分组,就能取到字段的名字了.还能使用聚合函数. Group ...
 - 分组函数 ----group by  使用总结
		
总结:1,在where子句中不能用分组聚合函数. 2,如果没有group by 子句,select 不能同时出现字段与分组的聚合函数. 3,在有 group by 的子句的查询 ...
 
随机推荐
- Mysql索引失效的几种原因-mysql-suo-yin-shi-xiao-de-ji-zhong-yuan-yin
			
title: Mysql索引失效的几种原因 date: 2021-07-15 17:13:59.019 updated: 2021-12-26 17:43:12.489 url: https://ww ...
 - 【笔记】负载均衡Robbin之不同服务使用不同的策略
			
裂开裂开,搞这么久忘记导入依赖 妈卖批 又不报错 还能让我玩 我以为全部导入了. 话不多说,开始演示. 介绍 给不同的服务 配置 不同的 负载均衡策略 这里使用 用户模块 进行访问其它两个模块的con ...
 - Gh0st木马
			
https://www.secrss.com/articles/50209 Gh0st是一种远程控制软件,它可以在被攻击的计算机上运行并允许攻击者远程控制该计算机.为了查找Gh0st的进程.文件.注册 ...
 - st_geometry、st_transform配置及问题汇总
			
1.文件配置修改 路径上一定要使用双斜杠 2.使用sde账号登录,执行以下SQL,路径按照实际环境填写. select * from user_libraries; create or replace ...
 - 从源码分析 MySQL 身份验证插件的实现细节
			
最近在分析ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)这个报错的常见原因. 在 ...
 - 全量通过,华为云GaussDB首批完成信通院全密态数据库评测
			
摘要:100%全量通过!基于全栈创新计算架构的全密态数据库华为云GaussDB,完成了中国信通院组织的首批"全密态数据库"产品能力评测. 本文分享自华为云社区<全量通过!华为 ...
 - 细说Python Lambda函数的用法,建议收藏!
			
摘要:今天我就和大家聊聊lambda函数,在Python编程中,大家习惯将其称为表达式. 名称是用于引用或寻址任何实体的约定.我们周围的几乎所有事物都有名字.编程领域也与此一致.但这是必须命名的吗?还 ...
 - 鸿蒙轻内核源码分析:虚拟文件系统 VFS
			
本文分享自华为云社区<鸿蒙轻内核M核源码分析系列二一 01 虚拟文件系统VFS>,作者:zhushy . VFS(Virtual File System)是文件系统的虚拟层,它不是一个实际 ...
 - Vuex在TSX中的改造方案:TS改造Vue2项目Vuex如何处置?
			
vuex目前比较流行的有:vuex-aggregate . vuex-class.vuex-module-decorators npm搜到相关的,看下趋势图:https://www.npmtrends ...
 - Solon2 开发之IoC,一、注入或手动获取配置
			
约定 resources/app.yml( 或 app.properties ) #为应用配置文件 配置样例 track: name: xxx url: http://a.a.a db1: jdbcU ...