备份

[package] name = "mysql_backup" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1.0", features = ["full"] } command-group = "2.1" chrono = "0.4" thiserror = "1.0" log = "0.4"
全部回复
use chrono::Local; use std::{ env::current_exe, fs::{File, create_dir_all}, path::PathBuf, }; use thiserror::Error; #[derive(Debug, Error)] pub enum MysqlBackupError { #[error("IO 错误: {0}")] Io(#[from] std::io::Error), #[error("命令执行失败: {0}")] CommandFailed(String), #[error("参数错误: {0}")] InvalidParam(String), #[error("备份文件为空")] EmptyBackup, } #[derive(Debug, Clone, Default)] pub struct RemoteLocalhostMysql { // 远程 pub remote_host: String, pub remote_port: String, pub remote_user: String, pub remote_password: String, pub remote_dbname: String, // 本地 pub local_host: String, pub local_port: String, pub local_user: String, pub local_password: String, pub local_dbname: String, // 备份文件路径 pub backup_file: String, } impl RemoteLocalhostMysql { pub fn setup(&mut self, remote_host: &str) -> Option<&mut Self> { if remote_host.is_empty() { return None; } self.remote_host = remote_host.to_string(); if self.remote_port.is_empty() { self.remote_port = "3306".into(); } if self.remote_user.is_empty() { self.remote_user = "root".into(); } if self.remote_password.is_empty() { self.remote_password = "qq282246101".into(); } if self.remote_dbname.is_empty() { self.remote_dbname = "nobiggie".into(); } if self.local_host.is_empty() { self.local_host = "localhost".into(); } if self.local_port.is_empty() { self.local_port = "3306".into(); } if self.local_user.is_empty() { self.local_user = "root".into(); } if self.local_password.is_empty() { self.local_password = "qq282246101".into(); } if self.local_dbname.is_empty() { self.local_dbname = "nobiggie".into(); } Some(self) } pub async fn start(&mut self) -> Result<(), MysqlBackupError> { if self.remote_dbname.is_empty() { return Err(MysqlBackupError::InvalidParam("请先设置参数".into())); } // 获取程序目录 let exe = current_exe()?; let root_dir = exe.parent().ok_or_else(|| { MysqlBackupError::InvalidParam("无法获取程序根目录".into()) })?; let sql_dir = root_dir.join("tool").join("sql"); create_dir_all(&sql_dir)?; // 时间戳文件名 let timestamp = Local::now().format("%Y%m%d%H%M%S").to_string(); let backup_file = sql_dir.join(format!("{timestamp}.sql")); self.backup_file = backup_file.to_string_lossy().to_string(); // 备份远程 self.backup_database(&self.backup_file).await?; println!("远程数据库备份成功:{}", self.backup_file); // 恢复本地 self.restore_database(&self.backup_file).await?; println!("本地数据库恢复成功"); Ok(()) } pub async fn backup_database(&self, backup_file: &str) -> Result<(), MysqlBackupError> { let file = File::create(backup_file)?; let status = command_group::tokio::Command::new("mysqldump") .arg("-h").arg(&self.remote_host) .arg("-P").arg(&self.remote_port) .arg("-u").arg(&self.remote_user) .arg(format!("-p{}", self.remote_password)) .arg(&self.remote_dbname) .stdout(file) .stderr(std::process::Stdio::piped()) .status() .await?; if !status.success() { return Err(MysqlBackupError::CommandFailed("mysqldump 执行失败".into())); } // 检查文件大小 let meta = std::fs::metadata(backup_file)?; if meta.len() == 0 { return Err(MysqlBackupError::EmptyBackup); } Ok(()) } pub async fn restore_database(&self, backup_file: &str) -> Result<(), MysqlBackupError> { if !std::fs::exists(backup_file)? { return Err(MysqlBackupError::InvalidParam(format!("备份文件不存在:{backup_file}"))); } let file = File::open(backup_file)?; let status = command_group::tokio::Command::new("mysql") .arg("-h").arg(&self.local_host) .arg("-P").arg(&self.local_port) .arg("-u").arg(&self.local_user) .arg(format!("-p{}", self.local_password)) .arg(&self.local_dbname) .stdin(file) .stderr(std::process::Stdio::piped()) .status() .await?; if !status.success() { return Err(MysqlBackupError::CommandFailed("mysql 恢复失败".into())); } Ok(()) } pub async fn backup_mysql_table( &self, host: &str, port: &str, user: &str, password: &str, dbname: &str, tables: &[String], ) -> Result<(), MysqlBackupError> { let exe = current_exe()?; let root_dir = exe.parent().ok_or_else(|| { MysqlBackupError::InvalidParam("无法获取程序目录".into()) })?; let sql_dir = root_dir.join("tool").join("sql"); create_dir_all(&sql_dir)?; let timestamp = Local::now().format("paisong%Y%m%d%H%M%S").to_string(); let save_path = sql_dir.join(format!("{timestamp}.sql")); let file = File::create(&save_path)?; let mut cmd = command_group::tokio::Command::new("mysqldump"); cmd.arg("-h").arg(host) .arg("-P").arg(port) .arg("-u").arg(user) .arg(format!("-p{password}")) .arg("--default-character-set=utf8mb4") .arg("--single-transaction") .arg("--set-gtid-purged=OFF") .arg(dbname); // 追加表名 for t in tables { cmd.arg(t); } let status = cmd .stdout(file) .stderr(std::process::Stdio::inherit()) .status() .await?; if !status.success() { return Err(MysqlBackupError::CommandFailed("mysqldump 表备份失败".into())); } println!("备份成功!文件:{}", save_path.display()); Ok(()) } }

发表回复

或拖拽图片到框内

支持 JPG、PNG、GIF、WebP 格式