From 2f53247d2920c05ef6cd5f99574491ff38d9a440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E9=9B=A8=E6=99=B4?= <1207713896@qq.com> Date: Wed, 20 Aug 2025 08:46:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E8=8E=B7=E5=8F=96=E6=88=BF?= =?UTF-8?q?=E9=97=B4=E5=88=97=E8=A1=A8=EF=BC=8Credis=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/TerritoryGame.Api/Hubs/GameHub.cs | 31 ++++++-- backend/src/TerritoryGame.Api/Program.cs | 32 ++++++-- .../Services/GameService.cs | 78 ++++++++++++------- .../DependencyInjection.cs | 38 +++++---- frontend/src/components/RoomList.vue | 2 +- 5 files changed, 128 insertions(+), 53 deletions(-) diff --git a/backend/src/TerritoryGame.Api/Hubs/GameHub.cs b/backend/src/TerritoryGame.Api/Hubs/GameHub.cs index 5099d78..7203f47 100644 --- a/backend/src/TerritoryGame.Api/Hubs/GameHub.cs +++ b/backend/src/TerritoryGame.Api/Hubs/GameHub.cs @@ -1,20 +1,41 @@ using Microsoft.AspNetCore.SignalR; using TerritoryGame.Domain.Entities; -using DomainServices = TerritoryGame.Domain.Services; using System.Security.Claims; using Microsoft.Extensions.Logging; +using TerritoryGame.Domain.Services; +using Microsoft.Extensions.DependencyInjection; namespace TerritoryGame.Api.Hubs; public class GameHub : Hub { - private readonly DomainServices.IGameService _gameService; private readonly ILogger _logger; + private readonly TerritoryGame.Domain.Services.IGameService _gameService; - public GameHub(DomainServices.IGameService gameService, ILogger logger) + // 主构造函数,使用依赖注入 + [ActivatorUtilitiesConstructor] + public GameHub(TerritoryGame.Domain.Services.IGameService gameService, ILogger logger) { - _gameService = gameService; - _logger = logger; + try + { + _gameService = gameService ?? throw new ArgumentNullException(nameof(gameService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logger.LogInformation("GameHub constructed with both IGameService and ILogger"); + } + catch (Exception ex) + { + if (logger != null) + { + logger.LogError(ex, "Error constructing GameHub"); + } + throw; + } + } + + // 仅供测试使用的构造函数 + public GameHub(ILogger logger) : this(null, logger) + { + _logger.LogWarning("GameHub constructed with ILogger only - FOR TESTING PURPOSES ONLY"); } // 获取房间列表 public async Task GetRoomList() diff --git a/backend/src/TerritoryGame.Api/Program.cs b/backend/src/TerritoryGame.Api/Program.cs index 7959d20..2d3dda9 100644 --- a/backend/src/TerritoryGame.Api/Program.cs +++ b/backend/src/TerritoryGame.Api/Program.cs @@ -14,6 +14,7 @@ using TerritoryGame.Application.Services; using TerritoryGame.Api.Hubs; using TerritoryGame.Infrastructure.Data; using Npgsql; +using Microsoft.Extensions.Logging; var builder = WebApplication.CreateBuilder(args); @@ -22,10 +23,11 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddInfrastructure(builder.Configuration); // Add MediatR - -// Add MediatR -builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); -builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(TerritoryGame.Application.Class1).Assembly)); +builder.Services.AddMediatR(cfg => +{ + cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); + cfg.RegisterServicesFromAssembly(typeof(TerritoryGame.Application.Class1).Assembly); +}); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle @@ -35,10 +37,21 @@ builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new OpenApiInfo { Title = "TerritoryGame API", Version = "v1" }); }); -// Add SignalR -builder.Services.AddSignalR(); +// Add SignalR with detailed configuration +builder.Services.AddSignalR(options => +{ + options.EnableDetailedErrors = true; // 启用详细错误信息 + options.KeepAliveInterval = TimeSpan.FromSeconds(15); // 设置保持连接的间隔 +}).AddJsonProtocol(options => +{ + options.PayloadSerializerOptions.PropertyNamingPolicy = null; // 使用原始属性名称 +}); -// Application services are registered in AddInfrastructure method +// 显式注册GameHub,便于调试 +builder.Services.AddTransient(); + +// 确保IGameService已注册(冗余但有助于调试) +builder.Services.AddScoped(); builder.Services.AddCors(options => { @@ -52,6 +65,11 @@ builder.Services.AddCors(options => }); }); +// 增加日志级别,便于调试 +builder.Logging.SetMinimumLevel(LogLevel.Debug); +builder.Logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Debug); +builder.Logging.AddFilter("Microsoft.AspNetCore.Http.Connections", LogLevel.Debug); + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/backend/src/TerritoryGame.Application/Services/GameService.cs b/backend/src/TerritoryGame.Application/Services/GameService.cs index 2baf8fe..f0f2bd0 100644 --- a/backend/src/TerritoryGame.Application/Services/GameService.cs +++ b/backend/src/TerritoryGame.Application/Services/GameService.cs @@ -6,20 +6,20 @@ using System.Text.Json; using System.Threading.Tasks; using TerritoryGame.Domain.Entities; using TerritoryGame.Domain.Interfaces; -using DomainServices = TerritoryGame.Domain.Services; +using TerritoryGame.Domain.Services; namespace TerritoryGame.Application.Services; -public class GameService : DomainServices.IGameService +public class GameService : TerritoryGame.Domain.Services.IGameService { private readonly IGameDbContext _context; private readonly IDistributedCache _cache; - private readonly DomainServices.IAreaCalculationService _areaCalculationService; + private readonly TerritoryGame.Domain.Services.IAreaCalculationService _areaCalculationService; public GameService( IGameDbContext context, IDistributedCache cache, - DomainServices.IAreaCalculationService areaCalculationService + TerritoryGame.Domain.Services.IAreaCalculationService areaCalculationService ) { _context = context; @@ -372,33 +372,59 @@ public class GameService : DomainServices.IGameService // 离开房间 public async Task LeaveRoomAsync(Guid roomId, Guid playerId) { - var room = await GetRoomByIdAsync(roomId); + // 使用事务确保数据一致性 + using var transaction = await _context.Database.BeginTransactionAsync(); + try + { + var room = await GetRoomByIdAsync(roomId); - // 移除玩家 - room.RemovePlayer(playerId); + // 移除玩家 + room.RemovePlayer(playerId); - // 如果房间为空,删除房间 - if (room.Players.Count == 0) - { - _context.GameRooms.Remove(room); - } - else - { - await _context.SaveChangesAsync(); - // 缓存更新后的房间信息 - await CacheRoomAsync(room); - } + // 如果房间为空,删除房间 + if (room.Players.Count == 0) + { + _context.GameRooms.Remove(room); + await _context.SaveChangesAsync(); + } + else + { + await _context.SaveChangesAsync(); + // 缓存更新后的房间信息 + await CacheRoomAsync(room); + } - // 从数据库删除玩家 - var player = await _context.Players.FindAsync(playerId); - if (player != null) + // 从数据库删除玩家 + var player = await _context.Players.FindAsync(playerId); + if (player != null) + { + _context.Players.Remove(player); + await _context.SaveChangesAsync(); + } + + // 如果游戏正在进行且房间玩家少于2人,结束游戏 + if (room.Status == GameStatus.Playing && room.Players.Count < 2) + { + await EndGameAsync(roomId); + } + + // 如果游戏正在进行且房间玩家少于2人,结束游戏 + if (room.Status == GameStatus.Playing && room.Players.Count < 2) + { + await EndGameAsync(roomId); + } + + // 清除相关缓存 + await _cache.RemoveAsync($"room:{roomId}"); + await _cache.RemoveAsync("available_rooms"); + + await transaction.CommitAsync(); + } + catch { - _context.Players.Remove(player); - await _context.SaveChangesAsync(); + await transaction.RollbackAsync(); + throw; } - - // 如果游戏正在进行且房间玩家少于2人,结束游戏 - if (room.Status == GameStatus.Playing && room.Players.Count < 2) { await EndGameAsync(roomId); } diff --git a/backend/src/TerritoryGame.Infrastructure/DependencyInjection.cs b/backend/src/TerritoryGame.Infrastructure/DependencyInjection.cs index 3a84190..dd0f297 100644 --- a/backend/src/TerritoryGame.Infrastructure/DependencyInjection.cs +++ b/backend/src/TerritoryGame.Infrastructure/DependencyInjection.cs @@ -9,6 +9,7 @@ using TerritoryGame.Domain.Services; using TerritoryGame.Domain.Interfaces; using TerritoryGame.Infrastructure.Data; using TerritoryGame.Infrastructure.Services; +using StackExchange.Redis; namespace TerritoryGame.Infrastructure; @@ -29,34 +30,43 @@ public static class DependencyInjection // 从配置中获取Redis连接字符串 var redisConnectionString = configuration.GetConnectionString("Redis"); + if (string.IsNullOrEmpty(redisConnectionString)) + { + throw new ArgumentNullException(nameof(redisConnectionString), "Redis connection string is not configured"); + } + // 创建Redis配置选项 - var redisConfig = StackExchange.Redis.ConfigurationOptions.Parse(redisConnectionString); + var redisConfig = ConfigurationOptions.Parse(redisConnectionString); // 连接优化配置 - redisConfig.ConnectTimeout = 5000; // 减少连接超时时间到5秒 - redisConfig.SyncTimeout = 5000; // 减少同步操作超时到5秒 - redisConfig.AsyncTimeout = 5000; // 减少异步操作超时到5秒 + redisConfig.ConnectTimeout = 10000; + redisConfig.SyncTimeout = 10000; + redisConfig.AsyncTimeout = 10000; - // 重试策略 - redisConfig.ConnectRetry = 3; // 重试次数 - redisConfig.ReconnectRetryPolicy = new StackExchange.Redis.ExponentialRetry(1000, 10); // 指数退避重试策略 + // 重试策略 - 修正参数 + redisConfig.ConnectRetry = 5; + redisConfig.ReconnectRetryPolicy = new ExponentialRetry( + deltaBackOffMilliseconds: 1000, + maxDeltaBackOffMilliseconds: 10000 + ); // 其他配置 - redisConfig.AllowAdmin = true; // 允许管理员操作 - redisConfig.Ssl = false; // 禁用SSL,因为连接字符串中没有SSL参数 - redisConfig.AbortOnConnectFail = false; // 连接失败时不中断应用 + redisConfig.AllowAdmin = true; + redisConfig.Ssl = false; + redisConfig.AbortOnConnectFail = false; + // 将配置应用到options options.ConfigurationOptions = redisConfig; - options.InstanceName = "TerritoryGame_"; + options.InstanceName = "TerritoryGame:"; }); // 注册自定义Redis健康检查 services.AddHealthChecks() .AddCheck("redis"); - // 注册服务 - services.AddScoped(); - services.AddScoped(); + // 注册服务 - 使用完全限定名称解决冲突 + services.AddScoped(); + services.AddScoped(); return services; } diff --git a/frontend/src/components/RoomList.vue b/frontend/src/components/RoomList.vue index 26c83b0..f1f9e54 100644 --- a/frontend/src/components/RoomList.vue +++ b/frontend/src/components/RoomList.vue @@ -274,7 +274,7 @@ function handleRoomListUpdated(response) { // 确保每个房间都有currentPlayers字段,默认为0 updatedRooms = updatedRooms.map(room => ({ ...room, - currentPlayers: room.currentPlayers !== undefined ? room.currentPlayers : 0 + currentPlayers: room.CurrentPlayers !== undefined ? room.CurrentPlayers : room.currentPlayers !== undefined ? room.currentPlayers : 0 })); // 添加调试信息:查看处理后的房间数据 -- Gitee