QuotaResetCommand.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <?php
  2. namespace App\Module\ThirdParty\Commands;
  3. use Illuminate\Console\Command;
  4. use App\Module\ThirdParty\Models\ThirdPartyQuota;
  5. use App\Module\ThirdParty\Enums\QUOTA_TYPE;
  6. /**
  7. * 第三方服务配额重置命令
  8. */
  9. class QuotaResetCommand extends Command
  10. {
  11. /**
  12. * 命令签名
  13. *
  14. * @var string
  15. */
  16. protected $signature = 'thirdparty:quota-reset
  17. {--service= : 指定服务ID}
  18. {--type= : 指定配额类型}
  19. {--force : 强制重置所有配额}
  20. {--dry-run : 只显示将要重置的配额,不实际执行}';
  21. /**
  22. * 命令描述
  23. *
  24. * @var string
  25. */
  26. protected $description = '重置第三方服务配额';
  27. /**
  28. * 执行命令
  29. *
  30. * @return int
  31. */
  32. public function handle(): int
  33. {
  34. $this->info('开始检查和重置第三方服务配额...');
  35. try {
  36. $quotas = $this->getQuotasToReset();
  37. if ($quotas->isEmpty()) {
  38. $this->info('没有找到需要重置的配额');
  39. return Command::SUCCESS;
  40. }
  41. $this->displayQuotasToReset($quotas);
  42. if ($this->option('dry-run')) {
  43. $this->info('这是预览模式,没有实际重置配额');
  44. return Command::SUCCESS;
  45. }
  46. if (!$this->option('force') && !$this->confirm('确认要重置这些配额吗?')) {
  47. $this->info('操作已取消');
  48. return Command::SUCCESS;
  49. }
  50. $resetCount = $this->resetQuotas($quotas);
  51. $this->info("成功重置了 {$resetCount} 个配额");
  52. return Command::SUCCESS;
  53. } catch (\Exception $e) {
  54. $this->error("配额重置失败: {$e->getMessage()}");
  55. return Command::FAILURE;
  56. }
  57. }
  58. /**
  59. * 获取需要重置的配额
  60. *
  61. * @return \Illuminate\Database\Eloquent\Collection
  62. */
  63. protected function getQuotasToReset()
  64. {
  65. $query = ThirdPartyQuota::with('service')->where('is_active', true);
  66. // 按服务过滤
  67. if ($serviceId = $this->option('service')) {
  68. $query->where('service_id', $serviceId);
  69. }
  70. // 按类型过滤
  71. if ($type = $this->option('type')) {
  72. if (!QUOTA_TYPE::tryFrom($type)) {
  73. throw new \Exception("无效的配额类型: {$type}");
  74. }
  75. $query->where('type', $type);
  76. }
  77. // 如果强制重置,返回所有匹配的配额
  78. if ($this->option('force')) {
  79. return $query->get();
  80. }
  81. // 否则只返回需要重置的配额
  82. return $query->get()->filter(function ($quota) {
  83. return $quota->needsReset();
  84. });
  85. }
  86. /**
  87. * 显示将要重置的配额
  88. *
  89. * @param \Illuminate\Database\Eloquent\Collection $quotas
  90. * @return void
  91. */
  92. protected function displayQuotasToReset($quotas): void
  93. {
  94. $this->info("\n将要重置的配额:");
  95. $this->line(str_repeat('-', 100));
  96. $headers = ['服务', '配额类型', '限制值', '已使用', '使用率', '状态', '重置原因'];
  97. $rows = [];
  98. foreach ($quotas as $quota) {
  99. $usagePercentage = round($quota->getUsagePercentage(), 2) . '%';
  100. $status = $quota->getStatusLabel();
  101. $reason = '';
  102. if ($this->option('force')) {
  103. $reason = '强制重置';
  104. } elseif ($quota->needsReset()) {
  105. if ($quota->window_end && now()->gt($quota->window_end)) {
  106. $reason = '时间窗口已过期';
  107. } elseif ($quota->reset_at && now()->gte($quota->reset_at)) {
  108. $reason = '到达重置时间';
  109. } else {
  110. $reason = '需要重置';
  111. }
  112. }
  113. $rows[] = [
  114. $quota->service->name,
  115. $quota->getTypeLabel(),
  116. number_format($quota->limit_value),
  117. number_format($quota->used_value),
  118. $usagePercentage,
  119. $status,
  120. $reason,
  121. ];
  122. }
  123. $this->table($headers, $rows);
  124. }
  125. /**
  126. * 重置配额
  127. *
  128. * @param \Illuminate\Database\Eloquent\Collection $quotas
  129. * @return int
  130. */
  131. protected function resetQuotas($quotas): int
  132. {
  133. $resetCount = 0;
  134. $progressBar = $this->output->createProgressBar($quotas->count());
  135. $progressBar->start();
  136. foreach ($quotas as $quota) {
  137. try {
  138. $quota->resetQuota();
  139. $resetCount++;
  140. $this->line("\n重置配额: {$quota->service->name} - {$quota->getTypeLabel()}");
  141. } catch (\Exception $e) {
  142. $this->line("\n<fg=red>重置失败: {$quota->service->name} - {$quota->getTypeLabel()}: {$e->getMessage()}</fg>");
  143. }
  144. $progressBar->advance();
  145. }
  146. $progressBar->finish();
  147. $this->line('');
  148. return $resetCount;
  149. }
  150. /**
  151. * 显示配额统计信息
  152. *
  153. * @return void
  154. */
  155. protected function displayQuotaStats(): void
  156. {
  157. $totalQuotas = ThirdPartyQuota::where('is_active', true)->count();
  158. $exceededQuotas = ThirdPartyQuota::where('is_active', true)
  159. ->where('is_exceeded', true)
  160. ->count();
  161. $nearThresholdQuotas = ThirdPartyQuota::where('is_active', true)
  162. ->whereRaw('(used_value / limit_value * 100) >= alert_threshold')
  163. ->count();
  164. $this->info("\n配额统计:");
  165. $this->line("总配额数: {$totalQuotas}");
  166. $this->line("已超限配额: {$exceededQuotas}");
  167. $this->line("接近阈值配额: {$nearThresholdQuotas}");
  168. if ($exceededQuotas > 0) {
  169. $this->warn("\n警告: 有 {$exceededQuotas} 个配额已超限,可能影响服务调用");
  170. }
  171. }
  172. /**
  173. * 验证配额类型
  174. *
  175. * @param string $type
  176. * @return bool
  177. */
  178. protected function validateQuotaType(string $type): bool
  179. {
  180. return QUOTA_TYPE::tryFrom($type) !== null;
  181. }
  182. /**
  183. * 获取所有可用的配额类型
  184. *
  185. * @return array
  186. */
  187. protected function getAvailableQuotaTypes(): array
  188. {
  189. return array_map(fn($case) => $case->value, QUOTA_TYPE::cases());
  190. }
  191. }