Files
nxxmdata/backend/controllers/backupController.js
2025-09-12 20:08:42 +08:00

449 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 备份管理控制器
* @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
};