106 lines
3.2 KiB
Python
106 lines
3.2 KiB
Python
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}")
|