CaiWuXiTong/modules/EcommerceFinance/Services/AccountMappingResyncDispatcher.php

129 lines
4.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Modules\EcommerceFinance\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
use Throwable;
/**
* 科目映射表变更后,把受影响的「业绩明细」平台×月份放进后台重跑队列。
*
* 业绩明细的优惠/冲减/退款金额是同步时按映射表算好落库的,改映射必须重跑对应
* 平台×月份才会更新;这里把重跑做成后台自动执行,页面保存后无需人工执行命令。
*/
class AccountMappingResyncDispatcher
{
public const JOBS_TABLE = 'ec_account_mapping_resync_jobs';
/**
* 科目映射表的平台名 → [同步命令 platform 键, 业绩明细平台名]。
* 只保留已接入业绩同步的平台;其它平台(如唯品会JX)没有同步任务。
*/
private const PLATFORM_MAP = [
'淘宝' => [['taobao-alipay', '淘系支付宝'], ['taobao-wechat', '淘系微信']],
'天猫' => [['taobao-alipay', '淘系支付宝'], ['taobao-wechat', '淘系微信']],
'阿里巴巴' => [['taobao-alipay', '淘系支付宝']],
'拼多多' => [['pinduoduo', '拼多多']],
'京喜' => [['jingxi', '京喜账单']],
'京东' => [['jd-transactions', '京东账单'], ['jd-wallet', '京东网银钱包']],
'抖音' => [['douyin', '抖音']],
'得物' => [['dewu', '得物']],
];
/**
* @param list<string> $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());
}
}
}