diff --git a/src/rate-limit/README.md b/src/rate-limit/README.md index f4060b77b..4e8f51c27 100644 --- a/src/rate-limit/README.md +++ b/src/rate-limit/README.md @@ -27,6 +27,10 @@ composer require friendsofhyperf/rate-limit - **Flexible Usage** - Annotation-based rate limiting via Aspect - Custom middleware support +- **Smart Order for Multiple Annotations** + - Automatic prioritization of multiple RateLimit annotations + - Intelligent ordering by strictness (maxAttempts/decay ratio) + - More restrictive limits evaluated first for better performance - **Flexible Key Generation** - Default method/class-based keys - Custom key with placeholders support @@ -39,7 +43,7 @@ composer require friendsofhyperf/rate-limit ## Usage -### Method 1: Using Annotagion +### Method 1: Using Annotation The easiest way to add rate limiting is using the `#[RateLimit]` attribute: @@ -142,6 +146,49 @@ class UserController | `response` | `string` | `'Too Many Attempts.'` | Custom response when rate limit is exceeded | | `responseCode` | `int` | `429` | HTTP response code when rate limit is exceeded | +#### Multiple RateLimits with AutoSort + +When you need multiple rate limits on the same method (e.g., per-minute and per-hour limits), you can use the `AutoSort` annotation to automatically prioritize them: + +```php +getClientIp(); + } +} +``` + +然后在配置中注册中间件: + +```php +// config/autoload/middlewares.php +return [ + 'http' => [ + App\Middleware\ApiRateLimitMiddleware::class, + ], +]; +``` + +### 限流算法 + +#### 固定窗口(默认) + +最简单的算法,在固定时间窗口内计数请求。 + +```php +#[RateLimit(algorithm: Algorithm::FIXED_WINDOW)] +``` + +**优点**:简单,内存高效 +**缺点**:可能在窗口边界处允许突发请求 + +#### 滑动窗口 + +比固定窗口更准确,均匀分布请求。 + +```php +#[RateLimit(algorithm: Algorithm::SLIDING_WINDOW)] +``` + +**优点**:平滑突发流量,更准确 +**缺点**:稍微复杂一些 + +#### 令牌桶 + +允许突发流量,同时保持平均速率。 + +```php +#[RateLimit(algorithm: Algorithm::TOKEN_BUCKET)] +``` + +**优点**:允许突发流量,灵活 +**缺点**:需要更多配置 + +#### 漏桶 + +以恒定速率处理请求,排队突发流量。 + +```php +#[RateLimit(algorithm: Algorithm::LEAKY_BUCKET)] +``` + +**优点**:平滑输出速率,防止突发 +**缺点**:可能延迟请求 + +### 自定义限流器 + +你可以通过实现 `RateLimiterInterface` 来实现自己的限流器: + +```php +index(); +} catch (FriendsOfHyperf\RateLimit\Exception\RateLimitException $e) { + // 超出限流 + $message = $e->getMessage(); // "Too Many Attempts. Please try again in X seconds." + $code = $e->getCode(); // 429 +} +``` + +## 配置 + +组件使用 Hyperf 的 Redis 配置。你可以在注解或中间件中指定使用的 Redis 连接池: + +```php +// 使用特定的 Redis 连接池 +#[RateLimit(pool: 'rate_limit')] +``` + +确保在 `config/autoload/redis.php` 中配置 Redis 连接池: + +```php +return [ + 'default' => [ + 'host' => env('REDIS_HOST', 'localhost'), + 'port' => env('REDIS_PORT', 6379), + 'auth' => env('REDIS_AUTH', null), + 'db' => 0, + 'pool' => [ + 'min_connections' => 1, + 'max_connections' => 30, + ], + ], + 'rate_limit' => [ + 'host' => env('REDIS_HOST', 'localhost'), + 'port' => env('REDIS_PORT', 6379), + 'auth' => env('REDIS_AUTH', null), + 'db' => 1, + 'pool' => [ + 'min_connections' => 5, + 'max_connections' => 50, + ], + ], +]; +``` + +## 示例 + +### 示例 1:登录限流 + +限制登录尝试以防止暴力破解: + +```php +#[RateLimit( + key: 'login:{email}', + maxAttempts: 5, + decay: 300, // 5 分钟 + response: 'Too many login attempts. Please try again after 5 minutes.', + responseCode: 429 +)] +public function login(string $email, string $password) +{ + // 登录逻辑 +} +``` + +### 示例 2:API 端点限流 + +为不同的 API 端点设置不同的限流: + +```php +class ApiController +{ + // 公共 API:每分钟 100 次请求 + #[RateLimit(maxAttempts: 100, decay: 60)] + public function public() + { + // 公共端点 + } + + // 高级 API:每分钟 1000 次请求 + #[RateLimit(maxAttempts: 1000, decay: 60)] + public function premium() + { + // 高级端点 + } +} +``` + +### 示例 3:基于用户的限流 + +按用户限流: + +```php +#[RateLimit( + key: ['user', '{userId}', 'action'], + maxAttempts: 10, + decay: 3600 // 1 小时 +)] +public function performAction(int $userId) +{ + // 操作逻辑 +} +``` + +### 示例 4:基于 IP 的限流 + +使用中间件按 IP 地址限流: + +```php +class IpRateLimitMiddleware extends RateLimitMiddleware +{ + protected function resolveKey(ServerRequestInterface $request): string + { + return 'ip:' . $this->getClientIp(); + } +} +``` + +### 示例 5:使用 AutoSort 的多级限流 + +使用 AutoSort 高效处理昂贵操作的多级限流: + +```php +class ReportController +{ + /** + * 昂贵的报告生成,多级保护 + * AutoSort 确保优先检查严格的限制,提升性能 + */ + #[AutoSort] + #[RateLimit(maxAttempts: 5, decay: 60, response: 'Too many requests. Max 5 per minute')] // 紧急制动 + #[RateLimit(maxAttempts: 30, decay: 3600, response: 'Hourly limit exceeded. Max 30 per hour')] // 持续负载 + #[RateLimit(maxAttempts: 100, decay: 86400, response: 'Daily limit exceeded. Max 100 per day')] // 每日上限 + public function generateReport($reportType) + { + // 昂贵的报告生成逻辑 + } +} +``` + +## 许可证 + +[MIT](LICENSE) diff --git a/src/rate-limit/src/Annotation/AutoSort.php b/src/rate-limit/src/Annotation/AutoSort.php new file mode 100644 index 000000000..7cb85f163 --- /dev/null +++ b/src/rate-limit/src/Annotation/AutoSort.php @@ -0,0 +1,20 @@ +method[RateLimit::class] ?? null; + $isAutoSort = $metadata->method[AutoSort::class] ?? false; + /** @var SplPriorityQueue $queue */ + $queue = new SplPriorityQueue(); foreach ($annotations?->toAnnotations() ?? [] as $annotation) { + /** @var RateLimit $annotation */ + $priority = 0; + if ($isAutoSort) { + $priority = 0 - $annotation->maxAttempts / $annotation->decay; + } + $queue->insert($annotation, $priority); + } + + foreach ($queue as $annotation) { /** @var RateLimit $annotation */ $key = $this->resolveKey($annotation->key, $proceedingJoinPoint); $limiter = $this->factory->make($annotation->algorithm, $annotation->pool);