lock('store-receipt:auto-resync', 86400); if (! $lock->get()) { // 已有进程在跑;本次新入队的任务会在当前进程下一轮循环或每分钟兜底时执行。 return self::SUCCESS; } try { // 进程崩溃遗留的 running 任务超过 60 分钟视为可重试。 DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('status', 'running') ->where('updated_at', '<', now()->subMinutes(60)) ->update(['status' => 'pending', 'updated_at' => now()]); $processed = 0; $maxJobs = max(0, (int) $this->option('max-jobs')); $deadline = microtime(true) + max(1, (int) $this->option('max-seconds')); while (($maxJobs === 0 || $processed < $maxJobs) && microtime(true) < $deadline) { $job = DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('status', 'pending') ->orderBy('id') ->first(); if ($job === null) { break; } $processed++; DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('id', $job->id) ->where('status', 'pending') ->update([ 'status' => 'running', 'attempts' => DB::raw('attempts + 1'), 'updated_at' => now(), ]); try { [$succeeded, $output] = $this->runSync($job); // 同步期间又保存了映射时,dispatchFor 已把状态改回 pending; // 这里只在仍为 running 时写 done,不能吞掉新请求。 DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('id', $job->id) ->where('status', 'running') ->update([ 'status' => $succeeded ? 'done' : 'failed', 'message' => mb_substr( $output !== '' ? $output : ($succeeded ? 'OK' : '同步命令执行失败'), 0, 1000 ), 'updated_at' => now(), ]); } catch (Throwable $exception) { Log::error('科目映射表自动重跑失败:' . $exception->getMessage()); DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('id', $job->id) ->where('status', 'running') ->update([ 'status' => 'failed', 'message' => mb_substr($exception->getMessage(), 0, 1000), 'updated_at' => now(), ]); } } } finally { $lock->release(); } return self::SUCCESS; } /** @return array{bool,string} */ protected function runSync(object $job): array { $process = new Process([ PHP_BINARY ?: 'php', 'artisan', 'store-receipt:order-match:sync', (string) $job->month, '--platform=' . $job->platform, ], base_path()); $process->setTimeout(7200); $process->run(); return [$process->isSuccessful(), trim($process->getOutput() . "\n" . $process->getErrorOutput())]; } }