Files
d1kt/server/routes/leaderboard.ts

146 lines
4.2 KiB
TypeScript

// server/routes/leaderboard.ts
import express, { Request, Response } from 'express';
import { PracticeRecord } from '../models/PracticeRecord';
import { auth } from '../middleware/auth';
// 定义排序字段类型
type SortField = 'totalWords' | 'accuracy' | 'duration' | 'speed';
// 排序字段映射
const sortFieldMapping = {
totalWords: { field: '$totalWords', label: '总单词数' },
accuracy: { field: '$avgAccuracy', label: '平均正确率' },
duration: { field: '$totalDuration', label: '练习总时长' },
speed: { field: '$avgSpeed', label: '平均速度' }
};
const router = express.Router();
// 获取排行榜数据
router.get('/:type', async (req: Request, res: Response) => {
try {
const { type } = req.params;
const {
page = '1',
limit = '10',
sortBy = 'totalWords' // 默认按总单词数排序
} = req.query;
const pageNum = parseInt(page as string);
const pageSize = parseInt(limit as string);
const skipCount = (pageNum - 1) * pageSize;
const sortField = sortBy as SortField;
if (!Object.keys(sortFieldMapping).includes(sortField)) {
return res.status(400).json({ error: '无效的排序字段' });
}
// 聚合查询
const records = await PracticeRecord.aggregate([
{ $match: { type } },
{
$group: {
_id: '$userId',
username: { $first: '$username' },
fullname: { $first: '$fullname' },
totalWords: { $sum: '$stats.totalWords' },
avgAccuracy: { $avg: '$stats.accuracy' },
totalDuration: { $sum: '$stats.duration' },
avgSpeed: { $avg: '$stats.wordsPerMinute' },
lastPractice: { $max: '$stats.endTime' },
practiceCount: { $sum: 1 }
}
},
{
$sort: {
[sortFieldMapping[sortField].field.substring(1)]: -1
}
},
{ $skip: skipCount },
{ $limit: pageSize },
{
$project: {
userId: '$_id',
username: 1,
fullname: 1,
stats: {
totalWords: '$totalWords',
accuracy: '$avgAccuracy',
duration: '$totalDuration',
wordsPerMinute: '$avgSpeed',
practiceCount: '$practiceCount',
lastPractice: '$lastPractice'
}
}
}
]);
// 获取总用户数
const totalUsers = await PracticeRecord.distinct('userId', { type });
res.json({
records,
total: totalUsers.length,
currentPage: pageNum,
totalPages: Math.ceil(totalUsers.length / pageSize),
sortField,
sortLabel: sortFieldMapping[sortField].label
});
} catch (error) {
console.error('获取排行榜失败:', error);
res.status(500).json({ error: '获取排行榜失败' });
}
});
// 获取用户排名
router.get('/:type/my-rank', auth, async (req: Request, res: Response) => {
try {
const { type } = req.params;
const { sortBy = 'totalWords' } = req.query;
const sortField = sortBy as SortField;
if (!req.user?._id) {
return res.status(401).json({ error: '未登录' });
}
if (!Object.keys(sortFieldMapping).includes(sortField)) {
return res.status(400).json({ error: '无效的排序字段' });
}
// 获取所有用户的排序后记录
const allRecords = await PracticeRecord.aggregate([
{ $match: { type } },
{
$group: {
_id: '$userId',
totalWords: { $sum: '$stats.totalWords' },
avgAccuracy: { $avg: '$stats.accuracy' },
totalDuration: { $sum: '$stats.duration' },
avgSpeed: { $avg: '$stats.wordsPerMinute' }
}
},
{
$sort: {
[sortFieldMapping[sortField].field.substring(1)]: -1
}
}
]);
// 找到用户的排名
const userRank = allRecords.findIndex(record =>
record._id.toString() === req.user!._id
) + 1;
res.json({
rank: userRank > 0 ? userRank : null,
totalParticipants: allRecords.length,
sortField,
sortLabel: sortFieldMapping[sortField].label
});
} catch (error) {
console.error('获取排名失败:', error);
res.status(500).json({ error: '获取排名失败' });
}
});
export default router;