import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { PrismaService } from '../../infrastructure/database/prisma.service'; @Injectable() export class QuizService { constructor(private readonly prisma: PrismaService) {} async create(userId: string, dto: { knowledgeBaseId: string; title?: string; sourceType?: string; sourceId?: string; questionCount?: number }) { const count = dto.questionCount ?? 5; const kb = await this.prisma.knowledgeBase.findUnique({ where: { id: dto.knowledgeBaseId } }); if (!kb || kb.deletedAt) throw new NotFoundException('知识库不存在'); const quiz = await this.prisma.quiz.create({ data: { userId, knowledgeBaseId: dto.knowledgeBaseId, title: dto.title || `${kb.title} - 自测`, sourceType: dto.sourceType ?? 'kb', sourceId: dto.sourceId ?? null, questionCount: count, }, }); // Generate questions from KB items const items = await this.prisma.knowledgeItem.findMany({ where: { knowledgeBaseId: dto.knowledgeBaseId, deletedAt: null }, orderBy: { updatedAt: 'desc' }, take: count * 2, }); if (items.length === 0) throw new BadRequestException('知识库中没有知识点,无法生成测验'); const shuffled = items.sort(() => Math.random() - 0.5).slice(0, count); const questions: any[] = []; for (let i = 0; i < shuffled.length; i++) { const item = shuffled[i]; const otherItems = items.filter(x => x.id !== item.id); // Alternate question types const types = ['choice', 'fill', 'judge']; const qType = types[i % 3]; let stem = '', options: string[] = [], answer = ''; if (qType === 'choice') { stem = `${item.title} 是什么?`; const correct = item.content?.slice(0, 80) ?? '正确答案'; options = [correct]; for (const o of otherItems.slice(0, 3)) { options.push(o.content?.slice(0, 80) ?? '其他选项'); } options = options.sort(() => Math.random() - 0.5); answer = String(options.indexOf(correct)); } else if (qType === 'fill') { const words = (item.content ?? '').split(/[,。;\s]+/).filter(w => w.length >= 3); const blank = words.length > 0 ? words[Math.floor(Math.random() * words.length)] : '关键概念'; stem = `${item.title}:请填写缺失的关键词。${(item.content ?? '').replace(blank, '____')}`; answer = blank; } else { const isCorrect = Math.random() > 0.5; stem = `关于「${item.title}」,以下说法是否正确?${isCorrect ? item.content?.slice(0, 100) ?? '正确描述' : '错误描述'}`; answer = String(isCorrect); } questions.push({ quizId: quiz.id, type: qType, stem, options: options.length > 0 ? options : undefined, answer, explanation: item.content?.slice(0, 200) ?? '', orderIndex: i, }); } await this.prisma.quizQuestion.createMany({ data: questions }); return this.prisma.quiz.findUnique({ where: { id: quiz.id }, include: { questions: { orderBy: { orderIndex: 'asc' } } } }); } async findAll(userId: string, kbId?: string) { return this.prisma.quiz.findMany({ where: { userId, ...(kbId ? { knowledgeBaseId: kbId } : {}) }, orderBy: { createdAt: 'desc' }, }); } async findOne(id: string) { const quiz = await this.prisma.quiz.findUnique({ where: { id }, include: { questions: { orderBy: { orderIndex: 'asc' } } }, }); if (!quiz) throw new NotFoundException('测验不存在'); return quiz; } async start(userId: string, quizId: string) { const quiz = await this.prisma.quiz.findUnique({ where: { id: quizId } }); if (!quiz) throw new NotFoundException('测验不存在'); return this.prisma.quizAttempt.create({ data: { quizId, userId, totalQuestions: quiz.questionCount }, }); } async submit(userId: string, attemptId: string, answers: { questionId: string; answer: string }[]) { const attempt = await this.prisma.quizAttempt.findUnique({ where: { id: attemptId } }); if (!attempt || attempt.userId !== userId) throw new NotFoundException('答题记录不存在'); if (attempt.finishedAt) throw new BadRequestException('已提交过答案'); let correctCount = 0; for (const a of answers) { const q = await this.prisma.quizQuestion.findUnique({ where: { id: a.questionId } }); const isCorrect = q?.answer === a.answer; if (isCorrect) correctCount++; await this.prisma.quizAnswer.create({ data: { attemptId, questionId: a.questionId, userAnswer: a.answer, isCorrect }, }); } const score = answers.length > 0 ? Math.round((correctCount / answers.length) * 100) : 0; await this.prisma.quizAttempt.update({ where: { id: attemptId }, data: { correctCount, score, finishedAt: new Date() }, }); return { score, correctCount, totalQuestions: answers.length, finishedAt: new Date() }; } async getResults(attemptId: string) { const attempt = await this.prisma.quizAttempt.findUnique({ where: { id: attemptId }, include: { answers: { include: { question: true } } }, }); if (!attempt) throw new NotFoundException('答题记录不存在'); return attempt; } async getHistory(userId: string) { return this.prisma.quizAttempt.findMany({ where: { userId }, orderBy: { startedAt: 'desc' }, take: 50, include: { quiz: { select: { title: true } } }, }); } }