web_ppt_7.7 / backend /src /services /autoBackupService.js
CatPtain's picture
Upload 85 files
28e1dba verified
import cron from 'node-cron';
import githubService from './githubService.js';
import huggingfaceStorageService from './huggingfaceStorageService.js';
import fs from 'fs/promises';
import path from 'path';
/**
* 自动备份服务
* 负责定时将修改的PPT数据同步到GitHub仓库
*/
class AutoBackupService {
constructor() {
this.isInitialized = false;
this.cronJob = null;
this.backupInterval = '0 */8 * * *'; // 每8小时执行一次
this.lastBackupTime = null;
this.backupStats = {
totalBackups: 0,
successfulBackups: 0,
failedBackups: 0,
lastBackupResult: null
};
// 跟踪修改的PPT数据
this.modifiedPPTs = new Map(); // userId -> Set of pptIds
this.lastModificationCheck = new Date();
console.log('🔄 AutoBackupService initialized');
}
/**
* 初始化自动备份服务
*/
async initialize() {
try {
console.log('🚀 Initializing AutoBackupService...');
// 加载上次备份时间
await this.loadBackupState();
// 启动定时任务
this.startCronJob();
this.isInitialized = true;
console.log('✅ AutoBackupService initialized successfully');
console.log(`⏰ Next backup scheduled: ${this.getNextBackupTime()}`);
} catch (error) {
console.error('❌ Failed to initialize AutoBackupService:', error);
throw error;
}
}
/**
* 启动定时任务
*/
startCronJob() {
if (this.cronJob) {
this.cronJob.stop();
}
console.log(`⏰ Starting auto backup cron job: ${this.backupInterval}`);
this.cronJob = cron.schedule(this.backupInterval, async () => {
console.log('🔄 Auto backup triggered by cron job');
await this.performAutoBackup();
}, {
scheduled: true,
timezone: 'UTC'
});
console.log('✅ Auto backup cron job started');
}
/**
* 停止定时任务
*/
stopCronJob() {
if (this.cronJob) {
this.cronJob.stop();
this.cronJob = null;
console.log('🛑 Auto backup cron job stopped');
}
}
/**
* 记录PPT修改
*/
recordPPTModification(userId, pptId) {
if (!this.modifiedPPTs.has(userId)) {
this.modifiedPPTs.set(userId, new Set());
}
this.modifiedPPTs.get(userId).add(pptId);
console.log(`📝 Recorded modification: User ${userId}, PPT ${pptId}`);
}
/**
* 获取修改的PPT列表
*/
getModifiedPPTs() {
const result = {};
for (const [userId, pptIds] of this.modifiedPPTs.entries()) {
result[userId] = Array.from(pptIds);
}
return result;
}
/**
* 清除修改记录
*/
clearModificationRecords() {
this.modifiedPPTs.clear();
console.log('🧹 Cleared modification records');
}
/**
* 执行自动备份
*/
async performAutoBackup() {
const startTime = new Date();
console.log(`🔄 Starting auto backup at ${startTime.toISOString()}`);
const backupResult = {
timestamp: startTime,
success: false,
totalUsers: 0,
totalPPTs: 0,
successfulBackups: 0,
failedBackups: 0,
errors: [],
duration: 0
};
try {
this.backupStats.totalBackups++;
// 获取修改的PPT列表
const modifiedPPTs = this.getModifiedPPTs();
const userIds = Object.keys(modifiedPPTs);
backupResult.totalUsers = userIds.length;
if (userIds.length === 0) {
console.log('ℹ️ No modified PPTs found, skipping backup');
backupResult.success = true;
return backupResult;
}
console.log(`📊 Found ${userIds.length} users with modified PPTs`);
// 为每个用户备份修改的PPT
for (const userId of userIds) {
const pptIds = modifiedPPTs[userId];
backupResult.totalPPTs += pptIds.length;
console.log(`👤 Backing up ${pptIds.length} PPTs for user ${userId}`);
for (const pptId of pptIds) {
try {
// 获取PPT数据
const pptData = await githubService.getPPT(userId, pptId);
if (!pptData) {
console.warn(`⚠️ PPT ${pptId} not found for user ${userId}`);
backupResult.failedBackups++;
continue;
}
// 执行备份到GitHub
await githubService.savePPT(userId, pptId, pptData);
console.log(`✅ Successfully backed up PPT ${pptId} for user ${userId}`);
backupResult.successfulBackups++;
} catch (error) {
console.error(`❌ Failed to backup PPT ${pptId} for user ${userId}:`, error);
backupResult.failedBackups++;
backupResult.errors.push({
userId,
pptId,
error: error.message
});
}
}
}
// 清除修改记录
this.clearModificationRecords();
backupResult.success = backupResult.failedBackups === 0;
if (backupResult.success) {
this.backupStats.successfulBackups++;
console.log(`✅ Auto backup completed successfully: ${backupResult.successfulBackups} PPTs backed up`);
} else {
this.backupStats.failedBackups++;
console.error(`❌ Auto backup completed with errors: ${backupResult.failedBackups} failures`);
}
} catch (error) {
console.error('❌ Auto backup failed:', error);
backupResult.success = false;
backupResult.errors.push({
type: 'system',
error: error.message
});
this.backupStats.failedBackups++;
} finally {
const endTime = new Date();
backupResult.duration = endTime.getTime() - startTime.getTime();
this.lastBackupTime = startTime;
this.backupStats.lastBackupResult = backupResult;
// 保存备份状态
await this.saveBackupState();
console.log(`🏁 Auto backup finished in ${backupResult.duration}ms`);
}
return backupResult;
}
/**
* 手动触发备份
*/
async triggerManualBackup() {
console.log('🔄 Manual backup triggered');
return await this.performAutoBackup();
}
/**
* 获取下次备份时间
*/
getNextBackupTime() {
if (!this.cronJob) {
return 'Not scheduled';
}
// 计算下次执行时间(简化版本)
const now = new Date();
const nextHour = Math.ceil(now.getHours() / 8) * 8;
const nextBackup = new Date(now);
if (nextHour >= 24) {
nextBackup.setDate(nextBackup.getDate() + 1);
nextBackup.setHours(0, 0, 0, 0);
} else {
nextBackup.setHours(nextHour, 0, 0, 0);
}
return nextBackup.toISOString();
}
/**
* 获取备份统计信息
*/
getBackupStats() {
return {
...this.backupStats,
lastBackupTime: this.lastBackupTime,
nextBackupTime: this.getNextBackupTime(),
modifiedPPTsCount: Array.from(this.modifiedPPTs.values())
.reduce((total, pptSet) => total + pptSet.size, 0),
isRunning: !!this.cronJob,
backupInterval: this.backupInterval
};
}
/**
* 加载备份状态
*/
async loadBackupState() {
try {
const stateFile = path.join(process.cwd(), 'data', 'backup-state.json');
try {
const stateData = await fs.readFile(stateFile, 'utf8');
const state = JSON.parse(stateData);
this.lastBackupTime = state.lastBackupTime ? new Date(state.lastBackupTime) : null;
this.backupStats = { ...this.backupStats, ...state.backupStats };
console.log('📂 Loaded backup state from file');
} catch (error) {
if (error.code !== 'ENOENT') {
console.warn('⚠️ Failed to load backup state:', error.message);
}
}
} catch (error) {
console.warn('⚠️ Failed to load backup state:', error.message);
}
}
/**
* 保存备份状态
*/
async saveBackupState() {
try {
const stateFile = path.join(process.cwd(), 'data', 'backup-state.json');
const stateDir = path.dirname(stateFile);
// 确保目录存在
await fs.mkdir(stateDir, { recursive: true });
const state = {
lastBackupTime: this.lastBackupTime,
backupStats: this.backupStats,
savedAt: new Date().toISOString()
};
await fs.writeFile(stateFile, JSON.stringify(state, null, 2));
console.log('💾 Saved backup state to file');
} catch (error) {
console.warn('⚠️ Failed to save backup state:', error.message);
}
}
/**
* 更新备份间隔
*/
updateBackupInterval(cronExpression) {
this.backupInterval = cronExpression;
console.log(`⏰ Updated backup interval to: ${cronExpression}`);
// 重启定时任务
this.startCronJob();
}
/**
* 清理服务
*/
async cleanup() {
console.log('🧹 Cleaning up AutoBackupService...');
this.stopCronJob();
await this.saveBackupState();
console.log('✅ AutoBackupService cleanup completed');
}
}
// 创建单例实例
const autoBackupService = new AutoBackupService();
export default autoBackupService;