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 { LADDER_LEVEL_NAMES, LADDER_LEVEL_DESC, POWER_CALC } = require('../config/constants');
|
||||
const { success, error, getPagination, pageResult } = require('../utils/helper');
|
||||
const { Op } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
const { LadderUser, User, Store } = require("../models");
|
||||
const {
|
||||
LADDER_LEVEL_NAMES,
|
||||
LADDER_LEVEL_DESC,
|
||||
POWER_CALC,
|
||||
} = require("../config/constants");
|
||||
const {
|
||||
success,
|
||||
error,
|
||||
getPagination,
|
||||
pageResult,
|
||||
} = require("../utils/helper");
|
||||
const { Op } = require("sequelize");
|
||||
const sequelize = require("../config/database");
|
||||
|
||||
class LadderController {
|
||||
// 获取天梯排名
|
||||
async getRanking(req, res) {
|
||||
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);
|
||||
|
||||
if (!store_id) {
|
||||
return res.status(400).json(error('缺少门店ID', 400));
|
||||
return res.status(400).json(error("缺少门店ID", 400));
|
||||
}
|
||||
|
||||
const where = {
|
||||
store_id,
|
||||
status: 1
|
||||
status: 1,
|
||||
};
|
||||
|
||||
// 如果不是大屏显示,则需要满足每月最低参赛场次限制
|
||||
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) {
|
||||
@ -35,11 +53,11 @@ class LadderController {
|
||||
const { rows, count } = await LadderUser.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }
|
||||
{ model: User, as: "user", attributes: ["nickname", "avatar"] },
|
||||
],
|
||||
order: [['power_score', 'DESC']],
|
||||
order: [["power_score", "DESC"]],
|
||||
limit,
|
||||
offset
|
||||
offset,
|
||||
});
|
||||
|
||||
// 添加排名
|
||||
@ -57,13 +75,16 @@ class LadderController {
|
||||
powerScore: lu.power_score,
|
||||
matchCount: lu.match_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));
|
||||
} catch (err) {
|
||||
console.error('获取排名失败:', err);
|
||||
res.status(500).json(error('获取失败'));
|
||||
console.error("获取排名失败:", err);
|
||||
res.status(500).json(error("获取失败"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,13 +95,17 @@ class LadderController {
|
||||
|
||||
const ladderUser = await LadderUser.findByPk(id, {
|
||||
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) {
|
||||
return res.status(404).json(error('用户不存在', 404));
|
||||
return res.status(404).json(error("用户不存在", 404));
|
||||
}
|
||||
|
||||
// 计算排名
|
||||
@ -90,11 +115,12 @@ class LadderController {
|
||||
gender: ladderUser.gender,
|
||||
status: 1,
|
||||
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,
|
||||
userId: ladderUser.user_id,
|
||||
realName: ladderUser.real_name,
|
||||
@ -109,28 +135,102 @@ class LadderController {
|
||||
matchCount: ladderUser.match_count,
|
||||
winCount: ladderUser.win_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,
|
||||
storeId: ladderUser.store_id,
|
||||
storeName: ladderUser.store?.name,
|
||||
lastMatchTime: ladderUser.last_match_time
|
||||
}));
|
||||
lastMatchTime: ladderUser.last_match_time,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('获取用户详情失败:', err);
|
||||
res.status(500).json(error('获取失败'));
|
||||
console.error("获取用户详情失败:", err);
|
||||
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) {
|
||||
try {
|
||||
const levels = Object.keys(LADDER_LEVEL_NAMES).map(level => ({
|
||||
const levels = Object.keys(LADDER_LEVEL_NAMES).map((level) => ({
|
||||
level: parseInt(level),
|
||||
name: LADDER_LEVEL_NAMES[level],
|
||||
description: LADDER_LEVEL_DESC[level]
|
||||
description: LADDER_LEVEL_DESC[level],
|
||||
}));
|
||||
|
||||
res.json(success({
|
||||
res.json(
|
||||
success({
|
||||
levels,
|
||||
powerCalcRules: {
|
||||
baseWin: POWER_CALC.BASE_WIN,
|
||||
@ -140,12 +240,13 @@ class LadderController {
|
||||
maxChange: POWER_CALC.MAX_CHANGE,
|
||||
newbieProtection: POWER_CALC.NEWBIE_PROTECTION,
|
||||
minMonthlyMatches: POWER_CALC.MIN_MONTHLY_MATCHES,
|
||||
challengeCooldown: POWER_CALC.CHALLENGE_COOLDOWN
|
||||
}
|
||||
}));
|
||||
challengeCooldown: POWER_CALC.CHALLENGE_COOLDOWN,
|
||||
},
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('获取等级信息失败:', err);
|
||||
res.status(500).json(error('获取失败'));
|
||||
console.error("获取等级信息失败:", err);
|
||||
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) {
|
||||
try {
|
||||
|
||||
@ -9,6 +9,9 @@ router.get('/ranking', ladderController.getRanking);
|
||||
// 获取天梯用户详情
|
||||
router.get('/user/:id', ladderController.getUserDetail);
|
||||
|
||||
// 选手详情(兼容小程序端:/api/ladder/player?id=xxx)
|
||||
router.get('/player', ladderController.getPlayerDetail);
|
||||
|
||||
// 获取等级说明
|
||||
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('/history', authUser, matchController.getPlayerHistory);
|
||||
|
||||
// 获取我的比赛记录
|
||||
router.get('/my-matches', authUser, matchController.getMyMatches);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user