RateLimitMiddleware.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. namespace App\Module\OpenAPI\Middleware;
  3. use App\Module\OpenAPI\Models\OpenApiApp;
  4. use App\Module\OpenAPI\Services\OpenApiService;
  5. use Closure;
  6. use Illuminate\Http\Request;
  7. use Illuminate\Support\Facades\Cache;
  8. use Illuminate\Support\Facades\Log;
  9. /**
  10. * API频率限制中间件
  11. *
  12. * 用于控制API调用频率,防止滥用
  13. */
  14. class RateLimitMiddleware
  15. {
  16. protected OpenApiService $openApiService;
  17. public function __construct(OpenApiService $openApiService)
  18. {
  19. $this->openApiService = $openApiService;
  20. }
  21. /**
  22. * 处理请求
  23. *
  24. * @param Request $request
  25. * @param Closure $next
  26. * @param string|null $limit 限制规则,格式:60,1 表示每分钟60次
  27. * @return mixed
  28. */
  29. public function handle(Request $request, Closure $next, string $limit = null)
  30. {
  31. // 获取应用信息
  32. $app = $this->getAppFromRequest($request);
  33. if (!$app) {
  34. return $this->rateLimitResponse('应用信息不存在');
  35. }
  36. // 解析限制规则
  37. $rateLimit = $this->parseRateLimit($limit, $app);
  38. // 检查频率限制
  39. if (!$this->checkRateLimit($app, $rateLimit, $request)) {
  40. return $this->rateLimitResponse('请求频率超出限制');
  41. }
  42. // 记录请求
  43. $this->recordRequest($app, $request);
  44. return $next($request);
  45. }
  46. /**
  47. * 从请求中获取应用信息
  48. *
  49. * @param Request $request
  50. * @return OpenApiApp|null
  51. */
  52. protected function getAppFromRequest(Request $request): ?OpenApiApp
  53. {
  54. // 从请求属性中获取应用信息(由认证中间件设置)
  55. return $request->attributes->get('openapi_app');
  56. }
  57. /**
  58. * 解析限制规则
  59. *
  60. * @param string|null $limit
  61. * @param OpenApiApp $app
  62. * @return array
  63. */
  64. protected function parseRateLimit(?string $limit, OpenApiApp $app): array
  65. {
  66. // 如果没有指定限制,使用应用配置的限制
  67. if (!$limit) {
  68. $rateLimits = $app->rate_limits ?? [];
  69. return [
  70. 'requests_per_minute' => $rateLimits['requests_per_minute'] ?? 60,
  71. 'requests_per_hour' => $rateLimits['requests_per_hour'] ?? 1000,
  72. 'requests_per_day' => $rateLimits['requests_per_day'] ?? 10000,
  73. ];
  74. }
  75. // 解析格式:60,1 表示每分钟60次
  76. $parts = explode(',', $limit);
  77. $maxAttempts = (int)($parts[0] ?? 60);
  78. $decayMinutes = (int)($parts[1] ?? 1);
  79. return [
  80. 'requests_per_minute' => $decayMinutes === 1 ? $maxAttempts : 0,
  81. 'requests_per_hour' => $decayMinutes === 60 ? $maxAttempts : 0,
  82. 'requests_per_day' => $decayMinutes === 1440 ? $maxAttempts : 0,
  83. ];
  84. }
  85. /**
  86. * 检查频率限制
  87. *
  88. * @param OpenApiApp $app
  89. * @param array $rateLimit
  90. * @param Request $request
  91. * @return bool
  92. */
  93. protected function checkRateLimit(OpenApiApp $app, array $rateLimit, Request $request): bool
  94. {
  95. $ip = $request->ip();
  96. $appId = $app->app_id;
  97. // 检查每分钟限制
  98. if ($rateLimit['requests_per_minute'] > 0) {
  99. $key = "rate_limit:minute:{$appId}:{$ip}";
  100. if (!$this->checkLimit($key, $rateLimit['requests_per_minute'], 60)) {
  101. return false;
  102. }
  103. }
  104. // 检查每小时限制
  105. if ($rateLimit['requests_per_hour'] > 0) {
  106. $key = "rate_limit:hour:{$appId}:{$ip}";
  107. if (!$this->checkLimit($key, $rateLimit['requests_per_hour'], 3600)) {
  108. return false;
  109. }
  110. }
  111. // 检查每天限制
  112. if ($rateLimit['requests_per_day'] > 0) {
  113. $key = "rate_limit:day:{$appId}:{$ip}";
  114. if (!$this->checkLimit($key, $rateLimit['requests_per_day'], 86400)) {
  115. return false;
  116. }
  117. }
  118. return true;
  119. }
  120. /**
  121. * 检查单个限制
  122. *
  123. * @param string $key
  124. * @param int $maxAttempts
  125. * @param int $decaySeconds
  126. * @return bool
  127. */
  128. protected function checkLimit(string $key, int $maxAttempts, int $decaySeconds): bool
  129. {
  130. $attempts = Cache::get($key, 0);
  131. if ($attempts >= $maxAttempts) {
  132. Log::warning("Rate limit exceeded", [
  133. 'key' => $key,
  134. 'attempts' => $attempts,
  135. 'max_attempts' => $maxAttempts
  136. ]);
  137. return false;
  138. }
  139. // 增加计数
  140. Cache::put($key, $attempts + 1, $decaySeconds);
  141. return true;
  142. }
  143. /**
  144. * 记录请求
  145. *
  146. * @param OpenApiApp $app
  147. * @param Request $request
  148. * @return void
  149. */
  150. protected function recordRequest(OpenApiApp $app, Request $request): void
  151. {
  152. // 这里可以记录请求日志,用于统计分析
  153. Log::info("API request", [
  154. 'app_id' => $app->app_id,
  155. 'ip' => $request->ip(),
  156. 'uri' => $request->getRequestUri(),
  157. 'method' => $request->getMethod(),
  158. 'user_agent' => $request->userAgent(),
  159. ]);
  160. }
  161. /**
  162. * 返回限流响应
  163. *
  164. * @param string $message
  165. * @return \Illuminate\Http\JsonResponse
  166. */
  167. protected function rateLimitResponse(string $message)
  168. {
  169. return response()->json([
  170. 'error' => 'rate_limit_exceeded',
  171. 'message' => $message,
  172. 'retry_after' => 60, // 建议重试时间(秒)
  173. ], 429);
  174. }
  175. }