添加项目文件。

This commit is contained in:
2026-10-12 16:22:04 +08:00
parent 7c68a0ca0a
commit aad11be43d
1000 changed files with 120807 additions and 0 deletions
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\MongoRepository\MongoRepository.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations.Schema;
using MongoDB.Bson.Serialization.Attributes;
using MongoRepository;
using MongoRepository.Attributes;
using MongoRepository.Config;
namespace ChessGameDomain
{
[BsonCollection("game_room", ServiceDatabases.Chess)]
[BsonIgnoreExtraElements]
public class ChessGameRoom : MongoBaseEntity
{
[Column(TypeName = "jsonb")]
public List<RedisChessGameUser> users { get; set; } = new List<RedisChessGameUser>();
public string RoomId { get; set; }
public DateTime CreateTime { get; set; }
public int gameType { get; set; }
public string password { get; set; }
public string createUserId { get; set; }
public int state { get; set; } = 1;
public long gold { get; set; } = 1000;
public int robotCount { get; set; } = 0;
public Dictionary<string, long> EndGameRequests { get; set; } = new Dictionary<string, long>();
public long LastActiveTime { get; set; } = 0;
public int TimeoutSeconds { get; set; } = 60;
public int AfkTimeoutSeconds { get; set; } = 120;
public string TimeoutLoserUserId { get; set; } = "";
public string PendingWinUserId { get; set; } = "";
public bool IsGameEnded { get; set; } = false;
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChessGameDomain
{
public class RedisChessGameUser
{
public string Cid { get; set; }
public string userId { get; set; }
public string userName { get; set; }
public string userAvatar { get; set; }
public int type { get; set; }
public bool IsLeave { get; set; } = false;
public bool IsReady { get; set; } = false;
public long Gold { get; set; } = 0;
public long DisconnectTime { get; set; } = 0;
public bool IsAfk { get; set; } = false;
public long AfkTime { get; set; } = 0;
}
}
@@ -0,0 +1,600 @@
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);
}
}
}
}
}
@@ -0,0 +1,918 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using ChessGameDomain;
using ChessGameService.Param;
using Common.Redis;
using CommonDomain;
using CommonDomain.BaseModel;
using CommonDomain.Enum;
using DotNetCore.CAP;
using Microsoft.Extensions.Logging;
using RedisHelper;
using RedisService;
using UserDomain.Enum;
using UserService;
using UserService.Param;
using UserService.User;
namespace ChessGameService
{
public class ChessGameService : IChessGameService
{
private readonly IRedisService _redisService;
private readonly ILogger<ChessGameService> _logger;
private readonly IDistributedLock _distributedLock;
private readonly IUserService _userService;
private readonly ICapPublisher _capPublisher;
public ChessGameService(IRedisService redisService, ILogger<ChessGameService> logger, IDistributedLock distributedLock, IUserService userService, ICapPublisher capPublisher)
{
_redisService = redisService;
_logger = logger;
_distributedLock = distributedLock;
_userService = userService;
_capPublisher = capPublisher;
}
public async Task<(bool isok, string msg, ChessGameRoom room)> CreateRoomAsync(CreateRoomParam param, string cid)
{
var user = await _userService.GetUser(param.userId);
if (user == null)
return (false, "用户不存在", null);
var gameConfig = _redisService.GetGameConfig();
if (gameConfig == null)
return (false, "游戏配置不存在", null);
return await _distributedLock.TryExecuteAsync(
$"{RedisKey.DistributedLockPrefix}chess:room:{param.roomId}",
() =>
{
var room = _redisService.GetChessRoom(param.roomId);
if (room == null)
{
return Task.FromResult<(bool isok, string msg, ChessGameRoom room)>((false, "房间不存在", null));
}
var allRooms = _redisService.GetAllChessRooms();
if (allRooms != null)
{
foreach (var oldRoom in allRooms)
{
if (oldRoom.RoomId == param.roomId) continue;
var oldUser = oldRoom.users?.FirstOrDefault(u => u.userId == param.userId);
if (oldUser != null)
{
if (oldRoom.createUserId == param.userId)
{
oldRoom.users = new List<RedisChessGameUser>();
oldRoom.state = 3;
oldRoom.password = "";
oldRoom.createUserId = "";
oldRoom.gold = 0;
oldRoom.gameType = 0;
oldRoom.robotCount = 0;
oldRoom.IsGameEnded = false;
oldRoom.EndGameRequests?.Clear();
oldRoom.PendingWinUserId = null;
oldRoom.TimeoutLoserUserId = "";
oldRoom.LastActiveTime = 0;
}
else
{
oldRoom.users.Remove(oldUser);
if (oldRoom.users.Count == 0)
{
oldRoom.state = 3;
oldRoom.password = "";
oldRoom.createUserId = "";
oldRoom.gold = 0;
oldRoom.gameType = 0;
oldRoom.robotCount = 0;
oldRoom.IsGameEnded = false;
}
else
{
oldRoom.gold = 0;
}
}
_redisService.SetChessRoom(oldRoom);
}
}
}
if ((room.state == 3 && room.users.Count == 0) || room.state == 0)
{
bool isCreareRoom = false;
if (gameConfig.ChessConfig.CreateRoomLimit.Any(c=>c==3))
isCreareRoom = true;
if (gameConfig.ChessConfig.CreateRoomLimit.Any(c => c == 1) && user.Assets.VipLevel > 0)
{
var vipLevels = gameConfig.ChessConfig.CreateRoomVipLevels ?? new List<int>();
if (vipLevels.Count == 0 || vipLevels.Contains(user.Assets.VipLevel))
isCreareRoom = true;
}
if (gameConfig.ChessConfig.CreateRoomLimit.Any(c => c == 2) && user.UserType==2)
isCreareRoom = true;
if (!isCreareRoom)
{
var roles = new List<string>();
if (gameConfig.ChessConfig.CreateRoomLimit.Any(c => c == 1)) roles.Add("VIP");
if (gameConfig.ChessConfig.CreateRoomLimit.Any(c => c == 2)) roles.Add("代理");
var msg = roles.Count > 0 ? $"只有{string.Join("/", roles)}才能配置密码" : "没有权限配置密码";
return Task.FromResult<(bool isok, string msg, ChessGameRoom room)>((false, msg, null));
}
room.users = new List<RedisChessGameUser>() { new RedisChessGameUser
{
Cid = cid,
userId = param.userId,
userName = user.BaseInfo.UserName,
userAvatar = user.BaseInfo.AvatarUrl,
Gold = user.Assets.Gold,
type = param.gameType,
IsLeave = false,
IsReady = false
}};
room.gameType = param.gameType;
room.password = param.password;
room.createUserId = param.userId;
room.gold = param.gold;
room.state = 1;
room.robotCount = 0;
room.IsGameEnded = false;
}
else if (room.users.Any(c => c.userId == param.userId))
{
var existing = room.users.First(c => c.userId == param.userId);
existing.Cid = cid;
existing.IsLeave = false;
existing.DisconnectTime = 0;
room.robotCount = Math.Clamp(room.robotCount, 0, Math.Max(0, 2 - room.users.Count));
_redisService.SetChessRoom(room);
return Task.FromResult<(bool isok, string msg, ChessGameRoom room)>((true, "", room));
}
else if (room.state == 3)
return Task.FromResult<(bool isok, string msg, ChessGameRoom room)>((false, "上一局玩家尚未退出", room));
else if (room.users.Count + room.robotCount >= 2 || room.state == 2)
return Task.FromResult<(bool isok, string msg, ChessGameRoom room)>((false, "当前房间已有玩家", room));
else
{
var firstPlayerType = room.gameType;
var secondPlayerType = firstPlayerType == 1 ? 2 : 1;
room.users.Add(new RedisChessGameUser
{
Cid = cid,
userId = param.userId,
userName = user.BaseInfo.UserName,
userAvatar = user.BaseInfo.AvatarUrl,
Gold = user.Assets.Gold,
type = secondPlayerType,
IsLeave = false,
IsReady = false
});
room.state = 1;
}
_redisService.SetChessRoom(room);
return Task.FromResult<(bool isok, string msg, ChessGameRoom room)>((true, "", room));
},
expiry: 10
);
}
public async Task<(bool success, string message, ChessGameRoom room)> JoinGameAsync(string gameId, string pwd, string userId, string cid)
{
var user = await _userService.GetUser(userId);
if (user == null)
return (false, "用户不存在", null);
return await _distributedLock.TryExecuteAsync(
$"{RedisKey.DistributedLockPrefix}chess:room:{gameId}",
() =>
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state == 3)
{
return Task.FromResult((false, "房间不存在", (ChessGameRoom)null));
}
if (string.IsNullOrEmpty(room.password))
{
return Task.FromResult((false, "房间未设置密码,无法进入", (ChessGameRoom)null));
}
if (room.password != pwd)
{
return Task.FromResult((false, "密码错误", (ChessGameRoom)null));
}
if (room.users.Any(c => c.userId == userId))
{
var existing = room.users.First(c => c.userId == userId);
existing.Cid = cid;
existing.IsLeave = false;
existing.DisconnectTime = 0;
room.robotCount = Math.Clamp(room.robotCount, 0, Math.Max(0, 2 - room.users.Count));
_redisService.SetChessRoom(room);
return Task.FromResult((true, "", room));
}
if (room.state == 3 && room.IsGameEnded && room.users.Count < 2)
{
var secondPlayerType = room.gameType == 1 ? 2 : 1;
room.users.Add(new RedisChessGameUser
{
Cid = cid,
userId = userId,
Gold = user.Assets.Gold,
userName = user.BaseInfo.UserName,
userAvatar = user.BaseInfo.AvatarUrl,
type = secondPlayerType,
IsLeave = false,
IsReady = false
});
room.robotCount = 0;
room.state = 1;
room.IsGameEnded = false;
room.gold = 0;
_redisService.SetChessRoom(room);
return Task.FromResult((true, "", room));
}
if (room.users.Count + room.robotCount >= 2)
{
return Task.FromResult((false, "房间已满", (ChessGameRoom)null));
}
room.users.Add(new RedisChessGameUser
{
Cid = cid,
userId = userId,
Gold = user.Assets.Gold,
userName = user.BaseInfo.UserName,
userAvatar = user.BaseInfo.AvatarUrl,
type = room.gameType == 1 ? 2 : 1,
IsLeave = false,
IsReady = false
});
room.robotCount = 0;
_redisService.SetChessRoom(room);
return Task.FromResult((true, "", room));
},
expiry: 10
);
}
public List<ChessGameRoom> GetRoomList(int partition, int maxRooms = 100)
{
var existingRooms = _redisService.GetAllChessRooms();
if (existingRooms == null || existingRooms.Count == 0)
return new List<ChessGameRoom>();
return existingRooms
.Where(c => c != null
&& (partition <= 0 || GetChessRoomPartition(c.RoomId) == partition)
&& !(c.state == 1
&& string.IsNullOrWhiteSpace(c.createUserId)
&& (c.users == null || c.users.Count == 0)))
.OrderBy(c => c.RoomId)
.Take(maxRooms)
.ToList();
}
private static int GetChessRoomPartition(string roomId)
{
if (string.IsNullOrWhiteSpace(roomId) || roomId.Length < 4)
return 0;
var partitionText = roomId.Substring(1, roomId.Length - 3);
return int.TryParse(partitionText, out var partition) ? partition : 0;
}
public (bool success, string message, ChessGameRoom room) StartGame(string gameId, string userId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state == 3)
return (false, "房间不存在", null);
if (room.state != 1)
return (false, "游戏已开始", null);
if (room.createUserId != userId)
return (false, "只有房主才能开始游戏", null);
if (room.users.Count < 2)
return (false, "人数不足,无法开始", null);
return (true, "", room);
}
public (bool success, string message, bool allReady) GameReady(string gameId, string userId)
{
if (string.IsNullOrWhiteSpace(gameId) || string.IsNullOrWhiteSpace(userId))
return (false, "房间或玩家参数不能为空", false);
return _distributedLock.TryExecute(
$"{RedisKey.DistributedLockPrefix}chess:room:{gameId}",
() =>
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state == 3)
return (false, "房间不存在", false);
var user = room.users.FirstOrDefault(c => c.userId == userId);
if (user == null)
return (false, "玩家不存在", false);
if (room.gold == 0)
return (false, "未设置金币", false);
user.IsReady = true;
var allReady = room.users.Count >= 2 && room.users.All(c => c.IsReady);
if (allReady)
room.state = 2;
_redisService.SetChessRoom(room);
return (true, "", allReady);
},
expiry: 10,
retry: true,
retryDelay: 20);
}
public async Task<(bool success, string message, ChessGameRoom room)> EndGame(string gameId, string winUserId)
{
return await _distributedLock.TryExecuteAsync(
$"{RedisKey.DistributedLockPrefix}chess:room:{gameId}",
async () =>
{
var room = _redisService.GetChessRoom(gameId);
if (room == null)
return (false, "房间不存在", null);
if (room.state != 2 || room.IsGameEnded)
return (true, "游戏已结束", room);
if (!string.IsNullOrWhiteSpace(winUserId) && !room.users.Any(u => u.userId == winUserId))
return (false, "胜者不在房间中", (ChessGameRoom)null);
room.state = 3;
room.IsGameEnded = true;
_redisService.SetChessRoom(room);
if (!string.IsNullOrWhiteSpace(winUserId))
{
var loser = room.users.FirstOrDefault(u => u.userId != winUserId);
var winner = room.users.FirstOrDefault(u => u.userId == winUserId);
var winnerName = winner?.userName ?? "";
var loserName = loser?.userName ?? "";
var loserId = loser?.userId ?? "";
var winnerEntity = winner != null ? await _userService.GetUser(winner.userId) : null;
var loserEntity = loser != null ? await _userService.GetUser(loser.userId) : null;
var realLoserGold = loserEntity?.Assets.Gold ?? 0;
var actualBet = Math.Min(room.gold, realLoserGold);
if (actualBet > 0)
{
if (loserEntity != null)
{
var deductRemark = $"房间号({room.RoomId}) {winnerName}({winner?.userId})赢得对局 {loserName}({loserId})输 金额{actualBet}";
var deductResult = await _userService.UpdateAssets(new UserAssetParam()
{
ChangeType = UserAssetChangeTypeEnum.减少,
Gold = actualBet,
TotalRechargeGoldDelta = -actualBet,
Remark = deductRemark,
Source = RewardSource.象棋,
UserId = loserEntity.Id,
RoomInfo = room.RoomId,
OtherUserId = winUserId
});
if (!deductResult.isok)
{
room.state = 2;
room.IsGameEnded = false;
_redisService.SetChessRoom(room);
return (false, "结算失败:" + deductResult.msg, room);
}
loser.Gold = Math.Max(0, realLoserGold - actualBet);
}
if (winnerEntity != null)
{
var winRemark = $"房间号({room.RoomId}) {winnerName}({winner?.userId})赢得对局 {loserName}({loserId})输 金额{actualBet}";
var winResult = await _userService.UpdateAssets(new UserAssetParam()
{
ChangeType = UserAssetChangeTypeEnum.增加,
Gold = actualBet,
TotalRechargeGoldDelta = actualBet,
Remark = winRemark,
Source = RewardSource.象棋,
UserId = winnerEntity.Id,
RoomInfo = room.RoomId,
OtherUserId = loserId
});
if (!winResult.isok)
{
if (loserEntity != null)
{
await _userService.UpdateAssets(new UserAssetParam()
{
ChangeType = UserAssetChangeTypeEnum.增加,
Gold = actualBet,
TotalRechargeGoldDelta = actualBet,
Remark = $"房间号({room.RoomId}) 结算异常回退 {winnerName}({winner?.userId}) 金额{actualBet}",
Source = RewardSource.象棋,
UserId = loserEntity.Id,
RoomInfo = room.RoomId,
OtherUserId = winUserId
});
}
}
winner.Gold = winnerEntity.Assets.Gold + actualBet;
}
var newWinnerEntity = winnerEntity != null ? await _userService.GetUser(winnerEntity.Id) : null;
var newLoserEntity = loserEntity != null ? await _userService.GetUser(loserEntity.Id) : null;
if (winnerEntity != null)
{
var newWinnerGold = newWinnerEntity?.Assets.Gold ?? winnerEntity.Assets.Gold;
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
{
UserId = winnerEntity.Id,
CurrencyType = 1,
ChangeType = 1,
Amount = actualBet,
NewGold = newWinnerGold,
Source = "象棋对局胜利",
Remark = $"房间号({room.RoomId})赢得{actualBet}金币"
});
}
if (loserEntity != null)
{
var newLoserGold = newLoserEntity?.Assets.Gold ?? loser.Gold;
_capPublisher.Publish("BackgroundOperation.NotifyUser", new BackgroundOperationNotifyMessage
{
UserId = loserEntity.Id,
CurrencyType = 1,
ChangeType = 2,
Amount = actualBet,
NewGold = newLoserGold,
Source = "象棋对局失败",
Remark = $"房间号({room.RoomId})输掉{actualBet}金币"
});
}
}
}
var userIds = room.users?.Select(u => u.userId).Where(id => !string.IsNullOrWhiteSpace(id)).ToList()
?? new List<string>();
room.gold = 0;
room.gameType = 0;
room.robotCount = 0;
room.EndGameRequests?.Clear();
room.PendingWinUserId = null;
room.TimeoutLoserUserId = "";
room.LastActiveTime = 0;
room.users.Clear();
_redisService.SetChessRoom(room);
foreach (var userId in userIds)
{
try
{
await UpdateUserGameType(userId, 0);
}
catch (Exception ex)
{
_logger.LogError(ex, "[象棋] EndGame 清除游戏状态失败 userId={UserId}", userId);
}
}
return (true, "", room);
},
expiry: 30,
retry: false);
}
public (bool success, string message, ChessGameRoom room) RestartGame(string gameId, string userId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null)
{
return (false, "房间不存在", null);
}
if (room.state == 3 && room.IsGameEnded)
{
room.state = 1;
room.IsGameEnded = false;
room.gold = 0;
room.robotCount = 0;
room.EndGameRequests?.Clear();
room.PendingWinUserId = null;
room.TimeoutLoserUserId = "";
room.LastActiveTime = 0;
if (room.users == null) room.users = new List<RedisChessGameUser>();
var existingUser = room.users.FirstOrDefault(u => u.userId == userId);
if (existingUser == null)
{
room.users.Add(new RedisChessGameUser
{
userId = userId,
userName = "",
type = room.gameType,
IsLeave = false,
IsReady = false
});
}
else
{
existingUser.IsReady = false;
existingUser.IsLeave = false;
existingUser.DisconnectTime = 0;
}
_redisService.SetChessRoom(room);
return (true, "", room);
}
if (!room.IsGameEnded)
{
return (false, "对局尚未结束,无法重新开始", null);
}
var user = room.users.FirstOrDefault(u => u.userId == userId);
if (user == null)
{
return (false, "对局已结束,房间已释放,请重新创建房间", null);
}
room.state = 1;
room.IsGameEnded = false;
room.gold = 0;
room.EndGameRequests?.Clear();
room.PendingWinUserId = null;
room.TimeoutLoserUserId = "";
room.LastActiveTime = 0;
foreach (var u in room.users)
{
u.IsReady = false;
}
_redisService.SetChessRoom(room);
return (true, "", room);
}
public async Task<(bool success, string message, ChessGameRoom room, bool allConfirmed)> RequestEndGame(string gameId, string userId, string winUserId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state == 3)
{
return (false, "房间不存在", null, false);
}
if (room.state != 2)
{
return (false, "游戏未开始", null, false);
}
var user = room.users.FirstOrDefault(u => u.userId == userId);
if (user == null)
{
return (false, "参数异常", null, false);
}
if (!string.IsNullOrWhiteSpace(winUserId) && !room.users.Any(u => u.userId == winUserId))
return (false, "胜者不在房间中", null, false);
if (room.EndGameRequests == null)
room.EndGameRequests = new Dictionary<string, long>();
room.EndGameRequests[userId] = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (!string.IsNullOrEmpty(winUserId) && string.IsNullOrEmpty(room.PendingWinUserId))
{
room.PendingWinUserId = winUserId;
}
_redisService.SetChessRoom(room);
var roomUserIds = room.users.Select(u => u.userId).ToHashSet();
var confirmedCount = room.EndGameRequests.Keys.Count(roomUserIds.Contains);
if (confirmedCount >= room.users.Count && room.users.Count >= 2)
{
string finalWinUserId = room.PendingWinUserId ?? "";
room.EndGameRequests.Clear();
room.PendingWinUserId = null;
_redisService.SetChessRoom(room);
var result = await EndGame(gameId, finalWinUserId);
return (result.success, result.message, result.room, true);
}
return (true, "等待对方确认结束", room, false);
}
public async Task<(bool isTimeout, string loserUserId)> CheckRoomTimeout(string gameId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state != 2)
{
return (false, "");
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var timeout = room.TimeoutSeconds > 0 ? room.TimeoutSeconds : 60;
foreach (var user in room.users)
{
if (user.IsLeave && user.DisconnectTime > 0 && (now - user.DisconnectTime) > timeout)
{
var winUserId = room.users.FirstOrDefault(u => u.userId != user.userId)?.userId ?? "";
room.TimeoutLoserUserId = user.userId;
_redisService.SetChessRoom(room);
await EndGame(gameId, winUserId);
return (true, user.userId);
}
}
return (false, "");
}
public async Task<(bool isTimeout, string loserUserId)> CheckRoomAfkTimeout(string gameId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state != 2 || room.users.Count < 2)
{
return (false, "");
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var afkUsers = room.users.Where(u => u.IsAfk && u.AfkTime > 0).OrderBy(u => u.AfkTime).ToList();
if (afkUsers.Count == 2)
{
var firstAfkUser = afkUsers[0];
var winUserId = room.users.FirstOrDefault(u => u.userId != firstAfkUser.userId)?.userId ?? "";
await EndGame(gameId, winUserId);
return (true, firstAfkUser.userId);
}
if (afkUsers.Count == 1)
{
var afkUser = afkUsers[0];
var timeout = room.AfkTimeoutSeconds > 0 ? room.AfkTimeoutSeconds : 120;
if ((now - afkUser.AfkTime) > timeout)
{
var winUserId = room.users.FirstOrDefault(u => u.userId != afkUser.userId)?.userId ?? "";
await EndGame(gameId, winUserId);
return (true, afkUser.userId);
}
}
return (false, "");
}
public async Task UpdateUserActiveTime(string gameId, string userId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state != 2)
{
return;
}
var user = room.users.FirstOrDefault(u => u.userId == userId);
if (user == null)
{
return;
}
room.LastActiveTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
_redisService.SetChessRoom(room);
}
public async Task SetPlayerAfk(string gameId, string userId)
{
var room = string.IsNullOrWhiteSpace(gameId) ? null : _redisService.GetChessRoom(gameId);
var user = room?.users?.FirstOrDefault(u => u.userId == userId);
if (user == null)
{
return;
}
user.IsAfk = true;
user.AfkTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
_redisService.SetChessRoom(room);
await Task.CompletedTask;
}
public async Task CancelPlayerAfk(string gameId, string userId)
{
var room = string.IsNullOrWhiteSpace(gameId) ? null : _redisService.GetChessRoom(gameId);
var user = room?.users?.FirstOrDefault(u => u.userId == userId);
if (user == null)
{
return;
}
user.IsAfk = false;
user.AfkTime = 0;
_redisService.SetChessRoom(room);
await Task.CompletedTask;
}
public bool ExitRoom(ChessGameRoom room)
{
room.users = new List<RedisChessGameUser>();
room.state = 3;
room.password = "";
room.createUserId = "";
room.gold = 0;
room.gameType = 0;
room.robotCount = 0;
room.IsGameEnded = false;
room.EndGameRequests?.Clear();
room.PendingWinUserId = null;
room.TimeoutLoserUserId = "";
room.LastActiveTime = 0;
_redisService.SetChessRoom(room);
return true;
}
public (bool isok, string msg, ChessGameRoom room) ExitRoom(string gameId, string userId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null)
{
return (false, "房间不存在", null);
}
if (room.state == 3 && (room.users == null || room.users.Count == 0))
{
ExitRoom(room);
return (true, "房间已清空", room);
}
var user = room.users.FirstOrDefault(c => c.userId == userId);
if (user == null)
{
return (false, "玩家不存在", null);
}
if (room.state == 2)
{
user.IsLeave = true;
user.DisconnectTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
user.Cid = string.Empty;
if (room.users.All(u => u.IsLeave))
{
ExitRoom(room);
return (true, "所有玩家已退出,房间已重置", room);
}
_redisService.SetChessRoom(room);
return (true, "玩家已退出,等待其他玩家", room);
}
if (room.createUserId == userId)
{
ExitRoom(room);
return (true, "房主退出,房间已清空", room);
}
room.users.Remove(user);
if (room.users.Count == 0 || (room.state == 3 && room.users.Count <= 1))
{
ExitRoom(room);
return (true, "房间已清空", room);
}
room.gold = 0;
room.robotCount = Math.Clamp(room.robotCount, 0, Math.Max(0, 2 - room.users.Count));
_redisService.SetChessRoom(room);
return (true, "", room);
}
public (bool isok, string msg, ChessGameRoom room) AdminKickPlayer(string gameId, string userId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null)
return (false, "房间不存在", null);
if (room.state == 2)
return (false, "游戏进行中不能直接踢人,请先结束对局", room);
var user = room.users?.FirstOrDefault(c => c.userId == userId);
if (user == null)
return (false, "玩家不存在", room);
room.users.Remove(user);
if (room.users.Count == 0)
{
ExitRoom(room);
return (true, "玩家已踢出,房间已重置", room);
}
if (room.createUserId == userId)
room.createUserId = room.users[0].userId;
room.state = 1;
room.gold = 0;
room.IsGameEnded = false;
room.EndGameRequests?.Clear();
room.PendingWinUserId = null;
room.TimeoutLoserUserId = string.Empty;
room.LastActiveTime = 0;
room.robotCount = Math.Clamp(room.robotCount, 0, Math.Max(0, 2 - room.users.Count));
foreach (var remainingUser in room.users)
remainingUser.IsReady = false;
_redisService.SetChessRoom(room);
return (true, "玩家已踢出", room);
}
public async Task UpdateUserGameType(string userId, int gameType, string roomNumber = "")
{
try
{
var user = await _userService.GetUser(userId);
if (user != null)
{
user.Static.CurrentGameType = gameType;
user.Static.RoomNumber = gameType > 0 ? (roomNumber ?? string.Empty) : string.Empty;
await _userService.UpdateUser(user);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "更新用户当前游戏类型失败 userId={UserId} gameType={GameType} roomNumber={RoomNumber}", userId, gameType, roomNumber);
}
}
public (bool success, string message, ChessGameRoom room) GoldConfig(string gameId, long gold, string userId)
{
var room = _redisService.GetChessRoom(gameId);
if (room == null || room.state == 3)
{
return (false, "房间不存在", null);
}
var gameconfig= _redisService.GetGameConfig();
if (gameconfig==null)
{
return (false, "游戏配置不存在", null);
}
if(gold<gameconfig.ChessConfig.MinBetGold)
return (false, $"金币不能小于{gameconfig.ChessConfig.MinBetGold}", null);
if (!room.users.Any(c => c.type == 2 && c.userId == userId))
return (false, "只有红方才可以配置金币", null);
var redPlayer = room.users.FirstOrDefault(c => c.type == 2);
if (redPlayer != null && redPlayer.Gold < gold)
return (false, "金币不足", null);
room.gold = gold;
_redisService.SetChessRoom(room);
return (true, "", room);
}
public ChessGameRoom GetUserRoom(string cid)
{
var rooms = _redisService.GetAllChessRooms();
if (rooms == null) return null;
return rooms.FirstOrDefault(c => c.users.Any(d => d.Cid == cid && (c.state == 1 || c.state == 2)));
}
public ChessGameRoom GetRoom(string gameId)
{
return _redisService.GetChessRoom(gameId);
}
public List<ChessGameRoom> GetAllPlayingRooms()
{
var allRooms = _redisService.GetAllChessRooms();
if (allRooms == null)
return new List<ChessGameRoom>();
return allRooms.Where(r => r != null && r.state == 2).ToList();
}
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.2.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\RedisService\RedisService.csproj" />
<ProjectReference Include="..\..\UserSrc\UserService\UserService.csproj" />
<ProjectReference Include="..\ChessGameDomain\ChessGameDomain.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ChessGameDomain;
using ChessGameService.Param;
namespace ChessGameService
{
public interface IChessGameService
{
Task<(bool isok, string msg, ChessGameRoom room)> CreateRoomAsync(CreateRoomParam param, string cid);
Task<(bool success, string message, ChessGameRoom room)> JoinGameAsync(string gameId, string pwd, string userId, string cid);
List<ChessGameRoom> GetRoomList(int partition, int maxRooms = 100);
(bool success, string message, ChessGameRoom room) StartGame(string gameId, string userId);
(bool success, string message, bool allReady) GameReady(string gameId, string userId);
Task<(bool success, string message, ChessGameRoom room)> EndGame(string gameId, string winUserId);
(bool success, string message, ChessGameRoom room) RestartGame(string gameId, string userId);
Task<(bool success, string message, ChessGameRoom room, bool allConfirmed)> RequestEndGame(string gameId, string userId, string endRequestId);
Task<(bool isTimeout, string loserUserId)> CheckRoomTimeout(string gameId);
Task<(bool isTimeout, string loserUserId)> CheckRoomAfkTimeout(string gameId);
Task UpdateUserActiveTime(string gameId, string userId);
Task SetPlayerAfk(string gameId, string userId);
Task CancelPlayerAfk(string gameId, string userId);
bool ExitRoom(ChessGameRoom room);
(bool isok, string msg, ChessGameRoom room) ExitRoom(string gameId, string userId);
(bool isok, string msg, ChessGameRoom room) AdminKickPlayer(string gameId, string userId);
(bool success, string message, ChessGameRoom room) GoldConfig(string gameId, long gold, string userId);
ChessGameRoom GetUserRoom(string cid);
ChessGameRoom GetRoom(string gameId);
List<ChessGameRoom> GetAllPlayingRooms();
Task UpdateUserGameType(string userId, int gameType, string roomNumber = "");
}
}
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChessGameService.Param
{
public class CreateRoomParam
{
public string roomId { get; set; }
public string userId { get; set; }
/// <summary>
public int gameType { get; set; }
public string password { get; set; }
public long gold { get; set; } = 0;
}
}
@@ -0,0 +1,66 @@
using CommonDomain;
using DotNetCore.CAP;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
namespace ChessGameService.Subscribers;
public sealed class AdminKickChessPlayerSubscriber : ICapSubscribe
{
private readonly IChessGameService _chessService;
private readonly IHubContext<ChessGameHub> _hubContext;
public AdminKickChessPlayerSubscriber(
IChessGameService chessService,
IHubContext<ChessGameHub> hubContext)
{
_chessService = chessService;
_hubContext = hubContext;
}
[CapSubscribe(TopicKeys.后台踢出象棋玩家)]
public async Task Handle(AdminKickChessPlayerMessage message)
{
if (string.IsNullOrWhiteSpace(message.RoomId) || string.IsNullOrWhiteSpace(message.UserId))
{
return;
}
var room = _chessService.GetRoom(message.RoomId);
var target = room?.users?.FirstOrDefault(u => u.userId == message.UserId);
if (room == null || target == null)
{
return;
}
if (room.state == 2)
{
return;
}
var targetCid = target.Cid;
var (success, resultMessage, updatedRoom) = _chessService.AdminKickPlayer(message.RoomId, message.UserId);
if (!success || updatedRoom == null)
{
return;
}
await _chessService.UpdateUserGameType(message.UserId, 0);
if (!string.IsNullOrWhiteSpace(targetCid))
{
await _hubContext.Clients.Client(targetCid).SendAsync("KickedFromRoom", new
{
gameId = message.RoomId,
userId = message.UserId,
reason = string.IsNullOrWhiteSpace(message.Reason) ? "被管理员移出房间" : message.Reason,
source = "admin"
});
await _hubContext.Groups.RemoveFromGroupAsync(targetCid, message.RoomId);
await _hubContext.Groups.AddToGroupAsync(targetCid, ChessGameHub.LobbyGroup);
}
await _hubContext.Clients.Group(message.RoomId).SendAsync("RoomStateNotice", updatedRoom);
await _hubContext.Clients.Group(message.RoomId).SendAsync("BeginGame", updatedRoom);
await _hubContext.Clients.Group(ChessGameHub.LobbyGroup).SendAsync("ExitRoomNotice", updatedRoom);
}
}
@@ -0,0 +1,83 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using ChessGameService;
using CommonDomain;
using DotNetCore.CAP;
using Microsoft.Extensions.Logging;
using RedisService;
namespace ChessGameService.Subscribers
{
public class ChessLoginSubscriber : ICapSubscribe
{
private readonly IChessGameService _chessService;
private readonly IRedisService _redisService;
private readonly ICapPublisher _capPublisher;
private readonly ILogger<ChessLoginSubscriber> _logger;
public ChessLoginSubscriber(
IChessGameService chessService,
IRedisService redisService,
ICapPublisher capPublisher,
ILogger<ChessLoginSubscriber> logger)
{
_chessService = chessService;
_redisService = redisService;
_capPublisher = capPublisher;
_logger = logger;
}
[CapSubscribe(TopicKeys.登录绑定)]
public async Task HandleLoginBind(LoginBindMessage msg)
{
if (string.IsNullOrWhiteSpace(msg.UserId))
return;
var rooms = _redisService.GetAllChessRooms();
if (rooms == null)
return;
foreach (var room in rooms)
{
if (room == null || room.state != 2 || room.IsGameEnded)
continue;
var player = room.users?.FirstOrDefault(u => u.userId == msg.UserId);
if (player == null)
continue;
var winnerUserId = room.users.FirstOrDefault(u => u.userId != msg.UserId)?.userId ?? "";
if (string.IsNullOrWhiteSpace(winnerUserId))
{
continue;
}
try
{
await _chessService.EndGame(room.RoomId, winnerUserId);
await _capPublisher.PublishAsync(TopicKeys.登录结果通知, new LoginBindResultMessage
{
UserId = msg.UserId,
RoomId = room.RoomId,
OperationId = msg.OperationId,
Success = true
});
}
catch (Exception ex)
{
_logger.LogError(ex, "[象棋] 登录结算失败 userId={UserId} roomId={RoomId}", msg.UserId, room.RoomId);
await _capPublisher.PublishAsync(TopicKeys.登录结果通知, new LoginBindResultMessage
{
UserId = msg.UserId,
RoomId = room.RoomId,
OperationId = msg.OperationId,
Success = false,
Error = ex.Message
});
}
return;
}
}
}
}