[['taobao-alipay', '淘系支付宝'], ['taobao-wechat', '淘系微信']], '天猫' => [['taobao-alipay', '淘系支付宝'], ['taobao-wechat', '淘系微信']], '阿里巴巴' => [['taobao-alipay', '淘系支付宝']], '拼多多' => [['pinduoduo', '拼多多']], '京喜' => [['jingxi', '京喜账单']], '京东' => [['jd-transactions', '京东账单'], ['jd-wallet', '京东网银钱包']], '抖音' => [['douyin', '抖音']], '得物' => [['dewu', '得物']], ]; /** * @param list $mappingPlatforms 科目映射表里的平台名 * @return array{jobs:int} */ public function dispatchFor(array $mappingPlatforms): array { $targets = []; foreach ($mappingPlatforms as $platform) { foreach (self::PLATFORM_MAP[trim((string) $platform)] ?? [] as [$key, $label]) { $targets[$key] = $label; } } if ($targets === []) { return ['jobs' => 0]; } $mirror = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); $periods = $mirror->table('store_receipt_order_match_rows') ->whereIn('platform', array_values($targets)) ->select(['platform', 'report_month']) ->distinct() ->orderBy('report_month') ->get()->groupBy('platform'); if ($periods->isEmpty()) { return ['jobs' => 0]; } $now = now(); $jobs = 0; foreach ($targets as $key => $label) { $months = $periods->get($label, collect())->map(fn ($row) => substr((string) $row->report_month, 0, 7))->unique(); foreach ($months as $month) { // running 时再次保存映射,会把状态改回 pending;当前进程完成时发现状态已变, // 不会覆盖成 done,于是会紧接着再跑一遍,避免漏掉刚保存的新规则。 $affected = DB::table(self::JOBS_TABLE)->updateOrInsert( ['platform' => (string) $key, 'month' => (string) $month], [ 'status' => 'pending', 'message' => null, 'updated_at' => $now, 'created_at' => $now, ] ); if ($affected) { $jobs++; } } } if ($jobs > 0) { $this->spawnWorker(); } return ['jobs' => $jobs]; } /** @return array{jobs:int} 所有已接入平台一起重跑(用于「恢复初始 112 条」) */ public function dispatchAll(): array { return $this->dispatchFor(array_keys(self::PLATFORM_MAP)); } /** * 起一个后台进程处理队列。 * * 已有进程在跑时新进程会拿不到锁直接退出;正在跑的进程处理完当前任务后会继续取新任务。 */ public function spawnWorker(): void { // Production cron consumes the durable queue in the resource-limited batch profile. if (config('upstream_pipeline.batch_worker_enabled', false)) { return; } $this->launchWorker(); } protected function launchWorker(): void { try { $php = escapeshellarg(PHP_BINARY ?: 'php'); $command = 'cd ' . escapeshellarg(base_path()) . ' && nohup ' . $php . ' artisan store-receipt:auto-resync >/dev/null 2>&1 &'; Process::fromShellCommandline($command, base_path()) ->disableOutput() ->setTimeout(10) ->run(); } catch (Throwable $exception) { // 后台进程起不来时任务仍留在队列里,可手动执行 store-receipt:auto-resync 补跑。 Log::warning('科目映射表自动重跑进程启动失败:' . $exception->getMessage()); } } }