116 lines
4.1 KiB
PHP
116 lines
4.1 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use Illuminate\Console\Command;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
|
||
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
|
||
use Modules\EcommerceFinance\Services\BusinessMirrorSynchronizer;
|
||
use Throwable;
|
||
|
||
class SyncBusinessMirrors extends Command
|
||
{
|
||
protected $signature = 'business-mirror:sync-tables
|
||
{dataset? : 只同步指定数据集}
|
||
{--full : 执行或继续首次全量/校准同步}
|
||
{--batch= : 每批最多读取行数}
|
||
{--max-batches= : 每个数据集本轮最多批次数,0 表示直到完成}
|
||
{--max-datasets= : 本轮最多处理的数据集数量}';
|
||
|
||
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;
|
||
}
|
||
|
||
$requested = $this->argument('dataset');
|
||
$keys = $requested === null
|
||
? $this->scheduledKeys($datasets, $mirror)
|
||
: [(string) $requested];
|
||
$maxDatasets = max((int) ($this->option('max-datasets')
|
||
?? config('upstream_mirrors.table_sync.max_datasets_per_run', 1)), 1);
|
||
$processed = 0;
|
||
$failed = false;
|
||
|
||
foreach ($keys as $key) {
|
||
if ($processed >= $maxDatasets) {
|
||
break;
|
||
}
|
||
|
||
try {
|
||
$result = $synchronizer->sync(
|
||
$key,
|
||
(bool) $this->option('full'),
|
||
$this->option('batch') === null ? null : (int) $this->option('batch'),
|
||
$this->option('max-batches') === null ? null : (int) $this->option('max-batches'),
|
||
);
|
||
} catch (Throwable $exception) {
|
||
$failed = true;
|
||
Log::error('共享业务镜像表同步失败。', ['dataset' => $key, 'exception' => $exception::class]);
|
||
$this->error($key . ':同步失败,已保留上一版共享数据。');
|
||
$processed++;
|
||
continue;
|
||
}
|
||
|
||
if ($result['status'] === 'requires_full_sync') {
|
||
if ($requested !== null) {
|
||
$this->warn($key . ':尚未完成首次全量同步,请显式使用 --full。');
|
||
}
|
||
continue;
|
||
}
|
||
|
||
$processed++;
|
||
$this->info(sprintf(
|
||
'%s:%s,本轮 %d 批,累计处理 %d 行。',
|
||
$key,
|
||
$result['status'] === 'succeeded' ? '同步完成' : '同步进行中',
|
||
$result['batches'],
|
||
$result['rows'],
|
||
));
|
||
}
|
||
|
||
return $failed ? self::FAILURE : self::SUCCESS;
|
||
}
|
||
|
||
/** @return list<string> */
|
||
private function scheduledKeys(
|
||
BusinessMirrorDatasetRegistry $datasets,
|
||
GenericBusinessMirrorRepository $mirror,
|
||
): array {
|
||
$scheduled = [];
|
||
$dedicated = array_fill_keys(array_map(
|
||
'strval',
|
||
(array) config('upstream_mirrors.table_sync.dedicated_datasets', []),
|
||
), true);
|
||
|
||
foreach ($datasets->all() as $key => $dataset) {
|
||
if (isset($dedicated[$key])) {
|
||
continue;
|
||
}
|
||
|
||
$state = $mirror->state($dataset);
|
||
if (! is_array($state) || ($state['initial_completed_at'] ?? null) === null) {
|
||
continue;
|
||
}
|
||
|
||
$scheduled[] = [
|
||
'key' => $key,
|
||
'last_attempted_at' => (string) ($state['last_attempted_at'] ?? ''),
|
||
];
|
||
}
|
||
|
||
usort($scheduled, fn (array $left, array $right): int =>
|
||
[$left['last_attempted_at'], $left['key']] <=> [$right['last_attempted_at'], $right['key']]
|
||
);
|
||
|
||
return array_column($scheduled, 'key');
|
||
}
|
||
}
|