177 lines
4.8 KiB
TypeScript
177 lines
4.8 KiB
TypeScript
import express from 'express';
|
||
import { promises as fs } from 'fs';
|
||
import path from 'path';
|
||
|
||
const app = express();
|
||
const PORT = process.env.PORT || 3001;
|
||
const SCORES_FILE = path.join(__dirname, 'scores.json');
|
||
|
||
// 分数数据类型
|
||
interface ScoreEntry {
|
||
name: string;
|
||
time: number;
|
||
region: string;
|
||
store: string;
|
||
timestamp: number;
|
||
}
|
||
|
||
// 中间件
|
||
app.use(express.json());
|
||
app.use(express.static('dist')); // 服务静态文件
|
||
|
||
// 初始化分数文件
|
||
async function initScoresFile() {
|
||
try {
|
||
await fs.access(SCORES_FILE);
|
||
} catch {
|
||
// 文件不存在,创建空数组
|
||
await fs.writeFile(SCORES_FILE, JSON.stringify([]));
|
||
}
|
||
}
|
||
|
||
// 读取分数数据
|
||
async function readScores(): Promise<ScoreEntry[]> {
|
||
try {
|
||
const data = await fs.readFile(SCORES_FILE, 'utf-8');
|
||
return JSON.parse(data);
|
||
} catch {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// 写入分数数据
|
||
async function writeScores(scores: ScoreEntry[]): Promise<void> {
|
||
await fs.writeFile(SCORES_FILE, JSON.stringify(scores, null, 2));
|
||
}
|
||
|
||
// POST /api/score - 提交分数
|
||
app.post('/api/score', async (req, res) => {
|
||
try {
|
||
const { region, store, name, time } = req.body;
|
||
|
||
// 验证必要字段
|
||
if (!region || !store || !name || typeof time !== 'number') {
|
||
return res.status(400).json({
|
||
error: '缺少必要字段: region, store, name, time'
|
||
});
|
||
}
|
||
|
||
// 验证时间值
|
||
if (time <= 0) {
|
||
return res.status(400).json({
|
||
error: '时间必须大于0'
|
||
});
|
||
}
|
||
|
||
const scores = await readScores();
|
||
|
||
// 创建新的分数记录
|
||
const newScore: ScoreEntry = {
|
||
name: String(name).trim(),
|
||
time: Number(time),
|
||
region: String(region).trim(),
|
||
store: String(store).trim(),
|
||
timestamp: Date.now()
|
||
};
|
||
|
||
// 检查是否已有相同用户的记录
|
||
const existingIndex = scores.findIndex(
|
||
score => score.name === newScore.name &&
|
||
score.region === newScore.region &&
|
||
score.store === newScore.store
|
||
);
|
||
|
||
if (existingIndex !== -1) {
|
||
// 如果新时间更好(更短),则更新记录
|
||
if (newScore.time < scores[existingIndex].time) {
|
||
scores[existingIndex] = newScore;
|
||
}
|
||
} else {
|
||
// 添加新记录
|
||
scores.push(newScore);
|
||
}
|
||
|
||
await writeScores(scores);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '分数提交成功',
|
||
score: newScore
|
||
});
|
||
} catch (error) {
|
||
console.error('提交分数时出错:', error);
|
||
res.status(500).json({
|
||
error: '服务器内部错误'
|
||
});
|
||
}
|
||
});
|
||
|
||
// GET /api/score - 获取排行榜(前8名)
|
||
app.get('/api/score', async (req, res) => {
|
||
try {
|
||
const scores = await readScores();
|
||
|
||
// 按时间排序(时间短的在前)并取前8名
|
||
const leaderBoard = scores
|
||
.sort((a, b) => a.time - b.time)
|
||
.slice(0, 8)
|
||
.map(score => ({
|
||
name: score.name,
|
||
time: score.time,
|
||
region: score.region,
|
||
store: score.store
|
||
}));
|
||
|
||
res.json(leaderBoard);
|
||
} catch (error) {
|
||
console.error('获取排行榜时出错:', error);
|
||
res.status(500).json({
|
||
error: '服务器内部错误'
|
||
});
|
||
}
|
||
});
|
||
|
||
// GET /api/score/all - 获取所有分数(可选,用于管理)
|
||
app.get('/api/score/all', async (req, res) => {
|
||
try {
|
||
const scores = await readScores();
|
||
res.json(scores.sort((a, b) => a.time - b.time));
|
||
} catch (error) {
|
||
console.error('获取所有分数时出错:', error);
|
||
res.status(500).json({
|
||
error: '服务器内部错误'
|
||
});
|
||
}
|
||
});
|
||
|
||
// 健康检查端点
|
||
app.get('/api/health', (req, res) => {
|
||
res.json({
|
||
status: 'ok',
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
});
|
||
|
||
// 处理 SPA 路由 - 将所有未匹配的路由返回 index.html
|
||
app.get('/*', (req, res) => {
|
||
try {
|
||
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
||
} catch (error) {
|
||
res.status(404).send('Page not found');
|
||
}
|
||
});
|
||
|
||
// 启动服务器
|
||
async function startServer() {
|
||
await initScoresFile();
|
||
|
||
app.listen(PORT, () => {
|
||
console.log(`🚀 服务器运行在 http://localhost:${PORT}`);
|
||
console.log(`📊 API 端点:`);
|
||
console.log(` POST /api/score - 提交分数`);
|
||
console.log(` GET /api/score - 获取排行榜`);
|
||
console.log(` GET /api/health - 健康检查`);
|
||
});
|
||
}
|
||
|
||
startServer().catch(console.error); |