文档目录
速率限制中间件

简介

本技术文档围绕 DouPHP 的速率限制中间件,系统阐述其设计目标、实现原理与使用方式。速率限制用于防止暴力破解、DDoS 攻击和资源滥用,通过“时间窗口 + 计数”的方式对敏感端点进行限流。DouPHP 采用“定向限流(targeted)”策略:默认不限流,仅对显式配额的敏感路由进行限制,从而兼顾安全与性能。

  • 适用场景
    • 登录、注册、找回密码、短信验证码下发等认证相关接口
    • 公共匿名写接口(留言、咨询、邮件订阅等)
    • 防伪查询、AI 流式接口等高成本或易被滥用的端点
  • 保护目标
    • 防暴力破解:限制同一 IP 在短时间内的尝试次数
    • 防 DDoS:限制高频请求,避免资源耗尽
    • 防滥用:控制高成本操作(如 AI 调用、短信发送)的频率

项目结构

速率限制由“基类 + 模块实现 + 存储后端 + 配置 + 路由集成”构成:

  • 基类:定义通用处理流程(解析路由、计算配额、读取/写入计数、超限拒绝)
  • 模块实现:前台与 API 各自维护自己的配额表与超限响应策略
  • 存储后端:基于文件的轻量计数器,适合单机或小规模部署
  • 配置:限流存储路径、是否启用全局默认限流等
  • 路由集成:在前台与 API 的 Resolver 中注册中间件,确保 TrustProxy 先于限流执行
graph TB
A["请求进入"] --> B["前端/API 路由解析<br/>FrontResolver / ApiResolver"]
B --> C["中间件栈组装<br/>security_headers -> trust_proxy -> throttle -> ..."]
C --> D["ThrottleMiddleware.handle()"]
D --> E{"是否有配额?"}
E -- 否 --> F["直接放行 next()"]
E -- 是 --> G["ThrottleStore::tooMany()"]
G -- 超限 --> H["reject(): 返回 429 / 抛出异常"]
G -- 未超限 --> I["ThrottleStore::hit() 自增计数"]
I --> J["继续业务逻辑"]

图表来源

  • ApiResolver.php:100-107
  • FrontResolver.php:174-199
  • AbstractThrottleMiddleware.php:60-84
  • ThrottleStore.php:50-80

章节来源

  • ApiResolver.php:100-107
  • FrontResolver.php:174-199

核心组件

  • 抽象基类 AbstractThrottleMiddleware
    • 负责通用流程:获取路由信息、确定配额、检查计数、记录命中、超限拒绝
    • 提供候选键生成 candidates() 与可覆盖的路由级参数 setRouteParameters()
  • 模块实现
    • API 中间件:对认证与公共写接口按 IP 限流,超限返回 JSON 429
    • 前台中间件:对认证与公共表单提交按 IP 限流,超限以 DomainException 渲染错误页
  • 存储后端 ThrottleStore
    • 基于文件系统,每个键一个 JSON 文件,包含 count 与 reset 时间戳
    • 支持 tooMany/hit/availableIn/clear/purgeExpired 等操作
  • 配置 security.throttle
    • store:限流数据存放目录
    • default:全局默认限流策略(null 表示默认不限流,仅对敏感端点生效)

章节来源

  • AbstractThrottleMiddleware.php:32-159
  • API ThrottleMiddleware.php:31-91
  • Front ThrottleMiddleware.php:30-85
  • ThrottleStore.php:30-181
  • security.php:74-77

架构总览

下图展示一次请求从进入路由到限流判断再到业务处理的完整链路,以及不同模块的差异化响应。

sequenceDiagram
participant Client as "客户端"
participant Resolver as "Resolver(前台/API)"
participant MW as "中间件栈"
participant Throttle as "ThrottleMiddleware"
participant Store as "ThrottleStore"
participant Controller as "控制器/业务"
Client->>Resolver : HTTP 请求
Resolver->>MW : 组装中间件链
MW->>Throttle : handle(next)
Throttle->>Throttle : 解析 module/action/sub/ip
Throttle->>Throttle : 查找配额 (throttleFor / routeLimit)
alt 无配额
Throttle-->>Controller : 放行 next()
else 有配额
Throttle->>Store : tooMany(key, max, window)
alt 超限
Throttle->>Throttle : reject(retryAfter)
Throttle-->>Client : 429 / 错误页
else 未超限
Throttle->>Store : hit(key, window)
Throttle-->>Controller : 放行 next()
end
end

图表来源

  • AbstractThrottleMiddleware.php:60-84
  • API ThrottleMiddleware.php:64-89
  • Front ThrottleMiddleware.php:58-83

详细组件分析

抽象基类:AbstractThrottleMiddleware

  • 职责
    • 统一入口 handle():解析路由、确定配额、检查计数、记录命中、超限拒绝
    • 提供 candidates():按精确度从高到低匹配 module/sub/action、module/sub、module/action、module
    • 提供 resolveKey():默认 key = module.action.ip;子类可重写以支持用户 ID、API Key 等多维限流
    • 提供 setRouteParameters():允许路由级覆盖配额(max/window),优先级高于 throttleFor 表
  • 关键流程
    • 若没有配额,直接放行
    • 若 tooMany() 为真,调用 reject() 并终止
    • 否则 hit() 自增计数并放行
flowchart TD
Start(["handle() 入口"]) --> Parse["解析 module/action/sub/ip"]
Parse --> FindLimit{"找到配额?"}
FindLimit -- 否 --> Next["next() 放行"]
FindLimit -- 是 --> CheckTooMany["tooMany(key, max, window)"]
CheckTooMany -- 是 --> Reject["reject(retryAfter)"]
CheckTooMany -- 否 --> Hit["hit(key, window)"]
Hit --> Next
Reject --> End(["结束"])
Next --> End

图表来源

  • AbstractThrottleMiddleware.php:60-84
  • AbstractThrottleMiddleware.php:136-157

章节来源

  • AbstractThrottleMiddleware.php:32-159

API 模块限流:API ThrottleMiddleware

  • 配额范围
    • 认证相关:登录、手机号登录、注册、找回密码、验证码校验
    • 公共匿名写:留言、咨询、邮件订阅
    • 其他:防伪查询、AI 流式接口
  • 超限响应
    • 设置 Retry-After 头
    • 返回 JSON 429,错误码来自 ApiCodes::RATE_LIMITED
    • 终止请求(exit)
classDiagram
class AbstractThrottleMiddleware {
+handle(next)
+setRouteParameters(params)
#throttleFor(module, action, sub) array|null
#reject(retryAfter) void
#resolveKey(module, action, sub, ip) string
#store() ThrottleStore
#candidates(module, action, sub) array
}
class ApiThrottleMiddleware {
-limits : array
#throttleFor(module, action, sub) array|null
#reject(retryAfter) void
}
AbstractThrottleMiddleware <|-- ApiThrottleMiddleware

图表来源

  • API ThrottleMiddleware.php:31-91
  • AbstractThrottleMiddleware.php:32-159

章节来源

  • API ThrottleMiddleware.php:31-91
  • ApiCodes.php:54-58

前台模块限流:Front ThrottleMiddleware

  • 配额范围
    • 认证相关:登录、手机号登录、注册、找回密码、验证码校验
    • 公共表单:留言、落地页提交、咨询、分销申请
    • 其他:AI 流式接口、新会话创建
  • 超限响应
    • 设置 Retry-After 头
    • 抛出 DomainException,渲染错误页面(与前台 UX 一致)

章节来源

  • Front ThrottleMiddleware.php:30-85

存储后端:ThrottleStore

  • 数据结构
    • 每个键对应一个 JSON 文件:&lt;dir>/&lt;md5(key)>.json
    • 内容包含 count(当前窗口内命中数)与 reset(窗口重置时间戳)
  • 主要方法
    • hit(key, window):窗口到期则重置,count++,写回文件
    • tooMany(key, max, window):窗口内达到上限返回 true
    • availableIn(key):返回距窗口重置剩余秒数
    • clear(key):删除文件
    • purgeExpired():机会式清理过期文件
  • 并发与一致性
    • 使用 LOCK_EX 写入,保证基本原子性
    • 适用于单机或小规模部署;分布式场景需替换为 Redis 等
classDiagram
class ThrottleStore {
-dir : string
+__construct(dir)
+hit(key, window) int
+tooMany(key, max, window) bool
+availableIn(key) int
+clear(key) void
+purgeExpired() void
-file(key) string
-read(key) array|null
-write(key, data) void
}

图表来源

  • ThrottleStore.php:30-181

章节来源

  • ThrottleStore.php:30-181

路由集成与中间件顺序

  • API 端
    • 默认中间件栈:security_headers -> trust_proxy -> throttle -> user_auth(可选)
    • 限流位于可信代理之后,确保 IP 可信
  • 前台端
    • 默认中间件栈:security_headers -> trust_proxy -> throttle -> user_auth(可选) -> csrf
    • 同样保证 TrustProxy 先于限流执行

章节来源

  • ApiResolver.php:100-107
  • FrontResolver.php:174-199

依赖关系分析

  • 组件耦合
    • 中间件依赖 Request 获取路由信息与 IP
    • 中间件依赖 Config 获取存储路径
    • 中间件依赖 ThrottleStore 进行计数读写
    • API 中间件依赖 ApiResponse 与 ApiCodes 输出标准错误
  • 外部依赖
    • 文件系统权限与磁盘空间影响 ThrottleStore 稳定性
    • 反向代理信任列表影响 IP 准确性
graph LR
A["ThrottleMiddleware"] --> B["Request"]
A --> C["Config"]
A --> D["ThrottleStore"]
A_E["API ThrottleMiddleware"] --> F["ApiResponse"]
A_E --> G["ApiCodes"]

图表来源

  • AbstractThrottleMiddleware.php:60-84
  • API ThrottleMiddleware.php:81-89

章节来源

  • AbstractThrottleMiddleware.php:60-84
  • API ThrottleMiddleware.php:81-89

性能考量

  • 存储后端
    • 文件计数适合单机或小规模;高并发建议替换为 Redis 等内存存储
    • 定期清理过期文件(purgeExpired)可减少磁盘占用
  • 中间件顺序
    • 将限流置于认证之前,可有效降低暴力破解成本
    • 确保 TrustProxy 先于限流,避免真实 IP 被伪造
  • 配额设计
    • 短窗口低阈值:针对验证码、短信等高风险操作
    • 长窗口高阈值:针对普通写接口,平衡体验与安全
  • 监控与告警
    • 统计 429 比例与热点 IP,动态调整配额
    • 结合日志与审计,识别异常流量模式

故障排查指南

  • 常见问题
    • 误判 IP:检查 trusted_proxies 配置是否正确,确保 TrustProxy 在限流前执行
    • 存储目录不可写:确认 storage/cache/throttle/ 目录存在且可写
    • 配额过严:根据业务指标调整 max 与 window
  • 诊断步骤
    • 查看 429 响应频率与热点路由
    • 检查 ThrottleStore 目录下 JSON 文件内容与过期情况
    • 核对各模块 $limits 表中的路由键与配额
  • 恢复措施
    • 临时放宽配额或关闭特定路由限流
    • 清理过期文件(purgeExpired)或手动删除异常键文件

章节来源

  • security.php:74-77
  • ThrottleStore.php:118-138

结论

DouPHP 的速率限制中间件采用“定向限流”的设计,既保证了安全性,又避免了不必要的性能开销。通过灵活的配额表、清晰的中间件顺序与可扩展的存储后端,系统能够应对多种滥用场景。建议在生产环境中结合监控与日志持续优化配额,并在高并发场景下考虑替换存储后端以提升稳定性。

附录:配置与调优

  • 基础配置
    • 限流存储路径:security.throttle.store
    • 全局默认限流:security.throttle.default(null 表示默认不限流)
  • 前台与 API 差异化策略
    • 前台侧重用户体验:超限后跳转或渲染错误页
    • API 侧重标准化:返回 429 JSON,附带 Retry-After
  • 多维限流扩展
    • 可通过重写 resolveKey() 支持用户 ID、API Key、设备指纹等维度
    • 结合业务层 isWaterByIp 等机制形成互补防护
  • 最佳实践
    • 对高风险接口(验证码、短信、支付回调)设置更严格的配额
    • 对高成本接口(AI 流式)设置合理阈值,避免资源耗尽
    • 定期评估配额,结合监控数据进行动态调优

章节来源

  • security.php:74-77
  • API ThrottleMiddleware.php:31-91
  • Front ThrottleMiddleware.php:30-85
  • AbstractThrottleMiddleware.php:113-116
添加日期:2026-10-05