import { v4 as uuidv4 } from 'uuid'; import { query, run, get } from '../database'; export interface Question { id: string; content: string; type: 'single' | 'multiple' | 'judgment' | 'text'; category: string; options?: string[]; answer: string | string[]; score: number; createdAt: string; } export interface CreateQuestionData { content: string; type: 'single' | 'multiple' | 'judgment' | 'text'; category?: string; options?: string[]; answer: string | string[]; score: number; } export interface ExcelQuestionData { content: string; type: string; category?: string; answer: string; score: number; options?: string[]; } export class QuestionModel { // 创建题目 static async create(data: CreateQuestionData): Promise { const id = uuidv4(); const optionsStr = data.options ? JSON.stringify(data.options) : null; const answerStr = Array.isArray(data.answer) ? JSON.stringify(data.answer) : data.answer; const category = data.category && data.category.trim() ? data.category.trim() : '通用'; const sql = ` INSERT INTO questions (id, content, type, options, answer, score, category) VALUES (?, ?, ?, ?, ?, ?, ?) `; await run(sql, [id, data.content, data.type, optionsStr, answerStr, data.score, category]); return this.findById(id) as Promise; } // 批量创建题目 - 优化为使用事务批量插入 static async createMany(questions: CreateQuestionData[]): Promise<{ success: number; errors: string[] }> { const errors: string[] = []; let success = 0; // 使用事务提高性能 try { // 开始事务 await run('BEGIN TRANSACTION'); const sql = ` INSERT INTO questions (id, content, type, options, answer, score, category) VALUES (?, ?, ?, ?, ?, ?, ?) `; for (let i = 0; i < questions.length; i++) { try { const question = questions[i]; const id = uuidv4(); const optionsStr = question.options ? JSON.stringify(question.options) : null; const answerStr = Array.isArray(question.answer) ? JSON.stringify(question.answer) : question.answer; const category = question.category && question.category.trim() ? question.category.trim() : '通用'; // 直接执行插入,不调用单个create方法 await run(sql, [id, question.content, question.type, optionsStr, answerStr, question.score, category]); success++; } catch (error: any) { errors.push(`第${i + 1}题: ${error.message}`); } } // 提交事务 await run('COMMIT'); } catch (error: any) { // 回滚事务 await run('ROLLBACK'); errors.push(`事务错误: ${error.message}`); } return { success, errors }; } // 根据ID查找题目 static async findById(id: string): Promise { const sql = `SELECT * FROM questions WHERE id = ?`; const question = await get(sql, [id]); if (!question) return null; return this.formatQuestion(question); } // 根据题目内容查找题目 static async findByContent(content: string): Promise { const sql = `SELECT * FROM questions WHERE content = ?`; const question = await get(sql, [content]); if (!question) return null; return this.formatQuestion(question); } // 获取题目列表(支持筛选和分页) static async findAll(filters: { type?: string; category?: string; keyword?: string; startDate?: string; endDate?: string; limit?: number; offset?: number; } = {}): Promise<{ questions: Question[]; total: number }> { const { type, category, keyword, startDate, endDate, limit = 10, offset = 0 } = filters; const whereParts: string[] = []; const params: any[] = []; if (type) { whereParts.push('type = ?'); params.push(type); } if (category) { whereParts.push('category = ?'); params.push(category); } if (keyword) { whereParts.push('content LIKE ?'); params.push(`%${keyword}%`); } if (startDate) { whereParts.push('created_at >= ?'); params.push(`${startDate} 00:00:00`); } if (endDate) { whereParts.push('created_at <= ?'); params.push(`${endDate} 23:59:59`); } const whereClause = whereParts.length > 0 ? `WHERE ${whereParts.join(' AND ')}` : ''; const questionsSql = ` SELECT * FROM questions ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ? `; const countSql = ` SELECT COUNT(*) as total FROM questions ${whereClause} `; const [questions, countResult] = await Promise.all([ query(questionsSql, [...params, limit, offset]), get(countSql, params) ]); return { questions: questions.map((q) => this.formatQuestion(q)), total: countResult.total }; } // 随机获取题目(按类型和数量) static async getRandomQuestions(type: string, count: number, categories?: string[]): Promise { const whereParts: string[] = ['type = ?']; const params: any[] = [type]; if (categories && categories.length > 0) { whereParts.push(`category IN (${categories.map(() => '?').join(',')})`); params.push(...categories); } const sql = ` SELECT * FROM questions WHERE ${whereParts.join(' AND ')} ORDER BY RANDOM() LIMIT ? `; const questions = await query(sql, [...params, count]); return questions.map((q) => this.formatQuestion(q)); } // 更新题目 static async update(id: string, data: Partial): Promise { const fields: string[] = []; const values: any[] = []; if (data.content) { fields.push('content = ?'); values.push(data.content); } if (data.type) { fields.push('type = ?'); values.push(data.type); } if (data.options !== undefined) { fields.push('options = ?'); values.push(data.options ? JSON.stringify(data.options) : null); } if (data.answer !== undefined) { const answerStr = Array.isArray(data.answer) ? JSON.stringify(data.answer) : data.answer; fields.push('answer = ?'); values.push(answerStr); } if (data.score !== undefined) { fields.push('score = ?'); values.push(data.score); } if (data.category !== undefined) { fields.push('category = ?'); values.push(data.category && data.category.trim() ? data.category.trim() : '通用'); } if (fields.length === 0) { throw new Error('没有要更新的字段'); } values.push(id); const sql = `UPDATE questions SET ${fields.join(', ')} WHERE id = ?`; await run(sql, values); return this.findById(id) as Promise; } // 删除题目 static async delete(id: string): Promise { const sql = `DELETE FROM questions WHERE id = ?`; const result = await run(sql, [id]); return result.id !== undefined; } // 格式化题目数据 private static formatQuestion(row: any): Question { return { id: row.id, content: row.content, type: row.type, category: row.category || '通用', options: row.options ? JSON.parse(row.options) : undefined, answer: this.parseAnswer(row.answer, row.type), score: row.score, createdAt: row.created_at }; } // 解析答案 private static parseAnswer(answerStr: string, type: string): string | string[] { if (type === 'multiple') { try { return JSON.parse(answerStr); } catch { return answerStr; } } return answerStr; } // 验证题目数据 static validateQuestionData(data: CreateQuestionData): string[] { const errors: string[] = []; // 验证题目内容 if (!data.content || data.content.trim().length === 0) { errors.push('题目内容不能为空'); } // 验证题型 const validTypes = ['single', 'multiple', 'judgment', 'text']; if (!validTypes.includes(data.type)) { errors.push('题型必须是 single、multiple、judgment 或 text'); } // 验证选项 if (data.type === 'single' || data.type === 'multiple') { if (!data.options || data.options.length < 2) { errors.push('单选题和多选题必须至少包含2个选项'); } } // 验证答案 if (!data.answer) { errors.push('答案不能为空'); } // 验证分值 if (!data.score || data.score <= 0) { errors.push('分值必须是正数'); } if (data.category !== undefined && data.category.trim().length === 0) { errors.push('题目类别不能为空'); } return errors; } // 验证Excel数据格式 static validateExcelData(data: ExcelQuestionData[]): { valid: boolean; errors: string[] } { const errors: string[] = []; data.forEach((row, index) => { if (!row.content) { errors.push(`第${index + 1}行:题目内容不能为空`); } const validTypes = ['single', 'multiple', 'judgment', 'text']; if (!validTypes.includes(row.type)) { errors.push(`第${index + 1}行:题型必须是 single、multiple、judgment 或 text`); } if (!row.answer) { errors.push(`第${index + 1}行:答案不能为空`); } if (!row.score || row.score <= 0) { errors.push(`第${index + 1}行:分值必须是正数`); } }); return { valid: errors.length === 0, errors }; } }