backup: 修复备份命令的用户名密码问题

backup
This commit is contained in:
2026-04-12 09:46:20 +08:00
parent d75808951e
commit 4463f73efb
+140 -86
View File
@@ -42,7 +42,19 @@ class Backup extends Command
'password' => 'n1e5a6s6m7', 'password' => 'n1e5a6s6m7',
'useDocker' => false, 'useDocker' => false,
'dockerContainerName' => 'redis' // Docker 容器名称 '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() protected function configure()
@@ -81,35 +93,46 @@ class Backup extends Command
// 显示环境配置 // 显示环境配置
$output->writeln("\n环境配置:"); $output->writeln("\n环境配置:");
foreach ($this->dataSources as $source) { foreach ($this->dataSources as $_source) {
$name = $source['dockerContainerName'] ?? ucfirst($source['type']); $name = $_source['name'] ?? $_source['dockerContainerName'] ?? ucfirst($_source['type']);
$output->writeln("- {$name}: " . ($source['useDocker'] ? "Docker 容器" : "本地环境")); if($name == $source){
$source = $_source;
}
$output->writeln("- {$name}: " . ($_source['useDocker'] ? "Docker 容器" : "本地环境"));
} }
// 处理命令行选项 // 处理命令行选项
if ($backup) { if ($backup) {
if ($source === 'mongodb') { if(is_array($source)){
$this->backupMongoDB($mongoDir, $output); if ($source['type'] === 'mongodb') {
} elseif ($source === 'mysql') { $this->backupMongoDB($mongoDir, $output, $source);
$this->backupMySQL($mysqlDir, $output); }
if ($source['type'] === 'mysql') {
$this->backupMySQL($mysqlDir, $output, $source);
}
} else { } else {
// 备份所有数据库 foreach ($this->dataSources as $_source) {
$this->backupMongoDB($mongoDir, $output); if ($_source['type'] === 'mongodb') {
$this->backupMySQL($mysqlDir, $output); $this->backupMongoDB($mongoDir, $output, $_source);
} elseif ($_source['type'] === 'mysql') {
$this->backupMySQL($mysqlDir, $output, $_source);
}
}
} }
} elseif ($restore) { } elseif ($restore) {
if ($source === 'mongodb') { if(is_array($source)){
$this->restoreMongoDB($mongoDir, $output); if ($source['type'] === 'mongodb') {
} elseif ($source === 'mysql') { $this->restoreMongoDB($mongoDir, $output, $source);
$this->restoreMySQL($mysqlDir, $output); }
if ($source['type'] === 'mysql') {
$this->restoreMySQL($mysqlDir, $output, $source);
}
} else { } else {
// 交互式选择
$this->restoreMenu($output, $outputDir); $this->restoreMenu($output, $outputDir);
} }
} elseif ($clear) { } elseif ($clear) {
$this->clearRedis($output); $this->clearRedis($output);
} else { } else {
// 交互式菜单
$this->mainMenu($output, $outputDir); $this->mainMenu($output, $outputDir);
} }
@@ -167,7 +190,7 @@ class Backup extends Command
foreach ($this->dataSources as $index => $source) { foreach ($this->dataSources as $index => $source) {
if ($source['type'] !== 'redis') { 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 容器" : "本地环境") . ")"); $output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")");
} }
} }
@@ -192,13 +215,13 @@ class Backup extends Command
if (!is_dir($mongoDir)) { if (!is_dir($mongoDir)) {
mkdir($mongoDir, 0755, true); mkdir($mongoDir, 0755, true);
} }
$this->backupMongoDB($mongoDir, $output); $this->backupMongoDB($mongoDir, $output, $source);
} elseif ($source['type'] === 'mysql') { } elseif ($source['type'] === 'mysql') {
$mysqlDir = base_path($outputDir) . '/mysql'; $mysqlDir = base_path($outputDir) . '/mysql';
if (!is_dir($mysqlDir)) { if (!is_dir($mysqlDir)) {
mkdir($mysqlDir, 0755, true); mkdir($mysqlDir, 0755, true);
} }
$this->backupMySQL($mysqlDir, $output); $this->backupMySQL($mysqlDir, $output, $source);
} }
} else { } else {
$output->writeln("\n无效选择,请重新输入"); $output->writeln("\n无效选择,请重新输入");
@@ -216,7 +239,7 @@ class Backup extends Command
foreach ($this->dataSources as $index => $source) { foreach ($this->dataSources as $index => $source) {
if ($source['type'] !== 'redis') { 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 容器" : "本地环境") . ")"); $output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")");
} }
} }
@@ -241,30 +264,32 @@ class Backup extends Command
if (!is_dir($mongoDir)) { if (!is_dir($mongoDir)) {
mkdir($mongoDir, 0755, true); mkdir($mongoDir, 0755, true);
} }
$this->restoreMongoDB($mongoDir, $output); $this->restoreMongoDB($mongoDir, $output, $source);
} elseif ($source['type'] === 'mysql') { } elseif ($source['type'] === 'mysql') {
$mysqlDir = base_path($outputDir) . '/mysql'; $mysqlDir = base_path($outputDir) . '/mysql';
if (!is_dir($mysqlDir)) { if (!is_dir($mysqlDir)) {
mkdir($mysqlDir, 0755, true); mkdir($mysqlDir, 0755, true);
} }
$this->restoreMySQL($mysqlDir, $output); $this->restoreMySQL($mysqlDir, $output, $source);
} }
} else { } else {
$output->writeln("\n无效选择,请重新输入"); $output->writeln("\n无效选择,请重新输入");
} }
} }
private function backupMongoDB($backupDir, $output): void private function backupMongoDB($backupDir, $output, $dataSource = null): void
{ {
$output->writeln("\n开始备份 MongoDB..."); $name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']);
$output->writeln("\n开始备份 {$name}...");
try { try {
// 从数据源配置中获取 MongoDB 配置 $mongoSource = $dataSource;
$mongoSource = null; if (!$mongoSource) {
foreach ($this->dataSources as $source) { foreach ($this->dataSources as $source) {
if ($source['type'] === 'mongodb') { if ($source['type'] === 'mongodb') {
$mongoSource = $source; $mongoSource = $source;
break; break;
}
} }
} }
@@ -278,22 +303,24 @@ class Backup extends Command
$database = $mongoSource['database']; $database = $mongoSource['database'];
$useDocker = $mongoSource['useDocker']; $useDocker = $mongoSource['useDocker'];
$dockerContainerName = $mongoSource['dockerContainerName']; $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"; $backupFileName = "{$database}_" . date("Y_m_d_H_i_s") . ".zip";
$backupFilePath = "{$backupDir}/{$backupFileName}"; $backupFilePath = "{$backupDir}/{$backupFileName}";
// 生成临时备份目录
$tempDir = "/tmp/mongo_backup_" . uniqid(); $tempDir = "/tmp/mongo_backup_" . uniqid();
if (!is_dir($tempDir)) { if (!is_dir($tempDir)) {
mkdir($tempDir, 0755, true); mkdir($tempDir, 0755, true);
} }
// 构建备份命令 $cmd = $this->getMongoDumpCommand($host, $port, $database, $tempDir, $useDocker, $dockerContainerName, $username, $password, $authSource);
$cmd = $this->getMongoDumpCommand($host, $port, $database, $tempDir, $useDocker, $dockerContainerName);
$output->writeln("执行命令: {$cmd}"); $output->writeln("执行命令: {$cmd}");
// 执行备份命令
exec($cmd, $outputLines, $returnCode); exec($cmd, $outputLines, $returnCode);
if ($returnCode === 0) { if ($returnCode === 0) {
@@ -329,17 +356,19 @@ class Backup extends Command
} }
} }
private function restoreMongoDB($backupDir, $output): void private function restoreMongoDB($backupDir, $output, $dataSource = null): void
{ {
$output->writeln("\n开始还原 MongoDB..."); $name = $dataSource['name'] ?? $dataSource['dockerContainerName'] ?? ucfirst($dataSource['type']);
$output->writeln("\n开始还原 {$name}...");
try { try {
// 从数据源配置中获取 MongoDB 配置 $mongoSource = $dataSource;
$mongoSource = null; if (!$mongoSource) {
foreach ($this->dataSources as $source) { foreach ($this->dataSources as $source) {
if ($source['type'] === 'mongodb') { if ($source['type'] === 'mongodb') {
$mongoSource = $source; $mongoSource = $source;
break; break;
}
} }
} }
@@ -353,8 +382,10 @@ class Backup extends Command
$database = $mongoSource['database']; $database = $mongoSource['database'];
$useDocker = $mongoSource['useDocker']; $useDocker = $mongoSource['useDocker'];
$dockerContainerName = $mongoSource['dockerContainerName']; $dockerContainerName = $mongoSource['dockerContainerName'];
$username = $mongoSource['username'] ?? '';
$password = $mongoSource['password'] ?? '';
$authSource = $mongoSource['authSource'] ?? $database;
// 列出备份文件
$backupFiles = glob("{$backupDir}/*.zip"); $backupFiles = glob("{$backupDir}/*.zip");
if (empty($backupFiles)) { if (empty($backupFiles)) {
$output->writeln("❌ 未找到备份文件"); $output->writeln("❌ 未找到备份文件");
@@ -402,27 +433,55 @@ class Backup extends Command
exec($unzipCmd, $unzipOutput, $unzipReturnCode); exec($unzipCmd, $unzipOutput, $unzipReturnCode);
if ($unzipReturnCode === 0) { if ($unzipReturnCode === 0) {
// 构建还原命令 $dbRestoreDir = $tempDir;
$restoreDir = $tempDir; $backupDbName = null;
$cmd = $this->getMongoRestoreCommand($host, $port, $database, $restoreDir, $useDocker, $dockerContainerName); $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}"); $output->writeln("执行命令: {$cmd}");
// 执行还原命令
exec($cmd, $outputLines, $returnCode); exec($cmd, $outputLines, $returnCode);
if ($returnCode === 0) { if ($returnCode === 0) {
$output->writeln("✅ MongoDB 还原成功"); $output->writeln("✅ MongoDB 还原成功");
if (!empty($outputLines)) {
$output->writeln(implode("\n", $outputLines));
}
} else { } else {
$output->writeln("❌ MongoDB 还原失败"); $output->writeln("❌ MongoDB 还原失败");
$output->writeln(implode("\n", $outputLines)); $output->writeln(implode("\n", $outputLines));
} }
// 清理临时目录
exec("rm -rf {$tempDir}"); exec("rm -rf {$tempDir}");
} else { } else {
$output->writeln("❌ 解压备份文件失败"); $output->writeln("❌ 解压备份文件失败");
$output->writeln(implode("\n", $unzipOutput)); $output->writeln(implode("\n", $unzipOutput));
// 清理临时目录
exec("rm -rf {$tempDir}"); exec("rm -rf {$tempDir}");
} }
} catch (\Exception $e) { } 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 { try {
// 从数据源配置中获取 MySQL 配置 $mysqlSource = $dataSource;
$mysqlSource = null; if (!$mysqlSource) {
foreach ($this->dataSources as $source) { foreach ($this->dataSources as $source) {
if ($source['type'] === 'mysql') { if ($source['type'] === 'mysql') {
$mysqlSource = $source; $mysqlSource = $source;
break; 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 { try {
// 从数据源配置中获取 MySQL 配置 $mysqlSource = $dataSource;
$mysqlSource = null; if (!$mysqlSource) {
foreach ($this->dataSources as $source) { foreach ($this->dataSources as $source) {
if ($source['type'] === 'mysql') { if ($source['type'] === 'mysql') {
$mysqlSource = $source; $mysqlSource = $source;
break; 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 认证信息 if ($authSource === null) {
$config = config('thinkorm.connections.immongodb'); $authSource = $database;
$username = $config['username'] ?? ''; }
$password = $config['password'] ?? '';
$authSource = $config['authSource'] ?? $database;
// 构建认证参数
$authParams = ''; $authParams = '';
if (!empty($username) && !empty($password)) { if (!empty($username) && !empty($password)) {
$authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}"; $authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}";
} }
if ($useDocker) { if ($useDocker) {
// 使用 Docker 容器
if (!$dockerContainerName) { if (!$dockerContainerName) {
return "echo '错误:未指定 Docker 容器名称' && exit 1"; return "echo '错误:未指定 Docker 容器名称' && exit 1";
} }
$port = 27017; $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}/"; 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 { } else {
// 不使用 Docker,直接使用本地命令
return "mongodump --host {$host}:{$port} {$authParams} --db {$database} --out {$outputDir}"; 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 认证信息 if ($authSource === null) {
$config = config('thinkorm.connections.immongodb'); $authSource = $database;
$username = $config['username'] ?? ''; }
$password = $config['password'] ?? '';
$authSource = $config['authSource'] ?? $database;
// 构建认证参数
$authParams = ''; $authParams = '';
if (!empty($username) && !empty($password)) { if (!empty($username) && !empty($password)) {
$authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}"; $authParams = "--username {$username} --password {$password} --authenticationDatabase {$authSource}";
} }
if ($useDocker) { if ($useDocker) {
// 使用 Docker 容器
if (!$dockerContainerName) { if (!$dockerContainerName) {
return "echo '错误:未指定 Docker 容器名称' && exit 1"; 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 { } else {
// 不使用 Docker,直接使用本地命令
return "mongorestore --host {$host}:{$port} {$authParams} --db {$database} {$restoreDir}"; return "mongorestore --host {$host}:{$port} {$authParams} --db {$database} {$restoreDir}";
} }
} }