# limiter **Repository Path**: fiberphp/limiter ## Basic Information - **Project Name**: limiter - **Description**: 🚦 FiberPHP 限流器组件 —— 支持令牌桶、滑动窗口、并发控制等多种限流策略,协程安全,开箱即用。 - **Primary Language**: PHP - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-23 - **Last Updated**: 2026-09-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fiberphp/limiter 基于 Redis 的限流器组件,适用于 FiberPHP 框架。采用固定窗口算法,通过 Redis Hash 分桶计数,热路径仅需一次 `HINCRBY` 调用,性能开销极低。内置 IP 白名单机制(支持单 IP、CIDR、通配符三种匹配方式)。 ## 特性 - 固定窗口限流算法,窗口按 TTL 对齐到 epoch - Redis Hash 按天分桶存储,热路径仅一次 `HINCRBY`,过期时间通过 Lua 脚本惰性延长(只增不减) - IP 白名单支持单 IP、CIDR(如 `192.168.0.0/16`)、通配符(如 `192.168.*.*`)三种格式 - 提供 `attempt()`(返回布尔值)与 `check()`(超限自动抛异常)两种调用方式 - `#[RateLimit]` 注解 + `RateLimitMiddleware` 注解式限流,零样板代码 - 异常类与 HTTP 状态码可配置,默认返回 `429 Too Many Requests` ## 安装 ```bash composer require fiberphp/limiter ``` 包内置 `config/limiter.php` 默认配置由 config 包自动合并,装包即用;如需调整,在应用 `config/limiter.php` 放置同名键覆盖默认值。 ## 配置说明 配置文件位于 `config/limiter.php`,通过 `config('limiter.xxx')` 访问。 | 配置项 | 类型 | 默认值 | 说明 | |----------------|--------|-------------------------|----------------------------------------------------| | `enable` | bool | `true` | 是否启用限流器 | | `ip_whitelist` | array | `[]` | 不受频率限制的 IP 列表,支持单 IP、CIDR、通配符 | | `exception` | string | `LimitException::class` | 超出限流时抛出的异常类,需为 `Throwable` 子类且支持 `(string $message)` 构造(建议实现 contract `HttpCodeAware` 声明状态码、`UserFacingMessage` 透传消息) | 配置示例: ```php true, // 这些 ip 的请求不做频率限制 'ip_whitelist' => [ '127.0.0.1', // 单 IP '10.0.0.0/8', // CIDR '192.168.*.*', // 通配符 ], 'exception' => LimitException::class, ]; ``` > 注意:组件通过 `FiberPHP\Redis\Sync` 客户端连接 Redis,连接名为 `limiter`,需在 Redis 组件(`fiberphp/redis`)的配置中预先定义名为 > `limiter` 的连接。 ## 注解式限流(#[RateLimit]) `#[RateLimit]` 是方法级属性注解,标注在控制器方法上,配合 `RateLimitMiddleware` 自动执行限流——控制器无需手动调用 `check()`。 ### 基本用法 ```php use FiberPHP\Limiter\Attribute\RateLimit; use FiberPHP\Router\Attribute\Post; class SmsController extends BaseController { #[Post('/sms/send')] #[RateLimit(5, 1)] // 每秒最多 5 次,key 默认 ip:{ip} public function send(Request $request): Response { // 限流已通过,执行业务 return $this->success(['sent' => true]); } } ``` ### 限流键模板 `key` 参数支持 `:param` 路由参数占位符和 `{ip}` 客户端 IP 占位符: ```php #[RateLimit(100, 60)] // 每 60 秒 100 次,默认 key = ip:{ip} #[RateLimit(5, 1, 'ip:{ip}')] // 显式按 IP 限流 #[RateLimit(10, 60, 'user:{uid}')] // 按路由参数 uid 限流 #[RateLimit(3, 60, 'sms:{mobile}')] // 按请求参数 mobile 限流 ``` - `{ip}` → 客户端真实 IP(`$request->getRealIp()`) - `:param` → 路由参数或请求参数(`$request->input('param')`) ### 自定义提示消息 ```php #[RateLimit(5, 1, message: '操作过于频繁,请 1 分钟后再试')] ``` ### 中间件注册 在应用 `config/http.php` 中注册为全局中间件或别名: ```php // 全局注册(对所有控制器方法生效) return [ 'middleware' => [ 'global' => [ \FiberPHP\Limiter\Middleware\RateLimitMiddleware::class, ], ], ]; // 或别名注册(按需在路由/控制器上引用) return [ 'middleware' => [ 'aliases' => [ 'throttle' => \FiberPHP\Limiter\Middleware\RateLimitMiddleware::class, ], ], ]; ``` > 中间件仅在方法上有 `#[RateLimit]` 注解时执行限流,无注解的方法直接放行。IP 白名单内的请求也直接放行。 ## 手动调用 ### 1. 超限自动抛异常 `check()` 会在超出限制时自动抛出配置中指定的异常,适合在中间件或控制器入口直接调用: ```php use FiberPHP\Limiter\Limiter; // 每个 IP 每分钟(60 秒)最多 100 次请求,超限抛出 LimitException(HTTP 429) $ip = $request->getRealIp(); Limiter::check('ip:' . $ip, 100, 60, '请求过于频繁,请稍后再试'); ``` ### 2. 手动判断是否超限 `attempt()` 返回布尔值,由调用方自行决定后续逻辑: ```php use FiberPHP\Limiter\Limiter; use FiberPHP\Limiter\LimitException; // 同一用户每秒最多调用 5 次发短信接口 $key = 'sms:' . $userId; if (!Limiter::attempt($key, 5, 1)) { throw new LimitException('操作过于频繁', 429); } // 继续业务逻辑 ``` ### 3. 结合 IP 白名单 在限流前先判断 IP 是否在白名单,白名单内的请求直接放行: ```php use FiberPHP\Limiter\Limiter; $ip = $request->getRealIp(); if (!Limiter::isIpWhiteListed($ip)) { Limiter::check('ip:' . $ip, 100, 60, '请求过于频繁,请稍后再试'); } // 处理请求 ``` ## 限流算法说明 组件采用 **固定窗口算法(Fixed Window)**: 1. **窗口对齐**:窗口结束时间按 TTL 对齐到 epoch(Unix 时间戳原点)。例如 TTL 为 60 秒时,窗口边界为每分钟的整点(`00:00`、 `01:00`、`02:00` ...),所有落在同一窗口内的请求共享同一计数。 2. **按天分桶**:Redis 端以本地日期(`Y-m-d`)为单位,每天使用一个 Hash(`limiter-YYYY-MM-DD`),字段格式为 `{key}-{窗口结束时间}-{ttl}`,值为该窗口内的累计请求数。 3. **热路径优化**:计数仅执行一次 `HINCRBY`。过期时间的延长通过 Lua 脚本惰性触发,且只在本地缓存认为需要延长时才调用,脚本保证过期时间只增不减。 4. **自动回收**:Hash 的过期时间覆盖当天最后一个窗口的结束时刻,过期后由 Redis 自动清理;本地进程内的过期时间缓存每 60 秒清理一次。 相比滑动窗口,固定窗口实现简单、性能更高,但在窗口边界处可能出现瞬时双倍流量(相邻两个窗口各放过 `limit` 次)。如对边界突刺敏感,可结合业务适当调小 `limit` 或缩短 `ttl`。 ## IP 白名单机制 `isIpWhiteListed()` 依次匹配 `ip_whitelist` 配置中的每一项,命中任意一条即返回 `true`。匹配规则由 `ipInRange()` 实现,支持三种格式: | 格式 | 示例 | 说明 | |--------|------------------|------------------------------------| | 单 IP | `127.0.0.1` | 完全相等的 IPv4 地址 | | CIDR | `192.168.0.0/16` | 通过子网掩码按位匹配网段 | | 通配符 | `192.168.*.*` | `*` 匹配任意一段(一位或多位数字) | 匹配仅针对 IPv4。白名单在首次调用时一次性懒加载到进程内存,运行期直接读取内存,无额外开销。 ## 异常处理 超限时默认抛出 `FiberPHP\Limiter\LimitException`。它是纯库异常(继承 `\RuntimeException`), 通过 contract 契约参与框架渲染:实现 `HttpCodeAware`(HTTP 状态码 429)与 `UserFacingMessage` (消息透传给用户),在 FiberPHP 应用中未被 catch 时自动渲染为 429 响应: ```php namespace FiberPHP\Limiter; use FiberPHP\Contract\Exception\HttpCodeAware; use FiberPHP\Contract\Exception\UserFacingMessage; use RuntimeException; class LimitException extends RuntimeException implements HttpCodeAware, UserFacingMessage { protected int $httpCode = 429; public function getHttpCode(): int { return $this->httpCode; } public function isMessageSafe(): bool { return true; } } ``` 可通过 `exception` 配置项替换为自定义异常类(需为 `Throwable` 子类,支持 `(string $message)` 构造;想自定义 HTTP 状态码/透传消息则实现 `HttpCodeAware` / `UserFacingMessage` 契约)。调用 `check()` 时传入的 `message` 会作为异常消息: ```php Limiter::check($key, 100, 60, '自定义提示信息'); ``` 若不希望抛异常,改用 `attempt()` 自行处理: ```php if (!Limiter::attempt($key, 100, 60)) { // 自定义响应 return json(['code' => 429, 'msg' => 'Too Many Requests'])->withStatus(429); } ``` ## 目录结构 ``` src/ ├── Attribute/ │ └── RateLimit.php # #[RateLimit] 方法级注解 ├── Middleware/ │ └── RateLimitMiddleware.php # 注解式限流中间件 ├── Install.php # 安装钩子,定义配置路径 ├── LimitException.php # 限流异常,HTTP 429 ├── Limiter.php # 限流器核心,提供 attempt/check/白名单能力 ├── LimiterProvider.php # 服务提供者 └── Redis.php # Redis 存储实现,固定窗口计数与过期管理 ``` ## 依赖说明 | 依赖 | 说明 | |---------------------|---------------------------------------------------------------------------| | PHP >= 8.3 | 运行环境要求 | | `fiberphp/redis` | 提供 Redis 客户端,需配置名为 `limiter` 的连接 | | `fiberphp/contract` | `HttpCodeAware` / `UserFacingMessage` 契约 | | `fiberphp/config` | `config()` 助手读取限流配置 | | `fiberphp/container` | `LimiterProvider` 容器绑定 | | `fiberphp/discovery` | `PackageInstaller` 配置文件发布 | | `fiberphp/http` | (suggest)`RateLimitMiddleware` 依赖 Request/Response,仅注解式限流需要 | 命名空间为 `FiberPHP\Limiter\`(PSR-4,对应 `src/` 目录)。 ## License MIT License (c) 2026 庞斌,详见 [LICENSE](LICENSE)。