Files
BuYuHouDuan/FishGameSrc/FishGameService/Hubs/FishGameHub.cs
T
2026-10-12 16:22:04 +08:00

434 lines
16 KiB
C#

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<FishGameHub> _logger;
private readonly IHubContext<LoginHub> _hubLoginContext;
public FishGameHub(IFishingGameService fishingService, IFishingSendService send, IRedisService redisService, IUserService userService, ILogger<FishGameHub> logger, IHubContext<LoginHub> 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<int> 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<long> UseTorpedo(string roomId, string userId, string propNumber)
{
string cid = Context.ConnectionId;
return await _fishingService.UseTorpedo(cid, roomId, userId, propNumber);
}
public async Task<object> 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<object> KillFish(string roomId, string userId, string fishNumber, string fihsId, long gold, long timespan, string bulletId, long timestamp, string sign)
{
string cid = Context.ConnectionId;
var currentGold = await _fishingService.KillFish(cid, roomId, userId, fishNumber, fihsId, gold, timespan, bulletId, timestamp, sign);
return new { gold = currentGold, 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<List<string>> 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);
}
}
}