ThirdGameOrderBackfill.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Services\ThirdGameOrderService;
  4. use Carbon\Carbon;
  5. use Illuminate\Console\Command;
  6. class ThirdGameOrderBackfill extends Command
  7. {
  8. protected $signature = 'third-game:backfill-orders
  9. {--days=15 : 未指定时间时回填最近几天,1-15}
  10. {--start= : 开始时间,格式 Y-m-d H:i:s}
  11. {--end= : 结束时间,格式 Y-m-d H:i:s}';
  12. protected $description = '分段回填 api-bet 最近 15 天内的三方游戏历史订单';
  13. public function handle(ThirdGameOrderService $service): int
  14. {
  15. $timezone = config('app.timezone', 'Asia/Shanghai');
  16. $now = Carbon::now($timezone);
  17. $startOption = trim((string) $this->option('start'));
  18. $endOption = trim((string) $this->option('end'));
  19. if (($startOption === '') !== ($endOption === '')) {
  20. $this->error('--start 和 --end 必须同时提供');
  21. return self::FAILURE;
  22. }
  23. try {
  24. if ($startOption !== '') {
  25. $start = Carbon::createFromFormat('Y-m-d H:i:s', $startOption, $timezone);
  26. $end = Carbon::createFromFormat('Y-m-d H:i:s', $endOption, $timezone);
  27. } else {
  28. $days = (int) $this->option('days');
  29. if ($days < 1 || $days > 15) {
  30. $this->error('--days 必须是 1-15');
  31. return self::FAILURE;
  32. }
  33. $end = $now->copy();
  34. $start = $end->copy()->subDays($days);
  35. if ($days === 15) {
  36. $start->addMinute();
  37. }
  38. }
  39. } catch (\Throwable $e) {
  40. $this->error('时间格式错误,应为 Y-m-d H:i:s');
  41. return self::FAILURE;
  42. }
  43. if ($start->gte($end)) {
  44. $this->error('开始时间必须小于结束时间');
  45. return self::FAILURE;
  46. }
  47. if ($start->diffInSeconds($end) > 15 * 24 * 60 * 60) {
  48. $this->error('历史回填范围不能超过15天');
  49. return self::FAILURE;
  50. }
  51. if ($start->lt($now->copy()->subDays(15))) {
  52. $this->error('只能回填最近15天内的订单');
  53. return self::FAILURE;
  54. }
  55. $cursor = $start->copy();
  56. $totalSynced = 0;
  57. $windowCount = 0;
  58. $this->info(sprintf(
  59. '开始回填三方订单:%s 至 %s',
  60. $start->format('Y-m-d H:i:s'),
  61. $end->format('Y-m-d H:i:s')
  62. ));
  63. while ($cursor->lt($end)) {
  64. $windowEnd = $cursor->copy()->addHours(6);
  65. if ($windowEnd->gt($end)) {
  66. $windowEnd = $end->copy();
  67. }
  68. $page = 1;
  69. do {
  70. $result = $this->syncWithRateLimitRetry(
  71. $service,
  72. $cursor->format('Y-m-d H:i:s'),
  73. $windowEnd->format('Y-m-d H:i:s'),
  74. $page
  75. );
  76. if ($result === null) {
  77. return self::FAILURE;
  78. }
  79. $totalSynced += (int) ($result['synced'] ?? 0);
  80. $this->line(sprintf(
  81. '%s - %s page=%d received=%d synced=%d unmatched=%d',
  82. $cursor->format('Y-m-d H:i:s'),
  83. $windowEnd->format('Y-m-d H:i:s'),
  84. $page,
  85. (int) ($result['received'] ?? 0),
  86. (int) ($result['synced'] ?? 0),
  87. (int) ($result['unmatched_orders'] ?? 0)
  88. ));
  89. if (empty($result['has_more'])) {
  90. break;
  91. }
  92. if ($page >= 1000) {
  93. $this->error('单个时间段分页超过1000页,已停止回填');
  94. return self::FAILURE;
  95. }
  96. sleep(10);
  97. $page++;
  98. } while (true);
  99. $windowCount++;
  100. $cursor = $windowEnd;
  101. }
  102. $this->info("历史订单回填完成:{$windowCount} 个时间段,写入或更新 {$totalSynced} 条");
  103. return self::SUCCESS;
  104. }
  105. private function syncWithRateLimitRetry(
  106. ThirdGameOrderService $service,
  107. string $startTime,
  108. string $endTime,
  109. int $page
  110. ): ?array {
  111. while (true) {
  112. try {
  113. $result = $service->syncHistory($startTime, $endTime, $page, 2000);
  114. } catch (\Throwable $e) {
  115. $this->error($e->getMessage());
  116. return null;
  117. }
  118. if (!empty($result['ok'])) {
  119. return $result;
  120. }
  121. $message = (string) ($result['msg'] ?? '三方订单同步失败');
  122. if (!preg_match('/请在(\d+)秒后重试/u', $message, $matches)) {
  123. $this->error($message);
  124. return null;
  125. }
  126. $waitSeconds = max(1, (int) $matches[1] + 1);
  127. $this->warn("{$message},命令将在 {$waitSeconds} 秒后自动继续");
  128. sleep($waitSeconds);
  129. }
  130. }
  131. }