CaiWuXiTong/app/Console/Commands/SyncProductCatalogMirror.php

120 lines
4.3 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 App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use Modules\EcommerceFinance\Services\BusinessMirrorSynchronizer;
use Throwable;
class SyncProductCatalogMirror extends Command
{
protected $signature = 'business-mirror:sync-product-catalog
{--full : 强制启动或继续普通商品全量校准}
{--batch= : 每批最多读取行数}
{--max-batches= : 本轮最多批次数,0 表示直到完成}';
protected $description = '高频同步聚水潭普通商品,并在每周低峰期执行可断点全量校准';
public function handle(
BusinessMirrorDatasetRegistry $datasets,
BusinessMirrorSynchronizer $synchronizer,
GenericBusinessMirrorRepository $mirror,
): int {
if (! (bool) config('upstream_mirrors.refresh_enabled', false) && ! $this->option('full')) {
return self::SUCCESS;
}
$datasetKey = (string) config(
'upstream_mirrors.product_catalog.dataset_key',
'operations_replica.rpt.jst_sku',
);
try {
$dataset = $datasets->get($datasetKey);
$state = $mirror->state($dataset);
$full = $this->shouldRunFull($state);
$result = $synchronizer->sync(
$datasetKey,
$full,
$this->option('batch') === null
? (int) config('upstream_mirrors.product_catalog.batch_size', 1000)
: (int) $this->option('batch'),
$this->option('max-batches') === null
? (int) config('upstream_mirrors.product_catalog.max_batches_per_run', 5)
: (int) $this->option('max-batches'),
);
} catch (Throwable $exception) {
Log::error('普通商品共享镜像同步失败。', [
'dataset' => $datasetKey,
'exception' => $exception::class,
]);
$this->error('普通商品同步失败,已保留上一版共享数据。');
return self::FAILURE;
}
if ($result['status'] === 'requires_full_sync') {
$this->warn('普通商品尚未完成首次全量同步,请完成备份后显式使用 --full。');
return self::SUCCESS;
}
$this->info(sprintf(
'普通商品%s:%s,本轮 %d 批,累计处理 %d 行。',
$result['mode'] === 'full' ? '全量校准' : '增量同步',
$result['status'] === 'succeeded' ? '完成' : '进行中',
$result['batches'],
$result['rows'],
));
return self::SUCCESS;
}
/** @param array<string,mixed>|null $state */
private function shouldRunFull(?array $state): bool
{
if ((bool) $this->option('full')) {
return true;
}
if (is_array($state)
&& ($state['run_mode'] ?? null) === 'full'
&& in_array($state['status'] ?? null, ['running', 'failed'], true)) {
return true;
}
if (! is_array($state) || ($state['initial_completed_at'] ?? null) === null) {
return false;
}
$now = now();
if (! $this->isReconcileWindow($now)) {
return false;
}
$lastFull = $state['last_full_succeeded_at'] ?? null;
if ($lastFull === null || $lastFull === '') {
return true;
}
$hours = max((int) config('upstream_mirrors.product_catalog.full_reconcile_hours', 168), 1);
return Carbon::parse((string) $lastFull)->addHours($hours)->lte($now);
}
private function isReconcileWindow(Carbon $now): bool
{
$weekday = min(max((int) config('upstream_mirrors.product_catalog.reconcile_weekday', 0), 0), 6);
$startHour = min(max((int) config('upstream_mirrors.product_catalog.reconcile_start_hour', 2), 0), 23);
$windowHours = min(max((int) config('upstream_mirrors.product_catalog.reconcile_window_hours', 6), 1), 24 - $startHour);
return $now->dayOfWeek === $weekday
&& $now->hour >= $startHour
&& $now->hour < $startHour + $windowHours;
}
}