Mygin实现分组路由Group

这篇具有很好参考价值的文章主要介绍了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定义的,再返回路由定义的。文章来源地址https://www.toymoban.com/news/detail-820725.html

到了这里,关于Mygin实现分组路由Group的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • LVGL core group焦点分组管理(lv_group.c)

    更多源码分析请访问: LVGL 源码分析大全

    2024年02月09日
    浏览(47)
  • group by进行分组时查询数据

    group by进行分组时查询数据需要注意:  select后的字段: 要么就要包含在Group By语句的后面,作为分组的依据;        要么就要被包含在聚合函数中。 错误: SELECT name, salary FROM student GROUP BY name        select 后的字段 salary 不在 group by 后面,所以salary无法显示全部值。 正确

    2024年02月11日
    浏览(33)
  • MySQL查询分组Group By原理分析

    日常开发中,我们经常会使用到group by: 你是否知道group by的工作原理呢? group by和having有什么区别呢? group by的优化思路是怎样的呢? 使用group by有哪些需要注意的问题呢? 使用group by的简单例子 group by 工作原理 group by + where 和 having的区别 group by 优化思路 group by 使用注意

    2023年04月16日
    浏览(49)
  • 👾 笔记 | react-transition-group 实现路由切换过渡动画

    React 18 React router v6 React Transition Group 是一个 React 库,专门用于在 React 应用中管理和处理过渡动画效果。这个库提供了一组组件,包括 Transition、CSSTransition、SwitchTransition 和 TransitionGroup,帮助在组件的进入和退出时应用动画效果。 Transition 是一个与平台无关的组件,通常结合

    2024年03月09日
    浏览(69)
  • elasticsearch的group by分组和sum求和

    最近在使用es查询某个字段在特定查询条件下的某个字段的求和时,忘记了query语句是怎么写的,简单记录一下,方便自己和他人查阅。 一 什么是elasticsearch? elasticsearch是一个分布式的使用 REST 接口的搜索引擎,简称为ES,它是面向文档的,可以存储整个对象或文档。 二:

    2024年02月12日
    浏览(41)
  • ES聚合分页(group by分组后分页)

    案例:统计业务应用流量数据

    2024年02月11日
    浏览(55)
  • Sql group by 分组取时间最新的一条数据

    1.取时间最新的记录 不分组有重复(多条CreateTime一样的都是最新记录) 2.分组后取时间最新的记录 3.如果Id是uuid类型无法使用max(id)的解决办法(使用开窗函数)

    2024年02月11日
    浏览(55)
  • 【postgresql 基础入门】分组查询 group by 子句的写法,分组条件过滤having子句的写法,多列的分组以及与join联合的多表分组

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 个人主页 :我的主页 管理社区 :开源数据库 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. 本文主要分享在postgresql 数据库中对查询结果进行分组group by,以及对分组进行条件过滤having,同时对它

    2024年04月11日
    浏览(50)
  • MySQL修炼手册4:分组与聚合:GROUP BY与HAVING的应用

    MySQL数据库的强大功能为我们提供了丰富的数据处理工具,其中GROUP BY与HAVING的应用使得数据的分组与聚合变得更加灵活和高效。在本篇博客中,我们将深入研究GROUP BY与HAVING的基础知识,并通过实际案例,展示它们在数据分析中的强大威力。 首先,为了更好地演示GROUP BY与

    2024年02月01日
    浏览(93)
  • Mysql 合并多个分组。GROUP_CONCAT 函数 及其平替函数

    GROUP_CONCAT 函数用于将一个分组内的多行数据合并成一个字符串,并以指定的分隔符进行分隔。常用于需要将一个分组内的多条数据以字符串的形式展示的情况。语法如下: GROUP_CONCAT(expr [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [, expr ...]] [SEPARATOR str_val]) 其中,expr 表示要

    2024年02月02日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包