分组控制路由Group
感谢Geek 兔的教程
示例代码,解释摘抄 https://geektutu.com/ + 结合自己的理解
为什么需要分组
分组控制(Group Control)是 Web 框架应提供的基础功能之一。
所谓分组,是指路由的分组。如果没有路由分组,我们需要针对每一个路由进行控制。
但是真实的业务场景中,往往某一组路由需要相似的处理。
例如:
- 以
/post
开头的路由匿名可访问。 - 以
/admin
开头的路由需要鉴权。 - 以
/api
开头的路由是 RESTful 接口,可以对接第三方平台,需要三方平台鉴权。
大部分情况下的路由分组,是以相同的前缀来区分的。
因此,我们今天实现的分组控制也是以前缀来区分,并且支持分组的嵌套。
例如/post
是一个分组,/post/a
和/post/b
可以是该分组下的子分组。
作用在/post
分组上的中间件(middleware),也都会作用在子分组,子分组还可以应用自己特有的中间件。
中间件可以给框架提供无限的扩展能力,应用在分组上,可以使得分组控制的收益更为明显,而不是共享相同的路由前缀这么简单。
例如/admin
的分组,可以应用鉴权中间件;/
分组应用日志中间件,/
是默认的最顶层的分组,也就意味着给所有的路由,即整个框架增加了记录日志的能力。
这里我们先实现这个分组路由的功能!中间件本质上也是一个handleFunc 我们留一个空间给他,具体的中间件调用方式,后面再去看!
分析如何分组
一个 Group 对象需要具备哪些属性呢?
首先是前缀(prefix),比如/
,或者/api
;要支持分组嵌套,那么需要知道当前分组的父亲(parent
)是谁;
按照我们一开始的分析,中间件是应用在分组上的,那还需要存储应用在该分组上的中间件(middlewares)。
还记得,我们之前调用函数(*Engine).addRoute()
来映射所有的路由规则和 Handler 。如果Group对象需要直接映射路由规则的话(分组实例内部也是调用engine
的addRoute
方法)
比如我们想在使用框架时,这么调用:
r := gee.New()
v1 := r.Group("/v1")
v1.GET("/", func(c *gee.Context) {
c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
})
这和gin
的使用是一样的!
所以这里看到Group对象,需要有访问
Router
的能力,那们可以在Group中,保存一个指针,指向Engine
,
整个框架的所有资源都是由Engine
统一协调的,那么就可以通过Engine
间接地访问各种接口了。
所以,最后的 Group 的定义是这样的:
- 前缀
prefix
- 支持嵌套
parent
- 支持中间件
middles
- 访问
Router
的能力engine
type RouterGroup struct {
prefix string
middlewares []HandlerFunc // support middleware
parent *RouterGroup // support nesting
engine *EngineV2 // all groups share a EngineV2 instance
}
我们还可以进一步地抽象,将Engine
作为最顶层的分组,也就是说Engine
拥有RouterGroup
所有的能力
type EngineV2 struct {
*RouterGroup
router *TrieRouter
groups []*RouterGroup // store all groups
}
那我们就可以将和路由有关的函数,都交给RouterGroup
实现了。
这里因为太久没有写Go语言的东西,需要做个简单的复习——关于结构体嵌套
package main
import "fmt"
//Animal 动物
type Animal struct {
name string
}
func (a *Animal) move() {
fmt.Printf("%s会动!\n", a.name)
}
//Dog 狗
type Dog struct {
Feet int8
Animal // 通过嵌套匿名结构体实现继承
}
func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪~\n", d.name)
}
func main() {
d1 := Dog{
Feet: 4,
Animal: Animal{
name: "可乐",
},
}
d1.wang()
d1.move()
}
演示了如何使用结构体嵌套来实现继承和方法覆盖的概念。:
Animal
结构体定义了一个名为Animal
的类型,表示动物,具有一个字段name
表示动物的名字。move
方法是绑定到Animal
结构体的方法,用于打印动物的移动动作。Dog
结构体定义了一个名为Dog
的类型,它包括一个字段Feet
表示脚的数量,以及通过嵌套匿名结构体Animal
来实现继承。这意味着Dog
类型拥有Animal
类型的所有字段和方法。wang
方法是绑定到Dog
结构体的方法,用于打印狗狗发出的声音。
在 main
函数中:
- 创建了一个名为
d1
的Dog
实例,设置了它的Feet
为 4,并创建了一个嵌套的Animal
实例,给它起名为 “可乐”。 - 调用
d1.wang()
方法,打印出 “可乐会汪汪汪~”,这是Dog
类型的方法。 - 调用
d1.move()
方法,打印出 “可乐会动!”,这是Animal
类型的方法,但由于Dog
嵌套了Animal
,它也可以访问和调用Animal
的方法。
RouterGroup
实现
func NewV2() *EngineV2 {
engine := &EngineV2{router: newTrieRouter()}
engine.RouterGroup = &RouterGroup{engine: engine} // 初始化RouterGroup
engine.groups = []*RouterGroup{engine.RouterGroup} // 初始化groups切片
return engine
}
Group 方法,用于添加Group~
func (group *RouterGroup) Group(prefix string) *RouterGroup {
engine := group.engine // 整个路由引擎的实例
newGroup := &RouterGroup{
prefix: group.prefix + prefix, // 新子组的前缀是当前组的前缀与传入的 prefix 字符串拼接的。
parent: group, // 新子组的父组是当前组 (group),这用于建立路由组的嵌套关系。
engine: engine,// 新子组的引擎属性与当前组相同,这将确保它们在同一个引擎下运行。
}
// 新创建的子路由组 newGroup 添加到引擎 (engine) 的 groups 切片中
engine.groups = append(engine.groups, newGroup)
// 最后,该方法返回新创建的子路由组 newGroup 的指针,以便在需要时可以进一步配置和使用它。
// 比如返回的示例做链式调用
return newGroup
}
RouterGroup 的实例也需要允许直接添加路由!
func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) {
// 路由组的前缀 (group.prefix) 和组件 (comp) 拼接在一起,构建完整的路由模式 (pattern)。
pattern := group.prefix + comp
log.Printf("Route %4s - %s", method, pattern)
// 将路由规则添加到底层的路由器中,以便后续请求可以匹配和处理。
group.engine.router.addRoute(method, pattern, handler)
}
// GET defines the method to add GET request
func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
group.addRoute("GET", pattern, handler)
}
// POST defines the method to add POST request
func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
group.addRoute("POST", pattern, handler)
}
测试demo
package main
import (
"fmt"
"gee"
"net/http"
)
func main() {
r := gee.NewV2()
tourister := r.Group("/tourister")
touristerV1 := tourister.Group("/v1")
touristerV1.GET("/post/:id", func(ctx *gee.Context) {
ctx.String(http.StatusOK, fmt.Sprintf("游客阅读Post%s", ctx.Param("id")))
})
admin := r.Group("/admin")
adminV1 := admin.Group("/v1")
adminV1.GET("/post/:id", func(ctx *gee.Context) {
ctx.String(http.StatusOK, fmt.Sprintf("Admin阅读Post%s", ctx.Param("id")))
})
r.Run(":9999")
}
❯ curl http://127.0.0.1:9999/tourister/v1/post/1
游客阅读Post1%
❯ curl http://127.0.0.1:9999/admin/v1/post/1
Admin阅读Post1%
评论区