284 lines
7.7 KiB
JavaScript
284 lines
7.7 KiB
JavaScript
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: `格式示:
|
||
<20><><EFBFBD>题内容
|
||
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: '搜索题目失败' });
|
||
}
|
||
} |