import os import sys import asyncio import aiomysql import subprocess import datetime import gzip import shutil from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from config import settings BACKUP_DIR = os.path.join(Path(__file__).parent.parent, "backups") os.makedirs(BACKUP_DIR, exist_ok=True) async def backup_database(name=None, compress=True): """备份MySQL数据库""" timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") if name: backup_name = f"{name}_{timestamp}" else: backup_name = f"backup_{timestamp}" output_file = os.path.join(BACKUP_DIR, f"{backup_name}.sql") cmd = [ "mysqldump", "-h", settings.db_host, "-P", str(settings.db_port), "-u", settings.db_user, f"-p{settings.db_password}", "--single-transaction", "--routines", "--triggers", "--events", settings.db_name ] try: print(f"开始备份数据库...") with open(output_file, 'w') as f: process = subprocess.Popen( cmd, stdout=f, stderr=subprocess.PIPE ) _, stderr = process.communicate() if process.returncode != 0: print(f"备份失败: {stderr.decode()}") if os.path.exists(output_file): os.remove(output_file) return False if compress: compressed_file = f"{output_file}.gz" print(f"压缩备份文件...") with open(output_file, 'rb') as f_in: with gzip.open(compressed_file, 'wb') as f_out: shutil.copyfileobj(f_in, f_out) os.remove(output_file) # 删除未压缩的SQL文件 output_file = compressed_file file_size = os.path.getsize(output_file) / (1024 * 1024) # 转换为MB print(f"备份完成: {output_file} ({file_size:.2f} MB)") return output_file except Exception as e: print(f"备份过程中出错: {e}") if os.path.exists(output_file): os.remove(output_file) return False async def list_backups(): """列出所有可用的备份文件""" if not os.path.exists(BACKUP_DIR): return [] backups = [f for f in os.listdir(BACKUP_DIR) if f.endswith('.sql') or f.endswith('.sql.gz')] backups.sort(reverse=True) # 按文件名排序,最新的在前面 return backups async def cleanup_old_backups(keep=10): """清理旧备份,仅保留指定数量的最新备份""" backups = await list_backups() if len(backups) <= keep: print(f"当前共有 {len(backups)} 个备份,未超过保留数量 {keep},不需要清理") return to_delete = backups[keep:] print(f"将删除 {len(to_delete)} 个旧备份,保留 {keep} 个最新备份") for backup in to_delete: try: file_path = os.path.join(BACKUP_DIR, backup) os.remove(file_path) print(f"已删除: {backup}") except Exception as e: print(f"删除备份 {backup} 时出错: {e}")