修改管理后台
This commit is contained in:
448
backend/controllers/backupController.js
Normal file
448
backend/controllers/backupController.js
Normal file
@@ -0,0 +1,448 @@
|
||||
/**
|
||||
* 备份管理控制器
|
||||
* @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
|
||||
};
|
||||
Reference in New Issue
Block a user