简介
本文件面向运维与开发者,系统化说明 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 与前台分别提供合适的超限响应。运维人员可根据业务特征调整配额与窗口,并结合监控与告警保障稳定性。