文档目录
前台路由解析器

简介

FrontResolver 是前台请求的解析入口,负责将“外观 URL”映射到控制器方法,生成可执行的 DispatchPlan,并组装中间件链、装配表单目标、加载主题扩展。其设计遵循纯声明式路由:所有端点由 front/route/*.php 与系统内置端点集中声明,匹配器基于 RouteManifest 预编译的正则进行高效匹配,最终产出不可变的分发计划交由中央调度器执行。

项目结构

与 FrontResolver 相关的前台路由子系统位于 front/foundation/routing 下,包含解析器、匹配器与薄壳路由器;同时依赖 core/web/routing 中的通用分发计划与调度能力,以及 front/service/init 下的主题扩展加载器。

graph TB
A["前端入口 Router<br/>dispatch()"] --> B["FrontResolver::resolve()"]
B --> C["PrettyRouteMatcher::normalize()"]
C --> D["RouteManifest<br/>declared 条目"]
B --> E["MiddlewareRegistry<br/>composeFromSpec()"]
B --> F["Request 写入 route/params/baseUrl"]
B --> G["View::assign('cur', module)"]
B --> H["assignFormTarget()<br/>create/edit 自动装配"]
B --> I["loadThemeExtension()<br/>ThemeExtensionLoader"]
B --> J["DispatchPlan 返回"]
A --> K["Dispatcher::run(plan, container)"]

核心组件

  • FrontResolver:前台解析器,协调匹配、中间件、视图与主题扩展,输出 DispatchPlan。
  • PrettyRouteMatcher:基于 RouteManifest 的 declared 规则进行外观 URL 匹配,支持短地址策略与方法过滤。
  • Router:前台薄壳调度器,调用 FrontResolver 并处理未匹配时的 404 渲染。
  • DispatchPlan:不可变分发计划,承载控制器、方法、参数与中间件列表。
  • ThemeExtensionLoader:在控制器执行前按需加载主题扩展脚本。

架构总览

FrontResolver 的工作流如下:

  1. 从 Request 获取已剥离语言前缀的路由字符串。
  2. 调用 PrettyRouteMatcher 进行匹配,得到模块、动作、子段、控制器 FQCN、路径参数及路由级中间件细化信息。
  3. 校验控制器类存在性,必要时通过 Module 门控(如会员模块开关)。
  4. 组装中间件链:默认栈 + 路由级豁免/追加/参数。
  5. 设置 Request 基础信息与路由上下文,向视图注入当前模块,为 create/edit 自动装配表单目标,加载主题扩展。
  6. 构建并返回 DispatchPlan,交由 Dispatcher 执行。
sequenceDiagram
participant R as "Router"
participant FR as "FrontResolver"
participant M as "PrettyRouteMatcher"
participant MR as "MethodResolver"
participant MW as "MiddlewareRegistry"
participant DP as "DispatchPlan"
participant D as "Dispatcher"
R->>FR : resolve(request, container)
FR->>M : normalize(route, method)
M-->>FR : {module, action, sub, controller, params, mw_*}
FR->>MW : composeFromSpec(defaultAliases, skip/without/append/params)
MW-->>FR : middlewares[]
FR->>FR : setBaseUrl/setRoute/setRouteParams/mergeRouteInputs
FR->>FR : assignFormTarget(create/edit)
FR->>FR : loadThemeExtension(module, action)
FR->>MR : resolve(action, fqcn)
FR-->>R : new DispatchPlan(fqcn, method, params, middlewares)
R->>D : run(plan, container)

详细组件分析

FrontResolver 解析流程

  • 输入:Request(已剥离语言前缀)、Container。
  • 匹配:委托 PrettyRouteMatcher.normalize,返回标准化结果(含模块、动作、子段、控制器 FQCN、参数与路由级中间件配置)。
  • 安全门控:对会员衍生模块进行可用性断言,避免在未启用时反射 user 模块类。
  • 中间件:通过 MiddlewareRegistry 以别名方式组合默认栈与路由级细化。
  • 上下文:设置 baseUrl、route/module/action/sub、路由参数并入请求输入;向视图注入 cur;为 create/edit 装配 form_action/form_method;加载主题扩展。
  • 输出:DispatchPlan(fqcn、method、params、middlewares)。
flowchart TD
Start(["进入 FrontResolver::resolve"]) --> GetRoute["读取 request.routeString()"]
GetRoute --> Match["PrettyRouteMatcher::normalize()"]
Match --> |未匹配| NotFound["记录日志并返回 notFound()"]
Match --> |命中| Extract["提取 module/action/sub/params/controller"]
Extract --> Validate{"controller 存在?"}
Validate --> |否| NotFound
Validate --> |是| Gate["Module::assertUserAvailable()"]
Gate --> Compose["composeMiddlewares()"]
Compose --> Context["设置 baseUrl / route / params / merge inputs"]
Context --> ViewAssign["View::assign('cur', module)"]
ViewAssign --> FormTarget{"action 为 create/edit ?"}
FormTarget --> |是| AssignForm["assignFormTarget()"]
FormTarget --> |否| ThemeLoad
AssignForm --> ThemeLoad["loadThemeExtension()"]
ThemeLoad --> MethodRes["MethodResolver::resolve()"]
MethodRes --> Plan["new DispatchPlan(...)"]
Plan --> End(["返回"])

路由匹配机制(PrettyRouteMatcher)

  • 数据来源:仅消费 RouteManifest 中前台命名空间的 declared 条目(系统内置端点与业务路由)。
  • 匹配算法:
    • 收集全部正则命中的候选规则。
    • 按 specificity 排序:占位符少优先 → 字面段多优先 → 声明顺序兜底。
    • 若传入 HTTP 方法,则遍历候选首个方法命中即返回;否则 permissive 模式返回首条。
  • 短地址策略:启用后禁止带模块前缀的长格式;未命中时尝试补回模块前缀再匹配,保证短链与完整 pattern 可逆。
  • 结果结构:包含 matched/name/module/target/controller/is_detail/route_type/action/sub/params 以及路由级中间件字段 mw_append/mw_without/mw_params/mw_skip_all。
flowchart TD
S["normalize(route, httpMethod)"] --> Trim["trim('/')"]
Trim --> Blank{"route == '' ?"}
Blank --> |是| Home["返回 index 短路结果"]
Blank --> |否| ShortCheck{"ShortUrlPolicy.isPrefixedRoute(route) ?"}
ShortCheck --> |是| Reject["返回空匹配"]
ShortCheck --> |否| MatchAll["matchRoutePatterns() 收集候选"]
MatchAll --> Sort["specificity 排序"]
Sort --> MethodFilter{"httpMethod 提供?"}
MethodFilter --> |是| FirstAccept["首个方法命中即返回"]
MethodFilter --> |否| FirstRule["返回首条候选"]
FirstAccept --> Build["buildResult()"]
FirstRule --> Build
Build --> Return["返回标准化结果"]

中间件链组装逻辑

  • 默认栈顺序(安全优先):安全头 → 可信代理 → 限流 → [可选] 用户认证 → CSRF。
  • 条件入栈:当 features.user 开启时加入 user_auth;若对应中间件类缺失,由 MiddlewareRegistry 吞掉跳过,不致命。
  • 路由级细化:通过命中条目的 mw_* 字段控制豁免(mw_without)、追加(mw_append)、参数(mw_params)或整体跳过(mw_skip_all)。
  • 实现要点:FrontResolver 内部维护别名映射,调用 MiddlewareRegistry::composeFromSpec 完成组合。
classDiagram
class FrontResolver {
+static resolve(request, container)
-static composeMiddlewares(container, result)
}
class MiddlewareRegistry {
+composeFromSpec(defaultAliases, skipAll, without, append, params) array
}
class Aliases {
"security_headers"
"trust_proxy"
"throttle"
"user_auth"
"csrf"
}
FrontResolver --> MiddlewareRegistry : "组合默认栈+路由级细化"
FrontResolver --> Aliases : "别名映射"

表单目标自动装配(create/edit)

  • 触发条件:命中动作名为 create 或 edit,且路由名形如 module.sub.action(资源条目)。
  • 行为:
    • create:form_action 指向 base.store(POST),form_method 为空(等价原生 POST)。
    • edit:form_action 指向 base.update(PUT,携带成员 id 参数),form_method 为 PUT。
  • 原理:从命中结果中提取 name,截取 base = name 去掉末段动作,再根据动作选择 store/update 并生成 URL;模板统一通过 {$form_action} 与 {$form_method} 使用。
flowchart TD
Enter["assignFormTarget(action, result, params)"] --> Check{"action in ['create','edit'] ?"}
Check --> |否| Exit["直接返回"]
Check --> |是| Name["取 result.name"]
Name --> Dot{"name 含 '.' ?"}
Dot --> |否| Exit
Dot --> |是| Base["base = name 去掉末段动作"]
Base --> Create{"action == 'create' ?"}
Create --> |是| SetCreate["form_action=route(base+'.store')<br/>form_method=''"]
Create --> |否| SetEdit["form_action=route(base+'.update', params)<br/>form_method='PUT'"]
SetCreate --> Exit
SetEdit --> Exit

主题扩展加载机制

  • 时机:在控制器执行之前,已知 routeModule/routeAction 时调用。
  • 行为:构造 ThemeExtensionLoader,调用 loadForRoute(module, action)。
  • 加载逻辑:检查视图引擎是否可用,定位模板目录下的 inc/..from_theme.php,校验安全性后通过 Portal::boot 注入当前路由上下文并 include。
  • 作用:主题脚本可根据当前路由分支决定注入模板变量,避免全量赋值。
sequenceDiagram
participant FR as "FrontResolver"
participant TEL as "ThemeExtensionLoader"
participant PV as "Portal"
participant FS as "文件系统"
FR->>TEL : loadForRoute(module, action)
TEL->>TEL : 检查视图引擎可用
TEL->>FS : 检测 inc/..from_theme.php
FS-->>TEL : 存在且安全
TEL->>PV : boot(module, action, engine)
TEL->>FS : include_once(..from_theme.php)

错误处理策略

  • 未匹配:记录警告日志(包含 channel、route、lang、is_home),返回 DispatchPlan::notFound(),由 Router 渲染 page_wrong。
  • 控制器不存在:记录警告日志(包含 module/action/sub/controller),返回 notFound()。
  • 方法不允许:当前前台未显式返回 405,URL 命中但方法不被接受时按未命中处理(交给 FrontResolver 派发 page_wrong)。
  • 安全门控:Member 模块关闭时对 user 模块下发类进行断言,避免容器反射导致异常。

依赖关系分析

  • FrontResolver 依赖:
    • PrettyRouteMatcher:负责 URL→声明式规则的匹配。
    • MiddlewareRegistry:组合默认栈与路由级中间件。
    • MethodResolver:根据 action 解析控制器方法。
    • ThemeExtensionLoader:加载主题扩展。
    • Request/View:写入路由上下文与视图变量。
    • DispatchPlan:封装最终执行计划。
  • Router 依赖 FrontResolver 与 Dispatcher,作为薄壳协调入口。
  • PrettyRouteMatcher 依赖 RouteManifest、PrettyUrlCompiler、ShortUrlPolicy。
graph LR
FR["FrontResolver"] --> PRM["PrettyRouteMatcher"]
FR --> MW["MiddlewareRegistry"]
FR --> MR["MethodResolver"]
FR --> TEL["ThemeExtensionLoader"]
FR --> REQ["Request"]
FR --> VIEW["View"]
FR --> DP["DispatchPlan"]
RT["Router"] --> FR
RT --> DSP["Dispatcher"]

性能考量

  • 匹配阶段:
    • 预编译正则:每条 declared 规则在启动期编译为 _regex,减少运行时开销。
    • 候选收集与排序:仅对命中的候选进行 specificity 排序,避免全表扫描代价过高。
    • 短地址策略:启用后拦截带前缀长格式,减少无效匹配。
  • 中间件组合:
    • 默认栈固定且轻量,路由级细化仅在命中条目上生效,避免全局额外负担。
  • 主题扩展:
    • 仅在控制器执行前按需加载,且具备文件存在性与安全性检查,避免不必要 IO。
  • 建议:
    • 保持 declared 规则简洁明确,减少复杂正则与深层嵌套。
    • 合理使用 mw_without/mw_append 控制中间件数量,避免过深管道。
    • 关注 features.user 开关对中间件栈的影响,确保最小必要中间件集。

故障排查指南

  • 404 页面频繁出现:
    • 检查路由字符串是否正确(已剥离语言前缀)。
    • 确认 declared 规则是否存在且 methods 白名单包含当前 HTTP 方法。
    • 查看日志通道 route 的警告信息,核对 route/lang/is_home。
  • 控制器无法解析:
    • 确认 FQCN 正确且类存在。
    • 检查 Member 模块开关(features.user)是否允许访问该模块。
  • 表单提交目标错误:
    • 确认路由名符合 resource 风格(module.sub.action),create/edit 才会自动装配。
    • 检查 route() 生成的 URL 是否符合预期。
  • 主题扩展未生效:
    • 确认模板目录下 inc/..from_theme.php 存在并通过 SQL 安全校验。
    • 检查 Portal::boot 是否被调用,确保当前路由上下文正确。

结论

FrontResolver 以前台纯声明式路由为核心,结合 PrettyRouteMatcher 的高效匹配、MiddlewareRegistry 的分层中间件、表单目标自动装配与主题扩展加载,形成清晰、可扩展且安全的前台请求解析链路。其职责边界明确,输出统一的 DispatchPlan,便于中央调度器执行,提升了可维护性与一致性。

附录:使用示例

以下示例展示如何基于现有路由声明与 FrontResolver 的工作方式进行理解与使用(不直接粘贴代码,仅提供路径参考):

  • 定义资源型路由(user/contact 的 CRUD):

    • 参考:user.php:75-80
    • 说明:通过 Route::resource 声明标准 CRUD 动作,配合 prefix/sub/name 等 DSL 生成具名路由与中间件配置。
  • 访问会员中心登录/注册等公开入口:

    • 参考:user.php:51-56
    • 说明:通过 group/prefix/sub/name 与 get/post 动词方法声明多个动作,auth_modes 在 front/init/middleware.php 中登记为 public 以允许匿名访问。
  • 表单自动装配(create/edit):

    • 参考:FrontResolver.php:123-143
    • 说明:在模板中使用 {$form_action} 与 {$form_method},无需手动计算提交 URL 与方法伪装值。
  • 中间件豁免与追加:

    • 参考:FrontResolver.php:174-199
    • 说明:在 declared 条目中通过 mw_without/mw_append/mw_params/mw_skip_all 精细控制中间件链。
  • 主题扩展加载:

    • 参考:ThemeExtensionLoader.php:50-68
    • 说明:在 inc/..from_theme.php 中依据 Portal::routeModule()/Portal::routeAction() 等门面注入数据。
添加日期:2026-10-05