1
This commit is contained in:
@@ -0,0 +1,752 @@
|
|||||||
|
<?php
|
||||||
|
namespace app\command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
|
||||||
|
class Backup extends Command
|
||||||
|
{
|
||||||
|
protected static $defaultName = 'backup';
|
||||||
|
protected static $defaultDescription = '备份 MongoDB 和 MySQL 数据库';
|
||||||
|
|
||||||
|
// 数据源配置
|
||||||
|
private $dataSources = [
|
||||||
|
[
|
||||||
|
'type' => 'mongodb',
|
||||||
|
'host' => '127.0.0.1',
|
||||||
|
'port' => 27017,
|
||||||
|
'database' => 'openim_v3',
|
||||||
|
'username' => 'openIM',
|
||||||
|
'password' => 'n1e5a6s6m7',
|
||||||
|
'useDocker' => true,
|
||||||
|
'dockerContainerName' => 'mongo' // Docker 容器名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'mysql',
|
||||||
|
'host' => '127.0.0.1',
|
||||||
|
'port' => 3306,
|
||||||
|
'database' => 'imadmin',
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => 'n1e5a6s6m7',
|
||||||
|
'useDocker' => true,
|
||||||
|
'dockerContainerName' => 'my_mysql' // Docker 容器名称
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'type' => 'redis',
|
||||||
|
'host' => '127.0.0.1',
|
||||||
|
'port' => 16379,
|
||||||
|
'database' => 0,
|
||||||
|
'username' => '',
|
||||||
|
'password' => 'n1e5a6s6m7',
|
||||||
|
'useDocker' => false,
|
||||||
|
'dockerContainerName' => 'redis' // Docker 容器名称
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this->addOption('backup', 'b', InputOption::VALUE_NONE, '备份数据库');
|
||||||
|
$this->addOption('restore', 'r', InputOption::VALUE_NONE, '还原数据库');
|
||||||
|
$this->addOption('clear', 'c', InputOption::VALUE_NONE, '清空 Redis');
|
||||||
|
$this->addOption('source', 's', InputOption::VALUE_OPTIONAL, '数据源名称 (mongodb, mysql, redis)');
|
||||||
|
$this->addOption('output', 'o', InputOption::VALUE_OPTIONAL, '备份输出目录', '/backup');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$backup = $input->getOption('backup');
|
||||||
|
$restore = $input->getOption('restore');
|
||||||
|
$clear = $input->getOption('clear');
|
||||||
|
$source = $input->getOption('source');
|
||||||
|
$outputDir = $input->getOption('output');
|
||||||
|
|
||||||
|
// 确保备份目录存在
|
||||||
|
$mongoDir = base_path($outputDir) . '/mongo';
|
||||||
|
$mysqlDir = base_path($outputDir) . '/mysql';
|
||||||
|
|
||||||
|
if (!is_dir($mongoDir)) {
|
||||||
|
mkdir($mongoDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir($mysqlDir)) {
|
||||||
|
mkdir($mysqlDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示备份目录
|
||||||
|
$output->writeln("\n备份目录:");
|
||||||
|
$output->writeln("- MongoDB: {$mongoDir}");
|
||||||
|
$output->writeln("- MySQL: {$mysqlDir}");
|
||||||
|
|
||||||
|
// 显示环境配置
|
||||||
|
$output->writeln("\n环境配置:");
|
||||||
|
foreach ($this->dataSources as $source) {
|
||||||
|
$name = $source['dockerContainerName'] ?? ucfirst($source['type']);
|
||||||
|
$output->writeln("- {$name}: " . ($source['useDocker'] ? "Docker 容器" : "本地环境"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理命令行选项
|
||||||
|
if ($backup) {
|
||||||
|
if ($source === 'mongodb') {
|
||||||
|
$this->backupMongoDB($mongoDir, $output);
|
||||||
|
} elseif ($source === 'mysql') {
|
||||||
|
$this->backupMySQL($mysqlDir, $output);
|
||||||
|
} else {
|
||||||
|
// 备份所有数据库
|
||||||
|
$this->backupMongoDB($mongoDir, $output);
|
||||||
|
$this->backupMySQL($mysqlDir, $output);
|
||||||
|
}
|
||||||
|
} elseif ($restore) {
|
||||||
|
if ($source === 'mongodb') {
|
||||||
|
$this->restoreMongoDB($mongoDir, $output);
|
||||||
|
} elseif ($source === 'mysql') {
|
||||||
|
$this->restoreMySQL($mysqlDir, $output);
|
||||||
|
} else {
|
||||||
|
// 交互式选择
|
||||||
|
$this->restoreMenu($output, $outputDir);
|
||||||
|
}
|
||||||
|
} elseif ($clear) {
|
||||||
|
$this->clearRedis($output);
|
||||||
|
} else {
|
||||||
|
// 交互式菜单
|
||||||
|
$this->mainMenu($output, $outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("\n✅ 操作完成!");
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主菜单
|
||||||
|
*/
|
||||||
|
private function mainMenu($output, $outputDir): void
|
||||||
|
{
|
||||||
|
while (true) {
|
||||||
|
$output->writeln("\n================================");
|
||||||
|
$output->writeln(" 备份工具");
|
||||||
|
$output->writeln("================================");
|
||||||
|
$output->writeln("1. 备份数据库");
|
||||||
|
$output->writeln("2. 还原数据库");
|
||||||
|
$output->writeln("3. 清空 Redis");
|
||||||
|
$output->writeln("0. 退出");
|
||||||
|
|
||||||
|
$output->write("\n请选择操作 (0-3): ");
|
||||||
|
$handle = fopen("php://stdin", "r");
|
||||||
|
$choice = fgets($handle);
|
||||||
|
fclose($handle);
|
||||||
|
$choice = trim($choice);
|
||||||
|
|
||||||
|
switch ($choice) {
|
||||||
|
case '1':
|
||||||
|
$this->backupMenu($output, $outputDir);
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
$this->restoreMenu($output, $outputDir);
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
$this->clearRedis($output);
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
$output->writeln("\n无效选择,请重新输入");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备份菜单
|
||||||
|
*/
|
||||||
|
private function backupMenu($output, $outputDir): void
|
||||||
|
{
|
||||||
|
$output->writeln("\n================================");
|
||||||
|
$output->writeln(" 备份数据库");
|
||||||
|
$output->writeln("================================");
|
||||||
|
|
||||||
|
foreach ($this->dataSources as $index => $source) {
|
||||||
|
if ($source['type'] !== 'redis') {
|
||||||
|
$name = $source['dockerContainerName'] ?? ucfirst($source['type']);
|
||||||
|
$output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("0. 返回上一级");
|
||||||
|
|
||||||
|
$output->write("\n请选择要备份的数据源 (0-" . count($this->dataSources) . "): ");
|
||||||
|
$handle = fopen("php://stdin", "r");
|
||||||
|
$choice = fgets($handle);
|
||||||
|
fclose($handle);
|
||||||
|
$choice = trim($choice);
|
||||||
|
|
||||||
|
if ($choice === '0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($choice) && $choice > 0 && $choice <= count($this->dataSources)) {
|
||||||
|
$source = $this->dataSources[$choice - 1];
|
||||||
|
|
||||||
|
if ($source['type'] === 'mongodb') {
|
||||||
|
$mongoDir = base_path($outputDir) . '/mongo';
|
||||||
|
if (!is_dir($mongoDir)) {
|
||||||
|
mkdir($mongoDir, 0755, true);
|
||||||
|
}
|
||||||
|
$this->backupMongoDB($mongoDir, $output);
|
||||||
|
} elseif ($source['type'] === 'mysql') {
|
||||||
|
$mysqlDir = base_path($outputDir) . '/mysql';
|
||||||
|
if (!is_dir($mysqlDir)) {
|
||||||
|
mkdir($mysqlDir, 0755, true);
|
||||||
|
}
|
||||||
|
$this->backupMySQL($mysqlDir, $output);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$output->writeln("\n无效选择,请重新输入");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 还原菜单
|
||||||
|
*/
|
||||||
|
private function restoreMenu($output, $outputDir): void
|
||||||
|
{
|
||||||
|
$output->writeln("\n================================");
|
||||||
|
$output->writeln(" 还原数据库");
|
||||||
|
$output->writeln("================================");
|
||||||
|
|
||||||
|
foreach ($this->dataSources as $index => $source) {
|
||||||
|
if ($source['type'] !== 'redis') {
|
||||||
|
$name = $source['dockerContainerName'] ?? ucfirst($source['type']);
|
||||||
|
$output->writeln(($index + 1) . ". " . $name . " (" . ($source['useDocker'] ? "Docker 容器" : "本地环境") . ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("0. 返回上一级");
|
||||||
|
|
||||||
|
$output->write("\n请选择要还原的数据源 (0-" . count($this->dataSources) . "): ");
|
||||||
|
$handle = fopen("php://stdin", "r");
|
||||||
|
$choice = fgets($handle);
|
||||||
|
fclose($handle);
|
||||||
|
$choice = trim($choice);
|
||||||
|
|
||||||
|
if ($choice === '0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($choice) && $choice > 0 && $choice <= count($this->dataSources)) {
|
||||||
|
$source = $this->dataSources[$choice - 1];
|
||||||
|
|
||||||
|
if ($source['type'] === 'mongodb') {
|
||||||
|
$mongoDir = base_path($outputDir) . '/mongo';
|
||||||
|
if (!is_dir($mongoDir)) {
|
||||||
|
mkdir($mongoDir, 0755, true);
|
||||||
|
}
|
||||||
|
$this->restoreMongoDB($mongoDir, $output);
|
||||||
|
} elseif ($source['type'] === 'mysql') {
|
||||||
|
$mysqlDir = base_path($outputDir) . '/mysql';
|
||||||
|
if (!is_dir($mysqlDir)) {
|
||||||
|
mkdir($mysqlDir, 0755, true);
|
||||||
|
}
|
||||||
|
$this->restoreMySQL($mysqlDir, $output);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$output->writeln("\n无效选择,请重新输入");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function backupMongoDB($backupDir, $output): void
|
||||||
|
{
|
||||||
|
$output->writeln("\n开始备份 MongoDB...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从数据源配置中获取 MongoDB 配置
|
||||||
|
$mongoSource = null;
|
||||||
|
foreach ($this->dataSources as $source) {
|
||||||
|
if ($source['type'] === 'mongodb') {
|
||||||
|
$mongoSource = $source;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$mongoSource) {
|
||||||
|
$output->writeln("❌ 未找到 MongoDB 数据源配置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = $mongoSource['host'];
|
||||||
|
$port = $mongoSource['port'];
|
||||||
|
$database = $mongoSource['database'];
|
||||||
|
$useDocker = $mongoSource['useDocker'];
|
||||||
|
$dockerContainerName = $mongoSource['dockerContainerName'];
|
||||||
|
|
||||||
|
// 生成备份文件名
|
||||||
|
$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);
|
||||||
|
$output->writeln("执行命令: {$cmd}");
|
||||||
|
|
||||||
|
// 执行备份命令
|
||||||
|
exec($cmd, $outputLines, $returnCode);
|
||||||
|
|
||||||
|
if ($returnCode === 0) {
|
||||||
|
// 保存当前工作目录
|
||||||
|
$currentDir = getcwd();
|
||||||
|
|
||||||
|
// 切换到临时目录并压缩
|
||||||
|
chdir($tempDir);
|
||||||
|
$zipCmd = "zip -r {$backupFilePath} .";
|
||||||
|
$output->writeln("创建压缩文件: {$backupFilePath}");
|
||||||
|
exec($zipCmd, $zipOutput, $zipReturnCode);
|
||||||
|
|
||||||
|
// 切换回原来的工作目录
|
||||||
|
chdir($currentDir);
|
||||||
|
|
||||||
|
if ($zipReturnCode === 0) {
|
||||||
|
$output->writeln("✅ MongoDB 备份成功: {$backupFilePath}");
|
||||||
|
} else {
|
||||||
|
$output->writeln("❌ MongoDB 压缩失败");
|
||||||
|
$output->writeln(implode("\n", $zipOutput));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理临时目录
|
||||||
|
exec("rm -rf {$tempDir}");
|
||||||
|
} else {
|
||||||
|
$output->writeln("❌ MongoDB 备份失败");
|
||||||
|
$output->writeln(implode("\n", $outputLines));
|
||||||
|
// 清理临时目录
|
||||||
|
exec("rm -rf {$tempDir}");
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln("❌ MongoDB 备份失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function restoreMongoDB($backupDir, $output): void
|
||||||
|
{
|
||||||
|
$output->writeln("\n开始还原 MongoDB...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从数据源配置中获取 MongoDB 配置
|
||||||
|
$mongoSource = null;
|
||||||
|
foreach ($this->dataSources as $source) {
|
||||||
|
if ($source['type'] === 'mongodb') {
|
||||||
|
$mongoSource = $source;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$mongoSource) {
|
||||||
|
$output->writeln("❌ 未找到 MongoDB 数据源配置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = $mongoSource['host'];
|
||||||
|
$port = $mongoSource['port'];
|
||||||
|
$database = $mongoSource['database'];
|
||||||
|
$useDocker = $mongoSource['useDocker'];
|
||||||
|
$dockerContainerName = $mongoSource['dockerContainerName'];
|
||||||
|
|
||||||
|
// 列出备份文件
|
||||||
|
$backupFiles = glob("{$backupDir}/*.zip");
|
||||||
|
if (empty($backupFiles)) {
|
||||||
|
$output->writeln("❌ 未找到备份文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按修改时间排序
|
||||||
|
usort($backupFiles, function ($a, $b) {
|
||||||
|
return filemtime($b) - filemtime($a);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示备份文件列表
|
||||||
|
$output->writeln("\n可用的备份文件:");
|
||||||
|
foreach ($backupFiles as $index => $file) {
|
||||||
|
$fileName = basename($file);
|
||||||
|
$fileSize = filesize($file) / 1024 / 1024;
|
||||||
|
$modTime = date("Y-m-d H:i:s", filemtime($file));
|
||||||
|
$output->writeln(($index + 1) . ". {$fileName} (" . round($fileSize, 2) . " MB, {$modTime})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择备份文件
|
||||||
|
$output->write("\n请选择要还原的备份文件 (1-" . count($backupFiles) . "): ");
|
||||||
|
$handle = fopen("php://stdin", "r");
|
||||||
|
$choice = fgets($handle);
|
||||||
|
fclose($handle);
|
||||||
|
$choice = trim($choice);
|
||||||
|
|
||||||
|
if (!is_numeric($choice) || $choice < 1 || $choice > count($backupFiles)) {
|
||||||
|
$output->writeln("\n无效选择");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedFile = $backupFiles[$choice - 1];
|
||||||
|
$output->writeln("\n选择的备份文件: " . basename($selectedFile));
|
||||||
|
|
||||||
|
// 生成临时还原目录
|
||||||
|
$tempDir = "/tmp/mongo_restore_" . uniqid();
|
||||||
|
if (!is_dir($tempDir)) {
|
||||||
|
mkdir($tempDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解压备份文件
|
||||||
|
$unzipCmd = "unzip {$selectedFile} -d {$tempDir}";
|
||||||
|
$output->writeln("解压备份文件...");
|
||||||
|
exec($unzipCmd, $unzipOutput, $unzipReturnCode);
|
||||||
|
|
||||||
|
if ($unzipReturnCode === 0) {
|
||||||
|
// 构建还原命令
|
||||||
|
$restoreDir = $tempDir;
|
||||||
|
$cmd = $this->getMongoRestoreCommand($host, $port, $database, $restoreDir, $useDocker, $dockerContainerName);
|
||||||
|
$output->writeln("执行命令: {$cmd}");
|
||||||
|
|
||||||
|
// 执行还原命令
|
||||||
|
exec($cmd, $outputLines, $returnCode);
|
||||||
|
|
||||||
|
if ($returnCode === 0) {
|
||||||
|
$output->writeln("✅ MongoDB 还原成功");
|
||||||
|
} 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) {
|
||||||
|
$output->writeln("❌ MongoDB 还原失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function backupMySQL($backupDir, $output): void
|
||||||
|
{
|
||||||
|
$output->writeln("\n开始备份 MySQL...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从数据源配置中获取 MySQL 配置
|
||||||
|
$mysqlSource = null;
|
||||||
|
foreach ($this->dataSources as $source) {
|
||||||
|
if ($source['type'] === 'mysql') {
|
||||||
|
$mysqlSource = $source;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$mysqlSource) {
|
||||||
|
$output->writeln("❌ 未找到 MySQL 数据源配置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = $mysqlSource['host'];
|
||||||
|
$port = $mysqlSource['port'];
|
||||||
|
$database = $mysqlSource['database'];
|
||||||
|
$username = $mysqlSource['username'];
|
||||||
|
$password = $mysqlSource['password'];
|
||||||
|
$useDocker = $mysqlSource['useDocker'];
|
||||||
|
$dockerContainerName = $mysqlSource['dockerContainerName'];
|
||||||
|
|
||||||
|
// 生成备份文件名
|
||||||
|
$backupFileName = "{$database}_" . date("Y_m_d_H_i_s") . ".sql";
|
||||||
|
$backupFilePath = "{$backupDir}/{$backupFileName}";
|
||||||
|
|
||||||
|
// 构建备份命令
|
||||||
|
$cmd = $this->getMySqlDumpCommand($host, $port, $database, $username, $password, $backupFilePath, $useDocker, $dockerContainerName);
|
||||||
|
$output->writeln("执行命令: {$cmd}");
|
||||||
|
|
||||||
|
// 执行备份命令
|
||||||
|
exec($cmd, $outputLines, $returnCode);
|
||||||
|
|
||||||
|
if ($returnCode === 0) {
|
||||||
|
$output->writeln("✅ MySQL 备份成功: {$backupFilePath}");
|
||||||
|
} else {
|
||||||
|
$output->writeln("❌ MySQL 备份失败");
|
||||||
|
$output->writeln(implode("\n", $outputLines));
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln("❌ MySQL 备份失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function restoreMySQL($backupDir, $output): void
|
||||||
|
{
|
||||||
|
$output->writeln("\n开始还原 MySQL...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从数据源配置中获取 MySQL 配置
|
||||||
|
$mysqlSource = null;
|
||||||
|
foreach ($this->dataSources as $source) {
|
||||||
|
if ($source['type'] === 'mysql') {
|
||||||
|
$mysqlSource = $source;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$mysqlSource) {
|
||||||
|
$output->writeln("❌ 未找到 MySQL 数据源配置");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = $mysqlSource['host'];
|
||||||
|
$port = $mysqlSource['port'];
|
||||||
|
$database = $mysqlSource['database'];
|
||||||
|
$username = $mysqlSource['username'];
|
||||||
|
$password = $mysqlSource['password'];
|
||||||
|
$useDocker = $mysqlSource['useDocker'];
|
||||||
|
$dockerContainerName = $mysqlSource['dockerContainerName'];
|
||||||
|
|
||||||
|
// 列出备份文件(支持 SQL 文件和 zip 文件)
|
||||||
|
$backupFiles = array_merge(
|
||||||
|
glob("{$backupDir}/*.sql"),
|
||||||
|
glob("{$backupDir}/*.zip")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($backupFiles)) {
|
||||||
|
$output->writeln("❌ 未找到备份文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按修改时间排序
|
||||||
|
usort($backupFiles, function ($a, $b) {
|
||||||
|
return filemtime($b) - filemtime($a);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示备份文件列表
|
||||||
|
$output->writeln("\n可用的备份文件:");
|
||||||
|
foreach ($backupFiles as $index => $file) {
|
||||||
|
$fileName = basename($file);
|
||||||
|
$fileSize = filesize($file) / 1024 / 1024;
|
||||||
|
$modTime = date("Y-m-d H:i:s", filemtime($file));
|
||||||
|
$output->writeln(($index + 1) . ". {$fileName} (" . round($fileSize, 2) . " MB, {$modTime})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择备份文件
|
||||||
|
$output->write("\n请选择要还原的备份文件 (1-" . count($backupFiles) . "): ");
|
||||||
|
$handle = fopen("php://stdin", "r");
|
||||||
|
$choice = fgets($handle);
|
||||||
|
fclose($handle);
|
||||||
|
$choice = trim($choice);
|
||||||
|
|
||||||
|
if (!is_numeric($choice) || $choice < 1 || $choice > count($backupFiles)) {
|
||||||
|
$output->writeln("\n无效选择");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedFile = $backupFiles[$choice - 1];
|
||||||
|
$output->writeln("\n选择的备份文件: " . basename($selectedFile));
|
||||||
|
|
||||||
|
$sqlFile = $selectedFile;
|
||||||
|
|
||||||
|
// 如果是 zip 文件,需要解压
|
||||||
|
if (pathinfo($selectedFile, PATHINFO_EXTENSION) === 'zip') {
|
||||||
|
// 生成临时还原目录
|
||||||
|
$tempDir = "/tmp/mysql_restore_" . uniqid();
|
||||||
|
if (!is_dir($tempDir)) {
|
||||||
|
mkdir($tempDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解压备份文件
|
||||||
|
$unzipCmd = "unzip {$selectedFile} -d {$tempDir}";
|
||||||
|
$output->writeln("解压备份文件...");
|
||||||
|
exec($unzipCmd, $unzipOutput, $unzipReturnCode);
|
||||||
|
|
||||||
|
if ($unzipReturnCode !== 0) {
|
||||||
|
$output->writeln("❌ 解压备份文件失败");
|
||||||
|
$output->writeln(implode("\n", $unzipOutput));
|
||||||
|
// 清理临时目录
|
||||||
|
exec("rm -rf {$tempDir}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到解压后的 SQL 文件
|
||||||
|
$sqlFiles = glob("{$tempDir}/*.sql");
|
||||||
|
if (empty($sqlFiles)) {
|
||||||
|
$output->writeln("❌ 未找到 SQL 文件");
|
||||||
|
// 清理临时目录
|
||||||
|
exec("rm -rf {$tempDir}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlFile = $sqlFiles[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建还原命令
|
||||||
|
$cmd = $this->getMySqlRestoreCommand($host, $port, $database, $username, $password, $sqlFile, $useDocker, $dockerContainerName);
|
||||||
|
$output->writeln("执行命令: {$cmd}");
|
||||||
|
|
||||||
|
// 执行还原命令
|
||||||
|
exec($cmd, $outputLines, $returnCode);
|
||||||
|
|
||||||
|
if ($returnCode === 0) {
|
||||||
|
$output->writeln("✅ MySQL 还原成功");
|
||||||
|
} else {
|
||||||
|
$output->writeln("❌ MySQL 还原失败");
|
||||||
|
$output->writeln(implode("\n", $outputLines));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理临时目录(如果使用了临时目录)
|
||||||
|
if (pathinfo($selectedFile, PATHINFO_EXTENSION) === 'zip') {
|
||||||
|
exec("rm -rf {$tempDir}");
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln("❌ MySQL 还原失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function clearRedis($output): void
|
||||||
|
{
|
||||||
|
$output->writeln("\n开始清空 Redis...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从数据源配置中获取 Redis 配置
|
||||||
|
$redisSource = null;
|
||||||
|
foreach ($this->dataSources as $source) {
|
||||||
|
if ($source['type'] === 'redis') {
|
||||||
|
$redisSource = $source;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($redisSource && $redisSource['useDocker']) {
|
||||||
|
// 使用 Docker 容器
|
||||||
|
$redisContainer = $redisSource['dockerContainerName'];
|
||||||
|
if (!$redisContainer) {
|
||||||
|
$output->writeln("❌ 未指定 Redis Docker 容器名称");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$output->writeln("使用 Docker 容器清空 Redis");
|
||||||
|
$cmd = "docker exec -it {$redisContainer} redis-cli flushall";
|
||||||
|
$output->writeln("执行命令: {$cmd}");
|
||||||
|
|
||||||
|
exec($cmd, $outputLines, $returnCode);
|
||||||
|
|
||||||
|
if ($returnCode === 0) {
|
||||||
|
$output->writeln("✅ Redis 清空成功");
|
||||||
|
} else {
|
||||||
|
$output->writeln("❌ Redis 清空失败");
|
||||||
|
$output->writeln(implode("\n", $outputLines));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 尝试使用 Redis 连接
|
||||||
|
$redis = new \Redis();
|
||||||
|
$config = config('cache.stores.redis');
|
||||||
|
$host = $config['host'] ?? '127.0.0.1';
|
||||||
|
$port = $config['port'] ?? 6379;
|
||||||
|
$password = $config['password'] ?? '';
|
||||||
|
|
||||||
|
$output->writeln("连接 Redis: {$host}:{$port}");
|
||||||
|
|
||||||
|
if ($redis->connect($host, $port)) {
|
||||||
|
if (!empty($password)) {
|
||||||
|
$redis->auth($password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空 Redis
|
||||||
|
$result = $redis->flushAll();
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$output->writeln("✅ Redis 清空成功");
|
||||||
|
} else {
|
||||||
|
$output->writeln("❌ Redis 清空失败");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$output->writeln("❌ 无法连接到 Redis");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln("❌ Redis 操作失败: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMongoDumpCommand($host, $port, $database, $outputDir, $useDocker = false, $dockerContainerName = null): string
|
||||||
|
{
|
||||||
|
// 获取 MongoDB 认证信息
|
||||||
|
$config = config('thinkorm.connections.immongodb');
|
||||||
|
$username = $config['username'] ?? '';
|
||||||
|
$password = $config['password'] ?? '';
|
||||||
|
$authSource = $config['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
|
||||||
|
{
|
||||||
|
// 获取 MongoDB 认证信息
|
||||||
|
$config = config('thinkorm.connections.immongodb');
|
||||||
|
$username = $config['username'] ?? '';
|
||||||
|
$password = $config['password'] ?? '';
|
||||||
|
$authSource = $config['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";
|
||||||
|
} else {
|
||||||
|
// 不使用 Docker,直接使用本地命令
|
||||||
|
return "mongorestore --host {$host}:{$port} {$authParams} --db {$database} {$restoreDir}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMySqlDumpCommand($host, $port, $database, $username, $password, $outputFile, $useDocker = false, $dockerContainerName = null): string
|
||||||
|
{
|
||||||
|
if ($useDocker) {
|
||||||
|
// 使用 Docker 容器
|
||||||
|
if (!$dockerContainerName) {
|
||||||
|
return "echo '错误:未指定 Docker 容器名称' && exit 1";
|
||||||
|
}
|
||||||
|
return "docker exec -it {$dockerContainerName} mysqldump -h {$host} -P {$port} -u {$username} --password={$password} {$database} > {$outputFile}";
|
||||||
|
} else {
|
||||||
|
// 不使用 Docker,直接使用本地命令
|
||||||
|
return "mysqldump -h {$host} -P {$port} -u {$username} --password={$password} {$database} > {$outputFile}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMySqlRestoreCommand($host, $port, $database, $username, $password, $sqlFile, $useDocker = false, $dockerContainerName = null): string
|
||||||
|
{
|
||||||
|
if ($useDocker) {
|
||||||
|
// 使用 Docker 容器
|
||||||
|
if (!$dockerContainerName) {
|
||||||
|
return "echo '错误:未指定 Docker 容器名称' && exit 1";
|
||||||
|
}
|
||||||
|
return "docker cp {$sqlFile} {$dockerContainerName}:/tmp/mysql_restore.sql && docker exec -it {$dockerContainerName} bash -c 'mysql -h {$host} -P {$port} -u {$username} --password={$password} {$database} < /tmp/mysql_restore.sql'";
|
||||||
|
} else {
|
||||||
|
// 不使用 Docker,直接使用本地命令
|
||||||
|
return "mysql -h {$host} -P {$port} -u {$username} --password={$password} {$database} < {$sqlFile}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user