Files
ulthon_admin/extend/base/common/command/scheme/Sync.php

168 lines
5.9 KiB
PHP

<?php
namespace base\common\command\scheme;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Config;
use think\facade\Db;
use app\common\service\scheme\SchemeToDbService;
use app\common\scheme\attribute\Table;
use ReflectionClass;
class Sync extends Command
{
protected function configure()
{
$this->setName('scheme:sync')
->addOption('skip-data', null, Option::VALUE_NONE, 'Skip data migration')
->addOption('force', null, Option::VALUE_NONE, 'Force execution without confirmation')
->setDescription('Synchronize Scheme classes to Database');
}
protected function execute(Input $input, Output $output)
{
$skipData = $input->getOption('skip-data');
$service = new SchemeToDbService();
$schemeDir = app()->getAppPath() . 'admin/scheme/';
$ignoreTables = Config::get('scheme.ignore_tables', []);
$connection = Config::get('database.default', 'mysql');
$prefix = Config::get('database.connections.' . $connection . '.prefix', '');
$backupPrefix = Config::get('scheme.backup_prefix', 'backup');
if (!is_dir($schemeDir)) {
$output->writeln("<error>Scheme directory not found: $schemeDir</error>");
return;
}
$files = glob($schemeDir . '*.php');
foreach ($files as $file) {
require_once $file;
$className = 'app\\admin\\scheme\\' . basename($file, '.php');
if (class_exists($className)) {
$tableName = $this->getTableNameFromScheme($className);
if (empty($tableName)) {
$output->writeln("Skipping $className (missing table name)");
continue;
}
$fullTableName = $this->getFullTableName($tableName, $prefix);
if ($this->isBackupTable($fullTableName, $prefix, $backupPrefix)) {
$output->writeln("Skipping $className (backup table: $fullTableName)");
continue;
}
if ($this->isIgnoredTable($fullTableName, $ignoreTables, $prefix)) {
$output->writeln("Skipping $className (ignored table: $fullTableName)");
continue;
}
try {
$diffs = $service->diff($className);
} catch (\Throwable $e) {
$output->writeln("<error>Check failed for $className: " . $e->getMessage() . "</error>");
continue;
}
if (count($diffs) === 1 && str_starts_with($diffs[0], '无法读取数据库表结构')) {
$output->writeln("<error>Check failed for $className: {$diffs[0]}</error>");
continue;
}
if (empty($diffs)) {
$output->writeln("Skipping $className (no schema changes)");
continue;
}
$output->writeln("Syncing $className...");
try {
$backup = $service->sync($className, $skipData);
$output->writeln("<info>Success!</info>");
if ($backup) {
$output->writeln("Backup created: $backup");
}
} catch (\Exception $e) {
$output->writeln("<error>Failed: " . $e->getMessage() . "</error>");
}
}
}
}
protected function getTableNameFromScheme(string $className): string
{
try {
$ref = new ReflectionClass($className);
$tableAttrs = $ref->getAttributes(Table::class);
if (empty($tableAttrs)) {
return '';
}
$tableAttr = $tableAttrs[0]->newInstance();
return (string)($tableAttr->name ?? '');
} catch (\Throwable $e) {
return '';
}
}
protected function isIgnoredTable(string $tableName, array $ignoreTables, string $prefix): bool
{
if (empty($ignoreTables)) {
return false;
}
$shortName = $tableName;
if ($prefix && str_starts_with($tableName, $prefix)) {
$shortName = substr($tableName, strlen($prefix));
}
return in_array($shortName, $ignoreTables, true) || in_array($tableName, $ignoreTables, true);
}
protected function normalizeCode(string $code): string
{
$code = str_replace(["\r\n", "\r"], "\n", $code);
$code = preg_replace('/[ \t]+$/m', '', $code);
return trim((string)$code);
}
protected function checkTableExists(string $connection, string $tableName): bool
{
$database = (string)Config::get('database.connections.' . $connection . '.database', '');
if ($database !== '') {
$rows = Db::connect($connection)->query(
'SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? LIMIT 1',
[$database, $tableName]
);
return !empty($rows);
}
$escaped = addcslashes($tableName, "\\_%");
$tables = Db::connect($connection)->query("SHOW TABLES LIKE '$escaped'");
return !empty($tables);
}
protected function getFullTableName(string $tableName, string $prefix): string
{
if ($prefix && !str_starts_with($tableName, $prefix)) {
return $prefix . $tableName;
}
return $tableName;
}
protected function isBackupTable(string $tableName, string $prefix, string $backupPrefix): bool
{
$basePrefix = $prefix . $backupPrefix;
if ($basePrefix === '') {
return false;
}
$pattern = '/^' . preg_quote($basePrefix, '/') . '_?\\d{14}(?:_.*)?$/';
return preg_match($pattern, $tableName) === 1;
}
}