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()]); while (true) { $job = DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('status', 'pending') ->orderBy('id') ->first(); if ($job === null) { break; } DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('id', $job->id) ->where('status', 'pending') ->update([ 'status' => 'running', 'attempts' => DB::raw('attempts + 1'), 'updated_at' => now(), ]); try { $process = Process::fromShellCommandline( implode(' ', [ escapeshellarg(PHP_BINARY ?: 'php'), 'artisan', 'store-receipt:order-match:sync', escapeshellarg((string) $job->month), '--platform=' . escapeshellarg((string) $job->platform), ]), base_path() ); $process->setTimeout(7200); $process->run(); $output = trim($process->getOutput() . "\n" . $process->getErrorOutput()); // 同步期间又保存了映射时,dispatchFor 已把状态改回 pending; // 这里只在仍为 running 时写 done,不能吞掉新请求。 DB::table(AccountMappingResyncDispatcher::JOBS_TABLE) ->where('id', $job->id) ->where('status', 'running') ->update([ 'status' => $process->isSuccessful() ? 'done' : 'failed', 'message' => mb_substr( $output !== '' ? $output : ($process->isSuccessful() ? '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; } }