const db = require('../db/index') const multer = require('multer') const fs = require('fs') const { importQuestionsByFile } = require('./stb') const sanitizeInput = { toPositiveInt: (value, defaultValue) => { const num = parseInt(value) return (num > 0) ? num : defaultValue }, escapeSearch: (str) => { if (!str) return '' return str.replace(/[<>&"']/g, '') } } const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, './uploads') }, filename: function (req, file, cb) { cb(null, Date.now() + '-' + file.originalname) } }) // 文件类型过滤 const fileFilter = (req, file, cb) => { if ( file.mimetype === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || // xlsx file.mimetype === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || // docx file.mimetype === 'text/plain' ) { cb(null, true) } else { cb(new Error('不支持的文件类型'), false) } } const upload = multer({ storage: storage, fileFilter: fileFilter }) // 上传试卷 exports.uploadPaper = [ upload.single('file'), async (req, res) => { try { if (!req.file) { return res.json({ status: 1, message: '请选择文件' }) } const filePath = req.file.path await importQuestionsByFile(filePath) fs.unlinkSync(filePath) res.json({ status: 0, message: '上传成功' }) } catch (error) { if (req.file && req.file.path) { fs.unlinkSync(req.file.path) } res.json({ status: 1, message: '上传失败', error: error.message, format: { excel: "必须包含列:'题目', '选项A', '选项B', '选项C', '选项D', '答案'", word: `格式示例: 题目1:问题内容 A. 选项A内容 B. 选项B内容 C. 选项C内容 D. 选项D内容 答案:A 题目2:...`, txt: `格式示: ���题内容 A. 选项A内容 B. 选项B内容 C. 选项C内容 D. 选项D内容 答案:A 下一道题...` } }) } } ] // 获取试卷列表 exports.getList = async (req, res) => { try { const [papers] = await db.promise().query('SELECT * FROM paper'); const papersWithCount = papers.map(paper => ({ ...paper, topic_count: JSON.parse(paper.TopicsId || '[]').length })); res.json({ success: true, data: papersWithCount }); } catch (error) { console.error('获取试卷列表失败:', error); res.status(500).json({ success: false, message: '获取试卷列表失败' }); } } // 获取指定试卷的题目列表 exports.getPaperQuestions = async (req, res) => { try { const { paperId, page = 1, pageSize = 10 } = req.body; if (!paperId) { return res.status(400).json({ success: false, message: '无效的试卷ID' }); } const sanitizedPaperId = sanitizeInput.toPositiveInt(paperId, 0); const sanitizedPage = sanitizeInput.toPositiveInt(page, 1); const sanitizedPageSize = sanitizeInput.toPositiveInt(pageSize, 10); const limitedPageSize = Math.min(sanitizedPageSize, 100); const offset = (sanitizedPage - 1) * limitedPageSize; const [papers] = await db.promise().query('SELECT * FROM paper WHERE id = ?', [sanitizedPaperId]); if (papers.length === 0) { return res.status(404).json({ success: false, message: '试卷不存在' }); } const topicIds = JSON.parse(papers[0].TopicsId || '[]'); const total = topicIds.length; const pagedTopicIds = topicIds.slice(offset, offset + limitedPageSize); if (pagedTopicIds.length === 0) { return res.json({ success: true, data: { list: [], pagination: { current: sanitizedPage, pageSize: limitedPageSize, total } } }); } // 查询题目详情 const [topics] = await db.promise().query( 'SELECT * FROM topics WHERE id IN (?)', [pagedTopicIds] ); const orderedTopics = pagedTopicIds.map(id => topics.find(topic => topic.id === id) ).filter(Boolean); res.json({ success: true, data: { list: orderedTopics, pagination: { current: sanitizedPage, pageSize: limitedPageSize, total } } }); } catch (error) { console.error('获取试卷题目失败:', error); res.status(500).json({ success: false, message: '获取试卷题目失败' }); } } // 获取试卷详情 exports.getPaperDetail = async (req, res) => { try { console.log('Received body:', req.body); if (!paperId) { return res.status(400).json({ success: false, message: '无效的试卷ID' }); } const sanitizedPaperId = sanitizeInput.toPositiveInt(paperId, 0); const sanitizedPage = sanitizeInput.toPositiveInt(page, 1); const sanitizedPageSize = sanitizeInput.toPositiveInt(pageSize, 10); const limitedPageSize = Math.min(sanitizedPageSize, 100); const offset = (sanitizedPage - 1) * limitedPageSize; const [papers] = await db.promise().query('SELECT * FROM paper WHERE id = ?', [sanitizedPaperId]); if (papers.length === 0) { return res.status(404).json({ success: false, message: '试卷不存在' }); } const topicIds = JSON.parse(papers[0].TopicsId || '[]'); const total = topicIds.length; const pagedTopicIds = topicIds.slice(offset, offset + limitedPageSize); let topics = []; if (pagedTopicIds.length > 0) { const [topicsResult] = await db.promise().query( 'SELECT * FROM topics WHERE id IN (?)', [pagedTopicIds] ); topics = pagedTopicIds.map(id => topicsResult.find(topic => topic.id === id) ).filter(Boolean); } res.json({ success: true, data: { ...papers[0], topics: { list: topics, pagination: { current: sanitizedPage, pageSize: limitedPageSize, total } } } }); } catch (error) { console.error('获取试卷详情失败:', error); res.status(500).json({ success: false, message: '获取试卷详情失败' }); } } // 搜索题目 exports.searchTopics = async (req, res) => { try { let { keyword, page = 1, pageSize = 10 } = req.query; keyword = sanitizeInput.escapeSearch(keyword); if (!keyword) { return res.status(400).json({ success: false, message: '请提供有效的搜索关键词' }); } page = sanitizeInput.toPositiveInt(page, 1); pageSize = Math.min(sanitizeInput.toPositiveInt(pageSize, 10), 100); const offset = (page - 1) * pageSize; const [countResult] = await db.promise().query( 'SELECT COUNT(*) as total FROM topics WHERE title LIKE ?', [`%${keyword}%`] ); const total = countResult[0].total; const [topics] = await db.promise().query( `SELECT t.*, (SELECT title FROM paper WHERE JSON_CONTAINS(TopicsId, CAST(t.id AS JSON))) as paper_title FROM topics t WHERE t.title LIKE ? LIMIT ?, ?`, [`%${keyword}%`, offset, pageSize] ); res.json({ success: true, data: { list: topics, pagination: { current: page, pageSize, total } } }); } catch (error) { console.error('搜索题目失败:', error); res.status(500).json({ success: false, message: '搜索题目失败' }); } }