feat(backup): 实现自动备份功能及错误边界处理
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 命令行备份脚本
|
||||
* 用于独立执行数据备份,支持环境迁移
|
||||
*
|
||||
* 使用方法:
|
||||
* node scripts/backup.js [options]
|
||||
*
|
||||
* 选项:
|
||||
* --output, -o 指定备份文件输出路径
|
||||
* --description 备份描述
|
||||
* --no-files 不包含上传文件
|
||||
* --help, -h 显示帮助信息
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const options = {
|
||||
output: null,
|
||||
description: '',
|
||||
includeFiles: true,
|
||||
};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
switch (arg) {
|
||||
case '--output':
|
||||
case '-o':
|
||||
options.output = args[++i];
|
||||
break;
|
||||
case '--description':
|
||||
options.description = args[++i];
|
||||
break;
|
||||
case '--no-files':
|
||||
options.includeFiles = false;
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
console.log(`
|
||||
数据备份脚本
|
||||
|
||||
使用方法:
|
||||
node scripts/backup.js [options]
|
||||
|
||||
选项:
|
||||
--output, -o <path> 指定备份文件输出路径
|
||||
--description <text> 备份描述
|
||||
--no-files 不包含上传文件
|
||||
--help, -h 显示帮助信息
|
||||
|
||||
示例:
|
||||
node scripts/backup.js
|
||||
node scripts/backup.js -o /path/to/backup.json
|
||||
node scripts/backup.js --description "迁移前备份" --no-files
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
async function runBackup() {
|
||||
console.log('========================================');
|
||||
console.log(' IDC设备管理系统 - 数据备份工具');
|
||||
console.log('========================================\n');
|
||||
|
||||
try {
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
|
||||
const { createBackup, getBackupPath } = require('../utils/backup');
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功\n');
|
||||
|
||||
const backupPath = options.output ? path.dirname(options.output) : getBackupPath();
|
||||
|
||||
console.log('备份配置:');
|
||||
console.log(` 输出路径: ${backupPath}`);
|
||||
console.log(` 包含文件: ${options.includeFiles ? '是' : '否'}`);
|
||||
if (options.description) {
|
||||
console.log(` 备份描述: ${options.description}`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
const result = await createBackup({
|
||||
description: options.description,
|
||||
includeFiles: options.includeFiles,
|
||||
backupPath: options.output ? path.dirname(options.output) : null,
|
||||
});
|
||||
|
||||
if (options.output) {
|
||||
const targetPath = options.output;
|
||||
const sourcePath = result.path;
|
||||
if (sourcePath !== targetPath) {
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
result.path = targetPath;
|
||||
result.filename = path.basename(targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log(' 备份完成!');
|
||||
console.log('========================================');
|
||||
console.log(`\n备份文件: ${result.filename}`);
|
||||
console.log(`完整路径: ${result.path}`);
|
||||
console.log(`文件大小: ${(result.size / 1024).toFixed(2)} KB`);
|
||||
console.log(`数据记录: ${result.recordCount} 条`);
|
||||
console.log(`文件数量: ${result.fileCount} 个`);
|
||||
console.log(`创建时间: ${result.createdAt}`);
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('\n备份失败:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
runBackup();
|
||||
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 命令行恢复脚本
|
||||
* 用于独立执行数据恢复,支持跨环境迁移
|
||||
*
|
||||
* 使用方法:
|
||||
* node scripts/restore.js <backup-file> [options]
|
||||
*
|
||||
* 选项:
|
||||
* --skip-users 跳过用户数据恢复
|
||||
* --skip-files 跳过文件恢复
|
||||
* --no-overwrite 不覆盖现有数据(追加模式)
|
||||
* --help, -h 显示帮助信息
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const options = {
|
||||
file: null,
|
||||
skipUsers: false,
|
||||
skipFiles: false,
|
||||
overwrite: true,
|
||||
};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
switch (arg) {
|
||||
case '--skip-users':
|
||||
options.skipUsers = true;
|
||||
break;
|
||||
case '--skip-files':
|
||||
options.skipFiles = true;
|
||||
break;
|
||||
case '--no-overwrite':
|
||||
options.overwrite = false;
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
console.log(`
|
||||
数据恢复脚本
|
||||
|
||||
使用方法:
|
||||
node scripts/restore.js <backup-file> [options]
|
||||
|
||||
选项:
|
||||
--skip-users 跳过用户数据恢复
|
||||
--skip-files 跳过文件恢复
|
||||
--no-overwrite 不覆盖现有数据(追加模式)
|
||||
--help, -h 显示帮助信息
|
||||
|
||||
示例:
|
||||
node scripts/restore.js backup_2024-01-15.json
|
||||
node scripts/restore.js ./backups/backup_xxx.json --skip-users
|
||||
node scripts/restore.js /path/to/backup.json --no-overwrite
|
||||
`);
|
||||
process.exit(0);
|
||||
default:
|
||||
if (!arg.startsWith('-') && !options.file) {
|
||||
options.file = arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.file) {
|
||||
console.error('错误: 请指定备份文件路径');
|
||||
console.error('使用 --help 查看帮助信息');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function runRestore() {
|
||||
console.log('========================================');
|
||||
console.log(' IDC设备管理系统 - 数据恢复工具');
|
||||
console.log('========================================\n');
|
||||
|
||||
const backupFile = path.resolve(options.file);
|
||||
|
||||
if (!fs.existsSync(backupFile)) {
|
||||
console.error(`错误: 备份文件不存在: ${backupFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
|
||||
const { restoreBackup, validateBackupFile } = require('../utils/backup');
|
||||
const { sequelize } = require('../db');
|
||||
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功\n');
|
||||
|
||||
console.log('验证备份文件...');
|
||||
const validation = await validateBackupFile(backupFile);
|
||||
|
||||
if (!validation.valid) {
|
||||
console.error(`错误: 备份文件验证失败: ${validation.error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('\n备份文件信息:');
|
||||
console.log(` 版本: ${validation.version}`);
|
||||
console.log(` 类型: ${validation.backupType || 'full'}`);
|
||||
console.log(` 时间: ${validation.timestamp}`);
|
||||
console.log(` 表数: ${validation.metadata.tableCount}`);
|
||||
console.log(` 记录数: ${validation.metadata.totalRecords}`);
|
||||
console.log(` 文件数: ${validation.metadata.fileCount || 0}`);
|
||||
|
||||
if (validation.description) {
|
||||
console.log(` 描述: ${validation.description}`);
|
||||
}
|
||||
|
||||
console.log('\n恢复配置:');
|
||||
console.log(` 覆盖模式: ${options.overwrite ? '覆盖现有数据' : '追加模式'}`);
|
||||
console.log(` 恢复用户: ${options.skipUsers ? '否' : '是'}`);
|
||||
console.log(` 恢复文件: ${options.skipFiles ? '否' : '是'}`);
|
||||
console.log('');
|
||||
|
||||
const skipTables = [];
|
||||
if (options.skipUsers) {
|
||||
skipTables.push('User', 'UserRole', 'Permission');
|
||||
}
|
||||
|
||||
console.log('开始恢复数据...\n');
|
||||
|
||||
const result = await restoreBackup(backupFile, {
|
||||
overwriteExisting: options.overwrite,
|
||||
skipTables,
|
||||
skipFiles: options.skipFiles,
|
||||
onProgress: (tableName, status, count) => {
|
||||
const statusMap = {
|
||||
'restored': '✓ 已恢复',
|
||||
'skipped': '○ 已跳过',
|
||||
'empty': '- 无数据',
|
||||
'error': '✗ 错误',
|
||||
};
|
||||
const statusText = statusMap[status] || status;
|
||||
const countText = count ? ` (${count} 条)` : '';
|
||||
console.log(` ${tableName.padEnd(25)} ${statusText}${countText}`);
|
||||
},
|
||||
});
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log(' 恢复完成!');
|
||||
console.log('========================================');
|
||||
console.log(`\n恢复统计:`);
|
||||
console.log(` 恢复表数: ${result.tablesRestored} 个`);
|
||||
console.log(` 恢复记录: ${result.recordsRestored} 条`);
|
||||
console.log(` 恢复文件: ${result.filesRestored} 个`);
|
||||
console.log(` 完成时间: ${result.restoredAt}`);
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
console.log(`\n警告: 有 ${result.errors.length} 个错误`);
|
||||
result.errors.slice(0, 10).forEach(err => {
|
||||
console.log(` - ${err.table}: ${err.error}`);
|
||||
});
|
||||
if (result.errors.length > 10) {
|
||||
console.log(` ... 还有 ${result.errors.length - 10} 个错误`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n提示: 请重启后端服务以确保所有数据生效');
|
||||
|
||||
await sequelize.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('\n恢复失败:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
runRestore();
|
||||
Reference in New Issue
Block a user