1208 lines
40 KiB
JavaScript
1208 lines
40 KiB
JavaScript
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();
|