yingsa/server/src/controllers/matchController.js

1208 lines
40 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { Match, MatchGame, MatchPlayer, MatchRound, LadderUser, User, Store, sequelize } = require('../models');
const { MATCH_TYPES, MATCH_STATUS, CONFIRM_STATUS, RANKING_STAGE, MATCH_WEIGHTS, POWER_CALC } = require('../config/constants');
const { generateMatchCode, success, error, getPagination, pageResult } = require('../utils/helper');
const PowerCalculator = require('../services/powerCalculator');
const { sendChallengeNotification, sendScoreConfirmNotification, sendMatchNotification } = require('../websocket');
const { Op } = require('sequelize');
class MatchController {
// 检查是否可以发起挑战
async checkChallenge(req, res) {
try {
const { targetMemberCode } = req.params;
const { store_id } = req.query;
const user = req.user;
// 获取自己的天梯用户
const myLadderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id, status: 1 }
});
if (!myLadderUser) {
return res.json(success({ canChallenge: false, reason: '您还不是该门店的天梯用户' }));
}
// 通过会员码获取对手
const targetUser = await User.findOne({ where: { member_code: targetMemberCode } });
if (!targetUser) {
return res.json(success({ canChallenge: false, reason: '未找到该用户' }));
}
if (targetUser.id === user.id) {
return res.json(success({ canChallenge: false, reason: '不能挑战自己' }));
}
// 获取对手的天梯用户
const targetLadderUser = await LadderUser.findOne({
where: { user_id: targetUser.id, store_id, status: 1 }
});
if (!targetLadderUser) {
return res.json(success({ canChallenge: false, reason: '对方不是该门店的天梯用户' }));
}
// 检查30天内是否已经比赛过
const recentMatch = await MatchGame.findOne({
where: {
[Op.or]: [
{ player1_id: myLadderUser.id, player2_id: targetLadderUser.id },
{ player1_id: targetLadderUser.id, player2_id: myLadderUser.id }
],
confirm_status: CONFIRM_STATUS.CONFIRMED,
created_at: { [Op.gte]: new Date(Date.now() - POWER_CALC.CHALLENGE_COOLDOWN * 24 * 60 * 60 * 1000) }
},
include: [{
model: Match,
as: 'match',
where: { type: MATCH_TYPES.CHALLENGE }
}]
});
if (recentMatch) {
const days = PowerCalculator.getCooldownDays(recentMatch.created_at);
return res.json(success({
canChallenge: false,
reason: `30天内已与对方比赛过还需等待${days}`
}));
}
res.json(success({
canChallenge: true,
targetUser: {
id: targetUser.id,
nickname: targetUser.nickname,
avatar: targetUser.avatar,
ladderUser: {
id: targetLadderUser.id,
realName: targetLadderUser.real_name,
level: targetLadderUser.level,
powerScore: targetLadderUser.power_score
}
},
myLadderUser: {
id: myLadderUser.id,
realName: myLadderUser.real_name,
level: myLadderUser.level,
powerScore: myLadderUser.power_score
}
}));
} catch (err) {
console.error('检查挑战失败:', err);
res.status(500).json(error('检查失败'));
}
}
// 发起挑战
async createChallenge(req, res) {
const t = await sequelize.transaction();
try {
const { store_id, target_member_code } = req.body;
const user = req.user;
// 获取双方天梯用户
const myLadderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id, status: 1 }
});
const targetUser = await User.findOne({ where: { member_code: target_member_code } });
const targetLadderUser = await LadderUser.findOne({
where: { user_id: targetUser.id, store_id, status: 1 }
});
if (!myLadderUser || !targetLadderUser) {
await t.rollback();
return res.status(400).json(error('天梯用户信息无效', 400));
}
// 检查发起者是否有未结束的挑战赛
const myOngoingMatch = await Match.findOne({
where: {
store_id,
type: MATCH_TYPES.CHALLENGE,
status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] },
[Op.or]: [
{ challenger_id: myLadderUser.id },
{ defender_id: myLadderUser.id }
]
}
});
if (myOngoingMatch) {
await t.rollback();
return res.status(400).json(error('您有未结束的挑战赛,请先完成后再发起新的挑战', 400));
}
// 检查被挑战者是否有未结束的挑战赛
const targetOngoingMatch = await Match.findOne({
where: {
store_id,
type: MATCH_TYPES.CHALLENGE,
status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] },
[Op.or]: [
{ challenger_id: targetLadderUser.id },
{ defender_id: targetLadderUser.id }
]
}
});
if (targetOngoingMatch) {
await t.rollback();
return res.status(400).json(error('对方有未结束的挑战赛,请稍后再试', 400));
}
// 创建挑战赛
const match = await Match.create({
store_id,
match_code: generateMatchCode(),
type: MATCH_TYPES.CHALLENGE,
name: `${myLadderUser.real_name} vs ${targetLadderUser.real_name}`,
weight: MATCH_WEIGHTS.CHALLENGE,
challenger_id: myLadderUser.id,
defender_id: targetLadderUser.id,
status: MATCH_STATUS.PENDING
}, { transaction: t });
// 创建对局
await MatchGame.create({
match_id: match.id,
player1_id: myLadderUser.id,
player2_id: targetLadderUser.id,
status: 0
}, { transaction: t });
await t.commit();
// 发送WebSocket通知给被挑战者
sendChallengeNotification(targetUser.id, {
matchId: match.id,
matchCode: match.match_code,
challenger: {
id: myLadderUser.id,
realName: myLadderUser.real_name,
level: myLadderUser.level,
powerScore: myLadderUser.power_score,
nickname: user.nickname,
avatar: user.avatar
}
});
res.json(success({
matchId: match.id,
matchCode: match.match_code
}, '挑战已发起'));
} catch (err) {
await t.rollback();
console.error('发起挑战失败:', err);
res.status(500).json(error('发起挑战失败'));
}
}
// 响应挑战
async respondChallenge(req, res) {
const t = await sequelize.transaction();
try {
const { match_id, accept } = req.body;
const user = req.user;
const match = await Match.findByPk(match_id, {
include: [{ model: MatchGame, as: 'games' }]
});
if (!match || match.status !== MATCH_STATUS.PENDING) {
await t.rollback();
return res.status(400).json(error('比赛不存在或状态无效', 400));
}
// 验证是否是被挑战者
const defenderLadderUser = await LadderUser.findByPk(match.defender_id);
if (defenderLadderUser.user_id !== user.id) {
await t.rollback();
return res.status(403).json(error('您不是被挑战者', 403));
}
// 如果接受挑战,检查是否有其他未结束的挑战赛
if (accept) {
const otherOngoingMatch = await Match.findOne({
where: {
store_id: match.store_id,
type: MATCH_TYPES.CHALLENGE,
status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] },
id: { [Op.ne]: match_id },
[Op.or]: [
{ challenger_id: defenderLadderUser.id },
{ defender_id: defenderLadderUser.id }
]
}
});
if (otherOngoingMatch) {
await t.rollback();
return res.status(400).json(error('您有未结束的挑战赛,请先完成后再接受新的挑战', 400));
}
}
if (accept) {
// 接受挑战
await match.update({ status: MATCH_STATUS.ONGOING, start_time: new Date() }, { transaction: t });
await MatchGame.update(
{ status: 1 },
{ where: { match_id: match.id }, transaction: t }
);
} else {
// 拒绝挑战
await match.update({ status: MATCH_STATUS.CANCELLED }, { transaction: t });
}
await t.commit();
// 通知挑战者
const challengerLadderUser = await LadderUser.findByPk(match.challenger_id);
const challengerUser = await User.findByPk(challengerLadderUser.user_id);
sendChallengeNotification(challengerUser.id, {
type: accept ? 'challenge_accepted' : 'challenge_rejected',
matchId: match.id
});
res.json(success(null, accept ? '已接受挑战' : '已拒绝挑战'));
} catch (err) {
await t.rollback();
console.error('响应挑战失败:', err);
res.status(500).json(error('操作失败'));
}
}
// 提交比分
async submitScore(req, res) {
const t = await sequelize.transaction();
try {
const { match_id, my_score, opponent_score } = req.body;
const user = req.user;
const match = await Match.findByPk(match_id, {
include: [{ model: MatchGame, as: 'games' }]
});
if (!match || match.status !== MATCH_STATUS.ONGOING) {
await t.rollback();
return res.status(400).json(error('比赛不存在或状态无效', 400));
}
const game = match.games[0];
// 获取提交者的天梯用户
const myLadderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id: match.store_id, status: 1 }
});
if (!myLadderUser) {
await t.rollback();
return res.status(403).json(error('您不是参赛者', 403));
}
// 确定比分
let player1Score, player2Score, winnerId, loserId;
if (myLadderUser.id === game.player1_id) {
player1Score = my_score;
player2Score = opponent_score;
} else if (myLadderUser.id === game.player2_id) {
player1Score = opponent_score;
player2Score = my_score;
} else {
await t.rollback();
return res.status(403).json(error('您不是参赛者', 403));
}
// 确定胜败(比分高者胜)
if (player1Score > player2Score) {
winnerId = game.player1_id;
loserId = game.player2_id;
} else {
winnerId = game.player2_id;
loserId = game.player1_id;
}
// 只有胜者可以提交比分
if (myLadderUser.id !== winnerId) {
await t.rollback();
return res.status(400).json(error('败方不能提交比分,请等待胜方提交', 400));
}
await game.update({
player1_score: player1Score,
player2_score: player2Score,
winner_id: winnerId,
loser_id: loserId,
submit_by: myLadderUser.id,
confirm_status: CONFIRM_STATUS.PENDING,
status: 2
}, { transaction: t });
await t.commit();
// 通知败方确认
const loserLadderUser = await LadderUser.findByPk(loserId);
const loserUser = await User.findByPk(loserLadderUser.user_id);
sendScoreConfirmNotification(loserUser.id, {
matchId: match.id,
gameId: game.id,
player1Score,
player2Score,
submitter: {
realName: myLadderUser.real_name
}
});
res.json(success(null, '比分已提交,等待对方确认'));
} catch (err) {
await t.rollback();
console.error('提交比分失败:', err);
res.status(500).json(error('提交失败'));
}
}
// 确认比分
async confirmScore(req, res) {
const t = await sequelize.transaction();
try {
const { game_id, confirm } = req.body;
const user = req.user;
const game = await MatchGame.findByPk(game_id, {
include: [{ model: Match, as: 'match' }]
});
if (!game || game.confirm_status !== CONFIRM_STATUS.PENDING) {
await t.rollback();
return res.status(400).json(error('比赛不存在或状态无效', 400));
}
// 获取确认者的天梯用户
const myLadderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id: game.match.store_id, status: 1 }
});
// 败方确认
if (myLadderUser.id !== game.loser_id) {
await t.rollback();
return res.status(403).json(error('只有败方可以确认比分', 403));
}
if (!confirm) {
// 有争议,重置比分
await game.update({
player1_score: null,
player2_score: null,
winner_id: null,
loser_id: null,
submit_by: null,
confirm_status: CONFIRM_STATUS.DISPUTED,
status: 1
}, { transaction: t });
await t.commit();
return res.json(success(null, '已标记为有争议,请重新比赛'));
}
// 确认比分,计算战力值变动
const winner = await LadderUser.findByPk(game.winner_id);
const loser = await LadderUser.findByPk(game.loser_id);
const { winnerChange, loserChange } = PowerCalculator.calculate(
{ powerScore: winner.power_score, level: winner.level, protectedMatches: winner.protected_matches },
{ powerScore: loser.power_score, level: loser.level, protectedMatches: loser.protected_matches },
parseFloat(game.match.weight)
);
// 更新游戏记录
await game.update({
confirm_status: CONFIRM_STATUS.CONFIRMED,
confirmed_by: myLadderUser.id,
confirmed_at: new Date(),
winner_power_change: winnerChange,
loser_power_change: loserChange
}, { transaction: t });
// 更新胜者战力值和统计
await winner.update({
power_score: winner.power_score + winnerChange,
match_count: winner.match_count + 1,
monthly_match_count: winner.monthly_match_count + 1,
win_count: winner.win_count + 1,
last_match_time: new Date()
}, { transaction: t });
// 更新败者战力值和统计
const newLoserProtected = loser.level <= 2 && loser.protected_matches < POWER_CALC.NEWBIE_PROTECTION
? loser.protected_matches + 1
: loser.protected_matches;
await loser.update({
power_score: Math.max(0, loser.power_score + loserChange),
match_count: loser.match_count + 1,
monthly_match_count: loser.monthly_match_count + 1,
protected_matches: newLoserProtected,
last_match_time: new Date()
}, { transaction: t });
// 更新比赛状态
await game.match.update({
status: MATCH_STATUS.FINISHED,
end_time: new Date()
}, { transaction: t });
await t.commit();
res.json(success({
winnerChange,
loserChange,
winnerNewScore: winner.power_score + winnerChange,
loserNewScore: Math.max(0, loser.power_score + loserChange)
}, '比分已确认'));
} catch (err) {
await t.rollback();
console.error('确认比分失败:', err);
res.status(500).json(error('确认失败'));
}
}
// 加入排位赛
async joinRankingMatch(req, res) {
const t = await sequelize.transaction();
try {
const { match_code } = req.body;
const user = req.user;
const match = await Match.findOne({
where: { match_code, type: MATCH_TYPES.RANKING }
});
if (!match) {
await t.rollback();
return res.status(404).json(error('比赛不存在', 404));
}
if (match.stage !== RANKING_STAGE.SIGNUP) {
await t.rollback();
return res.status(400).json(error('比赛已开始,无法加入', 400));
}
// 获取天梯用户
const ladderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id: match.store_id, status: 1 }
});
if (!ladderUser) {
await t.rollback();
return res.status(400).json(error('您不是该门店的天梯用户', 400));
}
// 检查是否已加入
const existing = await MatchPlayer.findOne({
where: { match_id: match.id, ladder_user_id: ladderUser.id }
});
if (existing) {
await t.rollback();
return res.status(400).json(error('您已加入该比赛', 400));
}
// 加入比赛
await MatchPlayer.create({
match_id: match.id,
ladder_user_id: ladderUser.id,
initial_power: ladderUser.power_score,
player_status: 'waiting'
}, { transaction: t });
await t.commit();
res.json(success({
matchId: match.id,
matchName: match.name
}, '加入成功'));
} catch (err) {
await t.rollback();
console.error('加入排位赛失败:', err);
res.status(500).json(error('加入失败'));
}
}
// 获取排位赛详情
async getRankingDetail(req, res) {
try {
const { matchCode } = req.params;
const match = await Match.findOne({
where: { match_code: matchCode },
include: [
{
model: MatchPlayer,
as: 'players',
include: [{ model: LadderUser, as: 'ladderUser', include: [{ model: User, as: 'user' }] }]
},
{ model: MatchRound, as: 'rounds', include: [{ model: MatchGame, as: 'games' }] },
{ model: Store, as: 'store' }
]
});
if (!match) {
return res.status(404).json(error('比赛不存在', 404));
}
const stageNames = ['报名中', '循环赛', '淘汰赛', '已结束'];
const statusNames = ['待开始', '进行中', '已结束', '已取消'];
res.json(success({
id: match.id,
matchCode: match.match_code,
name: match.name,
status: match.status,
statusName: statusNames[match.status] || '未知',
stage: match.stage,
stageName: stageNames[match.stage] || '未知',
weight: match.weight,
storeName: match.store?.name,
startTime: match.start_time,
players: match.players.map(p => ({
id: p.id,
ladderUserId: p.ladder_user_id,
realName: p.ladderUser?.real_name,
nickname: p.ladderUser?.user?.nickname,
avatar: p.ladderUser?.user?.avatar,
level: p.ladderUser?.level,
initialPower: p.initial_power,
winCount: p.win_count,
loseCount: p.lose_count,
rank: p.rank,
status: p.player_status
})),
rounds: match.rounds.map(r => ({
id: r.id,
roundNumber: r.round_number,
roundType: r.round_type,
roundName: r.round_name,
status: r.status,
games: r.games.map(g => ({
id: g.id,
player1Id: g.player1_id,
player2Id: g.player2_id,
player1Score: g.player1_score,
player2Score: g.player2_score,
winnerId: g.winner_id,
confirmStatus: g.confirm_status,
status: g.status
}))
}))
}));
} catch (err) {
console.error('获取排位赛详情失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取我的当前对局
async getMyCurrentGame(req, res) {
try {
const { matchCode } = req.params;
const user = req.user;
const match = await Match.findOne({ where: { match_code: matchCode } });
if (!match) {
return res.status(404).json(error('比赛不存在', 404));
}
const ladderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id: match.store_id, status: 1 }
});
if (!ladderUser) {
return res.status(400).json(error('您不是参赛者', 400));
}
const player = await MatchPlayer.findOne({
where: { match_id: match.id, ladder_user_id: ladderUser.id }
});
if (!player) {
return res.status(400).json(error('您不是参赛者', 400));
}
// 查找当前进行中的对局
const currentGame = await MatchGame.findOne({
where: {
match_id: match.id,
[Op.or]: [
{ player1_id: ladderUser.id },
{ player2_id: ladderUser.id }
],
status: { [Op.lt]: 2 } // 未结束
},
include: [{ model: MatchRound, as: 'round' }]
});
if (!currentGame) {
return res.json(success({
status: player.player_status,
currentGame: null
}));
}
// 获取对手信息
const opponentId = currentGame.player1_id === ladderUser.id
? currentGame.player2_id
: currentGame.player1_id;
const opponent = await LadderUser.findByPk(opponentId, {
include: [{ model: User, as: 'user' }]
});
res.json(success({
status: player.player_status,
currentGame: {
id: currentGame.id,
roundName: currentGame.round?.round_name,
opponent: {
id: opponent.id,
realName: opponent.real_name,
nickname: opponent.user?.nickname,
avatar: opponent.user?.avatar,
level: opponent.level,
powerScore: opponent.power_score
},
myScore: currentGame.player1_id === ladderUser.id
? currentGame.player1_score
: currentGame.player2_score,
opponentScore: currentGame.player1_id === ladderUser.id
? currentGame.player2_score
: currentGame.player1_score,
confirmStatus: currentGame.confirm_status,
gameStatus: currentGame.status
}
}));
} catch (err) {
console.error('获取当前对局失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 提交排位赛比分
async submitRankingScore(req, res) {
// 逻辑与挑战赛类似,但需要处理排位赛的轮次逻辑
return this.submitScore(req, res);
}
// 确认排位赛比分
async confirmRankingScore(req, res) {
return this.confirmScore(req, res);
}
// 获取我的比赛记录
async getMyMatches(req, res) {
try {
const { store_id, page = 1, pageSize = 20 } = req.query;
const { limit, offset } = getPagination(page, pageSize);
const user = req.user;
const ladderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id, status: 1 }
});
if (!ladderUser) {
return res.json(pageResult([], 0, page, pageSize));
}
const { rows, count } = await MatchGame.findAndCountAll({
where: {
[Op.or]: [
{ player1_id: ladderUser.id },
{ player2_id: ladderUser.id }
],
confirm_status: CONFIRM_STATUS.CONFIRMED
},
include: [
{ model: Match, as: 'match', attributes: ['id', 'name', 'type', 'weight'] }
],
order: [['confirmed_at', 'DESC']],
limit,
offset
});
const list = await Promise.all(rows.map(async game => {
const isPlayer1 = game.player1_id === ladderUser.id;
const opponentId = isPlayer1 ? game.player2_id : game.player1_id;
const opponent = await LadderUser.findByPk(opponentId);
return {
id: game.id,
matchId: game.match_id,
matchName: game.match?.name,
matchType: game.match?.type,
opponentName: opponent?.real_name,
myScore: isPlayer1 ? game.player1_score : game.player2_score,
opponentScore: isPlayer1 ? game.player2_score : game.player1_score,
isWin: game.winner_id === ladderUser.id,
powerChange: game.winner_id === ladderUser.id
? game.winner_power_change
: game.loser_power_change,
confirmedAt: game.confirmed_at
};
}));
res.json(pageResult(list, count, page, pageSize));
} catch (err) {
console.error('获取比赛记录失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取正在进行中的比赛
async getOngoingMatches(req, res) {
try {
const { store_id } = req.query;
const user = req.user;
const ladderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id, status: 1 }
});
if (!ladderUser) {
return res.json(success([]));
}
// 先获取用户参与的排位赛比赛ID
const myMatchPlayers = await MatchPlayer.findAll({
where: { ladder_user_id: ladderUser.id },
attributes: ['match_id']
});
const rankingMatchIds = myMatchPlayers.map(mp => mp.match_id);
// 查找用户参与的正在进行中的比赛
const whereCondition = {
store_id,
status: { [Op.in]: [MATCH_STATUS.PENDING, MATCH_STATUS.ONGOING] },
[Op.or]: [
// 挑战赛:作为挑战者或被挑战者
{ challenger_id: ladderUser.id },
{ defender_id: ladderUser.id }
]
};
// 如果有排位赛参与记录,添加到查询条件
if (rankingMatchIds.length > 0) {
whereCondition[Op.or].push({ id: { [Op.in]: rankingMatchIds } });
}
const ongoingMatches = await Match.findAll({
where: whereCondition,
include: [
{ model: Store, as: 'store', attributes: ['id', 'name'] }
],
order: [['created_at', 'DESC']]
});
const list = await Promise.all(ongoingMatches.map(async match => {
let opponent = null;
let myStatus = 'waiting';
let currentGame = null;
if (match.type === MATCH_TYPES.CHALLENGE) {
// 挑战赛:获取对手信息
const opponentId = match.challenger_id === ladderUser.id
? match.defender_id
: match.challenger_id;
const opponentLadder = await LadderUser.findByPk(opponentId, {
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
});
if (opponentLadder) {
opponent = {
id: opponentLadder.id,
realName: opponentLadder.real_name,
nickname: opponentLadder.user?.nickname,
avatar: opponentLadder.user?.avatar,
level: opponentLadder.level,
powerScore: opponentLadder.power_score
};
}
myStatus = match.status === MATCH_STATUS.PENDING ? 'waiting' : 'playing';
} else {
// 排位赛:获取参赛者状态和当前对局
const player = await MatchPlayer.findOne({
where: { match_id: match.id, ladder_user_id: ladderUser.id }
});
if (player) {
myStatus = player.player_status;
// 获取当前对局
const game = await MatchGame.findOne({
where: {
match_id: match.id,
[Op.or]: [
{ player1_id: ladderUser.id },
{ player2_id: ladderUser.id }
],
status: { [Op.lt]: 2 }
}
});
if (game) {
const opponentId = game.player1_id === ladderUser.id
? game.player2_id
: game.player1_id;
const opponentLadder = await LadderUser.findByPk(opponentId, {
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
});
if (opponentLadder) {
opponent = {
id: opponentLadder.id,
realName: opponentLadder.real_name,
nickname: opponentLadder.user?.nickname,
avatar: opponentLadder.user?.avatar,
level: opponentLadder.level,
powerScore: opponentLadder.power_score
};
}
currentGame = {
id: game.id,
myScore: game.player1_id === ladderUser.id ? game.player1_score : game.player2_score,
opponentScore: game.player1_id === ladderUser.id ? game.player2_score : game.player1_score,
confirmStatus: game.confirm_status,
gameStatus: game.status
};
}
}
}
// 获取排位赛参赛人数
let playerCount = 0;
if (match.type === MATCH_TYPES.RANKING) {
playerCount = await MatchPlayer.count({ where: { match_id: match.id } });
}
return {
id: match.id,
matchCode: match.match_code,
type: match.type,
typeName: match.type === MATCH_TYPES.CHALLENGE ? '挑战赛' : '排位赛',
name: match.name,
weight: match.weight,
status: match.status,
statusName: match.status === MATCH_STATUS.PENDING ? '待开始' : '进行中',
stage: match.stage,
stageName: match.type === MATCH_TYPES.RANKING ?
['报名中', '循环赛', '淘汰赛', '已结束'][match.stage] : null,
myStatus,
opponent,
currentGame,
playerCount,
startTime: match.start_time,
createdAt: match.created_at
};
}));
res.json(success(list));
} catch (err) {
console.error('获取进行中比赛失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取待确认的比赛
async getPendingConfirm(req, res) {
try {
const { store_id } = req.query;
const user = req.user;
const ladderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id, status: 1 }
});
if (!ladderUser) {
return res.json(success([]));
}
// 查找需要我确认的比赛(我是败方)
const pendingGames = await MatchGame.findAll({
where: {
loser_id: ladderUser.id,
confirm_status: CONFIRM_STATUS.PENDING
},
include: [
{ model: Match, as: 'match' }
],
order: [['created_at', 'DESC']]
});
const list = await Promise.all(pendingGames.map(async game => {
const winner = await LadderUser.findByPk(game.winner_id);
return {
id: game.id,
matchId: game.match_id,
matchName: game.match?.name,
matchType: game.match?.type,
opponentName: winner?.real_name,
myScore: game.player1_id === ladderUser.id ? game.player1_score : game.player2_score,
opponentScore: game.player1_id === ladderUser.id ? game.player2_score : game.player1_score,
createdAt: game.created_at
};
}));
res.json(success(list));
} catch (err) {
console.error('获取待确认比赛失败:', err);
res.status(500).json(error('获取失败'));
}
}
// 获取比赛详情
async getMatchDetail(req, res) {
try {
const { id } = req.params;
const user = req.user;
const match = await Match.findByPk(id, {
include: [
{ model: MatchGame, as: 'games' },
{ model: Store, as: 'store' }
]
});
if (!match) {
return res.status(404).json(error('比赛不存在', 404));
}
// 获取当前用户的天梯用户信息
const myLadderUser = await LadderUser.findOne({
where: { user_id: user.id, store_id: match.store_id, status: 1 },
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
});
// 调试日志
console.log('获取比赛详情 - 用户信息:', {
userId: user.id,
storeId: match.store_id,
myLadderUserId: myLadderUser?.id,
matchChallengerId: match.challenger_id,
matchDefenderId: match.defender_id,
matchStatus: match.status
});
let challengerInfo = null;
let defenderInfo = null;
let myRole = null; // 'challenger' | 'defender' | null
let canAccept = false;
let canReject = false;
let canSubmitScore = false;
let canConfirmScore = false;
if (match.type === MATCH_TYPES.CHALLENGE) {
// 挑战赛:获取挑战者和被挑战者信息
const challengerLadder = await LadderUser.findByPk(match.challenger_id, {
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
});
const defenderLadder = await LadderUser.findByPk(match.defender_id, {
include: [{ model: User, as: 'user', attributes: ['nickname', 'avatar'] }]
});
if (challengerLadder) {
challengerInfo = {
id: challengerLadder.id,
realName: challengerLadder.real_name,
nickname: challengerLadder.user?.nickname,
avatar: challengerLadder.user?.avatar,
level: challengerLadder.level,
powerScore: challengerLadder.power_score,
userId: challengerLadder.user_id, // 添加 user_id用于前端判断
phone: challengerLadder.phone // 添加手机号,用于前端判断
};
}
if (defenderLadder) {
defenderInfo = {
id: defenderLadder.id,
realName: defenderLadder.real_name,
nickname: defenderLadder.user?.nickname,
avatar: defenderLadder.user?.avatar,
level: defenderLadder.level,
powerScore: defenderLadder.power_score,
userId: defenderLadder.user_id, // 添加 user_id用于前端判断
phone: defenderLadder.phone // 添加手机号,用于前端判断
};
}
// 确定当前用户角色
// 方法1通过 myLadderUser.id 比较(如果用户是该门店的天梯用户)
if (myLadderUser) {
// 使用 == 比较,因为可能是数字或字符串
if (myLadderUser.id == match.challenger_id) {
myRole = 'challenger';
console.log('用户是挑战者通过myLadderUser:', { myLadderUserId: myLadderUser.id, challengerId: match.challenger_id });
} else if (myLadderUser.id == match.defender_id) {
myRole = 'defender';
console.log('用户是被挑战者通过myLadderUser:', { myLadderUserId: myLadderUser.id, defenderId: match.defender_id });
} else {
console.log('用户角色不匹配通过myLadderUser:', {
myLadderUserId: myLadderUser.id,
challengerId: match.challenger_id,
defenderId: match.defender_id
});
}
}
// 方法2如果 myLadderUser 为 null通过挑战者和被挑战者的 user_id 来判断
if (!myRole && challengerLadder && defenderLadder) {
// 检查挑战者的 user_id
if (challengerLadder.user_id && challengerLadder.user_id == user.id) {
myRole = 'challenger';
console.log('用户是挑战者通过user_id:', {
userId: user.id,
challengerUserId: challengerLadder.user_id,
challengerLadderId: challengerLadder.id,
matchChallengerId: match.challenger_id
});
}
// 检查被挑战者的 user_id
else if (defenderLadder.user_id && defenderLadder.user_id == user.id) {
myRole = 'defender';
console.log('用户是被挑战者通过user_id:', {
userId: user.id,
defenderUserId: defenderLadder.user_id,
defenderLadderId: defenderLadder.id,
matchDefenderId: match.defender_id
});
} else {
console.log('用户角色不匹配通过user_id:', {
userId: user.id,
challengerUserId: challengerLadder.user_id,
challengerLadderId: challengerLadder.id,
defenderUserId: defenderLadder.user_id,
defenderLadderId: defenderLadder.id,
matchChallengerId: match.challenger_id,
matchDefenderId: match.defender_id
});
}
}
// 方法3如果前两种方法都失败尝试通过手机号匹配如果天梯用户有手机号且用户有手机号
if (!myRole && challengerLadder && defenderLadder && user.phone) {
if (challengerLadder.phone && challengerLadder.phone === user.phone) {
myRole = 'challenger';
console.log('用户是挑战者(通过手机号):', { userPhone: user.phone, challengerPhone: challengerLadder.phone });
} else if (defenderLadder.phone && defenderLadder.phone === user.phone) {
myRole = 'defender';
console.log('用户是被挑战者(通过手机号):', { userPhone: user.phone, defenderPhone: defenderLadder.phone });
}
}
if (!myRole) {
console.log('无法确定用户角色:', {
userId: user.id,
userPhone: user.phone,
storeId: match.store_id,
hasMyLadderUser: !!myLadderUser,
myLadderUserId: myLadderUser?.id,
challengerLadderId: challengerLadder?.id,
challengerUserId: challengerLadder?.user_id,
challengerPhone: challengerLadder?.phone,
defenderLadderId: defenderLadder?.id,
defenderUserId: defenderLadder?.user_id,
defenderPhone: defenderLadder?.phone,
matchChallengerId: match.challenger_id,
matchDefenderId: match.defender_id
});
}
// 判断操作权限
// 待接受状态且是被挑战者,可以接受或拒绝
if (match.status === MATCH_STATUS.PENDING) {
if (myRole === 'defender') {
canAccept = true;
canReject = true;
console.log('设置接受/拒绝权限为true:', { myRole, status: match.status, canAccept, canReject });
} else {
console.log('不能接受/拒绝:', { myRole, status: match.status, isDefender: myRole === 'defender' });
}
} else {
console.log('比赛状态不是待接受:', { status: match.status, myRole });
}
if (match.status === MATCH_STATUS.ONGOING && myLadderUser) {
const game = match.games?.[0];
if (game) {
// 检查是否可以提交比分(必须是胜者且未提交)
if (game.status === 1 && !game.submit_by) {
// 比赛进行中,还未提交比分,双方都可以填写
canSubmitScore = true;
} else if (game.status === 2 && game.submit_by && game.submit_by !== myLadderUser.id) {
// 对方已提交比分,等待我确认
if (game.confirm_status === CONFIRM_STATUS.PENDING) {
canConfirmScore = true;
}
}
}
}
}
const result = {
id: match.id,
matchCode: match.match_code,
type: match.type,
name: match.name,
weight: match.weight,
status: match.status,
stage: match.stage,
storeName: match.store?.name,
startTime: match.start_time,
endTime: match.end_time,
challenger: challengerInfo,
defender: defenderInfo,
myRole: myRole || null,
canAccept: Boolean(canAccept),
canReject: Boolean(canReject),
canSubmitScore: Boolean(canSubmitScore),
canConfirmScore: Boolean(canConfirmScore),
games: match.games?.map(game => ({
id: game.id,
player1Id: game.player1_id,
player2Id: game.player2_id,
player1Score: game.player1_score,
player2Score: game.player2_score,
winnerId: game.winner_id,
loserId: game.loser_id,
submitBy: game.submit_by,
confirmStatus: game.confirm_status,
status: game.status
})) || []
};
// 调试日志
console.log('比赛详情返回数据:', {
matchId: match.id,
matchStatus: match.status,
storeId: match.store_id,
userId: user.id,
myLadderUserId: myLadderUser?.id,
myLadderUserType: typeof myLadderUser?.id,
challengerId: match.challenger_id,
challengerIdType: typeof match.challenger_id,
defenderId: match.defender_id,
defenderIdType: typeof match.defender_id,
myRole,
canAccept: result.canAccept,
canReject: result.canReject,
hasMyLadderUser: !!myLadderUser,
challengerMatch: myLadderUser ? (myLadderUser.id == match.challenger_id) : false,
defenderMatch: myLadderUser ? (myLadderUser.id == match.defender_id) : false
});
// 确保返回所有必要的字段
res.json(success(result));
} catch (err) {
console.error('获取比赛详情失败:', err);
res.status(500).json(error('获取失败'));
}
}
}
module.exports = new MatchController();