目 录CONTENT

文章目录
Go

04.跟着Geek兔兔学习-Go语言动手写Web框架-分组控制路由Group

Hello!你好!我是村望~!
2023-09-05 / 0 评论 / 0 点赞 / 306 阅读 / 1,895 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

分组控制路由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对象需要直接映射路由规则的话(分组实例内部也是调用engineaddRoute方法)

比如我们想在使用框架时,这么调用:

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()
}

演示了如何使用结构体嵌套来实现继承和方法覆盖的概念。:

  1. Animal 结构体定义了一个名为 Animal 的类型,表示动物,具有一个字段 name 表示动物的名字。
  2. move 方法是绑定到 Animal 结构体的方法,用于打印动物的移动动作。
  3. Dog 结构体定义了一个名为 Dog 的类型,它包括一个字段 Feet 表示脚的数量,以及通过嵌套匿名结构体 Animal 来实现继承。这意味着 Dog 类型拥有 Animal 类型的所有字段和方法。
  4. wang 方法是绑定到 Dog 结构体的方法,用于打印狗狗发出的声音。

main 函数中:

  • 创建了一个名为 d1Dog 实例,设置了它的 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% 
0

评论区