Files
nxxmdata/backend/controllers/backupController.js

449 lines
11 KiB
JavaScript
Raw Normal View History

2025-09-12 20:08:42 +08:00
/**
* 备份管理控制器
* @file backupController.js
* @description 处理数据备份和恢复相关业务逻辑
*/
const backupService = require('../services/backupService');
const logger = require('../utils/logger');
const { validationResult } = require('express-validator');
const cron = require('node-cron');
/**
* 创建备份
* @route POST /api/backup/create
*/
const createBackup = async (req, res) => {
try {
const { type = 'full', description } = req.body;
logger.info(`用户 ${req.user.username} 开始创建备份,类型: ${type}`);
const backupResult = await backupService.createFullBackup({
type,
description,
createdBy: req.user.id
});
res.json({
success: true,
message: '备份创建成功',
data: backupResult
});
} catch (error) {
logger.error('创建备份失败:', error);
res.status(500).json({
success: false,
message: error.message || '创建备份失败',
error: error.message
});
}
};
/**
* 获取备份列表
* @route GET /api/backup/list
*/
const getBackupList = async (req, res) => {
try {
const { page = 1, limit = 10, type } = req.query;
let backups = await backupService.getBackupList();
// 按类型过滤
if (type) {
backups = backups.filter(backup => backup.type === type);
}
// 分页
const startIndex = (page - 1) * limit;
const endIndex = startIndex + parseInt(limit);
const paginatedBackups = backups.slice(startIndex, endIndex);
res.json({
success: true,
data: paginatedBackups,
total: backups.length,
page: parseInt(page),
limit: parseInt(limit)
});
} catch (error) {
logger.error('获取备份列表失败:', error);
res.status(500).json({
success: false,
message: '获取备份列表失败',
error: error.message
});
}
};
/**
* 获取备份统计
* @route GET /api/backup/stats
*/
const getBackupStats = async (req, res) => {
try {
const stats = await backupService.getBackupStats();
res.json({
success: true,
data: stats
});
} catch (error) {
logger.error('获取备份统计失败:', error);
res.status(500).json({
success: false,
message: '获取备份统计失败',
error: error.message
});
}
};
/**
* 删除备份
* @route DELETE /api/backup/:id
*/
const deleteBackup = async (req, res) => {
try {
const { id } = req.params;
const result = await backupService.deleteBackup(id);
if (result) {
logger.info(`用户 ${req.user.username} 删除备份: ${id}`);
res.json({
success: true,
message: '备份删除成功'
});
} else {
res.status(404).json({
success: false,
message: '备份不存在或删除失败'
});
}
} catch (error) {
logger.error('删除备份失败:', error);
res.status(500).json({
success: false,
message: '删除备份失败',
error: error.message
});
}
};
/**
* 恢复数据库
* @route POST /api/backup/:id/restore
*/
const restoreBackup = async (req, res) => {
try {
const { id } = req.params;
const { confirm } = req.body;
if (!confirm) {
return res.status(400).json({
success: false,
message: '请确认恢复操作'
});
}
logger.info(`用户 ${req.user.username} 开始恢复备份: ${id}`);
const result = await backupService.restoreDatabase(id);
if (result) {
res.json({
success: true,
message: '数据恢复成功'
});
} else {
res.status(500).json({
success: false,
message: '数据恢复失败'
});
}
} catch (error) {
logger.error('恢复备份失败:', error);
res.status(500).json({
success: false,
message: '恢复备份失败',
error: error.message
});
}
};
/**
* 下载备份文件
* @route GET /api/backup/:id/download
*/
const downloadBackup = async (req, res) => {
try {
const { id } = req.params;
const path = require('path');
const fs = require('fs');
const archivePath = path.join(__dirname, '../../backups', `${id}.zip`);
// 检查文件是否存在
if (!fs.existsSync(archivePath)) {
return res.status(404).json({
success: false,
message: '备份文件不存在'
});
}
const stats = fs.statSync(archivePath);
const filename = `backup_${id}.zip`;
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.setHeader('Content-Length', stats.size);
const readStream = fs.createReadStream(archivePath);
readStream.pipe(res);
logger.info(`用户 ${req.user.username} 下载备份: ${id}`);
} catch (error) {
logger.error('下载备份失败:', error);
res.status(500).json({
success: false,
message: '下载备份失败',
error: error.message
});
}
};
/**
* 清理过期备份
* @route POST /api/backup/cleanup
*/
const cleanupBackups = async (req, res) => {
try {
const deletedCount = await backupService.cleanupExpiredBackups();
logger.info(`用户 ${req.user.username} 执行备份清理,删除了 ${deletedCount} 个过期备份`);
res.json({
success: true,
message: `清理完成,删除了 ${deletedCount} 个过期备份`,
data: { deletedCount }
});
} catch (error) {
logger.error('清理备份失败:', error);
res.status(500).json({
success: false,
message: '清理备份失败',
error: error.message
});
}
};
/**
* 获取备份健康状态
* @route GET /api/backup/health
*/
const getBackupHealth = async (req, res) => {
try {
const health = await backupService.checkBackupHealth();
res.json({
success: true,
data: health
});
} catch (error) {
logger.error('获取备份健康状态失败:', error);
res.status(500).json({
success: false,
message: '获取备份健康状态失败',
error: error.message
});
}
};
/**
* 自动备份任务调度器
*/
class BackupScheduler {
constructor() {
this.tasks = new Map();
}
/**
* 启动自动备份调度
*/
start() {
// 每日备份凌晨2点
const dailyTask = cron.schedule('0 2 * * *', async () => {
try {
logger.info('开始执行自动日备份');
await backupService.createFullBackup({ type: 'daily', description: '自动日备份' });
logger.info('自动日备份完成');
} catch (error) {
logger.error('自动日备份失败:', error);
}
}, {
scheduled: false
});
// 每周备份周日凌晨3点
const weeklyTask = cron.schedule('0 3 * * 0', async () => {
try {
logger.info('开始执行自动周备份');
await backupService.createFullBackup({ type: 'weekly', description: '自动周备份' });
logger.info('自动周备份完成');
} catch (error) {
logger.error('自动周备份失败:', error);
}
}, {
scheduled: false
});
// 每月备份每月1号凌晨4点
const monthlyTask = cron.schedule('0 4 1 * *', async () => {
try {
logger.info('开始执行自动月备份');
await backupService.createFullBackup({ type: 'monthly', description: '自动月备份' });
logger.info('自动月备份完成');
} catch (error) {
logger.error('自动月备份失败:', error);
}
}, {
scheduled: false
});
// 自动清理任务每天凌晨5点
const cleanupTask = cron.schedule('0 5 * * *', async () => {
try {
logger.info('开始执行自动备份清理');
const deletedCount = await backupService.cleanupExpiredBackups();
logger.info(`自动备份清理完成,删除了 ${deletedCount} 个过期备份`);
} catch (error) {
logger.error('自动备份清理失败:', error);
}
}, {
scheduled: false
});
this.tasks.set('daily', dailyTask);
this.tasks.set('weekly', weeklyTask);
this.tasks.set('monthly', monthlyTask);
this.tasks.set('cleanup', cleanupTask);
// 启动所有任务
this.tasks.forEach((task, name) => {
task.start();
logger.info(`备份调度任务已启动: ${name}`);
});
}
/**
* 停止自动备份调度
*/
stop() {
this.tasks.forEach((task, name) => {
task.stop();
logger.info(`备份调度任务已停止: ${name}`);
});
}
/**
* 获取调度状态
*/
getStatus() {
const status = {};
this.tasks.forEach((task, name) => {
status[name] = {
running: task.running,
lastExecution: task.lastExecution,
nextExecution: task.nextExecution
};
});
return status;
}
}
// 创建调度器实例
const scheduler = new BackupScheduler();
/**
* 启动自动备份调度
* @route POST /api/backup/schedule/start
*/
const startScheduler = async (req, res) => {
try {
scheduler.start();
logger.info(`用户 ${req.user.username} 启动自动备份调度`);
res.json({
success: true,
message: '自动备份调度已启动'
});
} catch (error) {
logger.error('启动备份调度失败:', error);
res.status(500).json({
success: false,
message: '启动备份调度失败',
error: error.message
});
}
};
/**
* 停止自动备份调度
* @route POST /api/backup/schedule/stop
*/
const stopScheduler = async (req, res) => {
try {
scheduler.stop();
logger.info(`用户 ${req.user.username} 停止自动备份调度`);
res.json({
success: true,
message: '自动备份调度已停止'
});
} catch (error) {
logger.error('停止备份调度失败:', error);
res.status(500).json({
success: false,
message: '停止备份调度失败',
error: error.message
});
}
};
/**
* 获取调度状态
* @route GET /api/backup/schedule/status
*/
const getSchedulerStatus = async (req, res) => {
try {
const status = scheduler.getStatus();
res.json({
success: true,
data: status
});
} catch (error) {
logger.error('获取调度状态失败:', error);
res.status(500).json({
success: false,
message: '获取调度状态失败',
error: error.message
});
}
};
module.exports = {
createBackup,
getBackupList,
getBackupStats,
deleteBackup,
restoreBackup,
downloadBackup,
cleanupBackups,
getBackupHealth,
startScheduler,
stopScheduler,
getSchedulerStatus,
scheduler
};