Files
d1kt/server/routes/towerDefense.ts

228 lines
6.9 KiB
TypeScript

// server/routes/towerDefense.ts
import express, { Request, Response } from 'express';
import { TowerDefenseRecord } from '../models/TowerDefenseRecord';
import { TowerDefenseSave } from '../models/TowerDefenseSave';
import { auth } from '../middleware/auth';
const router = express.Router();
// 简单的内存去重/速率限制(仅用于示例,生产环境请使用 Redis 等持久/分布式存储)
const lastSubmissionByUser = new Map<string, { ts: number; score: number; wave: number }>();
const submissionWindow = new Map<string, { windowStart: number; count: number }>();
const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX = 20; // max submissions per user per window
// 提交塔防记录(需要登录)
router.post('/record', auth, async (req: Request, res: Response) => {
try {
const { wave, score, timeSeconds } = req.body;
if (!req.user?._id) {
return res.status(401).json({ error: '未登录' });
}
const uid = String(req.user._id);
// rate limit
try {
const now = Date.now();
const win = submissionWindow.get(uid) || { windowStart: now, count: 0 };
if (now - win.windowStart > RATE_LIMIT_WINDOW_MS) {
win.windowStart = now;
win.count = 0;
}
win.count++;
submissionWindow.set(uid, win);
if (win.count > RATE_LIMIT_MAX) {
return res.status(429).json({ error: '提交过于频繁,请稍后再试' });
}
} catch (e) {
console.warn('rate limit check error', e);
}
if (typeof wave !== 'number' || wave < 0) {
return res.status(400).json({ error: '无效的波次数据' });
}
if (typeof score !== 'number' || score < 0) {
return res.status(400).json({ error: '无效的分数数据' });
}
if (typeof timeSeconds !== 'number' || timeSeconds < 0) {
return res.status(400).json({ error: '无效的游戏时长' });
}
// dedupe:防止重复上报(比如 iframe 连续发送多次)
try {
const last = lastSubmissionByUser.get(uid);
const now = Date.now();
if (last && last.score === score && last.wave === wave && (now - last.ts) < 5000) {
// 视为重复提交
return res.status(200).json({ message: '重复提交,已忽略' });
}
// 保存最近提交摘要
lastSubmissionByUser.set(uid, { ts: now, score, wave });
} catch (e) {
console.warn('dedupe check error', e);
}
const record = new TowerDefenseRecord({
userId: req.user._id,
username: req.user.username,
fullname: req.user.fullname || req.user.username,
wave,
score,
timeSeconds
});
await record.save();
// 存储成功后可以清理或记录更多指标
// (保留 lastSubmission 已记录)
res.status(201).json({
message: '记录保存成功',
record
});
} catch (error) {
console.error('保存塔防记录失败:', error);
res.status(500).json({ error: '服务器内部错误' });
}
});
// 获取排行榜
router.get('/leaderboard', async (req: Request, res: Response) => {
try {
const { page = '1', limit = '10' } = req.query;
const pageNum = parseInt(page as string);
const pageSize = parseInt(limit as string);
const skipCount = (pageNum - 1) * pageSize;
const records = await TowerDefenseRecord.getLeaderboard(skipCount, pageSize);
const totalUsers = await TowerDefenseRecord.distinct('userId');
res.json({
records,
total: totalUsers.length,
currentPage: pageNum,
totalPages: Math.ceil(totalUsers.length / pageSize)
});
} catch (error) {
console.error('获取排行榜失败:', error);
res.status(500).json({ error: '获取排行榜失败' });
}
});
// 获取个人最佳
router.get('/personal-best', auth, async (req: Request, res: Response) => {
try {
if (!req.user?._id) {
return res.status(401).json({ error: '未登录' });
}
const bestRecord = await TowerDefenseRecord.findOne({
userId: req.user._id
}).sort({ score: -1 });
if (!bestRecord) {
return res.json({ hasBest: false });
}
res.json({
hasBest: true,
bestScore: bestRecord.score,
bestWave: bestRecord.wave,
createdAt: bestRecord.createdAt
});
} catch (error) {
console.error('获取个人最佳失败:', error);
res.status(500).json({ error: '获取个人最佳失败' });
}
});
// 保存当前游戏进度(需要登录)
router.post('/save', auth, async (req: Request, res: Response) => {
try {
if (!req.user?._id) {
return res.status(401).json({ error: '未登录' });
}
const { state } = req.body;
if (!state) {
return res.status(400).json({ error: '缺少保存的游戏状态' });
}
const name = String(Date.now());
const saveDoc = new TowerDefenseSave({
userId: req.user._id,
name,
state
});
await saveDoc.save();
res.status(201).json({ message: '已保存进度', save: saveDoc });
} catch (error) {
console.error('保存游戏进度失败:', error);
res.status(500).json({ error: '保存游戏进度失败' });
}
});
// 列出当前用户的保存记录
router.get('/saves', auth, async (req: Request, res: Response) => {
try {
if (!req.user?._id) {
return res.status(401).json({ error: '未登录' });
}
const saves = await TowerDefenseSave.find({ userId: req.user._id }).sort({ createdAt: -1 }).limit(50);
res.json({ saves });
} catch (error) {
console.error('获取保存列表失败:', error);
res.status(500).json({ error: '获取保存列表失败' });
}
});
// 获取指定的保存项
router.get('/save/:id', auth, async (req: Request, res: Response) => {
try {
if (!req.user?._id) {
return res.status(401).json({ error: '未登录' });
}
const { id } = req.params;
const save = await TowerDefenseSave.findById(id);
if (!save) return res.status(404).json({ error: '未找到保存项' });
if (String(save.userId) !== String(req.user._id)) return res.status(403).json({ error: '无权访问该保存项' });
res.json({ save });
} catch (error) {
console.error('获取保存项失败:', error);
res.status(500).json({ error: '获取保存项失败' });
}
});
// 删除指定保存项
router.delete('/save/:id', auth, async (req: Request, res: Response) => {
try {
if (!req.user?._id) {
return res.status(401).json({ error: '未登录' });
}
const { id } = req.params;
const save = await TowerDefenseSave.findById(id);
if (!save) return res.status(404).json({ error: '未找到保存项' });
if (String(save.userId) !== String(req.user._id)) return res.status(403).json({ error: '无权删除该保存项' });
await TowerDefenseSave.deleteOne({ _id: id });
res.json({ message: '已删除' });
} catch (error) {
console.error('删除保存项失败:', error);
res.status(500).json({ error: '删除保存项失败' });
}
});
export default router;