Files

172 lines
4.8 KiB
JavaScript

const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 3008;
const DATA_DIR = path.join(__dirname, '../data');
const DATA_FILE = path.join(DATA_DIR, 'quiz_stats.json');
const QUESTIONS_FILE = path.join(__dirname, '../questions.json');
// 保证数据目录存在
if (!fs.existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
// 内存中的状态缓存
let state = {
weights: {},
history: [],
totalAnswered: 0
};
// 获取所有题目的默认权重
function getDefaultWeights() {
let defaultWeights = {};
if (fs.existsSync(QUESTIONS_FILE)) {
try {
const qData = JSON.parse(fs.readFileSync(QUESTIONS_FILE, 'utf-8'));
qData.forEach(q => {
defaultWeights[q.id] = 1;
});
} catch (e) {
console.error('[Init] 读取 questions.json 失败:', e);
}
}
return defaultWeights;
}
// 重置状态
function resetState() {
state = {
weights: getDefaultWeights(),
history: [],
totalAnswered: 0
};
saveState();
console.log('[Reset] 全局刷题数据已重置。');
}
// 初始化状态
function initState() {
if (fs.existsSync(DATA_FILE)) {
try {
const content = fs.readFileSync(DATA_FILE, 'utf-8');
state = JSON.parse(content);
console.log(`[Init] 已从文件载入全局数据,累计答题数: ${state.totalAnswered}`);
return;
} catch (e) {
console.error('[Init] 读取已有数据文件失败,将重新初始化:', e);
}
}
resetState();
}
// 异步持久化保存
function saveState() {
const tmpFile = `${DATA_FILE}.tmp`;
fs.writeFile(tmpFile, JSON.stringify(state, null, 2), 'utf-8', (err) => {
if (err) {
console.error('[Save] 保存临时数据文件失败:', err);
return;
}
fs.rename(tmpFile, DATA_FILE, (renameErr) => {
if (renameErr) {
console.error('[Save] 原子重命名数据文件失败:', renameErr);
}
});
});
}
initState();
const server = http.createServer((req, res) => {
// 设置 CORS 标头
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname.replace(/^\/api/, ''); // 统一兼容 /api 前缀与无前缀
if (req.method === 'GET' && (pathname === '/stats' || pathname === '/stats/')) {
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(state));
return;
}
if (req.method === 'POST' && (pathname === '/answer' || pathname === '/answer/')) {
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
try {
const payload = JSON.parse(body || '{}');
const questionId = Number(payload.questionId);
const isCorrect = Boolean(payload.isCorrect);
if (!questionId) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Missing questionId' }));
return;
}
// 更新历史记录(保留最近 100 条)
state.history.push(isCorrect);
if (state.history.length > 100) {
state.history = state.history.slice(-100);
}
// 更新全局总数
state.totalAnswered = (state.totalAnswered || 0) + 1;
// 更新题目权重
const currentWeight = state.weights[questionId] || 1;
if (isCorrect) {
state.weights[questionId] = Math.max(1, currentWeight - 1);
} else {
state.weights[questionId] = currentWeight + 3; // 答错大幅增加权重
}
saveState();
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({
success: true,
weights: state.weights,
history: state.history,
totalAnswered: state.totalAnswered
}));
} catch (err) {
console.error('[Error] 处理 /api/answer 异常:', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Internal Server Error' }));
}
});
return;
}
if (req.method === 'POST' && (pathname === '/reset' || pathname === '/reset/')) {
resetState();
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ success: true, message: 'Stats reset' }));
return;
}
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not Found' }));
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`[Server] 全局刷题服务已启动,监听端口: ${PORT}`);
});