ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

beego路由树Tree

beego路由树Tree 吃透tree.go:数据结构、建树算法 addseg、匹配算法 match、参数解析 splitSegment。1. 数据结构(tree.go:32-41)type Tree struct { prefix string // 当前节点的静态段,如 user fixrouters []*Tree // 静态子节点(按序优先匹配) wildcard *Tree // 参数子节点(:id 这类,整棵树只有一个 wildcard 通道) leaves []*leafInfo // 本节点终结的路由 } type leafInfo struct { // tree.go:387-395 wildcards []string // 参数名列表,如 [:id] regexps *regexp.Regexp // 整条路由编译出的完整正则(可空) runObject interface{} // *ControllerInfo(路由对象) }设计要点:三层匹配顺序:fixrouters(精确)→ wildcard(参数)→ leaves(吞剩余段)每个ControllerRegister.routers[method]是一棵*Tree;HTTP method 各自独立leaves 是头插:addseg里leaves append([]*leafInfo{leaf}, leaves...)(tree.go:210-213)→同前缀后注册的优先匹配2. splitSegment:单段解析的状态机(tree.go:482-588)输入一段(两个/之间),输出 (isParam, wildcards, regex):输入段wildcardsregexadmin--:id[:id]-?:id[:id](可空)-:id:int[:id][0-9]:name:string[:name][\w]:id(\d{4})[:id]\d{4}*[:splat]-*.*[:path :ext]-实现是逐字符状态机,处理:、(、)、?、\转义。源码注释即完整规则表,阅读时对照 tree_test.go 的 TestSplitSegment。3. 建树:addseg(tree.go:207-281)注册/api/user/:id/comments的递归过程:addseg(segments[api,user,:id,comments], wildcard[], reg) 1. 段 api 是静态: 在 fixrouters 找 prefixapi 的子树,没有则建 → 递归 addseg([user,:id,comments]) 2. user 静态 → 同上递归 3. :id 是参数: wildcard 子树不存在则建(w.prefix:id) wildcards 累加 [:id],reg 累加 [^/]? → 递归 addseg([comments], [:id], …) 4. segments 空:构造 leaf{wildcards,regexps,runObject},头插进当前节点 leaves关键代码形态:if len(segments) 0 { // 终止:挂叶子 leaf : leafInfo{wildcards, regexp.MustCompile(reg), runObject} p.leaves append([]*leafInfo{leaf}, p.leaves...) // 头插! }参数段的处理(tree.go:238-262 附近):?:id可空时会同时尝试跳过该段的路径(构建双路径)。AddTree / addtree(合并,tree.go:50-159)Namespace 合并用:把子树所有 leaves带前缀重新 addseg进父树;filterTreeWithPrefix(161-198)把前缀上的 wildcards/regexps 合并进每个 leaf——beego 没有真正的子树挂接,合并即重建。4. 匹配:Match → match(tree.go:284-385)func (t *Tree) Match(url string) (runObject, params) { url path.Clean(url) // 规整 //、. 等 return t.match(url, nil, nil, nil) }递归 match 的每一层,对当前段:① 遍历 fixrouters:prefix 精确等于段 → 进入子树递归 ② 失败且段带 .json/.xml/.html 后缀: 剥后缀再试 fixrouters(343-358),命中则 params[:ext]后缀 ③ 失败 → 若 wildcard 非空: 段值入 wildcardValues,进 wildcard 子树(359-361) ④ 子树都失败 → 尝试本层 leaves: 剩余所有段并入 wildcardValues(363-383) 对每个 leaf 调 leafInfo.match 验证leafInfo.match(tree.go:397-457)分两类:无 regexps(纯:id/:splat):按 wildcards 顺序把值ctx.Input.SetParam(name, val)写回。:splat特殊:匹配多段时自动展开:splat0,:splat1…(router.go:1124-1128 处理)。有 regexps(如:id:int):regexps.MatchString(path.Join(wildcardValues...));命中后FindStringSubmatch取捕获组回填。不命中则该 leaf 失败,回到上层试下一个 leaf / 回溯。5. 匹配示例走读注册:web.Router(/api/:id:int, A{}) // leaf1: regex ^[0-9]$ web.Router(/api/:name, B{}) // leaf2 web.Router(/api/list, C{})树结构:root ── api(fixrouter) ├─ list(fixrouter)→ leaf: C ├─ wildcard(:id) │ └─ leaves: [leaf2(:name 后注册在前?), leaf1(:id:int)] │ (实际按注册顺序头插:若先注册 leaf1 后注册 leaf2,leaves[leaf2, leaf1])请求/api/123:root 层段 api → fixrouters 命中,进子树子树段 123:list 不等;无后缀;进 wildcard,wildcardValues[123]wildcard 子树无 fixrouters,剩余段空,试 leaves:leaf2(无 regex):SetParam(:name,123)→ 返回 B ✓(头插优先)若 leaf2 不存在,leaf1 的^[0-9]$命中 → A请求/api/list:api → 子树list 精确命中 → C(静态永远优先于参数)请求/api/list.json:list 不等(整段是 list.json)剥.json后缀 → list 命中,:ext json → C6. 性能与局限静态路由:逐层 map 无,但 fixrouters 是 slice 线性扫——子路由多时 O(段数 × 平均子节点数);一般应用 (100 路由) 足够参数路由:无前缀树压缩(radix),与 httprouter 等相比是朴素实现回溯成本:fixrouters 失败后剥后缀、wildcard、leaves 层层兜底,最坏复杂度偏高,但日常路由深度浅优化技巧(源码可学):RouterCaseSensitivefalse 时注册即 lowercase(addToRouter:320-331),匹配路径也 lowercase,大小写不敏感零开销7. 与 tree_test.go 对读推荐阅读的用例:TestSplitTrailingColon、TestTreeReg::id([0-9])捕获组TestAddPrefix:AddTree 前缀合并TestPreFixRoute:静态优先级TestWildcard:*splat展开行为测试即规范,改动 tree.go 必须全绿这些用例。
返回列表