GoWeb之路由通配符匹配(四)

一、通配符匹配

书接上回,我们已经成功实现了如何进行静态匹配,主要就是不断循环直到我们的子节点为nil,就停止。

那么有了之前的基础,我们来学习一下如何实现通配符匹配。

所谓通配符匹配就是 当我注册 /user/* 的时候,如果我们网页输入 /user/adc 就会帮我们命中通配符匹配。所以,我们的路由首先会优先进行静态匹配,然后匹配不到了,再进行通配符匹配,这就要求我们需要在 route 里面多添加一个用于通配符匹配的字段。

二、代码实现

话不多说直接上代码,该代码是在上一期静态匹配的基础上进行修改,可以说会了静态匹配,通配符也是手到擒来,如若不熟练,可以去上一期学习

package serv

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"strings"
)

type router struct {
	trees map[string]*node
}

type node struct {
	path string

	children map[string]*node

	// 带通配符匹配的
	startChild *node

	handler HandleFunc
}

func newRouter() *router {
	return &router{
		trees: map[string]*node{},
	}
}

func (r *router) addRoute(method string, path string, handler HandleFunc) {
	if path == "" {
		panic("路径不能为空")
	}

	gin.Default()
	if path[0] != '/' {
		panic("路径只能以 / 开头")
	}

	if path != "/" && path[len(path)-1] == '/' {
		panic("路径不能以 / 结尾")
	}

	root, ok := r.trees[method]
	if !ok {
		// 不ok说明没有根节点,所以我们新建一个
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}

	if path == "/" {
		// 根节点重复注册
		if root.handler != nil {
			panic(fmt.Sprintf("路径 [%s] 重复注册", path))
		}
		root.handler = handler
		return
	}

	// 因为我们的path是 “/xxx/xxx”,这样第一个/会切割出一个空字符串
	// 所以我们需要刨除第一个 /
	path = path[1:]
	segs := strings.Split(path, "/")
	for _, seg := range segs {
		// 防止中间出现多个 /
		if seg == "" {
			panic("路径不能出现连续多个 / ")
		}
		child := root.childOrCreate(seg)
		root = child
	}

	// 普通路径重复注册,主要判断依据就是,这个路径下是否依旧存在handlerFunc了
	if root.handler != nil {
		panic(fmt.Sprintf("路径 [%s] 重复注册", path))
	}
	root.handler = handler
}

func (r *node) childOrCreate(seg string) *node {
	if seg == "*" {
		r.startChild = &node{
			path: seg,
		}
		return r.startChild
	}
	if r.children == nil {
		// 如果children为nil的话,下面的逻辑将会崩溃,所以需要建立一个新的node
		r.children = map[string]*node{}
	}
	res, ok := r.children[seg]
	if !ok {
		res = &node{
			path: seg,
		}
		r.children[seg] = res
	}
	return res
}

func (r *router) findRoute(method string, path string) (*node, bool) {
	// 查找是否有该请求方法,有的话就拿到整条链路
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}

	if path == "/" {
		return root, true
	}

	// 前面要求 path必须是 /xxx/xxx
	// 把前置和后置的斜杠都去掉
	path = strings.Trim(path, "/")
	segs := strings.Split(path, "/")
	for _, seg := range segs {
		// 查找的过程
		c, found := root.childOf(seg)
		if !found {
			return nil, false
		}
		// 不断深入查找
		root = c
	}
	return root, true
}

func (n *node) childOf(path string) (*node, bool) {
	if n.children == nil {
		return n.startChild, n.startChild != nil
	}
	child, ok := n.children[path]
	if !ok {
		return n.startChild, n.startChild != nil
	}
	return child, ok
}

主要的两个关键点在于:

  • addRoute的时候,在 childOfCreate 中追加一个对于通配符匹配的校验
  • findRoute的时候,在 childOf 中也同样追加一个,因为我们是如果子节点的children 不存在,则有可能是通配符的 startChild 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值