Files
zhang1106 7f1df28858 feat: 实现ID生成器并重构模型ID生成逻辑
refactor: 统一ID生成方式,使用新的idGenerator模块
refactor: 重构模型ID生成逻辑,允许空ID并在创建前自动生成
fix(roomSchema): 使机房ID字段变为可选并更新前端表单验证
style: 格式化代码并优化导入语句
test: 添加操作日志集成测试文件
docs: 添加错误处理模块文档
2026-04-02 10:34:13 +08:00

176 lines
4.1 KiB
JavaScript

const { EventEmitter } = require('events');
const path = require('path');
const fs = require('fs');
class ImportJobManager extends EventEmitter {
constructor() {
super();
this.jobs = new Map();
this.JOB_STATUS = {
PENDING: 'pending',
PROCESSING: 'processing',
COMPLETED: 'completed',
FAILED: 'failed',
CANCELLED: 'cancelled',
};
}
createJob(jobId, type, totalItems) {
const job = {
jobId,
type,
status: this.JOB_STATUS.PENDING,
totalItems,
processedItems: 0,
successCount: 0,
failedCount: 0,
skippedCount: 0,
updatedCount: 0,
startTime: null,
endTime: null,
error: null,
canCancel: true,
result: null,
createdAt: new Date(),
};
this.jobs.set(jobId, job);
return job;
}
getJob(jobId) {
return this.jobs.get(jobId);
}
updateJob(jobId, updates) {
const job = this.jobs.get(jobId);
if (!job) return null;
Object.assign(job, updates);
this.emit('jobUpdate', job);
return job;
}
startJob(jobId) {
const job = this.jobs.get(jobId);
if (!job) return null;
job.status = this.JOB_STATUS.PROCESSING;
job.startTime = new Date();
this.emit('jobStart', job);
return job;
}
incrementProgress(jobId, success = 0, failed = 0, skipped = 0, updated = 0) {
const job = this.jobs.get(jobId);
if (!job) return null;
job.processedItems += success + failed + skipped + updated;
job.successCount += success;
job.failedCount += failed;
job.skippedCount += skipped;
job.updatedCount += updated;
this.emit('jobProgress', job);
return job;
}
completeJob(jobId, result = null) {
const job = this.jobs.get(jobId);
if (!job) return null;
job.status = this.JOB_STATUS.COMPLETED;
job.endTime = new Date();
job.canCancel = false;
job.result = result;
this.emit('jobComplete', job);
return job;
}
failJob(jobId, error) {
const job = this.jobs.get(jobId);
if (!job) return null;
job.status = this.JOB_STATUS.FAILED;
job.endTime = new Date();
job.canCancel = false;
job.error = error;
this.emit('jobFailed', job);
return job;
}
cancelJob(jobId) {
const job = this.jobs.get(jobId);
if (!job || !job.canCancel) return null;
job.status = this.JOB_STATUS.CANCELLED;
job.endTime = new Date();
job.canCancel = false;
this.emit('jobCancelled', job);
return job;
}
getJobProgress(jobId) {
const job = this.jobs.get(jobId);
if (!job) return null;
const progress = {
jobId: job.jobId,
status: job.status,
totalItems: job.totalItems,
processedItems: job.processedItems,
progressPercent:
job.totalItems > 0 ? Math.round((job.processedItems / job.totalItems) * 100) : 0,
successCount: job.successCount,
failedCount: job.failedCount,
skippedCount: job.skippedCount,
updatedCount: job.updatedCount,
canCancel: job.canCancel,
error: job.error,
startTime: job.startTime,
endTime: job.endTime,
elapsedTime: job.startTime ? Date.now() - new Date(job.startTime).getTime() : null,
};
return progress;
}
listJobs() {
return Array.from(this.jobs.values()).map(job => ({
jobId: job.jobId,
type: job.type,
status: job.status,
totalItems: job.totalItems,
processedItems: job.processedItems,
progressPercent:
job.totalItems > 0 ? Math.round((job.processedItems / job.totalItems) * 100) : 0,
canCancel: job.canCancel,
startTime: job.startTime,
endTime: job.endTime,
createdAt: job.createdAt,
}));
}
cleanupOldJobs(maxAgeMs = 24 * 60 * 60 * 1000) {
const now = Date.now();
for (const [jobId, job] of this.jobs.entries()) {
const jobEndTime = job.endTime || job.createdAt;
if (now - new Date(jobEndTime).getTime() > maxAgeMs) {
this.jobs.delete(jobId);
}
}
}
}
const importJobManager = new ImportJobManager();
setInterval(
() => {
importJobManager.cleanupOldJobs();
},
60 * 60 * 1000
);
module.exports = {
importJobManager,
ImportJobManager,
};