From 46c92cee8b89b69711dda0084e2ac57c43931558 Mon Sep 17 00:00:00 2001 From: Anyon Date: Thu, 2 Apr 2026 13:19:37 +0800 Subject: [PATCH] =?UTF-8?q?refactor(helper):=20=E7=A7=BB=E9=99=A4=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=E6=81=A2=E5=A4=8D=E5=91=BD=E4=BB=A4=E4=B8=8E=20DBAL?= =?UTF-8?q?=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/think-plugs-helper/composer.json | 3 +- .../think-plugs-helper/src/DbBackupStruct.php | 163 ------------ .../src/DbRestoreStruct.php | 251 ------------------ plugin/think-plugs-helper/src/Service.php | 2 - 4 files changed, 1 insertion(+), 418 deletions(-) delete mode 100644 plugin/think-plugs-helper/src/DbBackupStruct.php delete mode 100644 plugin/think-plugs-helper/src/DbRestoreStruct.php diff --git a/plugin/think-plugs-helper/composer.json b/plugin/think-plugs-helper/composer.json index 263f62526..fb83b6916 100644 --- a/plugin/think-plugs-helper/composer.json +++ b/plugin/think-plugs-helper/composer.json @@ -14,7 +14,6 @@ "php": "^8.1", "ext-json": "*", "ext-zlib": "*", - "doctrine/dbal": "^4.2", "topthink/think-orm": "^2.0|^3.0|^4.0", "topthink/think-ide-helper": "*" }, @@ -30,4 +29,4 @@ ] } } -} +} \ No newline at end of file diff --git a/plugin/think-plugs-helper/src/DbBackupStruct.php b/plugin/think-plugs-helper/src/DbBackupStruct.php deleted file mode 100644 index 366526e11..000000000 --- a/plugin/think-plugs-helper/src/DbBackupStruct.php +++ /dev/null @@ -1,163 +0,0 @@ -setName('xadmin:helper:backup'); - $this->addOption('all', 'a', Option::VALUE_NONE, 'Backup All Tables'); - $this->setDescription('恢复数据前是否强制清空所有表数据'); - } - - /** - * 指令执行入口. - */ - public function handle(): void - { - $this->backupSchema() && $this->backupTables(); - } - - /** - * 检查是否允许执行任务 - * @return bool 返回true表示允许执行 - */ - public function isEnabled(): bool - { - return SystemService::isDebug(); - } - - /** - * 备份数据库结构,使用 gzip 压缩保存. - */ - protected function backupSchema(): bool - { - try { - $outputFile = $this->getSchemaPath(); - if (!($gz = gzopen($outputFile, 'w9'))) { - $this->output->error("❌ 无法打开压缩文件写入数据库结构:{$outputFile}"); - return false; - } - $schema = $this->makeConnect()->createSchemaManager(); - gzwrite($gz, serialize($schema->introspectSchema())); - gzclose($gz); - $this->output->info("✅ 数据库结构已压缩保存至:{$outputFile}"); - return true; - } catch (\Throwable $throwable) { - $this->output->error("❌ 数据库结构导出失败:{$throwable->getMessage()}"); - return false; - } - } - - /** - * 备份数据表数据,gzip 压缩写入. - */ - protected function backupTables(): bool - { - $backupPath = $this->getBackupPath(); - is_dir(dirname($backupPath)) || mkdir(dirname($backupPath), 0755, true); - if (!($gz = gzopen($backupPath, 'w9'))) { - $this->output->error("❌ 无法打开压缩文件写入数据表数据:{$backupPath}"); - return false; - } - $force = (bool)$this->input->getOption('all'); - foreach ($this->getBkTables($force) as $table) { - $total = 0; - if (!empty($fields = $this->app->db->getFields($table))) { - $query = $this->app->db->table($table)->order(in_array('id', $fields) ? 'id' : array_values($fields)[0]); - in_array('ssid', $fields) && $query = $query->where('ssid', '0'); - in_array('deleted_at', $fields) && $query = $query->whereNull('deleted_at'); - $query->chunk(10000, function ($rows) use ($gz, $table, &$total) { - foreach ($rows as $row) { - $record = ['table' => $table, 'data' => (array)$row]; - gzwrite($gz, json_encode($record, JSON_UNESCAPED_UNICODE) . "\n"); - ++$total; - } - }); - } - $this->output->writeln("✅ 表 {$table} 备份完成,共 {$total} 行"); - } - - gzclose($gz); - $this->output->info("📂 表数据已压缩写入:{$backupPath}"); - return true; - } - - /** - * 获取需要备份的表. - */ - protected function getBkTables(bool $all = true): array - { - // 接收指定打包数据表 - if ($all) { - [$tables] = SystemService::getTables(); - } elseif (empty($tables = Library::$sapp->config->get('phinx.tables', []))) { - $this->output->error('❌ 配置文件未定义数据表列表,请检查配置项:phinx.tables'); - return []; - } - return $tables; - } - - /** - * 创建连接对接. - */ - protected function makeConnect(): Connection - { - $config = $this->app->db->connect()->getConfig(); - $config['host'] = $config['hostname'] ?? ''; - $config['user'] = $config['username'] ?? ''; - $config['dbname'] = $config['database'] ?? ''; - if (in_array($config['type'], ['mysql', 'sqlite', 'oci'])) { - $config['driver'] = 'pdo_' . $config['type']; - } - return DriverManager::getConnection($config); - } - - /** - * 结构文件路径,压缩格式. - */ - protected function getSchemaPath(): string - { - return syspath('database/backup.schema.gz'); - } - - /** - * 数据备份文件路径,压缩格式. - */ - protected function getBackupPath(): string - { - return syspath('database/backup.data.gz'); - } -} diff --git a/plugin/think-plugs-helper/src/DbRestoreStruct.php b/plugin/think-plugs-helper/src/DbRestoreStruct.php deleted file mode 100644 index 2f61c6b26..000000000 --- a/plugin/think-plugs-helper/src/DbRestoreStruct.php +++ /dev/null @@ -1,251 +0,0 @@ -setName('xadmin:helper:restore'); - $this->addOption('force', 'f', Option::VALUE_NONE, 'Force All Update'); - $this->setDescription('恢复数据前是否强制清空所有表数据'); - } - - /** - * 命令执行入口。 - * @throws Exception - */ - public function handle(): void - { - $this->restoreSchema() && $this->restoreBackup(); - } - - /** - * 检查是否允许执行任务 - * @return bool 返回true表示允许执行 - */ - public function isEnabled(): bool - { - return SystemService::isDebug(); - } - - /** - * 恢复数据库结构。 - * @throws Exception - */ - protected function restoreSchema(): bool - { - if (!is_file($gzPath = self::getSchemaPath())) { - $this->output->error("❌ 结构文件不存在:{$gzPath}"); - return false; - } - - $content = @gzdecode(file_get_contents($gzPath)); - if (empty($content) || !($backupSchema = @unserialize($content)) instanceof Schema) { - $this->output->error("❌ 解压或反序列化失败:{$gzPath}"); - return false; - } - - $connect = self::makeConnect(); - $platform = $connect->getDatabasePlatform(); - $diff = (new Comparator($platform))->compareSchemas( - $connect->createSchemaManager()->introspectSchema(), - $backupSchema - ); - - $sqls = []; - foreach ($diff->getCreatedTables() as $t) { - $sqls = [...$sqls, ...$platform->getCreateTableSQL($t)]; - } - foreach ($diff->getAlteredTables() as $t) { - $sqls = [...$sqls, ...$platform->getAlterTableSQL($t)]; - } - foreach ($diff->getDroppedTables() as $t) { - $sqls[] = $platform->getDropTableSQL($t->getName()); - } - - if (!$sqls) { - $this->output->info('✅ 数据库结构已一致,无需变更。'); - return true; - } - - try { - foreach ($sqls as $sql) { - $this->output->writeln("🔧 执行 SQL:{$sql}"); - $connect->executeStatement($sql); - } - $this->output->info('✅ 数据库结构同步完成。'); - return true; - } catch (\Throwable $throwable) { - $this->output->error('❌ 结构同步失败:' . $throwable->getMessage()); - return false; - } - } - - /** - * 恢复业务数据。 - * @throws Exception - */ - protected function restoreBackup(): bool - { - $force = (bool)$this->input->getOption('force'); - if (empty($tables = $this->getBkTables($force))) { - $this->output->error('❌ 未定义需恢复的数据表'); - return false; - } - - if (!file_exists($path = $this->getBackupPath())) { - $this->output->error("❌ 备份数据文件不存在:{$path}"); - return false; - } - - copy($path, $tmp = syspath('runtime/backup_data_tmp.gz')); - $schemaManager = $this->makeConnect()->createSchemaManager(); - - // 先清空 forceCleanTables 表,无需 count 检查 - $forceCleanTables = ['system_menu', 'system_dict_data', 'system_dict_type']; - foreach (array_intersect($forceCleanTables, $tables) as $table) { - _query($table)->empty(); - $this->output->writeln("🧹 强制清空表:{$table}"); - } - - if ($force) { - // 如果是强制恢复,需要清空所有表(非 forceCleanTables 表) - foreach ($schemaManager->listTableNames() as $table) { - if (!in_array($table, $forceCleanTables, true)) { - _query($table)->empty(); - $this->output->writeln("✅ 已经清空表:{$table}"); - } - } - } - - // 计算需要恢复的数据表 - $restoreTableFlags = []; - foreach ($tables as $table) { - $restoreTableFlags[$table] = $force || in_array($table, $forceCleanTables, true) || $this->app->db->table($table)->count() === 0; - } - - if (!($fp = gzopen($tmp, 'rb'))) { - $this->output->writeln("❌ 无法打开备份文件:{$tmp}"); - return false; - } - - $totalLines = 0; - $currentLine = 0; - $batchInsert = []; - $insertCount = array_fill_keys($tables, 0); - - try { - while (!gzeof($fp)) { - ++$totalLines; - $row = json_decode(trim(gzgets($fp)), true); - $table = $row['table'] ?? '-'; - - // 判断是否跳过恢复,非强制恢复时,根据 tableFlags 决定是否插入 - if (empty($restoreTableFlags[$table]) || empty($row['data']) || !is_array($row['data'])) { - continue; - } - - ++$currentLine; - ++$insertCount[$table]; - $batchInsert[$table][] = $row['data']; - if (count($batchInsert[$table]) >= 1000) { - $this->flushBatchInsert($table, $batchInsert[$table]); - $this->output->writeln("📥 表 {$table} 批量插入 1000 行,已读取 {$totalLines} 行"); - } - } - - // 插入剩余数据 - foreach ($batchInsert as $table => $rows) { - if ($count = count($rows)) { - $this->flushBatchInsert($table, $rows); - $this->output->writeln("📥 表 {$table} 批量插入 {$count} 行,已读取 {$totalLines} 行"); - } - } - $this->output->writeln("✅ 数据恢复完成,共插入 {$currentLine} 行(读取 {$totalLines} 行)"); - foreach ($insertCount as $table => $count) { - $count > 0 && $this->output->writeln("✅ 表 {$table} 插入 {$count} 行"); - } - @unlink($tmp); - - // 恢复管理员数据 - $this->insertSuperUser(); - - // 清理系统运行缓存 - return RuntimeService::clear(false); - } catch (\Throwable $throwable) { - trace_file($throwable); - $this->output->error('❌ 数据恢复失败:' . $throwable->getMessage()); - return false; - } finally { - gzclose($fp); - } - } - - /** - * 批量插入数据。 - */ - private function flushBatchInsert(string $table, array &$rows): void - { - if (!$rows) { - return; - } - - try { - $this->app->db->table($table)->insertAll($rows); - } catch (\Throwable $throwable) { - $this->output->writeln("⚠️ 表 {$table} 插入失败:{$throwable->getMessage()}"); - } finally { - $rows = []; - } - } - - /** - * 插入默认管理员。 - */ - private function insertSuperUser(): void - { - $model = SystemUser::mk()->whereRaw('1=1')->findOrEmpty(); - $model->isEmpty() && $model->save([ - 'id' => '10000', - 'username' => 'admin', - 'nickname' => '超级管理员', - 'password' => '21232f297a57a5a743894a0e4a801fc3', - 'headimg' => 'https://thinkadmin.top/static/img/head.png', - ], true); - $this->output->writeln('✅ 管理员账号恢复成功'); - } -} diff --git a/plugin/think-plugs-helper/src/Service.php b/plugin/think-plugs-helper/src/Service.php index 38a713eef..8bfc8ca33 100644 --- a/plugin/think-plugs-helper/src/Service.php +++ b/plugin/think-plugs-helper/src/Service.php @@ -27,8 +27,6 @@ class Service extends \think\Service $this->commands([ DbModelStruct::class, DbIndexStruct::class, - DbBackupStruct::class, - DbRestoreStruct::class, ]); } }