文档目录
美化路由匹配器

简介

本文件围绕前台外观 URL 的声明式路由匹配器 PrettyRouteMatcher,系统阐述其 SEO 友好 URL 的匹配算法、normalize 方法对语言前缀剥离与路径标准化的处理、HTTP 方法校验、从 route/*.php 加载并展开为 declared 条目的机制、优先级消歧策略、以及缓存与性能优化。同时给出自定义路由规则的编写方法与常见模式示例。

项目结构

  • 入口解析由 FrontResolver 发起,调用 PrettyRouteMatcher::normalize 完成入站匹配。
  • 匹配器基于 RouteManifest 中“前台命名空间”的 declared 条目进行匹配,这些条目由框架在启动期从 config/route.php 及业务端 front/route/*.php 声明聚合而来。
  • 正则编译统一交由 PrettyUrlCompiler,保证入站匹配与出站生成双向一致。
  • 短地址模块通过 ShortUrlPolicy 控制是否省略模块名段,并在未命中时补回前缀重试。
graph TB
A["前端请求<br/>FrontResolver"] --> B["PrettyRouteMatcher::normalize"]
B --> C["RouteManifest::getEntriesByType('declared')"]
C --> D["PrettyUrlCompiler::compileToRegex"]
B --> E["ShortUrlPolicy<br/>短地址策略"]
B --> F["matchRoutePatterns<br/>specificity 排序 + 方法过滤"]
F --> G["buildResult<br/>组装控制器/动作/参数"]
G --> H["返回 DispatchPlan"]

图表来源

  • FrontResolver.php:52-106
  • PrettyRouteMatcher.php:61-114
  • RouteManifest.php:78-88
  • PrettyUrlCompiler.php:58-69
  • ShortUrlPolicy.php:115-151

章节来源

  • FrontResolver.php:52-106
  • PrettyRouteMatcher.php:25-43

核心组件

  • PrettyRouteMatcher:前台外观 URL 的唯一匹配器,负责 normalize、匹配、优先级消歧、结果构建。
  • PrettyUrlCompiler:pattern → PCRE 的正则编译器(含可选段、占位符、内联正则),进程级缓存已编译正则。
  • RouteManifest:不可变可枚举的路由清单数据源,提供按类型过滤的 declared 条目。
  • ShortUrlPolicy:短地址模块开关与路径工具,支持拦截长格式、补前缀重试、去前缀生成。
  • FrontResolver:将匹配结果装配为分发计划,挂载中间件链,设置路由上下文。

章节来源

  • PrettyRouteMatcher.php:44-52
  • PrettyUrlCompiler.php:21-36
  • RouteManifest.php:21-37
  • ShortUrlPolicy.php:24-35
  • FrontResolver.php:32-42

架构总览

PrettyRouteMatcher 的匹配流程遵循“收集全部命中 → specificity 排序 → HTTP 方法过滤 → 构建结果”的统一算法,确保精确匹配优先于模糊匹配、静态段优先于动态段。

sequenceDiagram
participant R as "FrontResolver"
participant M as "PrettyRouteMatcher"
participant RM as "RouteManifest"
participant PC as "PrettyUrlCompiler"
participant SU as "ShortUrlPolicy"
R->>M : normalize(route, method)
M->>SU : isPrefixedRoute(route)?
alt 启用短地址且为长格式
M-->>R : 空白结果(不匹配)
else 非长格式或短地址未启用
M->>RM : getEntriesByType('declared')
loop 遍历 declared 条目
M->>PC : compileToRegex(pattern, params)
PC-->>M : 预编译正则
M->>M : preg_match + 提取具名捕获
M->>M : 计算 placeholders / literals / order
end
M->>M : usort by specificity
M->>M : 若传入 method 则按顺序首个接受者命中
M-->>R : buildResult(rule, captured)
end

图表来源

  • PrettyRouteMatcher.php:132-169
  • PrettyUrlCompiler.php:58-69
  • RouteManifest.php:78-88
  • ShortUrlPolicy.php:115-134

详细组件分析

PrettyRouteMatcher 类

  • 职责:对外暴露 normalize;内部维护预编译规则表;实现 matchRoutePatterns、compareSpecificity、ruleAcceptsMethod、buildResult 等。
  • 关键行为:
    • 空路径直接命中首页 IndexController。
    • 短地址模式下拒绝带模块前缀的长格式 URL。
    • 首次匹配失败后,若启用短地址,尝试补回模块前缀再匹配一次。
    • 使用 specificity 排序:占位符更少优先、字面段更多优先、声明顺序兜底。
    • 支持 HEAD 等价 GET 的方法归一化。
    • 构建结果时仅保留具名且非空的捕获组作为 params。
classDiagram
class PrettyRouteMatcher {
-routePatterns : array
+normalize(route, httpMethod) array
-matchRoutePatterns(path, httpMethod) array|null
-buildResult(rule, captured) array
-extractAllCaptures(matches) array
-loadRoutePatterns() array
+static ruleAcceptsMethod(rule, httpMethod) bool
+static compareSpecificity(a, b) int
+static placeholderCount(pattern) int
+static literalSegmentCount(pattern) int
}

图表来源

  • PrettyRouteMatcher.php:44-353

章节来源

  • PrettyRouteMatcher.php:61-114
  • PrettyRouteMatcher.php:132-169
  • PrettyRouteMatcher.php:178-210
  • PrettyRouteMatcher.php:254-293
  • PrettyRouteMatcher.php:324-353

normalize 方法详解

  • 输入:已去除语言前缀的 route 字符串与当前 HTTP 方法。
  • 步骤:
    1. 去首尾斜杠;空串短路到首页。
    2. 短地址模式拦截长格式(如 module/...)。
    3. 调用 matchRoutePatterns 进行匹配。
    4. 若未命中且启用短地址,尝试补回模块前缀再匹配一次。
    5. 否则返回空白结果(由 FrontResolver 派发 page_wrong)。
flowchart TD
S["开始"] --> T["trim('/')"]
T --> Z{"是否为空?"}
Z -- 是 --> H["返回首页(IndexController)"]
Z -- 否 --> P{"短地址启用且为长格式?"}
P -- 是 --> N["返回空白(不匹配)"]
P -- 否 --> M["matchRoutePatterns"]
M --> MH{"是否命中?"}
MH -- 是 --> R["buildResult -> 返回"]
MH -- 否 --> SU{"短地址启用?"}
SU -- 是 --> RP["prefixRoute 补前缀重试"]
RP --> RP2{"再次命中?"}
RP2 -- 是 --> R
RP2 -- 否 --> NB["返回空白(不匹配)"]
SU -- 否 --> NB

图表来源

  • PrettyRouteMatcher.php:61-114
  • ShortUrlPolicy.php:115-151

章节来源

  • PrettyRouteMatcher.php:61-114

匹配算法与优先级

  • 收集全部命中规则,避免“首条即返回”导致的歧义。
  • specificity 排序:
    • 占位符数量少者优先(更具体);
    • 字面段数量多者优先(静态段优先);
    • 声明顺序兜底(保持注册先后稳定)。
  • HTTP 方法过滤:
    • 若规则声明 methods,则仅当请求方法在集合内才视为命中;
    • HEAD 视作 GET;
    • 未传 method(诊断场景)则跳过方法过滤。
flowchart TD
A["遍历 declared 规则"] --> B{"preg_match 命中?"}
B -- 否 --> C["order++"]
B -- 是 --> D["记录 candidates<br/>placeholders/literals/order"]
D --> E{"还有规则?"}
E -- 是 --> A
E -- 否 --> F{"candidates 为空?"}
F -- 是 --> X["返回 null"]
F -- 否 --> G["usort by specificity"]
G --> H{"是否传入 method?"}
H -- 否 --> I["返回首条"]
H -- 是 --> J["按序检查 ruleAcceptsMethod"]
J --> K{"找到接受者?"}
K -- 是 --> L["返回该候选"]
K -- 否 --> X

图表来源

  • PrettyRouteMatcher.php:132-169
  • PrettyRouteMatcher.php:178-210

章节来源

  • PrettyRouteMatcher.php:132-169
  • PrettyRouteMatcher.php:178-210

正则编译与模式语法

  • 统一由 PrettyUrlCompiler 编译 pattern 为带具名捕获组的 PCRE,并缓存进程级结果。
  • 支持的语法:
    • {name}:默认子模式 [^/]+,可由 params[name] 覆盖;
    • {name:regex}:内联正则优先;
    • [ ... ]:可选段,编译为 (?:...)?,填充时仅在内部任一占位符有值时渲染。
  • 入站匹配与出站生成共用同一编译器,保证双向可逆。

章节来源

  • PrettyUrlCompiler.php:21-36
  • PrettyUrlCompiler.php:58-69
  • PrettyUrlCompiler.php:78-122
  • PrettyUrlCompiler.php:131-180

声明式路由配置加载

  • 数据来源:
    • 系统内置端点(llms.txt/sitemap/captcha/search/plugin/index 等)由构建器以 declared 形式注入;
    • 业务端点来自 front/route/*.php 中的声明(column/simple/page/group/get 等),经 StyleRuleExpander 按当前风格展开为具体 pattern。
  • 加载过程:
    • PrettyRouteMatcher::loadRoutePatterns 遍历 RouteManifest::getEntriesByType('declared');
    • 仅保留前台命名空间的条目;
    • 将 pattern 编译为 _regex,并附加 explicit* 字段(module/target/action/sub/controller/middleware 等)供后续直接使用。

章节来源

  • PrettyRouteMatcher.php:25-43
  • PrettyRouteMatcher.php:324-353
  • RouteManifest.php:21-37
  • route.php:15-31

短地址策略

  • 启用条件:site.short_url_module 配置项与对应 features.&lt;short> 开关均开启。
  • 行为:
    • 解析前拦截带模块前缀的长格式 URL;
    • 未命中时补回模块前缀再用同一套 declared 规则重试;
    • 生成 URL 时移除模块前缀,保证长短 URL 可逆。

章节来源

  • ShortUrlPolicy.php:24-35
  • ShortUrlPolicy.php:51-79
  • ShortUrlPolicy.php:115-151
  • PrettyRouteMatcher.php:91-111

与 FrontResolver 的协作

  • FrontResolver 获取 routeString 后调用 PrettyRouteMatcher::normalize,并将结果装配为 DispatchPlan。
  • 未命中时记录日志并返回 notFound;命中后设置路由上下文、合并参数、加载主题扩展、组装中间件链。

章节来源

  • FrontResolver.php:52-106
  • FrontResolver.php:159-199

依赖关系分析

  • PrettyRouteMatcher 依赖:
    • RouteManifest:读取 declared 条目;
    • PrettyUrlCompiler:编译 pattern 为正则;
    • ShortUrlPolicy:短地址策略判断与前缀处理。
  • FrontResolver 依赖 PrettyRouteMatcher 输出,并负责中间件与分发。
graph LR
FR["FrontResolver"] --> PRM["PrettyRouteMatcher"]
PRM --> RM["RouteManifest"]
PRM --> PC["PrettyUrlCompiler"]
PRM --> SU["ShortUrlPolicy"]

图表来源

  • FrontResolver.php:52-106
  • PrettyRouteMatcher.php:17-19

章节来源

  • FrontResolver.php:52-106
  • PrettyRouteMatcher.php:17-19

性能与缓存

  • 正则编译缓存:PrettyUrlCompiler 进程级缓存已编译的正则,避免重复编译开销。
  • 路由清单缓存:RouteManifest 首次构建后缓存 entries/nameIndex/ruleGroups,支持 clearCache 重置。
  • 短地址策略缓存:ShortUrlPolicy 缓存解析出的短地址模块名。
  • 匹配复杂度:O(N) 遍历 declared 条目,N 为前台 declared 条目数;排序开销受 candidates 规模影响,通常较小。
  • 建议:
    • 合理拆分路由文件,避免过度宽泛的 pattern;
    • 将高频访问的精确路由放在前面,减少排序比较;
    • 使用具名参数与内联正则限制匹配范围,降低回溯成本。

章节来源

  • PrettyUrlCompiler.php:38-49
  • RouteManifest.php:40-59
  • ShortUrlPolicy.php:38-49

故障排查指南

  • 未命中导致 404:
    • 检查 normalize 是否被短地址策略拦截(isPrefixedRoute);
    • 确认 declared 条目是否存在且属于前台命名空间;
    • 检查 pattern 与 params 是否正确,必要时查看编译后的正则。
  • 方法不匹配:
    • 确认规则是否声明了 methods;
    • HEAD 请求会被视作 GET;
    • 诊断时可传入 null 方法跳过方法过滤。
  • 短地址冲突:
    • 启用短地址后,禁止使用长格式 URL;
    • 若出现歧义,调整 pattern 或关闭短地址。
  • 调试手段:
    • 使用 permissive 调用(不传 method)定位首条命中;
    • 打印 candidates 的 placeholders/literals 观察排序依据。

章节来源

  • FrontResolver.php:58-67
  • PrettyRouteMatcher.php:178-189
  • PrettyRouteMatcher.php:91-114

结论

PrettyRouteMatcher 以声明式为核心,结合统一的正则编译器与短地址策略,实现了高确定性、高性能的前台 URL 匹配。其 specificity 排序与方法过滤确保了精确优先、静态优先的语义一致性;配合 RouteManifest 与 FrontResolver,形成完整的入站解析链路。通过合理的 pattern 设计与缓存利用,可在大规模路由下保持稳定性能。

附录:自定义路由规则编写指南

  • 基本语法
    • {name}:占位符,默认匹配非斜杠字符;
    • {name:regex}:内联正则,优先于 params[name];
    • [ ... ]:可选段,内部任一占位符有值时才渲染/匹配。
  • 常见模式示例
    • 单页详情:{slug}.html 或 page/{slug};
    • 栏目列表与详情:{module}/collections/{category_slug} 与 {module}/{slug};
    • 归档日期:{year:\d{4}}/{month:\d{2}}/{id:\d+};
    • 简单模块操作:{module}/{action}/{sub_action} 与 {module}/{action}。
  • 优先级建议
    • 将更具体的规则写在前面(例如带 slug 的详情优先于通用 ID 详情);
    • 使用具名参数与内联正则缩小匹配范围;
    • 合理使用可选段减少歧义。
  • 短地址注意事项
    • 启用短地址后,URL 不应包含模块前缀;
    • 如需兼容旧链接,可利用补前缀重试逻辑,但需确保 pattern 无歧义。

章节来源

  • route.php:38-355
  • PrettyUrlCompiler.php:21-36
  • PrettyRouteMatcher.php:116-126
添加日期:2026-10-05