import { Request, Response } from 'express'; import { QuestionModel, QuizModel, SystemConfigModel } from '../models'; export class QuizController { static async generateQuiz(req: Request, res: Response) { try { const { userId, subjectId, taskId } = req.body; if (!userId) { return res.status(400).json({ success: false, message: '用户ID不能为空' }); } if (taskId) { const { ExamTaskModel } = await import('../models/examTask'); const result = await ExamTaskModel.generateQuizQuestions(taskId, userId); res.json({ success: true, data: { questions: result.questions, totalScore: result.totalScore, timeLimit: result.timeLimitMinutes } }); return; } if (!subjectId) { return res.status(400).json({ success: false, message: 'subjectId或taskId必须提供其一' }); } const { ExamSubjectModel } = await import('../models/examSubject'); const subject = await ExamSubjectModel.findById(subjectId); if (!subject) { return res.status(404).json({ success: false, message: '考试科目不存在' }); } let questions: Question[] = []; const remainingScore = subject.totalScore; // 构建包含所有类别的数组,根据比重重复对应次数 const allCategories: string[] = []; for (const [category, catRatio] of Object.entries(subject.categoryRatios)) { if (catRatio > 0) { // 根据比重计算该类别应占的总题目数比例 const count = Math.round((catRatio / 100) * 100); // 放大100倍避免小数问题 for (let i = 0; i < count; i++) { allCategories.push(category); } } } // 确保总题目数至少为1 if (allCategories.length === 0) { allCategories.push('通用'); } // 按题型分配题目 for (const [type, typeRatio] of Object.entries(subject.typeRatios)) { if (typeRatio <= 0) continue; // 计算该题型应占的总分 const targetTypeScore = Math.round((typeRatio / 100) * subject.totalScore); let currentTypeScore = 0; let typeQuestions: Question[] = []; // 尝试获取足够分数的题目 while (currentTypeScore < targetTypeScore) { // 随机选择一个类别 const randomCategory = allCategories[Math.floor(Math.random() * allCategories.length)]; // 获取该类型和类别的随机题目 const availableQuestions = await QuestionModel.getRandomQuestions( type as any, 10, // 一次获取多个,提高效率 [randomCategory] ); if (availableQuestions.length === 0) { break; // 该类型/类别没有题目,跳过 } // 过滤掉已选题目 const availableUnselected = availableQuestions.filter(q => !questions.some(selected => selected.id === q.id) ); if (availableUnselected.length === 0) { break; // 没有可用的新题目了 } // 选择分数最接近剩余需求的题目 const remainingForType = targetTypeScore - currentTypeScore; const selectedQuestion = availableUnselected.reduce((prev, curr) => { const prevDiff = Math.abs(remainingForType - prev.score); const currDiff = Math.abs(remainingForType - curr.score); return currDiff < prevDiff ? curr : prev; }); // 添加到题型题目列表 typeQuestions.push(selectedQuestion); currentTypeScore += selectedQuestion.score; // 防止无限循环 if (typeQuestions.length > 100) { break; } } questions.push(...typeQuestions); } // 如果总分不足,尝试补充题目 let totalScore = questions.reduce((sum, q) => sum + q.score, 0); while (totalScore < subject.totalScore) { // 获取所有类型的随机题目 const allTypes = Object.keys(subject.typeRatios).filter(type => subject.typeRatios[type] > 0); if (allTypes.length === 0) break; const randomType = allTypes[Math.floor(Math.random() * allTypes.length)]; const availableQuestions = await QuestionModel.getRandomQuestions( randomType as any, 10, allCategories ); if (availableQuestions.length === 0) break; // 过滤掉已选题目 const availableUnselected = availableQuestions.filter(q => !questions.some(selected => selected.id === q.id) ); if (availableUnselected.length === 0) break; // 选择分数最接近剩余需求的题目 const remainingScore = subject.totalScore - totalScore; const selectedQuestion = availableUnselected.reduce((prev, curr) => { const prevDiff = Math.abs(remainingScore - prev.score); const currDiff = Math.abs(remainingScore - curr.score); return currDiff < prevDiff ? curr : prev; }); questions.push(selectedQuestion); totalScore += selectedQuestion.score; // 防止无限循环 if (questions.length > 200) { break; } } // 如果总分超过,尝试移除一些题目 while (totalScore > subject.totalScore) { // 找到最接近剩余差值的题目 const excessScore = totalScore - subject.totalScore; let closestIndex = -1; let closestDiff = Infinity; for (let i = 0; i < questions.length; i++) { const diff = Math.abs(questions[i].score - excessScore); if (diff < closestDiff) { closestDiff = diff; closestIndex = i; } } if (closestIndex === -1) break; // 移除该题目 totalScore -= questions[closestIndex].score; questions.splice(closestIndex, 1); } res.json({ success: true, data: { questions, totalScore, timeLimit: subject.timeLimitMinutes } }); } catch (error: any) { res.status(500).json({ success: false, message: error.message || '生成试卷失败' }); } } static async submitQuiz(req: Request, res: Response) { try { const { userId, subjectId, taskId, answers } = req.body; if (!userId || !answers || !Array.isArray(answers)) { return res.status(400).json({ success: false, message: '参数不完整' }); } const processedAnswers = []; for (const answer of answers) { const question = await QuestionModel.findById(answer.questionId); if (!question) { processedAnswers.push(answer); continue; } if (question.type === 'multiple') { const optionCount = question.options ? question.options.length : 0; const unitScore = optionCount > 0 ? question.score / optionCount : 0; let userAnsList: string[] = []; if (Array.isArray(answer.userAnswer)) { userAnsList = answer.userAnswer; } else if (typeof answer.userAnswer === 'string') { try { userAnsList = JSON.parse(answer.userAnswer); } catch (e) { userAnsList = [answer.userAnswer]; } } let correctAnsList: string[] = []; if (Array.isArray(question.answer)) { correctAnsList = question.answer; } else if (typeof question.answer === 'string') { try { const parsed = JSON.parse(question.answer); if (Array.isArray(parsed)) correctAnsList = parsed; else correctAnsList = [question.answer]; } catch { correctAnsList = [question.answer]; } } const userSet = new Set(userAnsList); const correctSet = new Set(correctAnsList); let isFullCorrect = true; if (userSet.size !== correctSet.size) { isFullCorrect = false; } else { for (const a of userSet) { if (!correctSet.has(a)) { isFullCorrect = false; break; } } } if (isFullCorrect) { answer.score = question.score; answer.isCorrect = true; } else { let tempScore = 0; for (const uAns of userAnsList) { if (correctSet.has(uAns)) { tempScore += unitScore; } else { tempScore -= unitScore; } } let finalScore = Math.max(0, tempScore); finalScore = Math.round(finalScore * 10) / 10; answer.score = finalScore; answer.isCorrect = false; } } else if (question.type === 'single' || question.type === 'judgment') { const isCorrect = answer.userAnswer === question.answer; answer.score = isCorrect ? question.score : 0; answer.isCorrect = isCorrect; } processedAnswers.push(answer); } const result = await QuizModel.submitQuiz({ userId, answers: processedAnswers }); if (subjectId || taskId) { const sql = ` UPDATE quiz_records SET subject_id = ?, task_id = ? WHERE id = ? `; await import('../database').then(({ run }) => run(sql, [subjectId || null, taskId || null, result.record.id])); } res.json({ success: true, data: { recordId: result.record.id, totalScore: result.record.totalScore, correctCount: result.record.correctCount, totalCount: result.record.totalCount } }); } catch (error: any) { res.status(500).json({ success: false, message: error.message || '提交答题失败' }); } } static async getUserRecords(req: Request, res: Response) { try { const { userId } = req.params; const page = parseInt(req.query.page as string) || 1; const limit = parseInt(req.query.limit as string) || 10; const result = await QuizModel.findRecordsByUserId(userId, limit, (page - 1) * limit); res.json({ success: true, data: result.records, pagination: { page, limit, total: result.total, pages: Math.ceil(result.total / limit) } }); } catch (error: any) { res.status(500).json({ success: false, message: error.message || '获取答题记录失败' }); } } static async getRecordDetail(req: Request, res: Response) { try { const { recordId } = req.params; const record = await QuizModel.findRecordById(recordId); if (!record) { return res.status(404).json({ success: false, message: '答题记录不存在' }); } const answers = await QuizModel.findAnswersByRecordId(recordId); res.json({ success: true, data: { record, answers } }); } catch (error: any) { res.status(500).json({ success: false, message: error.message || '获取答题记录详情失败' }); } } static async getAllRecords(req: Request, res: Response) { try { const page = parseInt(req.query.page as string) || 1; const limit = parseInt(req.query.limit as string) || 10; const result = await QuizModel.findAllRecords(limit, (page - 1) * limit); res.json({ success: true, data: result.records, pagination: { page, limit, total: result.total, pages: Math.ceil(result.total / limit) } }); } catch (error: any) { res.status(500).json({ success: false, message: error.message || '获取答题记录失败' }); } } }