2026-05-09 18:25:04 +08:00
|
|
|
import { Injectable, Logger } from '@nestjs/common';
|
|
|
|
|
import { DocumentImportRepository } from './document-import.repository';
|
2026-05-18 10:07:57 +08:00
|
|
|
import { KnowledgeItemsRepository } from '../knowledge-items/knowledge-items.repository';
|
|
|
|
|
import { KnowledgeImportWorkflow } from '../ai/workflows/knowledge-import.workflow';
|
2026-05-09 18:25:04 +08:00
|
|
|
import { RedisService } from '../../infrastructure/redis/redis.service';
|
|
|
|
|
import { QueueService } from '../../infrastructure/queue/queue.service';
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class DocumentImportService {
|
|
|
|
|
private readonly logger = new Logger(DocumentImportService.name);
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly repository: DocumentImportRepository,
|
2026-05-18 10:07:57 +08:00
|
|
|
private readonly knowledgeItemsRepo: KnowledgeItemsRepository,
|
|
|
|
|
private readonly workflow: KnowledgeImportWorkflow,
|
2026-05-09 18:25:04 +08:00
|
|
|
private readonly redis: RedisService,
|
|
|
|
|
private readonly queue: QueueService,
|
|
|
|
|
) {}
|
|
|
|
|
|
2026-05-18 10:07:57 +08:00
|
|
|
async createImport(dto: {
|
|
|
|
|
userId?: string;
|
|
|
|
|
knowledgeBaseId?: string;
|
|
|
|
|
fileName?: string;
|
|
|
|
|
sourceType?: string;
|
|
|
|
|
rawText?: string;
|
|
|
|
|
}) {
|
2026-05-09 18:25:04 +08:00
|
|
|
const lockKey = `lock:document-import:${dto.fileName || Date.now()}`;
|
|
|
|
|
const lockToken = await this.redis.lock(lockKey, 1800);
|
|
|
|
|
if (!lockToken) {
|
|
|
|
|
throw new Error('相同文件正在导入中,请稍候');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const job = await this.repository.create(dto);
|
|
|
|
|
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:status`, 'pending', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:progress`, '0', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:message`, '任务已加入队列', 86400);
|
|
|
|
|
|
2026-05-18 10:07:57 +08:00
|
|
|
this.queue.add('document-import', {
|
|
|
|
|
importId: job.id,
|
|
|
|
|
userId: dto.userId || 'anonymous',
|
|
|
|
|
knowledgeBaseId: dto.knowledgeBaseId,
|
|
|
|
|
rawText: dto.rawText,
|
|
|
|
|
fileName: dto.fileName,
|
|
|
|
|
});
|
2026-05-09 18:25:04 +08:00
|
|
|
|
2026-05-18 10:07:57 +08:00
|
|
|
this.processImport(job, dto.rawText, dto.knowledgeBaseId, lockKey, lockToken);
|
2026-05-09 18:25:04 +08:00
|
|
|
|
|
|
|
|
return job;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 10:07:57 +08:00
|
|
|
private async processImport(
|
|
|
|
|
job: { id: string; userId?: string },
|
|
|
|
|
rawText: string | undefined,
|
|
|
|
|
knowledgeBaseId: string | undefined,
|
|
|
|
|
lockKey: string,
|
|
|
|
|
lockToken: string,
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
if (!rawText) {
|
|
|
|
|
await this.repository.updateStatus(job.id, 'completed');
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:status`, 'completed', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:progress`, '100', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:message`, '无需解析的空文件', 86400);
|
|
|
|
|
await this.redis.unlock(lockKey, lockToken);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.repository.updateStatus(job.id, 'processing');
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:status`, 'parsing', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:progress`, '25', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:message`, 'AI 正在分析文本,提取知识点...', 86400);
|
|
|
|
|
|
|
|
|
|
const result = await this.workflow.execute({
|
|
|
|
|
userId: job.userId || 'anonymous',
|
|
|
|
|
rawText,
|
|
|
|
|
sourceName: undefined,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:status`, 'saving', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:progress`, '80', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:message`, `正在保存 ${result.knowledgePoints.length} 个知识点...`, 86400);
|
|
|
|
|
|
|
|
|
|
if (knowledgeBaseId && result.knowledgePoints.length > 0) {
|
|
|
|
|
for (let i = 0; i < result.knowledgePoints.length; i++) {
|
|
|
|
|
const kp = result.knowledgePoints[i];
|
|
|
|
|
await this.knowledgeItemsRepo.create(job.userId || 'anonymous', knowledgeBaseId, {
|
|
|
|
|
title: kp.title,
|
|
|
|
|
content: kp.content,
|
|
|
|
|
itemType: 'lesson',
|
|
|
|
|
orderIndex: kp.suggestedOrder ?? i + 1,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.repository.updateStatus(job.id, 'completed');
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:status`, 'completed', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:progress`, '100', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:message`, `成功提取 ${result.knowledgePoints.length} 个知识点`, 86400);
|
|
|
|
|
await this.redis.unlock(lockKey, lockToken);
|
|
|
|
|
this.logger.log(`Import ${job.id} completed: ${result.knowledgePoints.length} knowledge points`);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
this.logger.error(`Import ${job.id} failed: ${error.message}`);
|
|
|
|
|
await this.repository.updateStatus(job.id, 'failed');
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:status`, 'failed', 86400);
|
|
|
|
|
await this.redis.set(`job:document-import:${job.id}:message`, `导入失败: ${error.message}`, 86400);
|
|
|
|
|
await this.redis.unlock(lockKey, lockToken);
|
|
|
|
|
}
|
2026-05-09 18:25:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getStatus(id: string) {
|
|
|
|
|
const redisStatus = await this.redis.get(`job:document-import:${id}:status`);
|
|
|
|
|
const redisProgress = await this.redis.get(`job:document-import:${id}:progress`);
|
|
|
|
|
const redisMessage = await this.redis.get(`job:document-import:${id}:message`);
|
|
|
|
|
const dbJob = await this.repository.findById(id);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id,
|
2026-05-17 00:39:46 +08:00
|
|
|
fileName: dbJob?.sourceName,
|
2026-05-09 18:25:04 +08:00
|
|
|
status: redisStatus || dbJob?.status || 'unknown',
|
|
|
|
|
progress: redisProgress ? parseInt(redisProgress, 10) : 0,
|
|
|
|
|
message: redisMessage || null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|