diff --git a/app/command/Backup.php b/app/command/Backup.php index dc8ecd8..494f7b8 100644 --- a/app/command/Backup.php +++ b/app/command/Backup.php @@ -42,7 +42,19 @@ class Backup extends Command 'password' => 'n1e5a6s6m7', 'useDocker' => false, 'dockerContainerName' => 'redis' // Docker 容器名称 - ] + ], + [ + 'name' => 'tettt_mongodb', + 'type' => 'mongodb', + 'host' => '127.0.0.1', + 'port' => 27017, + 'database' => 'tettt', + 'username' => 'commie', + 'password' => 'n1e5a6s6m7', + 'authSource' => 'admin', + 'useDocker' => true, + 'dockerContainerName' => 'mongo' + ], ]; protected function configure() @@ -81,35 +93,46 @@ class Backup extends Command // 显示环境配置 $output->writeln("\n环境配置:"); - foreach ($this->dataSources as $source) { - $name = $source['dockerContainerName'] ?? ucfirst($source['type']); - $output->writeln("- {$name}: " . ($source['useDocker'] ? "Docker 容器" : "本地环境")); + foreach ($this->dataSources as $_source) { + $name = $_source['name'] ?? $_source['dockerContainerName'] ?? ucfirst($_source['type']); + if($name == $source){ + $source = $_source; + } + $output->writeln("- {$name}: " . ($_source['useDocker'] ? "Docker 容器" : "本地环境")); } // 处理命令行选项 if ($backup) { - if ($source === 'mongodb') { - $this->backupMongoDB($mongoDir, $output); - } elseif ($source === 'mysql') { - $this->backupMySQL($mysqlDir, $output); + if(is_array($source)){ + if ($source['type'] === 'mongodb') { + $this->backupMongoDB($mongoDir, $output, $source); + } + if ($source['type'] === 'mysql') { + $this->backupMySQL($mysqlDir, $output, $source); + } } else { - // 备份所有数据库 - $this->backupMongoDB($mongoDir, $output); - $this->backupMySQL($mysqlDir, $output); + foreach ($this->dataSources as $_source) { + if ($_source['type'] === 'mongodb') { + $this->backupMongoDB($mongoDir, $output, $_source); + } elseif ($_source['type'] === 'mysql') { + $this->backupMySQL($mysqlDir, $output, $_source); + } + } } } elseif ($restore) { - if ($source === 'mongodb') { - $this->restoreMongoDB($mongoDir, $output); - } elseif ($source === 'mysql') { - $this->restoreMySQL($mysqlDir, $output); + if(is_array($source)){ + if ($source['type'] === 'mongodb') { + $this->restoreMongoDB($mongoDir, $output, $source); + } + if ($source['type'] === 'mysql') { + $this->restoreMySQL($mysqlDir, $output, $source); + } } else { - // 交互式选择 $this->restoreMenu($output, $outputDir); } } elseif ($clear) { $this->clearRedis($output); } else { - // 交互式菜单 $this->mainMenu($output, $outputDir); } @@ -167,7 +190,7 @@ class Backup extends Command foreach ($this->dataSources as $index => $source) { if ($source['type'] !== 'redis') { - $name = $source['dockerContainerName'] ?? ucfirst($source['type']); + $name = $source['name'] ?? $source['dockerContainerName'] ?? ucfirst($source['type']); $output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")"); } } @@ -192,13 +215,13 @@ class Backup extends Command if (!is_dir($mongoDir)) { mkdir($mongoDir, 0755, true); } - $this->backupMongoDB($mongoDir, $output); + $this->backupMongoDB($mongoDir, $output, $source); } elseif ($source['type'] === 'mysql') { $mysqlDir = base_path($outputDir) . '/mysql'; if (!is_dir($mysqlDir)) { mkdir($mysqlDir, 0755, true); } - $this->backupMySQL($mysqlDir, $output); + $this->backupMySQL($mysqlDir, $output, $source); } } else { $output->writeln("\n无效选择,请重新输入"); @@ -216,7 +239,7 @@ class Backup extends Command foreach ($this->dataSources as $index => $source) { if ($source['type'] !== 'redis') { - $name = $source['dockerContainerName'] ?? ucfirst($source['type']); + $name = $source['name'] ?? $source['dockerContainerName'] ?? ucfirst($source['type']); $output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")"); } } @@ -241,30 +264,32 @@ class Backup extends Command if (!is_dir($mongoDir)) { mkdir($mongoDir, 0755, true); } - $this->restoreMongoDB($mongoDir, $output); + $this->restoreMongoDB($mongoDir, $output, $source); } elseif ($source['type'] === 'mysql') { $mysqlDir = base_path($outputDir) . '/mysql'; if (!is_dir($mysqlDir)) { mkdir($mysqlDir, 0755, true); } - $this->restoreMySQL($mysqlDir, $output); + $this->restoreMySQL($mysqlDir, $output, $source); } } else { $output->writeln("\n无效选择,请重新输入"); } } - private function backupMongoDB($backupDir, $output): void - { - $output->writeln("\n开始备份 MongoDB..."); + private function backupMongoDB($backupDir, $output, $dataSource = null): void + { + $name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']); + $output->writeln("\n开始备份 {$name}..."); try { - // 从数据源配置中获取 MongoDB 配置 - $mongoSource = null; - foreach ($this->dataSources as $source) { - if ($source['type'] === 'mongodb') { - $mongoSource = $source; - break; + $mongoSource = $dataSource; + if (!$mongoSource) { + foreach ($this->dataSources as $source) { + if ($source['type'] === 'mongodb') { + $mongoSource = $source; + break; + } } } @@ -278,22 +303,24 @@ class Backup extends Command $database = $mongoSource['database']; $useDocker = $mongoSource['useDocker']; $dockerContainerName = $mongoSource['dockerContainerName']; + $username = $mongoSource['username'] ?? ''; + $password = $mongoSource['password'] ?? ''; + $authSource = $mongoSource['authSource'] ?? $database; + $username = $mongoSource['username'] ?? ''; + $password = $mongoSource['password'] ?? ''; + $authSource = $mongoSource['authSource'] ?? $database; - // 生成备份文件名 $backupFileName = "{$database}_" . date("Y_m_d_H_i_s") . ".zip"; $backupFilePath = "{$backupDir}/{$backupFileName}"; - // 生成临时备份目录 $tempDir = "/tmp/mongo_backup_" . uniqid(); if (!is_dir($tempDir)) { mkdir($tempDir, 0755, true); } - // 构建备份命令 - $cmd = $this->getMongoDumpCommand($host, $port, $database, $tempDir, $useDocker, $dockerContainerName); + $cmd = $this->getMongoDumpCommand($host, $port, $database, $tempDir, $useDocker, $dockerContainerName, $username, $password, $authSource); $output->writeln("执行命令: {$cmd}"); - // 执行备份命令 exec($cmd, $outputLines, $returnCode); if ($returnCode === 0) { @@ -329,17 +356,19 @@ class Backup extends Command } } - private function restoreMongoDB($backupDir, $output): void - { - $output->writeln("\n开始还原 MongoDB..."); + private function restoreMongoDB($backupDir, $output, $dataSource = null): void + { + $name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']); + $output->writeln("\n开始还原 {$name}..."); try { - // 从数据源配置中获取 MongoDB 配置 - $mongoSource = null; - foreach ($this->dataSources as $source) { - if ($source['type'] === 'mongodb') { - $mongoSource = $source; - break; + $mongoSource = $dataSource; + if (!$mongoSource) { + foreach ($this->dataSources as $source) { + if ($source['type'] === 'mongodb') { + $mongoSource = $source; + break; + } } } @@ -353,8 +382,10 @@ class Backup extends Command $database = $mongoSource['database']; $useDocker = $mongoSource['useDocker']; $dockerContainerName = $mongoSource['dockerContainerName']; + $username = $mongoSource['username'] ?? ''; + $password = $mongoSource['password'] ?? ''; + $authSource = $mongoSource['authSource'] ?? $database; - // 列出备份文件 $backupFiles = glob("{$backupDir}/*.zip"); if (empty($backupFiles)) { $output->writeln("❌ 未找到备份文件"); @@ -402,27 +433,55 @@ class Backup extends Command exec($unzipCmd, $unzipOutput, $unzipReturnCode); if ($unzipReturnCode === 0) { - // 构建还原命令 - $restoreDir = $tempDir; - $cmd = $this->getMongoRestoreCommand($host, $port, $database, $restoreDir, $useDocker, $dockerContainerName); + $dbRestoreDir = $tempDir; + $backupDbName = null; + $subDirs = glob("{$tempDir}/*", GLOB_ONLYDIR); + + $output->writeln("解压后的目录: " . implode(", ", array_map('basename', $subDirs))); + + if (!empty($subDirs)) { + foreach ($subDirs as $subDir) { + $bsonFiles = glob("{$subDir}/*.bson"); + if (!empty($bsonFiles)) { + $dbRestoreDir = $subDir; + $backupDbName = basename($subDir); + $output->writeln("找到备份目录: {$backupDbName}"); + break; + } + } + } + + $bsonFiles = glob("{$dbRestoreDir}/*.bson"); + if (empty($bsonFiles)) { + $output->writeln("❌ 备份文件中没有找到 BSON 数据文件"); + $output->writeln("目录内容: " . implode(", ", scandir($dbRestoreDir))); + exec("rm -rf {$tempDir}"); + return; + } + + $output->writeln("找到 " . count($bsonFiles) . " 个 BSON 文件"); + $output->writeln("还原目录: {$dbRestoreDir}"); + $output->writeln("备份数据库: {$backupDbName} -> 目标数据库: {$database}"); + + $cmd = $this->getMongoRestoreCommand($host, $port, $database, $dbRestoreDir, $useDocker, $dockerContainerName, $username, $password, $authSource); $output->writeln("执行命令: {$cmd}"); - // 执行还原命令 exec($cmd, $outputLines, $returnCode); if ($returnCode === 0) { $output->writeln("✅ MongoDB 还原成功"); + if (!empty($outputLines)) { + $output->writeln(implode("\n", $outputLines)); + } } else { $output->writeln("❌ MongoDB 还原失败"); $output->writeln(implode("\n", $outputLines)); } - // 清理临时目录 exec("rm -rf {$tempDir}"); } else { $output->writeln("❌ 解压备份文件失败"); $output->writeln(implode("\n", $unzipOutput)); - // 清理临时目录 exec("rm -rf {$tempDir}"); } } catch (\Exception $e) { @@ -430,17 +489,19 @@ class Backup extends Command } } - private function backupMySQL($backupDir, $output): void + private function backupMySQL($backupDir, $output, $dataSource = null): void { - $output->writeln("\n开始备份 MySQL..."); + $name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']); + $output->writeln("\n开始备份 {$name}..."); try { - // 从数据源配置中获取 MySQL 配置 - $mysqlSource = null; - foreach ($this->dataSources as $source) { - if ($source['type'] === 'mysql') { - $mysqlSource = $source; - break; + $mysqlSource = $dataSource; + if (!$mysqlSource) { + foreach ($this->dataSources as $source) { + if ($source['type'] === 'mysql') { + $mysqlSource = $source; + break; + } } } @@ -479,17 +540,19 @@ class Backup extends Command } } - private function restoreMySQL($backupDir, $output): void + private function restoreMySQL($backupDir, $output, $dataSource = null): void { - $output->writeln("\n开始还原 MySQL..."); + $name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']); + $output->writeln("\n开始还原 {$name}..."); try { - // 从数据源配置中获取 MySQL 配置 - $mysqlSource = null; - foreach ($this->dataSources as $source) { - if ($source['type'] === 'mysql') { - $mysqlSource = $source; - break; + $mysqlSource = $dataSource; + if (!$mysqlSource) { + foreach ($this->dataSources as $source) { + if ($source['type'] === 'mysql') { + $mysqlSource = $source; + break; + } } } @@ -669,55 +732,46 @@ class Backup extends Command } } - private function getMongoDumpCommand($host, $port, $database, $outputDir, $useDocker = false, $dockerContainerName = null): string + private function getMongoDumpCommand($host, $port, $database, $outputDir, $useDocker = false, $dockerContainerName = null, $username = '', $password = '', $authSource = null): string { - // 获取 MongoDB 认证信息 - $config = config('thinkorm.connections.immongodb'); - $username = $config['username'] ?? ''; - $password = $config['password'] ?? ''; - $authSource = $config['authSource'] ?? $database; + if ($authSource === null) { + $authSource = $database; + } - // 构建认证参数 $authParams = ''; if (!empty($username) && !empty($password)) { $authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}"; } if ($useDocker) { - // 使用 Docker 容器 if (!$dockerContainerName) { return "echo '错误:未指定 Docker 容器名称' && exit 1"; } $port = 27017; return "docker exec -it {$dockerContainerName} mongodump --host {$host}:{$port} {$authParams} --db {$database} --out /tmp/mongo_backup && docker cp {$dockerContainerName}:/tmp/mongo_backup/{$database} {$outputDir}/"; } else { - // 不使用 Docker,直接使用本地命令 return "mongodump --host {$host}:{$port} {$authParams} --db {$database} --out {$outputDir}"; } } - private function getMongoRestoreCommand($host, $port, $database, $restoreDir, $useDocker = false, $dockerContainerName = null): string + private function getMongoRestoreCommand($host, $port, $database, $restoreDir, $useDocker = false, $dockerContainerName = null, $username = '', $password = '', $authSource = null): string { - // 获取 MongoDB 认证信息 - $config = config('thinkorm.connections.immongodb'); - $username = $config['username'] ?? ''; - $password = $config['password'] ?? ''; - $authSource = $config['authSource'] ?? $database; + if ($authSource === null) { + $authSource = $database; + } - // 构建认证参数 $authParams = ''; if (!empty($username) && !empty($password)) { $authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}"; } if ($useDocker) { - // 使用 Docker 容器 if (!$dockerContainerName) { return "echo '错误:未指定 Docker 容器名称' && exit 1"; } - return "docker cp {$restoreDir} {$dockerContainerName}:/tmp/mongo_restore && docker exec -it {$dockerContainerName} mongorestore --host {$host}:{$port} {$authParams} --db {$database} /tmp/mongo_restore"; + $dirName = basename($restoreDir); + return "docker cp {$restoreDir} {$dockerContainerName}:/tmp/ && docker exec -it {$dockerContainerName} mongorestore --host {$host}:{$port} {$authParams} --db {$database} /tmp/{$dirName} && docker exec -it {$dockerContainerName} rm -rf /tmp/{$dirName}"; } else { - // 不使用 Docker,直接使用本地命令 return "mongorestore --host {$host}:{$port} {$authParams} --db {$database} {$restoreDir}"; } }