文档目录
请求限流中间件

简介

本文件面向运维与开发者,系统化说明 DouPHP 的请求限流中间件的实现、配置与使用方法。该中间件采用“定向限流”策略:默认不限流,仅对显式配额的敏感端点按 IP 进行频率限制;支持通过路由级参数覆盖配额;限流数据以文件为后端存储,具备窗口过期清理能力;超限后 API 返回 429 JSON,前台抛出异常并跳转首页。

项目结构

  • 限流基类位于核心框架层,提供统一的限流流程、键生成、候选规则匹配与存储抽象。
  • API 与前台分别实现各自的限流策略表与超限响应行为。
  • 限流数据存储由独立存储类负责,基于文件系统,每个键对应一个 JSON 文件。
  • 安全配置集中管理限流存储路径与全局默认限流策略。
  • API 解析器将限流中间件纳入默认中间件栈,确保在可信代理之后执行。
graph TB
Client["客户端"] --> API["API 入口"]
API --> MR["API 解析器<br/>注册默认中间件"]
MR --> Tm["API 限流中间件"]
Tm --> Store["限流存储<br/>文件后端"]
Tm --> Next["业务控制器"]
Front["前台入口"] --> FTm["前台限流中间件"]
FTm --> Store
FTm --> FNext["前台控制器"]
Config["安全配置<br/>throttle.store"] --> Store

图表来源

  • ApiResolver.php:40-109
  • AbstractThrottleMiddleware.php:60-84
  • ThrottleStore.php:21-30
  • security.php:74-77

章节来源

  • ApiResolver.php:40-109
  • security.php:74-77

核心组件

  • 抽象限流中间件:定义统一处理流程、键生成、候选规则匹配、存储访问与超限拒绝接口。
  • API 限流中间件:维护 API 端敏感端点的限流配额表,超限返回 JSON 429。
  • 前台限流中间件:维护前台敏感端点的限流配额表,超限抛出异常并跳转首页。
  • 限流存储:基于文件的计数器,支持窗口重置、命中计数、可用时间查询与过期清理。
  • 安全配置:集中管理限流存储路径与全局默认限流策略。

章节来源

  • AbstractThrottleMiddleware.php:24-159
  • ThrottleMiddleware.php(API):25-91
  • ThrottleMiddleware.php(前台):24-85
  • ThrottleStore.php:21-181
  • security.php:17-88

架构总览

限流中间件在 HTTP 边界执行,优先于业务控制器。其核心流程如下:

  • 解析当前请求的模块、动作、子段与 IP。
  • 根据路由级参数或 throttleFor 表确定配额(max/window)。
  • 生成限流键(默认 module.action.ip),检查是否超限。
  • 若超限则拒绝(API 返回 429,前台抛异常跳转);否则记录一次命中并放行。
sequenceDiagram
participant C as "客户端"
participant R as "API 解析器"
participant M as "限流中间件"
participant S as "限流存储"
participant B as "业务控制器"
C->>R : HTTP 请求
R->>M : 进入中间件链
M->>M : 解析 module/action/sub/ip
M->>M : 计算配额 max/window
M->>S : tooMany(key, max, window)?
alt 超限
S-->>M : true
M-->>C : 429 JSON + Retry-After
else 未超限
S-->>M : false
M->>S : hit(key, window)
M->>B : 继续处理
B-->>C : 业务响应
end

图表来源

  • ApiResolver.php:87-106
  • AbstractThrottleMiddleware.php:60-84
  • ThrottleStore.php:71-80

详细组件分析

抽象限流中间件(AbstractThrottleMiddleware)

  • 职责:统一限流流程、键生成、候选规则匹配、存储访问、超限拒绝抽象。
  • 关键方法:
    • handle:获取请求上下文,计算配额,检查并记录命中,调用 next。
    • resolveKey:默认键为 module.action.ip。
    • candidates:候选键优先级精确 > 父段 > 模块根,供子类匹配。
    • store:从配置读取存储目录并实例化 ThrottleStore。
    • setRouteParameters:支持路由级参数覆盖(如 throttle:max,window)。
classDiagram
class AbstractThrottleMiddleware {
+handle(next) mixed
-resolveKey(module, action, sub, ip) string
-candidates(module, action, sub) array
-store() ThrottleStore
#throttleFor(module, action, sub) array|null
#reject(retryAfter) void
+setRouteParameters(params) void
}
class ThrottleStore {
+hit(key, window) int
+tooMany(key, max, window) bool
+availableIn(key) int
+clear(key) void
+purgeExpired() void
}
AbstractThrottleMiddleware --> ThrottleStore : "使用"

图表来源

  • AbstractThrottleMiddleware.php:32-159
  • ThrottleStore.php:30-181

章节来源

  • AbstractThrottleMiddleware.php:32-159

API 限流中间件(Dou\Api\Middleware\ThrottleMiddleware)

  • 职责:针对 API 敏感端点按 IP 限流,超限返回 JSON 429。
  • 配额表:包含登录、注册、短信验证码、公共匿名写接口、防伪查询、LLM 成本端点等。
  • 超限响应:设置 Retry-After 头,返回标准错误码与消息,终止响应。
flowchart TD
Start(["进入 API 限流"]) --> Check["查找配额表<br/>module/action/sub"]
Check --> Found{"找到配额?"}
Found -- 否 --> Pass["放行到业务"]
Found -- 是 --> TooMany{"tooMany(key,max,window)?"}
TooMany -- 是 --> Reject["返回 429 JSON<br/>设置 Retry-After"]
TooMany -- 否 --> Hit["记录一次命中"]
Hit --> Pass

图表来源

  • ThrottleMiddleware.php(API):38-56
  • ThrottleMiddleware.php(API):64-89
  • ThrottleStore.php:71-80

章节来源

  • ThrottleMiddleware.php(API):25-91

前台限流中间件(Dou\Front\Middleware\ThrottleMiddleware)

  • 职责:针对前台敏感端点按 IP 限流,超限抛出异常并跳转首页。
  • 配额表:包含验证码、登录、注册、找回密码、公共表单提交、聊天相关端点等。
  • 超限响应:设置 Retry-After 头,抛出 DomainException,携带首页 URL。

章节来源

  • ThrottleMiddleware.php(前台):24-85

限流存储(ThrottleStore)

  • 存储方式:每个限流键对应一个 JSON 文件,文件名由 key 的 md5 值决定。
  • 数据结构:{ "count": int, "reset": timestamp }。
  • 窗口机制:当 now >= reset 时视为新窗口,自动重置计数。
  • 并发控制:写入时使用 LOCK_EX 保证原子性。
  • 清理策略:提供 purgeExpired 机会式清理过期文件。
flowchart TD
H["hit(key, window)"] --> Read["读取文件"]
Read --> Expired{"已过期或未初始化?"}
Expired -- 是 --> Init["初始化 count=0, reset=now+window"]
Expired -- 否 --> Inc["count++"]
Init --> Write["写入文件"]
Inc --> Write
Write --> Return["返回计数"]

图表来源

  • ThrottleStore.php:50-61
  • ThrottleStore.php:153-179

章节来源

  • ThrottleStore.php:21-181

依赖关系分析

  • API 解析器将限流中间件加入默认中间件栈,顺序为 security_headers -> trust_proxy -> throttle -> user_auth(可选)。
  • 限流中间件依赖安全配置中的 throttle.store 指定存储目录。
  • 限流中间件依赖 Request::ip() 获取客户端 IP,需在 TrustProxy 之后执行以保证 IP 可信。
  • 路由级参数覆盖通过 ParameterizedMiddleware 接口注入,不污染共享实例。
graph LR
Resolver["ApiResolver"] --> AliasMap["别名映射"]
AliasMap --> ThrottleAlias["'throttle' => 'Dou\\Api\\Middleware\\ThrottleMiddleware'"]
ThrottleAlias --> MiddlewareChain["中间件链"]
MiddlewareChain --> SecurityConfig["security.throttle.store"]
SecurityConfig --> Store["ThrottleStore"]

图表来源

  • ApiResolver.php:46-51
  • ApiResolver.php:100-106
  • security.php:74-77

章节来源

  • ApiResolver.php:46-106
  • ParameterizedMiddleware.php:21-42

性能与存储策略

  • 算法复杂度:每次请求 O(1) 文件读写(JSON 解码/编码),键空间随 IP×端点增长。
  • 存储介质:文件系统,适合单机或小规模集群;分布式场景可替换存储后端。
  • 窗口过期:自动重置,无需外部定时任务;提供 purgeExpired 用于清理历史文件。
  • 并发安全:写入使用 LOCK_EX,避免竞态条件。
  • 缓存策略:无额外缓存层,直接落盘;可通过调整窗口大小与配额平衡命中率与开销。

章节来源

  • ThrottleStore.php:21-30
  • ThrottleStore.php:118-138

监控与统计

  • 内置统计:限流中间件未暴露指标接口;可通过审计日志或系统监控采集 429 状态码频率。
  • 建议方案:
    • 在 Web 服务器或网关层收集 429 响应数量与来源 IP。
    • 定期扫描 storage/cache/throttle 目录,统计活跃键数量与文件大小。
    • 结合应用日志记录超限事件,便于定位热点端点与异常流量。

配置与使用指南

配置选项

  • 存储路径:security.throttle.store,默认指向 STORAGE_PATH . 'cache/throttle/'。
  • 全局默认限流:security.throttle.default,null 表示默认不限流,仅敏感端点在中间件配额表中生效。
  • 可信代理:security.trusted_proxies,确保 Request::ip() 正确解析。

章节来源

  • security.php:17-46
  • security.php:74-77

限流策略与优先级

  • 路由级参数覆盖:通过 ParameterizedMiddleware 接口注入 throttle:max,window,优先级高于 throttleFor 表。
  • 候选键匹配:精确 module/sub/action > 父段 module/sub > 模块根 module。
  • 默认行为:未命中任何配额的路由直接放行。

章节来源

  • ParameterizedMiddleware.php:21-42
  • AbstractThrottleMiddleware.php:128-157

不同接口的限流示例

  • API 端:
    • 登录/注册/短信验证码:严格限制(如 5 次/60 秒)。
    • 公共匿名写接口:适度限制(如 5 次/300 秒)。
    • LLM 成本端点:防滥用(如 chat/stream 20 次/60 秒)。
  • 前台端:
    • 验证码:较高频率(如 30 次/60 秒)。
    • 登录/注册/找回密码:严格限制(如 5 次/60 或 300 秒)。
    • 公共表单提交:适度限制(如 10 次/60 或 300 秒)。

章节来源

  • ThrottleMiddleware.php(API):38-56
  • ThrottleMiddleware.php(前台):37-50

触发后的响应格式与用户提示

  • API:返回 JSON 429,包含标准错误码与消息,设置 Retry-After 头。
  • 前台:设置 Retry-After 头,抛出 DomainException 并跳转首页,显示友好提示。

章节来源

  • ThrottleMiddleware.php(API):81-89
  • ThrottleMiddleware.php(前台):75-83

故障排除与调优

常见问题

  • IP 不准确:确认 trusted_proxies 配置正确,确保 TrustProxy 中间件先于限流执行。
  • 存储权限:确保 storage/cache/throttle 目录可写,避免写入失败导致限流失效。
  • 高并发瓶颈:文件 I/O 成为瓶颈时,考虑替换为内存缓存或分布式存储。
  • 误拦截:检查路由级参数覆盖是否正确,避免意外放宽或收紧配额。

章节来源

  • security.php:17-46
  • ApiResolver.php:100-106

调优建议

  • 调整窗口大小:对高频短请求增大窗口,降低抖动;对低频长请求缩短窗口,快速恢复。
  • 差异化配额:按端点敏感度设置不同 max/window,保护核心接口。
  • 监控告警:基于 429 频率与存储目录活跃度设置阈值告警。
  • 清理策略:定期运行 purgeExpired,避免磁盘占用增长。

结论

DouPHP 的请求限流中间件采用“定向限流”设计,聚焦敏感端点,默认不限流以降低开销;通过路由级参数覆盖与安全配置实现灵活策略;基于文件存储的轻量计数器满足单机与小规模场景;API 与前台分别提供合适的超限响应。运维人员可根据业务特征调整配额与窗口,并结合监控与告警保障稳定性。

添加日期:2026-10-05