using CommonDomain; using FishGameService.GameSend; using FishGameService.GameService; using System.Text.Json; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using RedisService; using RedisService.Param; using UserService.Hubs; using UserService.User; namespace FishGameService.Hubs { public class FishGameHub : Hub { private readonly IFishingGameService _fishingService; private readonly IFishingSendService _send; private readonly IRedisService _redisService; private readonly IUserService _userService; private readonly ILogger _logger; private readonly IHubContext _hubLoginContext; public FishGameHub(IFishingGameService fishingService, IFishingSendService send, IRedisService redisService, IUserService userService, ILogger logger, IHubContext hubLoginContext) { _fishingService = fishingService; _send = send; _redisService = redisService; _userService = userService; _logger = logger; _hubLoginContext = hubLoginContext; } public override async Task OnConnectedAsync() { try { await base.OnConnectedAsync(); } catch (Exception ex) { _logger.LogError(ex, "FishHub建立连接异常 cid={Cid}", Context.ConnectionId); } } public override async Task OnDisconnectedAsync(Exception? exception) { var cid = Context.ConnectionId; var userId = _redisService.GetUserId(cid); if (exception != null) { _logger.LogWarning(exception, "FishHub异常断开 cid={Cid} userId={UserId} reason={Reason}", cid, userId, exception.Message); } try { await _fishingService.HandleDisconnect(Context.ConnectionId); } catch (Exception ex) { _logger.LogError(ex, "FishHub断线处理失败 cid={Cid} userId={UserId}", cid, userId); } await base.OnDisconnectedAsync(exception); } public async Task Reconnect(string userId) { try { await ReconnectInternal(userId); } catch (Exception ex) { _logger.LogError(ex, "FishHub重连异常 cid={Cid} userId={UserId}", Context.ConnectionId, userId); throw; } } private async Task ReconnectInternal(string userId) { var cid = Context.ConnectionId; if (string.IsNullOrWhiteSpace(userId)) { _send.ErrorMessage(cid, ErrorCodes.用户不存在, "缺少 userId,无法重连"); return; } var userRedis = _redisService.GetRedisUserCId(userId); if (userRedis == null) { userRedis = new RedisUserCid { UserId = userId, CId = cid }; _redisService.SetRedisUserCId(userRedis); _redisService.SetUserId(cid, userId); _send.BindSuccess(cid); return; } var oldCid = userRedis.CId; if (!string.IsNullOrWhiteSpace(oldCid) && oldCid != cid) { if (!string.IsNullOrWhiteSpace(userRedis.RoomId)) { try { _send.RoomLeaveNotice(userRedis.RoomId, userId); } catch (Exception ex) { _logger.LogError(ex, "发送RoomLeaveNotice失败 userId={UserId} roomId={RoomId}", userId, userRedis.RoomId); } } _redisService.RemoveUserId(oldCid); try { if (!string.IsNullOrWhiteSpace(userRedis.RoomId)) await Groups.RemoveFromGroupAsync(oldCid, userRedis.RoomId); } catch (Exception ex) { _logger.LogWarning(ex, "FishHub重连移除旧连接房间组失败 oldCid={OldCid} roomId={RoomId}", oldCid, userRedis.RoomId); } } userRedis.CId = cid; _redisService.SetRedisUserCId(userRedis); _redisService.SetUserId(cid, userId); if (!string.IsNullOrWhiteSpace(userRedis.RoomId)) { var room = _redisService.GetGameRoom(userRedis.RoomId); if (room != null) { var player = room.Players.FirstOrDefault(p => p.UserId == userId); if (player != null) { await _fishingService.CancelPendingDisconnect(userId); player.CId = cid; player.State = 0; bool ownerRestored = false; if (!string.IsNullOrWhiteSpace(userId) && room.CreateUserId != userId) { var currentOwner = room.Players.FirstOrDefault(p => p.UserId == room.CreateUserId && !p.IsBot); if (string.IsNullOrWhiteSpace(room.CreateUserId) || currentOwner == null || currentOwner.State != 0) { room.CreateUserId = userId; ownerRestored = true; } } _redisService.SetGameRoom(room); await Groups.AddToGroupAsync(cid, room.RoomId); var activeFishes = _fishingService.GetActiveFishes(room.RoomId); await _send.SendRoomInfo(cid, room, activeFishes); _send.RoomJoinNotice(room.RoomId, player); await Clients.Caller.SendAsync("OnPlayerInfo", new { userId = player.UserId, userName = player.UserName, gold = player.Gold, diamond = player.Diamond, viplvl = player.VipLevel, cannonMultiple = player.CannonMultiple, cannonId = player.CannonId, wingsId = player.WingsId, position = player.Position }); await Clients.Group(room.RoomId).SendAsync("OnSeatChange", new { type = "join", playerId = player.Position + 1, position = player.Position, playerName = player.UserName, nickname = player.UserName, gold = player.Gold, diamond = player.Diamond, vipLevel = player.VipLevel, turretId = 1, turretRate = 1, turretLv = 1, headId = 1, roomRate = 1, bulletPower = 1 }); if (ownerRestored) _send.ChangeControllerUser(room.RoomId, room.CreateUserId); await _fishingService.SendRoomChange(room); await SendActiveFishSync(room.RoomId); try { var user = await _userService.GetUser(userId); if (user != null) { user.Static.RoomNumber = room.RoomId; user.Static.CurrentGameType = (int)room.GameType; await _userService.UpdateUser(user); } } catch (Exception ex) { _logger.LogWarning(ex, "FishHub重连后更新用户状态失败 userId={UserId} roomId={RoomId}", userId, room.RoomId); } try { await SendActiveFishSync(room.RoomId); } catch (Exception ex) { _logger.LogWarning(ex, "FishHub重连同服务端步活跃鱼失败 userId={UserId} roomId={RoomId}", userId, room.RoomId); } return; } } } _send.BindSuccess(cid); } public async Task JoinRoom(string roomId, string userId, int posituin) { string cid = Context.ConnectionId; try { var boundUserId = _redisService.GetUserId(cid); if (!string.IsNullOrWhiteSpace(boundUserId) && boundUserId != userId) { await Clients.Caller.SendAsync("KickToMainPage", new { userId }); return; } await _fishingService.JoinRoom(cid, roomId, userId, posituin); } catch (Exception ex) { _logger.LogError(ex, "FishHub加入房间异常 cid={Cid} roomId={RoomId} userId={UserId} position={Position}", cid, roomId, userId, posituin); _send.ErrorMessage(cid, ErrorCodes.未知错误, $"加入房间失败: {ex.Message}"); } } public async Task GetRoomInfo(string roomId) { string cid = Context.ConnectionId; if (!IsConnectionInRoom(cid, roomId)) return; var room = _redisService.GetGameRoom(roomId); await Clients.Caller.SendAsync("GetRoomInfoResult", room); } public async Task SyncGameData(string roomId, object data) { _send.SyncGameData(roomId, data); } private bool IsConnectionInRoom(string cid, string roomId) { if (string.IsNullOrWhiteSpace(cid) || string.IsNullOrWhiteSpace(roomId)) return false; var userId = _redisService.GetUserId(cid); if (string.IsNullOrWhiteSpace(userId)) return false; var userRedis = _redisService.GetRedisUserCId(userId); return userRedis != null && userRedis.CId == cid && userRedis.RoomId == roomId; } public async Task UseProp(string roomId, string userId, string propNumber, int diamond) { string cid = Context.ConnectionId; return await _fishingService.UseProp(cid, roomId, userId, propNumber, diamond); } public async Task UseTorpedo(string roomId, string userId, string propNumber) { string cid = Context.ConnectionId; return await _fishingService.UseTorpedo(cid, roomId, userId, propNumber); } public async Task FireCannon(string roomId, string userId, long gold, int count, long timestamp, string sign, object extra = null) { string cid = Context.ConnectionId; var result = extra is string extraSign && (sign == null || sign.Length != 8) ? await _fishingService.FireCannon(cid, roomId, userId, gold, count, timestamp, extraSign, null) : await _fishingService.FireCannon(cid, roomId, userId, gold, count, timestamp, sign, extra); return new { bullets = result.bullets.Select(bulletId => new { bulletId, gold }).ToList(), gold = result.gold, timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }; } public async Task KillFish(string roomId, string userId, string fishNumber, string fihsId, long gold, long timespan, string bulletId, long timestamp, string sign) { string cid = Context.ConnectionId; var result = await _fishingService.KillFish(cid, roomId, userId, fishNumber, fihsId, gold, timespan, bulletId, timestamp, sign); return new { gold = result.gold, gainExp = result.gainExp, exp = result.exp, level = result.level, expMax = result.expMax, timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }; } public async Task LeaveRoom(string roomId, string userId) { string cid = Context.ConnectionId; try { await _fishingService.LeaveRoom(cid, roomId, userId); if (IsConnectionInRoom(cid, roomId)) { return; } } catch (Exception ex) { _logger.LogError(ex, "FishHub离开房间异常 cid={Cid} roomId={RoomId} userId={UserId}", cid, roomId, userId); throw; } } public async Task ChangeGunMultiple(string roomId, string userId, int multiple) { string cid = Context.ConnectionId; await _fishingService.ChangeGunMultiple(cid, roomId, userId, multiple); } public async Task GetPlayerInfo(string roomId, string userId) { string cid = Context.ConnectionId; await _fishingService.GetPlayerInfo(cid, roomId, userId); } public async Task SetPlayerAfk(string roomId, string userId) { string cid = Context.ConnectionId; await _fishingService.SetPlayerAfk(cid, roomId, userId); } public async Task CancelPlayerAfk(string roomId, string userId) { string cid = Context.ConnectionId; await _fishingService.CancelPlayerAfk(cid, roomId, userId); try { var room = _redisService.GetGameRoom(roomId); if (room != null) { var activeFishes = _fishingService.GetActiveFishes(roomId); await _send.SendRoomInfo(cid, room, activeFishes); } } catch (Exception ex) { _logger.LogWarning(ex, "CancelPlayerAfk 同步房间信息失败 userId={UserId} roomId={RoomId}", userId, roomId); } } private async Task SendActiveFishSync(string roomId) { var activeFishes = _fishingService.GetActiveFishes(roomId); if (activeFishes == null || activeFishes.Count == 0) return; var now = DateTimeOffset.UtcNow; var syncFish = activeFishes.Select(f => new { fishId = f.FishId, fishIds = new[] { f.FishId }, fishNumber = string.IsNullOrWhiteSpace(f.FishArrayNumber) ? f.FishNumber : string.Empty, routeId = f.ClientRouteId, fishArrayNumber = f.FishArrayNumber ?? string.Empty, createdAt = new DateTimeOffset(f.CreatedAt).ToUnixTimeMilliseconds(), createdTime = f.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss.fff"), expireAt = new DateTimeOffset(f.ExpireAt).ToUnixTimeMilliseconds(), expireTime = f.ExpireAt.ToString("yyyy-MM-dd HH:mm:ss.fff"), remainingSeconds = (int)Math.Max(0, (new DateTimeOffset(f.ExpireAt) - now).TotalSeconds) }).ToList(); var payload = new { type = "sync", timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), fish = syncFish }; await Clients.Caller.SendAsync("OnFishSpawn", payload); } public async Task> BotFireCannon(string roomId, string botUserId, long gold, int count) { string cid = Context.ConnectionId; return await _fishingService.BotFireCannon(cid, roomId, botUserId, gold, count); } public async Task BotKillFish(string roomId, string botUserId, string fishNumber, string fishId, long gold, string bulletId) { string cid = Context.ConnectionId; await _fishingService.BotKillFish(cid, roomId, botUserId, fishNumber, fishId, gold, bulletId); } } }