| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- <?php
- namespace App\Module\OpenAPI\Middleware;
- use App\Module\OpenAPI\Models\OpenApiApp;
- use App\Module\OpenAPI\Services\OpenApiService;
- use Closure;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\Log;
- /**
- * API频率限制中间件
- *
- * 用于控制API调用频率,防止滥用
- */
- class RateLimitMiddleware
- {
- protected OpenApiService $openApiService;
- public function __construct(OpenApiService $openApiService)
- {
- $this->openApiService = $openApiService;
- }
- /**
- * 处理请求
- *
- * @param Request $request
- * @param Closure $next
- * @param string|null $limit 限制规则,格式:60,1 表示每分钟60次
- * @return mixed
- */
- public function handle(Request $request, Closure $next, string $limit = null)
- {
- // 获取应用信息
- $app = $this->getAppFromRequest($request);
-
- if (!$app) {
- return $this->rateLimitResponse('应用信息不存在');
- }
- // 解析限制规则
- $rateLimit = $this->parseRateLimit($limit, $app);
-
- // 检查频率限制
- if (!$this->checkRateLimit($app, $rateLimit, $request)) {
- return $this->rateLimitResponse('请求频率超出限制');
- }
- // 记录请求
- $this->recordRequest($app, $request);
- return $next($request);
- }
- /**
- * 从请求中获取应用信息
- *
- * @param Request $request
- * @return OpenApiApp|null
- */
- protected function getAppFromRequest(Request $request): ?OpenApiApp
- {
- // 从请求属性中获取应用信息(由认证中间件设置)
- return $request->attributes->get('openapi_app');
- }
- /**
- * 解析限制规则
- *
- * @param string|null $limit
- * @param OpenApiApp $app
- * @return array
- */
- protected function parseRateLimit(?string $limit, OpenApiApp $app): array
- {
- // 如果没有指定限制,使用应用配置的限制
- if (!$limit) {
- $rateLimits = $app->rate_limits ?? [];
- return [
- 'requests_per_minute' => $rateLimits['requests_per_minute'] ?? 60,
- 'requests_per_hour' => $rateLimits['requests_per_hour'] ?? 1000,
- 'requests_per_day' => $rateLimits['requests_per_day'] ?? 10000,
- ];
- }
- // 解析格式:60,1 表示每分钟60次
- $parts = explode(',', $limit);
- $maxAttempts = (int)($parts[0] ?? 60);
- $decayMinutes = (int)($parts[1] ?? 1);
- return [
- 'requests_per_minute' => $decayMinutes === 1 ? $maxAttempts : 0,
- 'requests_per_hour' => $decayMinutes === 60 ? $maxAttempts : 0,
- 'requests_per_day' => $decayMinutes === 1440 ? $maxAttempts : 0,
- ];
- }
- /**
- * 检查频率限制
- *
- * @param OpenApiApp $app
- * @param array $rateLimit
- * @param Request $request
- * @return bool
- */
- protected function checkRateLimit(OpenApiApp $app, array $rateLimit, Request $request): bool
- {
- $ip = $request->ip();
- $appId = $app->app_id;
- // 检查每分钟限制
- if ($rateLimit['requests_per_minute'] > 0) {
- $key = "rate_limit:minute:{$appId}:{$ip}";
- if (!$this->checkLimit($key, $rateLimit['requests_per_minute'], 60)) {
- return false;
- }
- }
- // 检查每小时限制
- if ($rateLimit['requests_per_hour'] > 0) {
- $key = "rate_limit:hour:{$appId}:{$ip}";
- if (!$this->checkLimit($key, $rateLimit['requests_per_hour'], 3600)) {
- return false;
- }
- }
- // 检查每天限制
- if ($rateLimit['requests_per_day'] > 0) {
- $key = "rate_limit:day:{$appId}:{$ip}";
- if (!$this->checkLimit($key, $rateLimit['requests_per_day'], 86400)) {
- return false;
- }
- }
- return true;
- }
- /**
- * 检查单个限制
- *
- * @param string $key
- * @param int $maxAttempts
- * @param int $decaySeconds
- * @return bool
- */
- protected function checkLimit(string $key, int $maxAttempts, int $decaySeconds): bool
- {
- $attempts = Cache::get($key, 0);
-
- if ($attempts >= $maxAttempts) {
- Log::warning("Rate limit exceeded", [
- 'key' => $key,
- 'attempts' => $attempts,
- 'max_attempts' => $maxAttempts
- ]);
- return false;
- }
- // 增加计数
- Cache::put($key, $attempts + 1, $decaySeconds);
-
- return true;
- }
- /**
- * 记录请求
- *
- * @param OpenApiApp $app
- * @param Request $request
- * @return void
- */
- protected function recordRequest(OpenApiApp $app, Request $request): void
- {
- // 这里可以记录请求日志,用于统计分析
- Log::info("API request", [
- 'app_id' => $app->app_id,
- 'ip' => $request->ip(),
- 'uri' => $request->getRequestUri(),
- 'method' => $request->getMethod(),
- 'user_agent' => $request->userAgent(),
- ]);
- }
- /**
- * 返回限流响应
- *
- * @param string $message
- * @return \Illuminate\Http\JsonResponse
- */
- protected function rateLimitResponse(string $message)
- {
- return response()->json([
- 'error' => 'rate_limit_exceeded',
- 'message' => $message,
- 'retry_after' => 60, // 建议重试时间(秒)
- ], 429);
- }
- }
|