feat: 添加选手详情与比赛记录接口以支持小程序端
- 新增 `/api/ladder/player` 接口,兼容小程序端选手详情查询 - 新增 `/api/match/history` 接口,用于获取选手比赛记录 - 选手详情接口增加 `loseCount` 字段,完善比赛数据统计 - 比赛记录接口提供分页查询,包含对手信息与比赛结果详情
This commit is contained in:
parent
74ed19eee1
commit
75760d25fd
@ -1,28 +1,46 @@
|
|||||||
const { LadderUser, User, Store } = require('../models');
|
const { LadderUser, User, Store } = require("../models");
|
||||||
const { LADDER_LEVEL_NAMES, LADDER_LEVEL_DESC, POWER_CALC } = require('../config/constants');
|
const {
|
||||||
const { success, error, getPagination, pageResult } = require('../utils/helper');
|
LADDER_LEVEL_NAMES,
|
||||||
const { Op } = require('sequelize');
|
LADDER_LEVEL_DESC,
|
||||||
const sequelize = require('../config/database');
|
POWER_CALC,
|
||||||
|
} = require("../config/constants");
|
||||||
|
const {
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
getPagination,
|
||||||
|
pageResult,
|
||||||
|
} = require("../utils/helper");
|
||||||
|
const { Op } = require("sequelize");
|
||||||
|
const sequelize = require("../config/database");
|
||||||
|
|
||||||
class LadderController {
|
class LadderController {
|
||||||
// 获取天梯排名
|
// 获取天梯排名
|
||||||
async getRanking(req, res) {
|
async getRanking(req, res) {
|
||||||
try {
|
try {
|
||||||
const { store_id, gender, level, page = 1, pageSize = 50, is_display } = req.query;
|
const {
|
||||||
|
store_id,
|
||||||
|
gender,
|
||||||
|
level,
|
||||||
|
page = 1,
|
||||||
|
pageSize = 50,
|
||||||
|
is_display,
|
||||||
|
} = req.query;
|
||||||
const { limit, offset } = getPagination(page, pageSize);
|
const { limit, offset } = getPagination(page, pageSize);
|
||||||
|
|
||||||
if (!store_id) {
|
if (!store_id) {
|
||||||
return res.status(400).json(error('缺少门店ID', 400));
|
return res.status(400).json(error("缺少门店ID", 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
store_id,
|
store_id,
|
||||||
status: 1
|
status: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果不是大屏显示,则需要满足每月最低参赛场次限制
|
// 如果不是大屏显示,则需要满足每月最低参赛场次限制
|
||||||
if (!is_display) {
|
if (!is_display) {
|
||||||
where.monthly_match_count = { [Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES };
|
where.monthly_match_count = {
|
||||||
|
[Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gender) {
|
if (gender) {
|
||||||
@ -35,11 +53,11 @@ class LadderController {
|
|||||||
const { rows, count } = await LadderUser.findAndCountAll({
|
const { rows, count } = await LadderUser.findAndCountAll({
|
||||||
where,
|
where,
|
||||||
include: [
|
include: [
|
||||||
{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }
|
{ model: User, as: "user", attributes: ["nickname", "avatar"] },
|
||||||
],
|
],
|
||||||
order: [['power_score', 'DESC']],
|
order: [["power_score", "DESC"]],
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加排名
|
// 添加排名
|
||||||
@ -57,13 +75,16 @@ class LadderController {
|
|||||||
powerScore: lu.power_score,
|
powerScore: lu.power_score,
|
||||||
matchCount: lu.match_count,
|
matchCount: lu.match_count,
|
||||||
winCount: lu.win_count,
|
winCount: lu.win_count,
|
||||||
winRate: lu.match_count > 0 ? Math.round(lu.win_count / lu.match_count * 100) : 0
|
winRate:
|
||||||
|
lu.match_count > 0
|
||||||
|
? Math.round((lu.win_count / lu.match_count) * 100)
|
||||||
|
: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res.json(pageResult(list, count, page, pageSize));
|
res.json(pageResult(list, count, page, pageSize));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取排名失败:', err);
|
console.error("获取排名失败:", err);
|
||||||
res.status(500).json(error('获取失败'));
|
res.status(500).json(error("获取失败"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,13 +95,17 @@ class LadderController {
|
|||||||
|
|
||||||
const ladderUser = await LadderUser.findByPk(id, {
|
const ladderUser = await LadderUser.findByPk(id, {
|
||||||
include: [
|
include: [
|
||||||
{ model: User, as: 'user', attributes: ['nickname', 'avatar', 'member_code'] },
|
{
|
||||||
{ model: Store, as: 'store', attributes: ['id', 'name'] }
|
model: User,
|
||||||
]
|
as: "user",
|
||||||
|
attributes: ["nickname", "avatar", "member_code"],
|
||||||
|
},
|
||||||
|
{ model: Store, as: "store", attributes: ["id", "name"] },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ladderUser || ladderUser.status !== 1) {
|
if (!ladderUser || ladderUser.status !== 1) {
|
||||||
return res.status(404).json(error('用户不存在', 404));
|
return res.status(404).json(error("用户不存在", 404));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算排名
|
// 计算排名
|
||||||
@ -90,11 +115,12 @@ class LadderController {
|
|||||||
gender: ladderUser.gender,
|
gender: ladderUser.gender,
|
||||||
status: 1,
|
status: 1,
|
||||||
power_score: { [Op.gt]: ladderUser.power_score },
|
power_score: { [Op.gt]: ladderUser.power_score },
|
||||||
monthly_match_count: { [Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES }
|
monthly_match_count: { [Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES },
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json(success({
|
res.json(
|
||||||
|
success({
|
||||||
id: ladderUser.id,
|
id: ladderUser.id,
|
||||||
userId: ladderUser.user_id,
|
userId: ladderUser.user_id,
|
||||||
realName: ladderUser.real_name,
|
realName: ladderUser.real_name,
|
||||||
@ -109,28 +135,102 @@ class LadderController {
|
|||||||
matchCount: ladderUser.match_count,
|
matchCount: ladderUser.match_count,
|
||||||
winCount: ladderUser.win_count,
|
winCount: ladderUser.win_count,
|
||||||
monthlyMatchCount: ladderUser.monthly_match_count,
|
monthlyMatchCount: ladderUser.monthly_match_count,
|
||||||
winRate: ladderUser.match_count > 0 ? Math.round(ladderUser.win_count / ladderUser.match_count * 100) : 0,
|
winRate:
|
||||||
|
ladderUser.match_count > 0
|
||||||
|
? Math.round(
|
||||||
|
(ladderUser.win_count / ladderUser.match_count) * 100,
|
||||||
|
)
|
||||||
|
: 0,
|
||||||
rank: higherCount + 1,
|
rank: higherCount + 1,
|
||||||
storeId: ladderUser.store_id,
|
storeId: ladderUser.store_id,
|
||||||
storeName: ladderUser.store?.name,
|
storeName: ladderUser.store?.name,
|
||||||
lastMatchTime: ladderUser.last_match_time
|
lastMatchTime: ladderUser.last_match_time,
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取用户详情失败:', err);
|
console.error("获取用户详情失败:", err);
|
||||||
res.status(500).json(error('获取失败'));
|
res.status(500).json(error("获取失败"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选手详情(兼容小程序端:/api/ladder/player?id=xxx)
|
||||||
|
async getPlayerDetail(req, res) {
|
||||||
|
try {
|
||||||
|
const id = req.query && req.query.id ? String(req.query.id) : null;
|
||||||
|
if (!id) {
|
||||||
|
return res.status(400).json(error("缺少选手ID", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ladderUser = await LadderUser.findByPk(id, {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: User,
|
||||||
|
as: "user",
|
||||||
|
attributes: ["nickname", "avatar", "member_code"],
|
||||||
|
},
|
||||||
|
{ model: Store, as: "store", attributes: ["id", "name"] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ladderUser || ladderUser.status !== 1) {
|
||||||
|
return res.status(404).json(error("用户不存在", 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
const higherCount = await LadderUser.count({
|
||||||
|
where: {
|
||||||
|
store_id: ladderUser.store_id,
|
||||||
|
gender: ladderUser.gender,
|
||||||
|
status: 1,
|
||||||
|
power_score: { [Op.gt]: ladderUser.power_score },
|
||||||
|
monthly_match_count: { [Op.gte]: POWER_CALC.MIN_MONTHLY_MATCHES },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const matchCount = ladderUser.match_count || 0;
|
||||||
|
const winCount = ladderUser.win_count || 0;
|
||||||
|
|
||||||
|
res.json(
|
||||||
|
success({
|
||||||
|
id: ladderUser.id,
|
||||||
|
userId: ladderUser.user_id,
|
||||||
|
realName: ladderUser.real_name,
|
||||||
|
nickname: ladderUser.user && ladderUser.user.nickname,
|
||||||
|
avatar: ladderUser.user && ladderUser.user.avatar,
|
||||||
|
memberCode: ladderUser.user && ladderUser.user.member_code,
|
||||||
|
gender: ladderUser.gender,
|
||||||
|
level: ladderUser.level,
|
||||||
|
levelName: LADDER_LEVEL_NAMES[ladderUser.level],
|
||||||
|
levelDesc: LADDER_LEVEL_DESC[ladderUser.level],
|
||||||
|
powerScore: ladderUser.power_score,
|
||||||
|
matchCount: matchCount,
|
||||||
|
winCount: winCount,
|
||||||
|
loseCount: Math.max(matchCount - winCount, 0),
|
||||||
|
monthlyMatchCount: ladderUser.monthly_match_count,
|
||||||
|
winRate:
|
||||||
|
matchCount > 0 ? Math.round((winCount / matchCount) * 100) : 0,
|
||||||
|
rank: higherCount + 1,
|
||||||
|
storeId: ladderUser.store_id,
|
||||||
|
storeName: ladderUser.store && ladderUser.store.name,
|
||||||
|
lastMatchTime: ladderUser.last_match_time,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("获取用户详情失败:", err);
|
||||||
|
res.status(500).json(error("获取失败"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取等级说明
|
// 获取等级说明
|
||||||
async getLevelInfo(req, res) {
|
async getLevelInfo(req, res) {
|
||||||
try {
|
try {
|
||||||
const levels = Object.keys(LADDER_LEVEL_NAMES).map(level => ({
|
const levels = Object.keys(LADDER_LEVEL_NAMES).map((level) => ({
|
||||||
level: parseInt(level),
|
level: parseInt(level),
|
||||||
name: LADDER_LEVEL_NAMES[level],
|
name: LADDER_LEVEL_NAMES[level],
|
||||||
description: LADDER_LEVEL_DESC[level]
|
description: LADDER_LEVEL_DESC[level],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res.json(success({
|
res.json(
|
||||||
|
success({
|
||||||
levels,
|
levels,
|
||||||
powerCalcRules: {
|
powerCalcRules: {
|
||||||
baseWin: POWER_CALC.BASE_WIN,
|
baseWin: POWER_CALC.BASE_WIN,
|
||||||
@ -140,12 +240,13 @@ class LadderController {
|
|||||||
maxChange: POWER_CALC.MAX_CHANGE,
|
maxChange: POWER_CALC.MAX_CHANGE,
|
||||||
newbieProtection: POWER_CALC.NEWBIE_PROTECTION,
|
newbieProtection: POWER_CALC.NEWBIE_PROTECTION,
|
||||||
minMonthlyMatches: POWER_CALC.MIN_MONTHLY_MATCHES,
|
minMonthlyMatches: POWER_CALC.MIN_MONTHLY_MATCHES,
|
||||||
challengeCooldown: POWER_CALC.CHALLENGE_COOLDOWN
|
challengeCooldown: POWER_CALC.CHALLENGE_COOLDOWN,
|
||||||
}
|
},
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取等级信息失败:', err);
|
console.error("获取等级信息失败:", err);
|
||||||
res.status(500).json(error('获取失败'));
|
res.status(500).json(error("获取失败"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -756,6 +756,90 @@ class MatchController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取选手比赛记录(用于选手详情页)
|
||||||
|
async getPlayerHistory(req, res) {
|
||||||
|
try {
|
||||||
|
const { player_id, page = 1, pageSize = 20 } = req.query;
|
||||||
|
const { limit, offset } = getPagination(page, pageSize);
|
||||||
|
|
||||||
|
if (!player_id) {
|
||||||
|
return res.status(400).json(error('缺少选手ID', 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = await LadderUser.findByPk(player_id);
|
||||||
|
if (!player || player.status !== 1) {
|
||||||
|
return res.json(pageResult([], 0, page, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rows, count } = await MatchGame.findAndCountAll({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{ player1_id: player.id },
|
||||||
|
{ player2_id: player.id }
|
||||||
|
],
|
||||||
|
confirm_status: CONFIRM_STATUS.CONFIRMED
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{ model: Match, as: 'match', attributes: ['id', 'name', 'type', 'weight'] }
|
||||||
|
],
|
||||||
|
order: [['confirmed_at', 'DESC']],
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
});
|
||||||
|
|
||||||
|
const opponentIds = [];
|
||||||
|
rows.forEach((g) => {
|
||||||
|
if (g.player1_id === player.id) opponentIds.push(g.player2_id);
|
||||||
|
else opponentIds.push(g.player1_id);
|
||||||
|
});
|
||||||
|
const uniqueOpponentIds = Array.from(new Set(opponentIds));
|
||||||
|
const opponents = await LadderUser.findAll({
|
||||||
|
where: { id: { [Op.in]: uniqueOpponentIds } },
|
||||||
|
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
|
||||||
|
});
|
||||||
|
const opponentMap = new Map(opponents.map((o) => [String(o.id), o]));
|
||||||
|
|
||||||
|
const list = rows.map((game) => {
|
||||||
|
const isPlayer1 = game.player1_id === player.id;
|
||||||
|
const opponentId = isPlayer1 ? game.player2_id : game.player1_id;
|
||||||
|
const opponent = opponentMap.get(String(opponentId));
|
||||||
|
|
||||||
|
const myScore = isPlayer1 ? game.player1_score : game.player2_score;
|
||||||
|
const opponentScore = isPlayer1 ? game.player2_score : game.player1_score;
|
||||||
|
const isWin = game.winner_id === player.id;
|
||||||
|
const typeName = game.match && game.match.type === MATCH_TYPES.CHALLENGE ? '挑战赛' : '排位赛';
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: game.id,
|
||||||
|
matchId: game.match_id,
|
||||||
|
name: (game.match && game.match.name) || typeName,
|
||||||
|
type: game.match && game.match.type,
|
||||||
|
typeName,
|
||||||
|
createTime: game.confirmed_at,
|
||||||
|
desc: opponent ? `vs ${opponent.real_name} ${myScore}:${opponentScore}` : `${myScore}:${opponentScore}`,
|
||||||
|
result: isWin ? 'win' : 'lose',
|
||||||
|
resultName: isWin ? '胜' : '负',
|
||||||
|
powerChange: isWin ? game.winner_power_change : game.loser_power_change,
|
||||||
|
opponent: opponent
|
||||||
|
? {
|
||||||
|
id: opponent.id,
|
||||||
|
realName: opponent.real_name,
|
||||||
|
nickname: opponent.user && opponent.user.nickname,
|
||||||
|
avatar: opponent.user && opponent.user.avatar,
|
||||||
|
level: opponent.level,
|
||||||
|
powerScore: opponent.power_score
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(pageResult(list, count, page, pageSize));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取选手比赛记录失败:', err);
|
||||||
|
res.status(500).json(error('获取失败'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取正在进行中的比赛
|
// 获取正在进行中的比赛
|
||||||
async getOngoingMatches(req, res) {
|
async getOngoingMatches(req, res) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -9,6 +9,9 @@ router.get('/ranking', ladderController.getRanking);
|
|||||||
// 获取天梯用户详情
|
// 获取天梯用户详情
|
||||||
router.get('/user/:id', ladderController.getUserDetail);
|
router.get('/user/:id', ladderController.getUserDetail);
|
||||||
|
|
||||||
|
// 选手详情(兼容小程序端:/api/ladder/player?id=xxx)
|
||||||
|
router.get('/player', ladderController.getPlayerDetail);
|
||||||
|
|
||||||
// 获取等级说明
|
// 获取等级说明
|
||||||
router.get('/levels', ladderController.getLevelInfo);
|
router.get('/levels', ladderController.getLevelInfo);
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,9 @@ router.post('/ranking/confirm-score', authUser, matchController.confirmRankingSc
|
|||||||
// 获取正在进行中的比赛
|
// 获取正在进行中的比赛
|
||||||
router.get('/ongoing', authUser, matchController.getOngoingMatches);
|
router.get('/ongoing', authUser, matchController.getOngoingMatches);
|
||||||
|
|
||||||
|
// 获取选手比赛记录(用于选手详情页)
|
||||||
|
router.get('/history', authUser, matchController.getPlayerHistory);
|
||||||
|
|
||||||
// 获取我的比赛记录
|
// 获取我的比赛记录
|
||||||
router.get('/my-matches', authUser, matchController.getMyMatches);
|
router.get('/my-matches', authUser, matchController.getMyMatches);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user