文档目录
中间件架构设计

简介

本文件面向 DouPHP 框架的中间件系统,系统性说明管道模式的设计原理、中间件的注册与执行顺序控制、请求在管道中的流转过程、上下文对象生命周期管理,以及前台、后台、API 三端中间件配置的差异。文档同时给出中间件容器实现要点、依赖注入机制与错误处理策略,并配套架构图与执行流程图,帮助开发者快速理解与扩展中间件能力。

项目结构

DouPHP 将中间件能力下沉到核心层,并在各应用端(前台 front、后台 admin、API api)通过各自的 Resolver 装配默认中间件栈,最终由中央分发器 Dispatcher 统一执行管道。

graph TB
subgraph "核心层"
MI["中间件接口<br/>MiddlewareInterface"]
PIPE["管道执行器<br/>MiddlewarePipeline"]
DISP["分发器<br/>Dispatcher"]
REQ["请求对象<br/>Request"]
end
subgraph "后台端"
A_RES["后台解析器<br/>AdminResolver"]
A_MW["后台中间件集合<br/>安全头/代理信任/认证/权限/CSRF/工作台"]
end
subgraph "API端"
API_RES["API解析器<br/>ApiResolver"]
API_MW["API中间件集合<br/>安全头/代理信任/限流/用户认证"]
end
subgraph "前台端"
F_MW["前台中间件<br/>CSRF等"]
end
A_RES --> DISP
API_RES --> DISP
DISP --> PIPE
PIPE --> A_MW
PIPE --> API_MW
PIPE --> F_MW
DISP --> REQ

核心组件

  • 中间件接口:定义统一的 handle($next) 契约,要求中间件必须调用 $next() 并将下层返回值原样冒泡,禁止吞掉控制器响应。
  • 安全头基类:提供按配置下发安全响应头的通用逻辑,三端可继承以保持一致行为。
  • 分发器:负责在中间件管道之前注入路由参数,并在管道中懒实例化控制器并调用方法。
  • 解析器:各端 Resolver 根据声明式路由命中结果,组装默认中间件别名栈,并通过 MiddlewareRegistry 生成最终中间件链。
  • 端侧中间件:后台 Auth/Permission/CSRF/Workspace;API Throttle/UserAuth;前台 CSRF 等。

架构总览

下图展示从请求进入、路由解析、中间件管道到控制器执行的完整流程。

sequenceDiagram
participant C as "客户端"
participant R as "端解析器<br/>AdminResolver/ApiResolver"
participant D as "分发器<br/>Dispatcher"
participant P as "管道执行器<br/>MiddlewarePipeline"
participant M as "中间件链"
participant Ctrl as "控制器"
C->>R : 发起HTTP请求
R-->>D : 返回DispatchPlan(含中间件别名栈)
D->>D : 注入路由参数至Request
D->>P : 创建管道并传入控制器闭包
P->>M : 依次执行中间件.handle(next)
M-->>P : 放行或短路返回
P->>Ctrl : 调用控制器方法
Ctrl-->>P : 返回Response/数据
P-->>D : 冒泡上层返回值
D-->>C : 输出响应

详细组件分析

管道模式与执行顺序控制

  • 管道入口:Dispatcher.run 接收 DispatchPlan,先写入 Request 的路由参数,再使用 MiddlewarePipeline::make($plan->middlewares)->run(...) 构建管道并执行。
  • 执行顺序:由各端 Resolver 的默认别名栈决定,例如后台默认栈为“安全头 → 代理信任 → 认证 → 权限 → CSRF → 工作台”,API 默认栈为“安全头 → 代理信任 → 限流 → 用户认证(可选)”。
  • 短路机制:中间件可在 handle 中直接返回响应或抛出异常,从而阻断后续链路;否则必须 return $next() 保证下层返回值冒泡。
flowchart TD
Start(["进入管道"]) --> Next1["中间件1.handle(next)"]
Next1 --> Check1{"是否放行?"}
Check1 -- 否 --> Return1["返回响应/抛异常"]
Check1 -- 是 --> Next2["中间件2.handle(next)"]
Next2 --> Check2{"是否放行?"}
Check2 -- 否 --> Return2["返回响应/抛异常"]
Check2 -- 是 --> Controller["控制器调用"]
Controller --> Bubble["返回值逐层冒泡"]
Bubble --> End(["结束"])

中间件注册机制

  • 别名映射:各端 Resolver 维护 aliasMap,将字符串别名映射到具体中间件类名。
  • 默认栈:Resolver 定义默认别名数组,作为“安全默认”的基础栈。
  • 组合规则:通过 MiddlewareRegistry::compose(默认栈, 命中条目携带的中继配置) 叠加路由级细化,得到最终中间件实例链。

不同应用层的中间件配置差异

  • 后台(Admin)
    • 默认栈包含:安全头、代理信任、认证、权限、CSRF、工作台。
    • 典型中间件:
      • 认证:从会话恢复管理员登录态,未登录跳转登录页。
      • 权限:基于工作区与角色进行模块准入校验。
      • CSRF:表单提交保护。
      • 工作台:注入全局视图变量。
  • API
    • 默认栈包含:安全头、代理信任、限流;当 features.user 开启时追加用户认证。
    • 典型中间件:
      • 限流:限制请求频率。
      • 用户认证:从 Authorization: Bearer 提取 token,解析登录态并注入上下文;拒绝时返回 JSON 错误。
  • 前台(Front)
    • 典型中间件:CSRF,支持一次性令牌与静态令牌模型,GET 特定路由也校验。

请求在管道中的流转与上下文生命周期

  • 路由参数注入:Dispatcher 在管道前将路由参数写入 Request,确保中间件与控制器均可通过 Request::route() 获取路径段参数。
  • 上下文对象:
    • 后台:认证后通过会话恢复管理员信息,权限与工作区上下文在后续中间件注入。
    • API:认证中间件从请求头解析 token,调用 auth('api') 解析用户上下文并 hydrate 到 guard,供后续控制器使用。
  • 生命周期:中间件在 handle 内完成前置处理(鉴权、限流、安全头等),调用 $next() 后继续后置处理(如记录日志、附加响应头)。

中间件容器的实现细节与依赖注入

  • 容器职责:Dispatcher 通过 Container::make 懒实例化控制器,并通过 Container::call 调用方法,传递场景参数 __scene。
  • 依赖注入:中间件与控制器均通过容器解析依赖,Resolver 仅负责组装中间件别名链,不直接耦合具体实现。
  • Guard 注册:
    • 后台 Init 注册 admin guard。
    • API Init 先以 GuestGuard 兜底,若 user 模块可用则替换为真实 Auth,并由 UserAuthMiddleware 在运行时注入上下文。

错误处理策略

  • 后台认证失败:抛出 HttpResponseException 并重定向到登录页。
  • API 认证失败:直接发送 JSON 错误响应(401/403)并终止。
  • 前台 CSRF 失败:抛出 DomainException 并提示页面过期,引导刷新或重新登录。
  • 安全头基类:仅在 headers 未发送且配置存在时下发,避免重复或无效设置。

依赖关系分析

  • 低耦合:Resolver 只负责别名到类的映射与默认栈组合;Dispatcher 只负责管道编排与控制器调用;中间件通过接口契约协作。
  • 关键依赖链:
    • AdminResolver/ApiResolver → MiddlewareRegistry → 中间件链
    • Dispatcher → MiddlewarePipeline → 控制器
    • 各端 Init → Guard 注册 → 中间件运行时使用
classDiagram
class MiddlewareInterface {
+handle(next) mixed
}
class AbstractSecurityHeadersMiddleware {
+handle(next) mixed
}
class AdminResolver {
-defaultAliases : string[]
-aliasMap : map
+resolve(request, container) DispatchPlan
}
class ApiResolver {
-defaultAliases() : string[]
-aliasMap : map
+resolve(request, container) DispatchPlan
}
class Dispatcher {
+run(plan, container) mixed
}
class AuthMiddleware
class UserAuthMiddleware
class CsrfMiddleware
AbstractSecurityHeadersMiddleware ..|> MiddlewareInterface
AuthMiddleware ..|> MiddlewareInterface
UserAuthMiddleware ..|> MiddlewareInterface
CsrfMiddleware ..|> MiddlewareInterface
AdminResolver --> Dispatcher : "产出DispatchPlan"
ApiResolver --> Dispatcher : "产出DispatchPlan"
Dispatcher --> MiddlewareInterface : "执行管道"

性能考量

  • 懒实例化:控制器在管道内部按需实例化,减少无谓开销。
  • 最小化头部操作:安全头中间件仅在 headers 未发送时设置,避免重复与性能损耗。
  • 条件启用:API 用户认证仅在 features.user 开启时加入默认栈,降低无关开销。
  • 路由参数提前注入:在管道前写入 Request,避免中间件重复解析。

故障排查指南

  • 认证失败
    • 后台:检查会话恢复逻辑与重定向目标是否正确。
    • API:检查 Authorization 头与 token 解析、guard 注入是否成功。
  • CSRF 校验失败
    • 前台:确认表单 token 生成与匹配逻辑,关注一次性令牌路由映射与 GET 校验列表。
  • 安全头未生效
    • 检查配置项 security.headers 是否存在,以及是否在 headers 已发送后尝试设置。
  • 中间件顺序问题
    • 核对 Resolver 默认栈顺序与路由级覆盖,确保鉴权在业务逻辑之前、限流在认证之前等。

结论

DouPHP 的中间件系统以清晰的接口契约与分层 Resolver 为核心,结合 Dispatcher 的统一管道编排,实现了跨前台、后台、API 的一致处理能力。通过别名映射与默认栈组合,既保证了“安全默认”,又允许路由级精细化定制。配合容器依赖注入与 Guard 机制,开发者可以便捷地扩展鉴权、限流、安全头等横切能力,并以可控的错误处理策略保障系统稳定性。

附录

  • 最佳实践建议
    • 始终 return $next() 以保证响应冒泡。
    • 将横切关注点拆分为单一职责中间件,便于复用与测试。
    • 使用 Resolver 的 withoutMiddleware 声明式豁免敏感路由,避免硬编码白名单。
    • 在 API 端优先限流与认证,再进入业务逻辑。
添加日期:2026-10-05