SyncWagering.php 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Models\Wallet;
  4. use App\Services\PromotionFacts;
  5. use App\Services\WageringService;
  6. use App\Support\SyncCommandLock;
  7. use Illuminate\Console\Command;
  8. use Illuminate\Support\Facades\Cache;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Log;
  11. use Illuminate\Support\Facades\Schema;
  12. /** 刷新钱包缓存的“当前打码量”。只重算最近有投注变动的会员;--all 用于上线后首次全量计算。 */
  13. class SyncWagering extends Command
  14. {
  15. private const SINCE_KEY = 'wagering:sync:since';
  16. /** 上次刷新失败的会员,下次运行重试;时间窗口照常前进,避免一个坏钱包让扫描范围无限扩大 */
  17. private const FAILED_KEY = 'wagering:sync:failed';
  18. protected $signature = 'wagering:sync {--all : 重算全部钱包} {--member= : 只重算指定会员ID}';
  19. protected $description = '按有效投注刷新会员当前打码量/剩余打码量';
  20. public function handle(SyncCommandLock $lock): int
  21. {
  22. $exit = $lock->run('wagering:sync', fn () => $this->synchronize());
  23. if ($exit === null) $this->info('打码量同步已在运行,跳过本次执行');
  24. return $exit ?? self::SUCCESS;
  25. }
  26. private function synchronize(): int
  27. {
  28. if (!WageringService::enabled()) return self::SUCCESS;
  29. $startedAt = PromotionFacts::now();
  30. $errors = 0; $count = 0; $failed = [];
  31. $refresh = function ($wallets) use (&$errors, &$count, &$failed) {
  32. $facts = new PromotionFacts();
  33. foreach ($wallets as $wallet) {
  34. try { WageringService::refresh($wallet, $facts); ++$count; }
  35. catch (\Throwable $e) {
  36. ++$errors; $failed[] = (string)$wallet->member_id;
  37. Log::error('wagering_sync_failed', ['member_id' => $wallet->member_id, 'exception' => $e]);
  38. }
  39. }
  40. };
  41. if ($this->option('member')) {
  42. $refresh(Wallet::where('member_id', (string)$this->option('member'))->get());
  43. } elseif ($this->option('all')) {
  44. Wallet::query()->orderBy('id')->chunkById(200, $refresh);
  45. } else {
  46. // 与上次运行重叠两分钟,覆盖注单延迟结算和同步时钟误差。
  47. $since = Cache::get(self::SINCE_KEY) ?? date('Y-m-d H:i:s', strtotime($startedAt) - 600);
  48. $since = date('Y-m-d H:i:s', strtotime($since) - 120);
  49. $members = array_values(array_unique(array_merge($this->changedMembers($since), (array)Cache::get(self::FAILED_KEY, []))));
  50. foreach (array_chunk($members, 200) as $chunk) {
  51. $refresh(Wallet::whereIn('member_id', $chunk)->get());
  52. }
  53. Cache::forever(self::SINCE_KEY, $startedAt);
  54. Cache::forever(self::FAILED_KEY, array_values(array_unique($failed)));
  55. }
  56. $this->info('打码量同步完成,刷新:' . $count . ',失败:' . $errors);
  57. return $errors ? self::FAILURE : self::SUCCESS;
  58. }
  59. /** @return string[] */
  60. private function changedMembers(string $since): array
  61. {
  62. $members = [];
  63. foreach (['share_bet_facts', 'bets', 'jisu_game_order'] as $table) {
  64. if (!Schema::hasTable($table) || !Schema::hasColumn($table, 'updated_at')) continue;
  65. foreach (DB::table($table)->where('updated_at', '>=', $since)->whereNotNull('member_id')->distinct()->pluck('member_id') as $memberId) {
  66. $members[(string)$memberId] = true;
  67. }
  68. }
  69. return array_keys($members);
  70. }
  71. }