CaiWuXiTong/app/Console/Commands/BusinessDataPlan.php

90 lines
4.1 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Modules\EcommerceFinance\Services\BusinessDataPlacement;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use Throwable;
class BusinessDataPlan extends Command
{
protected $signature = 'business-data:plan {--json : 输出机器可读 JSON}';
protected $description = '只读核对业务镜像数据集的页面用途、当前体量和目标存储层';
public function handle(BusinessMirrorDatasetRegistry $datasets, BusinessDataPlacement $placements): int
{
try {
$registered = $datasets->all();
$policies = $placements->policies();
$codeOwned = array_keys((array) config('upstream_mirrors.datasets', []));
$missing = array_values(array_diff($codeOwned, array_keys($policies)));
$unknown = array_values(array_diff(array_keys($policies), $codeOwned));
$sizes = $this->tableSizes($placements);
$rows = [];
foreach ($registered as $key => $dataset) {
$policy = in_array($key, $missing, true)
? ['role' => 'unclassified', 'consumer' => '', 'target_store' => 'unclassified', 'sync' => 'unknown']
: $placements->policy($key);
$size = $sizes[$dataset->targetTable()] ?? ['rows' => null, 'bytes' => null];
$state = $placements->state($key);
$rows[] = [
'dataset' => $key,
'table' => $dataset->targetTable(),
'role' => $policy['role'] ?? 'unclassified',
'consumer' => $policy['consumer'] ?? '',
'target_store' => $policy['target_store'] ?? 'unclassified',
'sync' => $policy['sync'] ?? 'always',
'estimated_rows' => $size['rows'],
'size_mib' => $size['bytes'] === null ? null : round($size['bytes'] / 1024 / 1024, 1),
'migration_phase' => $state['phase'] ?? 'not_started',
];
}
$payload = ['datasets' => $rows, 'missing_policies' => $missing, 'unknown_policies' => $unknown];
if ($this->option('json')) {
$this->line(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));
} else {
$this->table(
['数据集', '当前表', '角色', '页面/用途', '目标层', '同步', '估算行数', 'MiB', '迁移阶段'],
array_map(static fn (array $row): array => array_values($row), $rows),
);
if ($missing !== []) {
$this->error('缺少存储策略:' . implode(', ', $missing));
}
if ($unknown !== []) {
$this->error('存在无对应数据集的存储策略:' . implode(', ', $unknown));
}
}
return $missing === [] && $unknown === [] ? self::SUCCESS : self::FAILURE;
} catch (Throwable $exception) {
report($exception);
$this->error('无法读取业务数据分层计划,请确认共享系统库和业务镜像库连接可用。');
return self::FAILURE;
}
}
/** @return array<string,array{rows:?int,bytes:?int}> */
private function tableSizes(BusinessDataPlacement $placements): array
{
$connection = $placements->connection();
if ($connection->getDriverName() !== 'mysql') {
return [];
}
$database = $connection->getDatabaseName();
return $connection->table('information_schema.TABLES')
->where('TABLE_SCHEMA', $database)
->get(['TABLE_NAME', 'TABLE_ROWS', 'DATA_LENGTH', 'INDEX_LENGTH'])
->mapWithKeys(static fn (object $row): array => [
(string) $row->TABLE_NAME => [
'rows' => $row->TABLE_ROWS === null ? null : (int) $row->TABLE_ROWS,
'bytes' => (int) $row->DATA_LENGTH + (int) $row->INDEX_LENGTH,
],
])->all();
}
}