Files
BuYuHouDuan/ChessGameSrc/ChessGameService/ChessGameHub.cs
T
2026-10-12 16:22:04 +08:00

601 lines
23 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ChessGameDomain;
using ChessGameService.Param;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using RedisService;
using XAct;
namespace ChessGameService
{
public class ChessGameHub : Hub
{
public const string LobbyGroup = "chess_lobby";
private readonly ILogger<ChessGameHub> _logger;
private readonly IChessGameService _chessService;
private readonly IRedisService _redisService;
public ChessGameHub(ILogger<ChessGameHub> logger, IChessGameService chessService, IRedisService redisService)
{
_logger = logger;
_chessService = chessService;
_redisService = redisService;
}
private async Task JoinLobbyAsync(string connectionId)
{
var cid = string.IsNullOrWhiteSpace(connectionId) ? Context.ConnectionId : connectionId;
await Groups.AddToGroupAsync(cid, LobbyGroup);
}
private async Task LeaveLobbyAsync(string connectionId)
{
var cid = string.IsNullOrWhiteSpace(connectionId) ? Context.ConnectionId : connectionId;
await Groups.RemoveFromGroupAsync(cid, LobbyGroup);
}
private async Task JoinLobbyBatchAsync(IEnumerable<string> connectionIds)
{
foreach (var connectionId in connectionIds.Distinct())
{
try
{
await Groups.AddToGroupAsync(connectionId, LobbyGroup);
}
catch (Exception ex)
{
_logger.LogError(ex, "[ChessLobby] 加入大厅组失败 cid={Cid}", connectionId);
}
}
}
public override async Task OnConnectedAsync()
{
var cid = Context.ConnectionId;
var reconnectUserId = Context.GetHttpContext()?.Request?.Query["userId"].ToString();
var room = !string.IsNullOrWhiteSpace(reconnectUserId)
? _redisService.GetAllChessRooms()?.FirstOrDefault(r => r.users.Any(u => u.userId == reconnectUserId && (r.state == 1 || r.state == 2 || r.IsGameEnded)))
: _chessService.GetUserRoom(cid);
if (room != null)
{
var user = room.users.FirstOrDefault(c => c.Cid == cid || c.userId == reconnectUserId);
if (user != null)
{
if (!user.IsLeave && user.Cid != cid)
{
await Clients.Caller.SendAsync("GameError", "用户已在其他连接中在线");
await base.OnConnectedAsync();
return;
}
user.Cid = cid;
user.DisconnectTime = 0;
user.IsLeave = false;
room.robotCount = Math.Clamp(room.robotCount, 0, Math.Max(0, 2 - room.users.Count));
_redisService.SetChessRoom(room);
var otherUser = room.users.FirstOrDefault(c => c.Cid != cid);
if (otherUser != null)
{
await Clients.Group(room.RoomId).SendAsync("Reconnect", new
{
userId = user.userId,
userName = user.userName
});
}
}
await Clients.Caller.SendAsync("BeginGame", room);
await Groups.AddToGroupAsync(cid, room.RoomId);
}
else
{
await JoinLobbyAsync(cid);
}
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
var cid = Context.ConnectionId;
var room = _chessService.GetUserRoom(cid);
if (room != null)
{
var user = room.users.FirstOrDefault(c => c.Cid == cid);
if (user != null)
{
if (room.state == 2 && room.users.Count == 2)
{
user.DisconnectTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
user.IsLeave = true;
var timeoutSeconds = room.TimeoutSeconds > 0 ? room.TimeoutSeconds : 60;
user.Cid = string.Empty;
if (room.users.All(u => u.IsLeave))
{
var affectedUserIds = room.users.Select(u => u.userId).Distinct().ToList();
_chessService.ExitRoom(room);
await Task.WhenAll(affectedUserIds.Select(id => _chessService.UpdateUserGameType(id, 0)));
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
}
else
{
_redisService.SetChessRoom(room);
await Clients.Group(room.RoomId).SendAsync("Disconnect", new
{
userId = user.userId,
disconnectTime = user.DisconnectTime,
timeoutSeconds = timeoutSeconds
});
}
}
else
{
var affectedUserIds = room.createUserId == user.userId
? room.users.Where(u => u != null).Select(u => u.userId).Distinct().ToList()
: new List<string> { user.userId };
var (_, _, updatedRoom) = _chessService.ExitRoom(room.RoomId, user.userId);
await Task.WhenAll(affectedUserIds.Select(id => _chessService.UpdateUserGameType(id, 0)));
await Groups.RemoveFromGroupAsync(Context.ConnectionId, room.RoomId);
if (updatedRoom != null && updatedRoom.users != null && updatedRoom.users.Count > 0)
{
await Clients.Group(updatedRoom.RoomId).SendAsync("BeginGame", updatedRoom);
}
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", updatedRoom ?? room);
}
}
}
}
public async Task JoinGame(string gameId, string pwd, string userId)
{
string cid = Context.ConnectionId;
var (success, message, room) = await _chessService.JoinGameAsync(gameId, pwd, userId, cid);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
await _chessService.UpdateUserGameType(userId, 3, room.RoomId);
await Groups.AddToGroupAsync(cid, room.RoomId);
await LeaveLobbyAsync(cid);
await Clients.Caller.SendAsync("JoinRoom", room);
await Clients.Group(room.RoomId).SendAsync("BeginGame", room);
await Clients.Group(room.RoomId).SendAsync("RoomStateNotice", room);
await Clients.Group(LobbyGroup).SendAsync("JoinRoomNotice", room);
if (room.users.Count == 2 && room.state == 1)
{
var redUser = room.users.FirstOrDefault(u => u.type == 2);
await Clients.Group(room.RoomId).SendAsync("GoldConfigRequired", new
{
targetUserId = redUser?.userId,
room
});
}
}
public async Task StartGame(string gameId, string userId)
{
if (!IsCaller(gameId, userId)) return;
var (success, message, room) = _chessService.StartGame(gameId, userId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
var redUser = room.users.FirstOrDefault(u => u.type == 2);
await Clients.Group(room.RoomId).SendAsync("GoldConfigRequired", new
{
targetUserId = redUser?.userId,
room
});
}
public async Task GameReady(string gameId, string userId)
{
if (!IsCaller(gameId, userId)) return;
var (success, message, allReady) = _chessService.GameReady(gameId, userId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
var room = _chessService.GetRoom(gameId);
await Clients.Groups(room.RoomId).SendAsync("ReadyNotice", userId);
if (allReady)
{
await Clients.Groups(room.RoomId).SendAsync("AllBeginTime", room);
}
}
public async Task EndGame(string gameId, string winUserId)
{
if (!IsCallerInRoom(gameId)) return;
var settledConnectionIds = GetRoomConnectionIds(gameId);
var (success, message, room) = await _chessService.EndGame(gameId, winUserId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
await Clients.Group(room.RoomId).SendAsync("GameEnd", new
{
winUserId = winUserId,
room = room
});
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
await JoinLobbyBatchAsync(settledConnectionIds);
await RemoveConnectionsFromRoomGroupAsync(room.RoomId, settledConnectionIds, "EndGame");
}
public async Task RestartGame(string gameId, string userId)
{
if (!IsCaller(gameId, userId))
{
var room0 = _chessService.GetRoom(gameId);
if (room0 == null || room0.createUserId != userId)
{
await Clients.Caller.SendAsync("GameError", "无权操作");
return;
}
}
var (success, message, room) = _chessService.RestartGame(gameId, userId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
await Groups.AddToGroupAsync(Context.ConnectionId, room.RoomId);
await LeaveLobbyAsync(Context.ConnectionId);
await Clients.Caller.SendAsync("RestartGameSuccess", room);
await Clients.Group(LobbyGroup).SendAsync("RestartRoomNotice", room);
}
public async Task RequestEndGame(string gameId, string userId, string winUserId)
{
if (!IsCaller(gameId, userId)) return;
if (string.IsNullOrEmpty(userId))
{
await Clients.Caller.SendAsync("GameError", "参数异常");
return;
}
var settledConnectionIds = GetRoomConnectionIds(gameId);
var (success, message, room, allConfirmed) = await _chessService.RequestEndGame(gameId, userId, winUserId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
if (allConfirmed)
{
await Clients.Group(room.RoomId).SendAsync("GameEnd", new
{
winUserId = winUserId,
room = room
});
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
await JoinLobbyBatchAsync(settledConnectionIds);
await RemoveConnectionsFromRoomGroupAsync(room.RoomId, settledConnectionIds, "RequestEndGame");
}
else
{
var otherUserId = room.users.FirstOrDefault(u => u.userId != userId)?.userId;
if (!string.IsNullOrEmpty(otherUserId))
{
await Clients.Group(room.RoomId).SendAsync("OpponentRequestEnd", userId);
}
await Clients.Caller.SendAsync("EndGameRequestSent", message);
}
}
public async Task Heartbeat(string gameId)
{
var userId = Context.GetHttpContext()?.Request?.Query["userId"].ToString();
if (string.IsNullOrEmpty(userId))
{
return;
}
if (!IsCaller(gameId, userId)) return;
await _chessService.UpdateUserActiveTime(gameId, userId);
}
public async Task SetPlayerAfk(string gameId, string userId)
{
if (!IsCaller(gameId, userId)) return;
await _chessService.SetPlayerAfk(gameId, userId);
await Clients.Group(gameId).SendAsync("PlayerAfkNotice", new { userId });
var room = _chessService.GetRoom(gameId);
if (room != null)
{
await Clients.Group(gameId).SendAsync("RoomStateNotice", room);
}
}
public async Task CancelPlayerAfk(string gameId, string userId)
{
if (!IsCaller(gameId, userId)) return;
await _chessService.CancelPlayerAfk(gameId, userId);
await Clients.Group(gameId).SendAsync("PlayerCancelAfkNotice", new { userId });
var room = _chessService.GetRoom(gameId);
if (room != null)
{
await Clients.Caller.SendAsync("BeginGame", room);
await Clients.Group(gameId).SendAsync("RoomStateNotice", room);
}
}
public async Task CheckTimeout(string gameId)
{
if (!IsCallerInRoom(gameId)) return;
var beforeEndRoom = _redisService.GetChessRoom(gameId);
var settledConnectionIds = GetRoomConnectionIds(beforeEndRoom);
var (isTimeout, loserUserId) = await _chessService.CheckRoomTimeout(gameId);
if (isTimeout)
{
var room = _redisService.GetChessRoom(gameId);
if (room != null && beforeEndRoom != null)
{
await Clients.Group(room.RoomId).SendAsync("GameEnd", new
{
winUserId = beforeEndRoom.users.FirstOrDefault(u => u.userId != loserUserId)?.userId ?? "",
room = room
});
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
await JoinLobbyBatchAsync(settledConnectionIds);
await RemoveConnectionsFromRoomGroupAsync(room.RoomId, settledConnectionIds, "CheckTimeout");
}
}
}
public async Task ExitRoom(string gameId, string userId)
{
if (!IsCaller(gameId, userId)) return;
var beforeExitRoom = _chessService.GetRoom(gameId);
var beforeExitUserIds = beforeExitRoom?.users?
.Where(u => u != null).Select(u => u.userId).Distinct().ToList()
?? new List<string> { userId };
var beforeExitConnectionIds = GetRoomConnectionIds(beforeExitRoom);
var (success, message, room) = _chessService.ExitRoom(gameId, userId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
var affectedUserIds = room.users == null || room.users.Count == 0
? beforeExitUserIds
: new List<string> { userId };
await Task.WhenAll(affectedUserIds.Select(id => _chessService.UpdateUserGameType(id, 0)));
await Groups.RemoveFromGroupAsync(Context.ConnectionId, gameId);
await JoinLobbyAsync(Context.ConnectionId);
if (room.users == null || room.users.Count == 0)
{
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
}
else
{
await Clients.Group(room.RoomId).SendAsync("ExitRoomSuccess", userId);
await Clients.Group(room.RoomId).SendAsync("BeginGame", room);
await Clients.Group(room.RoomId).SendAsync("RoomStateNotice", room);
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
}
}
public async Task CreateRoom(CreateRoomParam param)
{
string cid = Context.ConnectionId;
var (success, message, room) = await _chessService.CreateRoomAsync(param, cid);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
await _chessService.UpdateUserGameType(param.userId, 3, room.RoomId);
await Clients.Caller.SendAsync("BeginGame", room);
await Groups.AddToGroupAsync(cid, room.RoomId);
await LeaveLobbyAsync(cid);
await Clients.Group(LobbyGroup).SendAsync("CreateRoomNotice", room);
}
public async Task GoldConfig(string gameId, long gold, string userId)
{
if (!IsCaller(gameId, userId)) return;
var (success, message, room) = _chessService.GoldConfig(gameId, gold, userId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
await Clients.Groups(room.RoomId).SendAsync("GoldConfigSuccess", room);
await Clients.Group(room.RoomId).SendAsync("GoldConfigRoomNotice", room);
}
public async Task GetRoomList(int partition = 0)
{
await JoinLobbyAsync(Context.ConnectionId);
var list = _chessService.GetRoomList(partition, 100);
await Clients.Caller.SendAsync("GetRoomList", list);
}
public async Task GetRoomState(string gameId)
{
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
if (room == null || !IsCallerInRoom(gameId))
{
await Clients.Caller.SendAsync("GameError", "房间不存在或当前连接不在房间中");
return;
}
await Clients.Caller.SendAsync("RoomStateNotice", room);
}
public async Task KickPlayer(string gameId, string targetUserId)
{
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
var caller = room?.users?.FirstOrDefault(u => u.Cid == Context.ConnectionId && !u.IsLeave);
if (room == null || caller == null)
{
await Clients.Caller.SendAsync("GameError", "房间不存在或当前连接不在房间中");
return;
}
if (room.createUserId != caller.userId)
{
await Clients.Caller.SendAsync("GameError", "只有房主可以踢人");
return;
}
if (room.state == 2)
{
await Clients.Caller.SendAsync("GameError", "游戏进行中不能踢人");
return;
}
if (string.IsNullOrWhiteSpace(targetUserId) || targetUserId == caller.userId)
{
await Clients.Caller.SendAsync("GameError", "不能踢出自己");
return;
}
var target = room.users.FirstOrDefault(u => u.userId == targetUserId);
if (target == null)
{
await Clients.Caller.SendAsync("GameError", "目标玩家不在房间中");
return;
}
var targetCid = target.Cid;
var (success, message, updatedRoom) = _chessService.ExitRoom(gameId, targetUserId);
if (!success)
{
await Clients.Caller.SendAsync("GameError", message);
return;
}
await _chessService.UpdateUserGameType(targetUserId, 0);
if (!string.IsNullOrWhiteSpace(targetCid))
{
await Clients.Client(targetCid).SendAsync("KickedFromRoom", new { gameId, userId = targetUserId });
await Groups.RemoveFromGroupAsync(targetCid, gameId);
await JoinLobbyAsync(targetCid);
}
await Clients.Group(gameId).SendAsync("RoomStateNotice", updatedRoom);
await Clients.Group(gameId).SendAsync("BeginGame", updatedRoom);
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", updatedRoom);
}
public async Task SyncGameData(string gameId, string data)
{
var room = _redisService.GetChessRoom(gameId);
var otherUser = room?.users?.FirstOrDefault(u => u.Cid != Context.ConnectionId);
if (otherUser != null && !otherUser.IsLeave)
await Clients.Client(otherUser.Cid).SendAsync("SyncGameData", data);
}
public async Task GameOperation(string gameId, int type, string data)
{
var userId = Context.GetHttpContext()?.Request?.Query["userId"].ToString();
if (!string.IsNullOrEmpty(userId))
{
if (!IsCaller(gameId, userId)) return;
await _chessService.UpdateUserActiveTime(gameId, userId);
}
if (type == 2 && !string.IsNullOrEmpty(userId))
{
var room = _redisService.GetChessRoom(gameId);
if (room != null && room.state == 2)
{
var winUserId = room.users.FirstOrDefault(u => u.userId != userId)?.userId ?? "";
await EndGame(gameId, winUserId);
return;
}
}
await Clients.Group(gameId).SendAsync("GameOperation", new { type, data });
}
private bool IsCaller(string gameId, string userId)
{
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
return room?.users?.Any(u => u.userId == userId && u.Cid == Context.ConnectionId && !u.IsLeave) == true;
}
private bool IsCallerInRoom(string gameId)
{
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
return room?.users?.Any(u => u.Cid == Context.ConnectionId && !u.IsLeave) == true;
}
private List<string> GetRoomConnectionIds(string gameId)
{
return GetRoomConnectionIds(_redisService.GetChessRoom(gameId));
}
private static List<string> GetRoomConnectionIds(ChessGameRoom? room)
{
return room?.users?
.Where(user => !string.IsNullOrWhiteSpace(user.Cid))
.Select(user => user.Cid)
.Distinct()
.ToList() ?? new List<string>();
}
private async Task RemoveConnectionsFromRoomGroupAsync(
string roomId, IEnumerable<string> connectionIds, string reason)
{
foreach (var connectionId in connectionIds.Distinct())
{
try
{
await Groups.RemoveFromGroupAsync(connectionId, roomId);
}
catch (Exception ex)
{
_logger.LogError(ex,
"[ChessGroup] 结算退组失败 roomId={RoomId} cid={Cid} reason={Reason}",
roomId, connectionId, reason);
}
}
}
}
}