diff --git a/src/CloudGaming/Api/CloudGaming.Api/Program.cs b/src/CloudGaming/Api/CloudGaming.Api/Program.cs index 0bbf42c..482d7a1 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Program.cs +++ b/src/CloudGaming/Api/CloudGaming.Api/Program.cs @@ -151,6 +151,7 @@ var app = builder.Build(); // app.UseSwagger(); // app.UseSwaggerUI(); //} + app.UseSwagger(); app.UseSwaggerUI(c => { @@ -165,6 +166,7 @@ app.UseSwaggerUI(c => app.UseHttpsRedirection(); +//注册身份验证中间件 app.UseAuthorization(); //数据库中间件 app.UseMultiTenant(); diff --git a/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs index 8344312..0688d43 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs @@ -126,13 +126,11 @@ namespace CloudGaming.Code.Account Token = jwt.AccessToken, UserId = user.Id, }; - //创建设备号 - var dev = await ManageUserDevicesAsync(user, account, jwt.AccessToken); - var key = $"user:login:{user.Id}"; + //获取登录的设备 + var (deviceList, currentDevice) = await ManageUserDevicesAsync(user, account, jwt.AccessToken); + // 管理设备和Redis缓存 + await ManageDeviceCacheAsync(user.Id, currentDevice.TokenMd5, deviceList); - //创建redis缓存 - await RedisCache.StringSetAsync(key, $"1", TimeSpan.FromHours(1)); - //accountUserLoginInfos.Add(new AccountUserLoginInfo()); T_User_Login_Log login_Log = new T_User_Login_Log() { Channel = this.AppRequestInfo.Channel, @@ -151,6 +149,42 @@ namespace CloudGaming.Code.Account return accountLogIn; } #region 注册用户 + + /// + /// 设备Redis缓存管理方法 + /// + /// + /// + /// + /// + private async Task ManageDeviceCacheAsync(int userId, string currentTokenMd5, List deviceList) + { + + + + // 获取用户当前所有的登录缓存key + var existingKeys = await RedisServerCache.ScanKeysAsync($"user:login:{userId}:*"); + + if (existingKeys != null && existingKeys.Count > 0) + { + // 查找和移除不在当前设备列表中的旧设备 + var activeKeys = deviceList.Select(dev => $"user:login:{userId}:{dev.TokenMd5}").ToHashSet(); + + foreach (var key in existingKeys) + { + if (!activeKeys.Contains(key)) + { + // 将无效设备状态标记为过期 + await RedisCache.StringSetAsync(key, "0", TimeSpan.FromMinutes(10)); + } + } + } + // 构建当前设备的Redis key + var currentDeviceKey = $"user:login:{userId}:{currentTokenMd5}"; + // 创建当前设备的缓存记录 + await RedisCache.StringSetAsync(currentDeviceKey, "1", TimeSpan.FromHours(1)); + } + /// /// 注册新用户或更新现有用户信息 /// @@ -220,7 +254,7 @@ namespace CloudGaming.Code.Account } // 管理用户设备 - private async Task> ManageUserDevicesAsync(T_User user, IUserAccount account, string accessToken) + private async Task<(List, T_User_Token)> ManageUserDevicesAsync(T_User user, IUserAccount account, string accessToken) { var currentTime = DateTime.Now; var dev = string.IsNullOrEmpty(account.DeviceNumber) @@ -274,7 +308,7 @@ namespace CloudGaming.Code.Account } await Dao.DaoUser.Context.SaveChangesAsync(); - return userLoginList; + return (userLoginList, existingDevice); } // 更新设备令牌信息 diff --git a/src/CloudGaming/Code/CloudGaming.Code/Account/Login/PhoneUserLogin.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/Login/PhoneUserLogin.cs index ed596f8..98455d3 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Account/Login/PhoneUserLogin.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Account/Login/PhoneUserLogin.cs @@ -34,7 +34,7 @@ namespace CloudGaming.Code.Account.Login throw MessageBox.Show(ResonseCode.ParamError, "验证码不能为空"); } //判断是否是测试账号 - if (!loginParams.PhoneNumber.Contains("999999999") && loginParams.VerificationCode == "1112") + if (!loginParams.PhoneNumber.Contains("99999999") && loginParams.VerificationCode == "1112") { if (!PhoneNumberValidator.IsPhoneNumber(loginParams.PhoneNumber)) { diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs index 92115ad..b11f00c 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs @@ -1,5 +1,6 @@ using AgileConfig.Client; +using CloudGaming.Code.DataAccess; using CloudGaming.Code.DataAccess.MultiTenantUtil; using HuanMeng.DotNetCore.CacheHelper; @@ -148,6 +149,16 @@ namespace CloudGaming.Code.AppExtend { return RedisConnection.GetRedis(appConfig.RedisConnectionString); } + /// + /// 获取Dao + /// + /// + /// + public static DAO GetDAO(this IServiceProvider serviceProvider) + { + return new DAO(serviceProvider); + } + /// /// diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CloudGamingBase.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CloudGamingBase.cs index 7c2b7f1..ec1a2c5 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CloudGamingBase.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CloudGamingBase.cs @@ -169,6 +169,21 @@ namespace CloudGaming.Code.AppExtend return _redis; } } + private IServer _redisServer; + /// + /// 数据库查询 + /// + public IServer RedisServerCache + { + get + { + if (_redisServer == null) + { + _redisServer = RedisConnection.GetServer(AppConfig.RedisConnectionString); + } + return _redisServer; + } + } #endregion #region 缓存 @@ -223,7 +238,7 @@ namespace CloudGaming.Code.AppExtend isChecking = new AppConfigBLL(this._serviceProvider).GetAppIsChecking(); RedisCache.StringSet(key, isChecking, new TimeSpan(1, 0, 0)); } - + } return isChecking ?? false; } diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs index b5f26aa..11c9b2a 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs @@ -43,6 +43,7 @@ namespace CloudGaming.Code.AppExtend options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { + options.RequireHttpsMetadata = true; options.SaveToken = true; //调试使用 @@ -70,20 +71,62 @@ namespace CloudGaming.Code.AppExtend { OnTokenValidated = async context => { - var userId = context.Principal.FindFirst("userId")?.Value; - if (userId == null || true) + + var token = context.Request.Headers.GetAuthorization(); + if (string.IsNullOrEmpty(token)) { - context.Fail("Token missing userId claim."); + context.Fail("非法请求接口"); + return; + } + var _userId = context.Principal.FindFirst("userId")?.Value; + int userId = 0; + if (_userId == null && !int.TryParse(_userId, out userId)) + { + context.Fail("请求标头错误"); + return; + } + var tokenMd5 = MD5Encryption.ComputeMD5Hash(token); + var appConfig = context.HttpContext.RequestServices.GetRequiredService(); + var host = context.Request.Host.Host; + var app = AppConfigurationExtend.GetAppConfig(host); + if (app == null) + { + context.Fail("未配置租户"); + return; + } + var redis = app.GetRedisDataBase(); + var isUserExpire = await redis.StringGetAsync($"user:login:{_userId}:{tokenMd5}"); + + var isUserExpireStatus = isUserExpire.ToString(); + if (string.IsNullOrEmpty(isUserExpireStatus)) + { + //再次去数据库中验证 + //IServiceProvider + var _serviceProvider = context.HttpContext.RequestServices.GetRequiredService(); + var dao = _serviceProvider.GetDAO(); + var c = await dao.DaoUser.Context.T_User_Token.Where(it => it.UserId == userId && it.TokenMd5 == tokenMd5).CountAsync(); + if (c <= 0) + { + //添加过期信息 + await redis.StringSetAsync($"user:login:{_userId}:{tokenMd5}", "0", TimeSpan.FromMinutes(15)); + //app.get + context.Fail("用户状态错误"); + return; + } + else + { + isUserExpireStatus = "1"; + //添加过期信息 + await redis.StringSetAsync($"user:login:{_userId}:{tokenMd5}", "1", TimeSpan.FromMinutes(30)); + } + } + if (isUserExpireStatus == "0") + { + //设备被顶掉 + context.Fail("用户在其它设备登录"); return; } - //// 你可以调用数据库或其他服务来验证用户状态 - //var userService = context.HttpContext.RequestServices.GetRequiredService(); - //var user = await userService.GetUserByIdAsync(userId); - //if (user == null || !user.IsActive) - //{ - // context.Fail("User is inactive or not found."); - //} }, // 处理认证失败的事件 OnAuthenticationFailed = context => diff --git a/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs b/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs index 3967f79..d5cef5b 100644 --- a/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs +++ b/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs @@ -7,17 +7,20 @@ using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; //var jsopn = JsonConvert.SerializeObject(new PhoneLoginParams()); //Server=192.168.1.17;Database=CloudGamingUser;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true; -var optionsBuilder = new DbContextOptionsBuilder(); -var option = optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingPhone;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;").Options; -CloudGamingPhoneContext cloudGamingPhoneContext = new CloudGamingPhoneContext(option); -//cloudGamingPhoneContext.Database.EnsureCreated(); -var x = cloudGamingPhoneContext.T_Epg_Cfg.Count(); -var ccc = cloudGamingPhoneContext.T_Epg_Cfg.ToList(); -Console.WriteLine("查询" + x.ToString()); -Console.ReadKey(); +//var optionsBuilder = new DbContextOptionsBuilder(); +//var option = optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingPhone;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;").Options; +//CloudGamingPhoneContext cloudGamingPhoneContext = new CloudGamingPhoneContext(option); +////cloudGamingPhoneContext.Database.EnsureCreated(); +//var x = cloudGamingPhoneContext.T_GameCBT.Count(); +//var ccc = cloudGamingPhoneContext.T_Epg_Cfg.ToList(); +//Console.WriteLine("查询" + x.ToString()); +//Console.ReadKey(); //Server=192.168.1.17;Database=CloudGamingPhone;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true; -//var optionsBuilder1 = new DbContextOptionsBuilder(); -//var option1 = optionsBuilder1.UseSqlServer("Server=192.168.1.17;Database=CloudGamingUser;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;").Options; -//CloudGamingUserContext cloud = new CloudGamingUserContext(option1); +var optionsBuilder1 = new DbContextOptionsBuilder(); +var option1 = optionsBuilder1.UseSqlServer("Server=192.168.195.6;Database=CloudGamingUser;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;").Options; +CloudGamingUserContext cloud = new CloudGamingUserContext(option1); +var xxx = cloud.T_User.Count(); +Console.WriteLine("查询" + xxx.ToString()); +Console.ReadKey(); //cloud.Database.EnsureCreated(); //cloud.Database. \ No newline at end of file diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs index da20118..2c953e1 100644 --- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs +++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs @@ -65,8 +65,8 @@ public partial class CloudGamingCBTContext : DbContext public virtual DbSet T_User_Login_Log { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer("Server=192.168.1.17;Database=CloudGamingCBT;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;"); - + {// => optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingCBT;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;"); + } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs index 25cbf26..d7da168 100644 --- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs +++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.EntityFrameworkCore; @@ -71,6 +71,7 @@ public partial class CloudGamingGameContext : DbContext protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {// => optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingGame;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;"); + //optionsBuilder.uses } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md b/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md index fb9e77d..b9bd754 100644 --- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md +++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md @@ -9,5 +9,5 @@ dotnet ef dbcontext scaffold "Server=192.168.195.6;Database=CloudGamingGame;User --Ext dotnet ef dbcontext scaffold "Server=192.168.1.17;Database=CloudGamingCBT;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o Db/Db_Ext/ --use-database-names --no-pluralize --force 内网穿透 -dotnet ef dbcontext scaffold "Server=192.168.195.6;Database=CloudGamingGame;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o Db/Db_Game/ --use-database-names --no-pluralize --force +dotnet ef dbcontext scaffold "Server=192.168.195.6;Database=CloudGamingCBT;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o Db/Db_Ext/ --use-database-names --no-pluralize --force ``` \ No newline at end of file diff --git a/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_Phone/CloudGamingPhoneContext.cs b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_Phone/CloudGamingPhoneContext.cs index eda4315..a9049b6 100644 --- a/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_Phone/CloudGamingPhoneContext.cs +++ b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_Phone/CloudGamingPhoneContext.cs @@ -147,6 +147,7 @@ public partial class CloudGamingPhoneContext : MultiTenantDbContext//DbContext protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) // => optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingPhone;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;"); { + optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingPhone;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs index cee90e6..54f59a9 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs @@ -1,3 +1,4 @@ +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.Identity.Client; using Newtonsoft.Json; @@ -25,6 +26,9 @@ namespace HuanMeng.DotNetCore.Redis /// public static ConcurrentDictionary Redis { get; set; } = new ConcurrentDictionary(); + /// + /// 数据库查询 + /// public static ConcurrentDictionary RedisServer { get; set; } = new ConcurrentDictionary(); @@ -55,13 +59,62 @@ namespace HuanMeng.DotNetCore.Redis if (!RedisServer.TryGetValue(redisConnection, out var server)) { var redis = ConnectionMultiplexer.Connect(redisConnection); - server = redis.GetServer("", ""); + var serverConn = ParseIpPortAndDatabase(redisConnection); + server = redis.GetServer(serverConn.ip, serverConn.port); //server.key //redis.GetServer() RedisServer.TryAdd(redisConnection, server); } return server; } + private static (string ip, int port, int database) ParseIpPortAndDatabase(string connectionString) + { + // 默认端口号和默认数据库 + int defaultPort = 6379; + int defaultDatabase = 0; + + if (string.IsNullOrEmpty(connectionString)) + { + return ("localhost", defaultPort, defaultDatabase); + } + + // 按逗号分割,获取主机部分和其他参数 + var parts = connectionString.Split(','); + var hostPart = parts[0]; // 例如:"192.168.195.6:6379" + + // 检查是否包含端口号 + string ip; + int port; + + if (hostPart.Contains(":")) + { + var hostParts = hostPart.Split(':'); + ip = hostParts[0]; + port = int.TryParse(hostParts[1], out int parsedPort) ? parsedPort : defaultPort; + } + else + { + // 如果没有端口号,使用默认端口 + ip = hostPart; + port = defaultPort; + } + + // 解析 defaultDatabase 参数 + int database = defaultDatabase; + foreach (var part in parts) + { + if (part.StartsWith("defaultDatabase=", StringComparison.OrdinalIgnoreCase)) + { + var dbPart = part.Split('='); + if (dbPart.Length == 2 && int.TryParse(dbPart[1], out int parsedDb)) + { + database = parsedDb; + } + } + } + + return (ip, port, database); + } /// /// @@ -141,7 +194,26 @@ namespace HuanMeng.DotNetCore.Redis /// public static List ScanKeys(this IServer server, string pattern, int pageSize = 100) { - var matchingKeys = server.Keys(pattern: pattern).Select(it => it.ToString()).ToList(); + var matchingKeys = server.Keys(pattern: pattern, pageSize: pageSize).Select(it => it.ToString()).ToList(); + return matchingKeys; + } + /// + /// 异步模糊查询key + /// + /// + /// + /// + /// + /// 匹配的键的字符串列表 + public static async Task> ScanKeysAsync(this IServer server, string pattern, int pageSize = 100, int database = -1) + { + var matchingKeys = new List(); + + await foreach (var key in server.KeysAsync(database: database, pattern: pattern, pageSize: pageSize)) + { + matchingKeys.Add(key.ToString()); + } + return matchingKeys; } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs index 65d3311..f67c3c6 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs @@ -35,5 +35,30 @@ namespace HuanMeng.DotNetCore.Utility // 如果X-Forwarded-For头部不存在,使用RemoteIpAddress return context.Connection.RemoteIpAddress?.ToString(); } + + /// + /// 从请求头中提取Authorization信息(JWT)。 + /// + /// 请求头字典。 + /// 如果包含有效的Authorization头,则返回JWT Token,否则返回null。 + public static string? GetAuthorization(this IHeaderDictionary headers) + { + // 尝试从请求头中获取Authorization字段 + if (headers.TryGetValue("Authorization", out var authHeaderObj)) + { + // 获取Authorization头的值并移除"Bearer "前缀 + var authHeader = authHeaderObj.ToString(); + + // 如果Authorization以"Bearer "开头,提取JWT Token + if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + { + return authHeader.Substring(7).Trim(); // 直接返回JWT Token + } + } + + // 如果没有Authorization头或格式不正确,返回null + return null; + } + } }