const fs = require('fs'); const xlsx = require('xlsx'); const mammoth = require('mammoth'); const db = require('../db/index'); // 答案转换函数 function convertAnswer(answer, type) { answer = answer.replace(/\s/g, ''); // 如果是多选题 if (type === 'checkbox') { let answers; if (answer.includes(',')) { answers = answer.split(','); } else { answers = answer.split(''); } return answers .map(a => convertSingleAnswer(a)) .filter(a => a !== null) .sort((a, b) => Number(a) - Number(b)) .join(','); } // 单选题直接转换 return convertSingleAnswer(answer); } // 单个答案转换 function convertSingleAnswer(answer) { const letterMap = { 'A': '0', 'B': '1', 'C': '2', 'D': '3', 'E': '4', 'F': '5' }; const numberMap = { '一': '0', '二': '1', '三': '2', '四': '3', '五': '4', '六': '5' }; // 检查字母映射 if (letterMap[answer.toUpperCase()]) { return letterMap[answer.toUpperCase()]; } // 检查数字文字映射 if (numberMap[answer]) { return numberMap[answer]; } return answer; } // 导入JSON题库 async function importJsonQuestions(jsonFilePath) { try { const content = fs.readFileSync(jsonFilePath, 'utf8'); const paperData = JSON.parse(content); await importQuestions(paperData); } catch (error) { console.error('导入失败:', error); } } // 处理Excel文件 async function importExcelQuestions(excelFilePath) { try { const workbook = xlsx.readFile(excelFilePath); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const data = xlsx.utils.sheet_to_json(worksheet); const paperData = { title: '从Excel导入的试卷', description: '从Excel文件导入的试题', topics: data.map(row => ({ title: row.title || row['题目'], type: row.type || row['类型'] || 'radio', options: [ `A:${row.A || row['选项A']}`, `B:${row.B || row['选项B']}`, `C:${row.C || row['选项C']}`, `D:${row.D || row['选项D']}` ], answer: row.answer || row['答案'] })) }; await importQuestions(paperData); } catch (error) { console.error('Excel导入失败:', error); } } // 处理Word文件 async function importWordQuestions(wordFilePath) { try { const result = await mammoth.extractRawText({ path: wordFilePath }); const text = result.value; const questions = text.split('\n\n').filter(q => q.trim()); const paperData = { title: '从Word导入的试卷', description: '从Word文件导入的试题', topics: questions.map(q => { const lines = q.split('\n'); return { title: lines[0].replace(/^\d+[\.、]?\s*/, ''), type: lines[0].includes('多选') ? 'checkbox' : 'radio', options: lines.slice(1, -1).map((opt, idx) => `${String.fromCharCode(65 + idx)}:${opt.replace(/^[A-Z][\.、]?\s*/, '')}` ), answer: lines[lines.length - 1].replace(/^答案[::]\s*/, '') }; }) }; await importQuestions(paperData); } catch (error) { console.error('Word导入失败:', error); } } // 处理txt文件 async function importTxtQuestions(txtFilePath) { try { const content = fs.readFileSync(txtFilePath, 'utf8'); const questions = content.split('-------------------\n\n'); const paperData = { title: '从TXT导入的试卷', description: '从文本文件导入的试题', topics: questions.map(q => { const titleMatch = q.match(/题目:(.*?)\n/); const typeMatch = q.match(/类型:(.*?)\n/); const optionsMatch = q.match(/选项:\n(.*?)\n答案:/s); const answerMatch = q.match(/答案:(.*?)(\n|$)/); if (!titleMatch || !typeMatch || !optionsMatch || !answerMatch) return null; return { title: titleMatch[1], type: typeMatch[1].includes('多选') ? 'checkbox' : 'radio', options: optionsMatch[1].split('\n') .filter(opt => opt.trim()) .map(opt => { const [key, value] = opt.split('.'); return `${key.trim()}:${value ? value.trim() : ''}`; }), answer: answerMatch[1] }; }).filter(Boolean) }; await importQuestions(paperData); } catch (error) { console.error('TXT导入失败:', error); } } // 通用导入函数 async function importQuestions(paperData) { const connection = await db.promise(); try { await connection.beginTransaction(); const topicsIds = []; for (const topic of paperData.topics) { const convertedAnswer = convertAnswer(topic.answer, topic.type); const [result] = await connection.execute( 'INSERT INTO topics (title, type, options, answer) VALUES (?, ?, ?, ?)', [ topic.title, topic.type, JSON.stringify(topic.options), convertedAnswer ] ); topicsIds.push(result.insertId); } await connection.execute( 'INSERT INTO paper (title, description, TopicsId, date, state, userId) VALUES (?, ?, ?, ?, ?, ?)', [ paperData.title, paperData.description, JSON.stringify(topicsIds), new Date(), 1, 'admin' ] ); await connection.commit(); console.log('题库导入成功'); } catch (error) { await connection.rollback(); throw error; } } const securityCheck = { // 允许的文件类型 allowedTypes: { 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xls': 'application/vnd.ms-excel', 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'doc': 'application/msword', 'txt': 'text/plain' }, // 文件大小限制(10MB) maxFileSize: 10 * 1024 * 1024, checkFileType(filePath) { const ext = filePath.toLowerCase().split('.').pop(); if (!this.allowedTypes[ext]) { throw new Error('不支持的文件类型'); } return ext; }, checkFileSize(filePath) { const stats = fs.statSync(filePath); if (stats.size > this.maxFileSize) { throw new Error('文件大小超过限制'); } }, // 检查文件内容 async checkFileContent(filePath, ext) { const buffer = Buffer.alloc(8); const fd = await fs.promises.open(filePath, 'r'); await fd.read(buffer, 0, 8, 0); await fd.close(); // 文件头签名 const signatures = { 'xlsx': Buffer.from([0x50, 0x4B, 0x03, 0x04]), 'docx': Buffer.from([0x50, 0x4B, 0x03, 0x04]), 'txt': Buffer.from([0xEF, 0xBB, 0xBF]) }; // 检查文件头 if (signatures[ext] && !buffer.includes(signatures[ext])) { throw new Error('文件格式不正确'); } // 检查文件内容是否包含可疑代码 const content = await fs.promises.readFile(filePath, 'utf8'); const suspiciousPatterns = [ /