From b58cc5f2292692ff03438645a9164c9d7011fa60 Mon Sep 17 00:00:00 2001 From: zpc Date: Fri, 11 Oct 2024 01:13:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0jwt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CloudGaming.Api/CloudGaming.Api.csproj | 13 + .../Api/CloudGaming.Api/Program.cs | 125 +++++++++- src/CloudGaming/CloudGaming.sln | 7 + .../CloudGaming.Code/AppExtend/AppConfig.cs | 109 +++++++++ .../AppExtend/AppConfigurationExtend.cs | 184 ++++++++++++++ .../AppExtend/JwtTokenManageExtension.cs | 74 ++++++ .../CloudGaming.Code/CloudGaming.Code.csproj | 8 + .../Code/CloudGaming.Code/DataAccess/DAO.cs | 43 ++-- .../MultiTenantUtil/MultiTenantExtension.cs | 63 +++++ .../MultiTenantTenantMiddleware.cs | 42 ++++ .../Code/CloudGaming.Code/GlobalUsings.cs | 15 ++ .../CloudGaming.DtoModel.csproj | 20 ++ .../CloudGaming.DtoModel/GlobalUsings.cs | 10 + .../CloudGaming.GameModel.csproj | 2 + .../CloudGaming.Model.csproj | 2 + .../HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs | 226 ++++++++---------- .../HuanMeng.DotNetCore.csproj | 3 + .../JwtInfrastructure/JwtManager.cs | 167 +++++++++++++ .../MultiTenant/Contract/ITenantInfo.cs | 4 - .../MultiTenant/TenantInfo.cs | 5 +- .../SwaggerUtile/LowercaseParameterFilter.cs | 59 +++++ .../Utility/AssemblyHelper/AssemblyInfo.cs | 12 +- .../Utility/HttpContextExtensions.cs | 3 + .../WeChat/WXBizDataCrypt.cs | 6 +- 24 files changed, 1022 insertions(+), 180 deletions(-) create mode 100644 src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs create mode 100644 src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs create mode 100644 src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs create mode 100644 src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantExtension.cs create mode 100644 src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantTenantMiddleware.cs create mode 100644 src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs create mode 100644 src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj create mode 100644 src/CloudGaming/Model/CloudGaming.DtoModel/GlobalUsings.cs create mode 100644 src/CloudGaming/Utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtManager.cs create mode 100644 src/CloudGaming/Utile/HuanMeng.DotNetCore/SwaggerUtile/LowercaseParameterFilter.cs diff --git a/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj b/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj index 8a0c1f7..09cae8e 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj +++ b/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj @@ -7,15 +7,28 @@ 3936c093-f18d-46a7-91d6-96cc43ffe3b8 Linux ..\.. + True + + + + embedded + + + + embedded + + + + diff --git a/src/CloudGaming/Api/CloudGaming.Api/Program.cs b/src/CloudGaming/Api/CloudGaming.Api/Program.cs index 48863a6..020c515 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Program.cs +++ b/src/CloudGaming/Api/CloudGaming.Api/Program.cs @@ -1,25 +1,136 @@ +using CloudGaming.Code.AppExtend; +using CloudGaming.Code.DataAccess.MultiTenantUtil; +using HuanMeng.DotNetCore.MiddlewareExtend; +using HuanMeng.DotNetCore.CustomExtension; +using System.Diagnostics; + +using System.Reflection; +using HuanMeng.DotNetCore.Utility.AssemblyHelper; +using HuanMeng.DotNetCore.SwaggerUtile; +using Microsoft.OpenApi.Models; +using Newtonsoft.Json.Serialization; +using Microsoft.AspNetCore.Authentication.JwtBearer; var builder = WebApplication.CreateBuilder(args); // Add services to the container. -builder.Services.AddControllers(); +builder.Services.AddControllers() + .AddNewtonsoftJson(options => + { + // 配置 Newtonsoft.Json 选项 + options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; // 忽略循环引用 + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();// 首字母小写(驼峰样式) + options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";// 时间格式化 +#if !DEBUG + options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None; +#endif + //options.SerializerSettings.Converters.Add() + // 其他配置... + }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(c => +{ + + var securityScheme = new OpenApiSecurityScheme + { + Name = "JWT 身份验证(Authentication)", + Description = "请输入登录后获取JWT的**token**", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "bearer", //必须小写 + BearerFormat = "JWT", + Reference = new OpenApiReference + { + Id = JwtBearerDefaults.AuthenticationScheme, + Type = ReferenceType.SecurityScheme + } + }; + c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + {securityScheme, Array.Empty()} + }); + + c.SwaggerDoc("v1", new OpenApiInfo { Title = "蒸汽云游戏", Version = "1.0.0", Description = "" }); + foreach (var assemblies in AppDomain.CurrentDomain.GetAssemblies()) + { + // 添加 XML 注释文件路径 + var xmlFile = $"{assemblies.GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + if (File.Exists(xmlPath)) + { + c.IncludeXmlComments(xmlPath); + } + } + c.ParameterFilter(); + c.RequestBodyFilter(); +}); +builder.AddAppConfigClient(); +//添加jwt验证 +builder.AddJwtConfig(); +#region 添加跨域 +var _myAllowSpecificOrigins = "_myAllowSpecificOrigins"; +builder.Services.AddCustomCors(_myAllowSpecificOrigins); +#endregion + +//添加jwt验证 +//builder.AddJwtConfig(); var app = builder.Build(); // Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) +//if (app.Environment.IsDevelopment()) +//{ +// app.UseSwagger(); +// app.UseSwaggerUI(); +//} +app.UseSwagger(); +app.UseSwaggerUI(c => { - app.UseSwagger(); - app.UseSwaggerUI(); -} + + c.EnableDeepLinking(); + c.DefaultModelsExpandDepth(3); + c.DefaultModelExpandDepth(3); + c.EnableFilter("true"); + //c.RoutePrefix = string.Empty; + c.SwaggerEndpoint("/swagger/v1/swagger.json", "蒸汽云游戏 API V1"); +}); app.UseHttpsRedirection(); app.UseAuthorization(); - +//数据库中间件 +app.UseMultiTenant(); +//使用跨域 +app.UseCors(_myAllowSpecificOrigins); app.MapControllers(); +app.UseStaticFiles();//静态文件访问配置 +//执行扩展中间件 +app.UseMiddlewareAll(); +#region 默认请求 +app.MapGet("/", () => "请求成功").WithName("默认请求"); +var startDateTime = DateTime.Now; +var InformationalVersion = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; +Console.WriteLine($"version:{InformationalVersion}"); +app.MapGet("/system", () => +{ + + using Process currentProcess = Process.GetCurrentProcess(); + // CPU使用率 (一般是一个0-100之间的值,但实际是时间占比,需要转换) + double cpuUsage = currentProcess.TotalProcessorTime.TotalMilliseconds / Environment.TickCount * 100; + // 已用内存 (字节) + long memoryUsage = currentProcess.WorkingSet64; + return new + { + msg = $"系统信息:启动时间:{startDateTime.ToString("yyyy-MM-dd HH:mm:ss")},已安全运行时间:{DateTime.Now.Subtract(startDateTime).TotalMinutes.ToString("0.##")}分钟", + + startDateTime, + MemoryUsage = $"{memoryUsage / (1024.0 * 1024.0):F2}MB", + CPUUsage = $"{cpuUsage:F2}%" + + }; +}).WithName("获取系统数据"); +#endregion app.Run(); diff --git a/src/CloudGaming/CloudGaming.sln b/src/CloudGaming/CloudGaming.sln index a7356f6..ae7e883 100644 --- a/src/CloudGaming/CloudGaming.sln +++ b/src/CloudGaming/CloudGaming.sln @@ -32,6 +32,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudGaming.CreateDataBase" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudGaming.GameModel", "Model\CloudGaming.GameModel\CloudGaming.GameModel.csproj", "{1120C146-6B83-4E4E-8A39-BD09466C7E1B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudGaming.DtoModel", "Model\CloudGaming.DtoModel\CloudGaming.DtoModel.csproj", "{96CD0865-0AD5-41B3-89A2-374FF17CDD16}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +64,10 @@ Global {1120C146-6B83-4E4E-8A39-BD09466C7E1B}.Debug|Any CPU.Build.0 = Debug|Any CPU {1120C146-6B83-4E4E-8A39-BD09466C7E1B}.Release|Any CPU.ActiveCfg = Release|Any CPU {1120C146-6B83-4E4E-8A39-BD09466C7E1B}.Release|Any CPU.Build.0 = Release|Any CPU + {96CD0865-0AD5-41B3-89A2-374FF17CDD16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96CD0865-0AD5-41B3-89A2-374FF17CDD16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96CD0865-0AD5-41B3-89A2-374FF17CDD16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96CD0865-0AD5-41B3-89A2-374FF17CDD16}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -73,6 +79,7 @@ Global {5F851D79-E435-4D16-974A-6D5E3A3269A7} = {FCA3CA4B-1993-429A-B2E9-2B05DB44F10E} {393ED915-3F88-4F84-AE2A-5C95F8867F16} = {9F7EF36C-17BB-4F93-927E-F462FE3C9337} {1120C146-6B83-4E4E-8A39-BD09466C7E1B} = {A3F00FB0-49D6-48B1-99D9-4619634DF8D9} + {96CD0865-0AD5-41B3-89A2-374FF17CDD16} = {A3F00FB0-49D6-48B1-99D9-4619634DF8D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1D299D92-FA27-47A0-8D78-43D1FAFE7628} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs new file mode 100644 index 0000000..400800d --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.AppExtend +{ + /// + /// 项目配置 + /// + public class AppConfig + { + public AppConfig() { } + /// + /// 用户数据库连接字符串 + /// + public string UserConnectionString { get; set; } + /// + /// 游戏 + /// + public string GameConnectionString { get; set; } + /// + /// 扩展 + /// + public string ExtConnectionString { get; set; } + + /// + /// 手机app配置 + /// + public string PhoneConnectionString { get; set; } + + /// + /// redis连接字符串 + /// + public string RedisConnectionString { get; set; } + + /// + /// 域名 + /// + public string DomainName { get; set; } + /// + /// 标识 + /// + public string Identifier { get; set; } + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 租户 + /// + public Guid TenantId { get; set; } + /// + /// 项目支付数据 + /// + //public PaymentModel? Payment { get; set; } + + + + /// + /// 获取数据库连接字符串 + /// + /// user,game,ext,phone + /// + /// + public string GetConnectionString(AppDataBaseType appDataBaseType) + { + switch (appDataBaseType) + { + case AppDataBaseType.User: + return UserConnectionString; + case AppDataBaseType.Game: + return GameConnectionString; + case AppDataBaseType.Ext: + return ExtConnectionString; + case AppDataBaseType.App: + return PhoneConnectionString; + default: + throw new NotImplementedException("数据库连接字符串不存在"); + } + } + } + + /// + /// 数据库选项 + /// + + public enum AppDataBaseType + { + /// + /// 用户数据库 + /// + User, + /// + /// 游戏数据库 + /// + Game, + /// + /// 扩展数据库 + /// + Ext, + /// + /// app数据库 + /// + App + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs new file mode 100644 index 0000000..525647b --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs @@ -0,0 +1,184 @@ +using CloudGaming.Code.DataAccess.MultiTenantUtil; + +using System.Collections.Concurrent; +using System.Collections.Frozen; + +using XLib.DotNetCore.CacheHelper; + +namespace CloudGaming.Code.AppExtend +{ + /// + /// app配置项扩展 + /// + public static class AppConfigurationExtend + { + /// + /// 配置数据 + /// + //public static ConfigClient AppConfigClient { get; set; } + + /// + /// + /// + public static IConfigurationManager ConfigurationManager { get; set; } + + /// + /// + /// + public static ConcurrentDictionary AppConfigs { get; set; } = new ConcurrentDictionary(); + + /// + /// 获取配置项 + /// + /// + /// + public static AppConfig GetAppConfig(string domainName) + { + if (AppConfigs.TryGetValue(domainName, out var appConfig)) + { + + return appConfig; + } + if (AppConfigs.TryGetValue("default", out appConfig)) + { + return appConfig; + } + + return AppConfigs.FirstOrDefault().Value; + } + + + /// + /// 初始化app + /// + /// + /// + public static IHostApplicationBuilder AddAppConfigClient(this IHostApplicationBuilder builder) + { + + //hostBuilder. + //builder..UseAgileConfig(configClient, ConfigClient_ConfigChanged); + //AppConfigClient = configClient; + ConfigurationManager = builder.Configuration; + AppConfigInit(builder.Configuration); + builder.Services.AddScoped(); + builder.AddDataBase(); + return builder; + + } + + + /// + /// 获取配置项 + /// + /// + /// + public static AppConfig? GetAppConfigIdentifier(string identifier) + { + var app = AppConfigs.Where(it => it.Value.Identifier == identifier).Select(it => it.Value).FirstOrDefault(); + + return app; + } + + + /// + /// 配置版本号 + /// + public static string AppVersion + { + get; + set; + } + + + + #region 租户 + /// + /// + /// + /// + /// + /// + public static ITenantInfo ToITenantInfo(this AppConfig appConfig, ITenantInfo? tenantInfo = null) + { + if (tenantInfo == null) + { + tenantInfo = new TenantInfo(); + } + tenantInfo.TenantId = appConfig.TenantId; + tenantInfo.Identifier = appConfig.Identifier; + tenantInfo.Name = appConfig.Name; + return tenantInfo; + } + + /// + /// + /// + /// + /// + /// + public static AppConfig ToAppConfig(this AppConfig appConfig, AppConfig? newAppConfig = null) + { + if (newAppConfig == null) + { + newAppConfig = new AppConfig(); + } + if (appConfig == null) + { + appConfig = new AppConfig(); + } + newAppConfig.TenantId = appConfig.TenantId; + newAppConfig.Identifier = appConfig.Identifier; + newAppConfig.Name = appConfig.Name; + newAppConfig.DomainName = appConfig.DomainName; + newAppConfig.RedisConnectionString = appConfig.RedisConnectionString; + newAppConfig.UserConnectionString = appConfig.UserConnectionString; + newAppConfig.GameConnectionString = appConfig.GameConnectionString; + newAppConfig.ExtConnectionString = appConfig.ExtConnectionString; + newAppConfig.PhoneConnectionString = appConfig.PhoneConnectionString; + return newAppConfig; + } + + + /// + /// 初始化租户数据 + /// + /// + private static void AppConfigInit(IConfigurationManager configurationManager) + { + var tenants = configurationManager.GetSection("Tenant").Get>(); + if (tenants != null) + { + ConcurrentDictionary _AppConfigs = new ConcurrentDictionary(); + if (tenants.Count > 0) + { + tenants?.ForEach(t => + { + + if (!_AppConfigs.TryAdd(t.DomainName, t)) + { + Console.WriteLine($"{t.DomainName}配置加载失败"); + } + if (t.Name == "default") + { + _AppConfigs.TryAdd("default", t); + } + }); + if (!_AppConfigs.TryGetValue("default", out var x)) + { + _AppConfigs.TryAdd("default", tenants[0]); + } + } + else + { + _AppConfigs.TryAdd("default", new AppConfig()); + } + AppConfigs = _AppConfigs; + } + } + #endregion + + + } + +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs new file mode 100644 index 0000000..c47534b --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs @@ -0,0 +1,74 @@ +using HuanMeng.DotNetCore.JwtInfrastructure.Interface; +using HuanMeng.DotNetCore.JwtInfrastructure; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; + +namespace CloudGaming.Code.AppExtend +{ + /// + /// + /// + public static class JwtTokenManageExtension + { + /// + /// 添加jwt安全验证,配置 + /// + /// + /// + /// + public static void AddJwtConfig(this IHostApplicationBuilder builder) + { + var jwtTokenConfig = builder.Configuration.GetSection("JwtTokenConfig").Get()!; + if (jwtTokenConfig == null) + { + jwtTokenConfig = new JwtTokenConfig() + { + Secret = Guid.NewGuid().ToString(), + AccessTokenExpiration = 60 * 60 + }; + } + //注册一个jwtTokenConfig的单例服务 + builder.Services.AddSingleton(jwtTokenConfig); + // + builder.Services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.RequireHttpsMetadata = true; + options.SaveToken = true; + //调试使用 + //options.Events = new JwtDebugBearerEvents().GetJwtBearerEvents(); + options.TokenValidationParameters = new TokenValidationParameters + { + //是否验证颁发者 + ValidateIssuer = true, + //是否验证受众 + ValidateAudience = true, + //指定是否验证令牌的生存期。设置为 true 表示要进行验证。 + ValidateLifetime = true, + //指定是否验证颁发者签名密钥。设置为 true 表示要进行验证。 + ValidateIssuerSigningKey = true, + //颁发者 + ValidIssuer = jwtTokenConfig.Issuer, + //受众 + ValidAudience = jwtTokenConfig.Audience, + //指定用于验证颁发者签名的密钥 + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtTokenConfig.Secret)), + //指定允许令牌的时钟偏移。允许令牌的过期时间与实际时间之间存在的时间差。在这里设置为 5 分钟,表示允许令牌的时钟偏移为 5 分钟。 + ClockSkew = TimeSpan.FromMinutes(5) + }; + }); + //注册一个JwtAuthManager的单例服务 + builder.Services.AddSingleton(); + } + + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj b/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj index 13eab54..ed85ee3 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj +++ b/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj @@ -4,9 +4,17 @@ net8.0 enable enable + True + True + + + + + + diff --git a/src/CloudGaming/Code/CloudGaming.Code/DataAccess/DAO.cs b/src/CloudGaming/Code/CloudGaming.Code/DataAccess/DAO.cs index bc71a8d..aa17b91 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/DataAccess/DAO.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/DataAccess/DAO.cs @@ -1,17 +1,4 @@ -using HuanMeng.DotNetCore.Base; -using HuanMeng.DotNetCore.MultiTenant.Contract; -using HuanMeng.DotNetCore.MultiTenant; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CloudGaming.GameModel.Db.Db_Ext; -using CloudGaming.GameModel.Db.Db_Game; -using CloudGaming.Model.DbSqlServer.Db_Phone; -using CloudGaming.Model.DbSqlServer.Db_User; namespace CloudGaming.Code.DataAccess { @@ -20,8 +7,7 @@ namespace CloudGaming.Code.DataAccess /// public class DAO : DaoBase { - //private IMultiTenantProvider _multiTenantProvider; - private ITenantInfo _tenantInfo; + private ITenantInfo? _tenantInfo; /// /// @@ -32,7 +18,8 @@ namespace CloudGaming.Code.DataAccess { if (_tenantInfo == null) { - this._tenantInfo = _serviceProvider.GetRequiredService(); + var app = _serviceProvider.GetRequiredService(); + this._tenantInfo = app.ToITenantInfo(); } return this._tenantInfo; } @@ -43,16 +30,16 @@ namespace CloudGaming.Code.DataAccess /// public DAO(IServiceProvider serviceProvider) : base(serviceProvider) { - this._tenantInfo = serviceProvider.GetRequiredService(); + } /// /// 构造函数 /// /// - public DAO(IServiceProvider serviceProvider, ITenantInfo tenantInfo) : base(serviceProvider) + public DAO(IServiceProvider serviceProvider, AppConfig appConfig) : base(serviceProvider) { - this._tenantInfo = tenantInfo; + this._tenantInfo = appConfig.ToITenantInfo(); } #region 扩展 @@ -61,7 +48,7 @@ namespace CloudGaming.Code.DataAccess /// 数据库[CloudGamingCBT], /// 更新删除,尽量只操作本bll实例dao获取到的对象,取和存要同一个dao /// - public EfCoreDaoBase daoExt + public EfCoreDaoBase DaoExt { get { @@ -69,7 +56,7 @@ namespace CloudGaming.Code.DataAccess { var dbContext = _serviceProvider.GetRequiredService(); _daoExt = new EfCoreDaoBase(dbContext); - _daoExt.context.TenantInfo = TenantInfo; + _daoExt.Context.TenantInfo = TenantInfo; } return _daoExt; } @@ -81,7 +68,7 @@ namespace CloudGaming.Code.DataAccess /// 数据库[CloudGamingGameContext], /// 更新删除,尽量只操作本bll实例dao获取到的对象,取和存要同一个dao /// - public EfCoreDaoBase daoGame + public EfCoreDaoBase DaoGame { get { @@ -89,7 +76,7 @@ namespace CloudGaming.Code.DataAccess { var dbContext = _serviceProvider.GetRequiredService(); _daoGame = new EfCoreDaoBase(dbContext); - _daoGame.context.TenantInfo = TenantInfo; + _daoGame.Context.TenantInfo = TenantInfo; } return _daoGame; } @@ -102,7 +89,7 @@ namespace CloudGaming.Code.DataAccess /// 数据库[CloudGamingGameContext], /// 更新删除,尽量只操作本bll实例dao获取到的对象,取和存要同一个dao /// - public EfCoreDaoBase daoPhone + public EfCoreDaoBase DaoPhone { get { @@ -110,20 +97,20 @@ namespace CloudGaming.Code.DataAccess { var dbContext = _serviceProvider.GetRequiredService(); _daoPhone = new EfCoreDaoBase(dbContext); - _daoPhone.context.TenantInfo = TenantInfo; + _daoPhone.Context.TenantInfo = TenantInfo; } return _daoPhone; } } #endregion - + #region 用户 private EfCoreDaoBase? _daoUser; /// /// 数据库[CloudGamingGameContext], /// 更新删除,尽量只操作本bll实例dao获取到的对象,取和存要同一个dao /// - public EfCoreDaoBase daoUser + public EfCoreDaoBase DaoUser { get { @@ -131,7 +118,7 @@ namespace CloudGaming.Code.DataAccess { var dbContext = _serviceProvider.GetRequiredService(); _daoUser = new EfCoreDaoBase(dbContext); - _daoUser.context.TenantInfo = TenantInfo; + _daoUser.Context.TenantInfo = TenantInfo; } return _daoUser; } diff --git a/src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantExtension.cs b/src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantExtension.cs new file mode 100644 index 0000000..fa5831a --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantExtension.cs @@ -0,0 +1,63 @@ +namespace CloudGaming.Code.DataAccess.MultiTenantUtil +{ + /// + /// 多租户扩展 + /// + public static class MultiTenantExtension + { + /// + /// 多租户 IServiceCollection 扩展 + /// + /// + public static void AddDataBase(this IHostApplicationBuilder builder) + { + // 获取初始连接字符串 + string userConnectionString = builder.Configuration.GetConnectionString("UserConnectionString") ?? ""; + string gameConnectionString = builder.Configuration.GetConnectionString("GameConnectionString") ?? ""; + string phoneConnectionString = builder.Configuration.GetConnectionString("PhoneConnectionString") ?? ""; + string extConnectionString = builder.Configuration.GetConnectionString("ExtConnectionString") ?? ""; + + // 注册 用户DbContext + RegisterDbContext(builder, userConnectionString, AppDataBaseType.User); + //注册 游戏Db + RegisterDbContext(builder, gameConnectionString, AppDataBaseType.Game); + //注册 app配置Db + RegisterDbContext(builder, phoneConnectionString, AppDataBaseType.App); + //注册 扩展Db + RegisterDbContext(builder, extConnectionString, AppDataBaseType.Ext); + } + + /// + /// 注册 DbContext,避免重复代码 + /// + /// 要注册的 DbContext 类型 + /// 应用构建器 + /// 默认连接字符串 + /// AppConfig 中的连接字符串键 + private static void RegisterDbContext(IHostApplicationBuilder builder, string defaultConnectionString, AppDataBaseType configKey) where TContext : DbContext + { + builder.Services.AddDbContext((serviceProvider, options) => + { + var appConfig = serviceProvider.GetRequiredService(); + string dbConnectionString = appConfig?.GetConnectionString(configKey) ?? defaultConnectionString; + + if (string.IsNullOrEmpty(dbConnectionString)) + { + dbConnectionString = defaultConnectionString; + } + + options.UseSqlServer(dbConnectionString); + }, ServiceLifetime.Scoped); + } + /// + /// 多租户IApplicationBuilder扩展 + /// + /// + /// + public static IApplicationBuilder UseMultiTenant(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantTenantMiddleware.cs b/src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantTenantMiddleware.cs new file mode 100644 index 0000000..40c30bd --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/DataAccess/MultiTenantUtil/MultiTenantTenantMiddleware.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Http; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.DataAccess.MultiTenantUtil +{ + /// + /// 多租户中间件 + /// + public class MultiTenantTenantMiddleware + { + private readonly RequestDelegate _next; + public MultiTenantTenantMiddleware(RequestDelegate next) + { + _next = next; + } + + + /// + /// 根据HttpContext获取并设置当前租户ID + /// + /// + /// + /// + /// + public virtual async Task Invoke(HttpContext context, + IServiceProvider _serviceProvider, + AppConfig appConfig + ) + { + + var host = context.Request.Host.Host; + var app = AppConfigurationExtend.GetAppConfig(host); + app.ToAppConfig(appConfig); + await _next.Invoke(context); + } + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs b/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs new file mode 100644 index 0000000..3876d89 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs @@ -0,0 +1,15 @@ +global using CloudGaming.Code.AppExtend; +global using CloudGaming.GameModel.Db.Db_Ext; +global using CloudGaming.GameModel.Db.Db_Game; +global using CloudGaming.Model.DbSqlServer.Db_Phone; +global using CloudGaming.Model.DbSqlServer.Db_User; + +global using HuanMeng.DotNetCore.Base; +global using HuanMeng.DotNetCore.MultiTenant; +global using HuanMeng.DotNetCore.MultiTenant.Contract; + +global using Microsoft.AspNetCore.Builder; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; \ No newline at end of file diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj b/src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj new file mode 100644 index 0000000..2558b98 --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + True + True + + + + + + + + + + + + diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/GlobalUsings.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/GlobalUsings.cs new file mode 100644 index 0000000..c36c453 --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/GlobalUsings.cs @@ -0,0 +1,10 @@ +global using CloudGaming.GameModel.Db.Db_Ext; +global using CloudGaming.GameModel.Db.Db_Game; +global using CloudGaming.Model.DbSqlServer.Db_Phone; +global using CloudGaming.Model.DbSqlServer.Db_User; + +global using HuanMeng.DotNetCore.Base; +global using HuanMeng.DotNetCore.MultiTenant; +global using HuanMeng.DotNetCore.MultiTenant.Contract; + +global using Microsoft.Extensions.DependencyInjection; \ No newline at end of file diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/CloudGaming.GameModel.csproj b/src/CloudGaming/Model/CloudGaming.GameModel/CloudGaming.GameModel.csproj index f46fdc8..8003f8b 100644 --- a/src/CloudGaming/Model/CloudGaming.GameModel/CloudGaming.GameModel.csproj +++ b/src/CloudGaming/Model/CloudGaming.GameModel/CloudGaming.GameModel.csproj @@ -4,6 +4,8 @@ net8.0 enable enable + True + True diff --git a/src/CloudGaming/Model/CloudGaming.Model/CloudGaming.Model.csproj b/src/CloudGaming/Model/CloudGaming.Model/CloudGaming.Model.csproj index dd1dea5..5072c74 100644 --- a/src/CloudGaming/Model/CloudGaming.Model/CloudGaming.Model.csproj +++ b/src/CloudGaming/Model/CloudGaming.Model/CloudGaming.Model.csproj @@ -4,6 +4,8 @@ net8.0 enable enable + True + True diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs index 270a99b..3706142 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs @@ -1,35 +1,18 @@ using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Linq.Expressions; namespace HuanMeng.DotNetCore.Base -{ /// - /// 基本数据库操作,需要安装 - /// Microsoft.EntityFrameworkCore - /// Microsoft.EntityFrameworkCore.Relational - /// - /// +{ + /// + /// 基本数据库操作,需要安装 Microsoft.EntityFrameworkCore 和 Microsoft.EntityFrameworkCore.Relational + /// + /// public class EfCoreDaoBase where TDbContext : DbContext - //, new() { private TDbContext _context; - public TDbContext context - { - get - { - //return _context ?? (_context = new TDbContext()); - return _context; - } - //set { _context = value; } - } - //public EfCoreDaoBase() { } + public TDbContext Context => _context; /// /// 构造函数 @@ -37,8 +20,9 @@ namespace HuanMeng.DotNetCore.Base /// public EfCoreDaoBase(TDbContext context) { - _context = context; + _context = context ?? throw new ArgumentNullException(nameof(context)); } + /// /// 是否手动提交 /// @@ -47,149 +31,133 @@ namespace HuanMeng.DotNetCore.Base /// /// SqlQueryRaw /// - /// - /// - /// - /// - public T SqlQuery(string sql, params object[] parameters) where T : class + public async Task SqlQueryAsync(string sql, params object[] parameters) where T : class { - var quiry = context.Database.SqlQueryRaw(sql, parameters); - return quiry.FirstOrDefault(); + return await Context.Database.SqlQueryRaw(sql, parameters).FirstOrDefaultAsync(); } /// /// SqlQueryList /// - /// - /// - /// - /// - public List SqlQueryList(string sql, params object[] parameters) + public async Task> SqlQueryListAsync(string sql, params object[] parameters) { - var quiry = context.Database.SqlQueryRaw(sql, parameters); - return quiry.ToList(); + return await Context.Database.SqlQueryRaw(sql, parameters).ToListAsync(); } /// /// ExecuteSql /// - /// - /// - /// - public int ExecuteSql(string sql, params object[] parameters) + public async Task ExecuteSqlAsync(string sql, params object[] parameters) { - int r = context.Database.ExecuteSqlRaw(sql, parameters); - context.SaveChanges(); - return r; + var result = await Context.Database.ExecuteSqlRawAsync(sql, parameters); + await AutoSaveChangesAsync(); + return result; } /// /// 添加实体 /// - /// - public void Add(T entity) where T : class + public async Task AddAsync(T entity) where T : class { - context.Set().Add(entity); - if (!IsManualSubmit) - context.SaveChanges(); + Context.Set().Add(entity); + await AutoSaveChangesAsync(); } /// /// 批量添加实体 /// - /// - /// - public void AddRange(List entity) where T : class + public async Task AddRangeAsync(IEnumerable entities) where T : class { - context.Set().AddRange(entity); - if (!IsManualSubmit) - context.SaveChanges(); + Context.Set().AddRange(entities); + await AutoSaveChangesAsync(); } + /// /// 删除某个实体 /// - /// - public void Delete(T entity) where T : class + public async Task DeleteAsync(T entity) where T : class { - context.Entry(entity).State = EntityState.Deleted; //整条更新 - //context.Set().Remove(entity); - if (!IsManualSubmit) - context.SaveChanges(); + Context.Entry(entity).State = EntityState.Deleted; + await AutoSaveChangesAsync(); } /// /// 更新实体 /// - /// - public void Update(T entity) where T : class + public async Task UpdateAsync(T entity) where T : class { - //一般不需要整条更新,按需更新字段即可 - if (context.Entry(entity).State == EntityState.Detached) + if (Context.Entry(entity).State == EntityState.Detached) { - context.Set().Attach(entity); - context.Entry(entity).State = EntityState.Modified; //整条更新 + Context.Set().Attach(entity); + Context.Entry(entity).State = EntityState.Modified; } + await AutoSaveChangesAsync(); + } + + /// + /// 清除上下文跟踪 + /// + public void RemoveTracking(T entity) where T : class + { + if (Context.Entry(entity).State != EntityState.Detached) + { + Context.Entry(entity).State = EntityState.Detached; + } + } + + /// + /// 获取实体,根据主键 + /// + public async Task GetModelAsync(params object[] keyValues) where T : class + { + return await Context.Set().FindAsync(keyValues); + } + + /// + /// 获取实体,根据条件 + /// + public async Task GetModelAsync(Expression> where, bool isNoTracking = false) where T : class + { + var query = Context.Set().AsQueryable(); + if (isNoTracking) + query = query.AsNoTracking(); + return await query.FirstOrDefaultAsync(where); + } + + /// + /// 获取列表数据 + /// + public IQueryable GetList(Expression> where, bool isNoTracking = false) where T : class + { + var query = Context.Set().Where(where); + return isNoTracking ? query.AsNoTracking() : query; + } + + /// + /// 获取记录数量 + /// + public async Task GetCountAsync(Expression> where) where T : class + { + return await Context.Set().AsNoTracking().CountAsync(where); + } + + /// + /// 判断是否存在记录 + /// + public async Task ExistsAsync(Expression> where) where T : class + { + return await Context.Set().AsNoTracking().AnyAsync(where); + } + + /// + /// 根据提交状态决定是否自动保存更改 + /// + private async Task AutoSaveChangesAsync() + { if (!IsManualSubmit) - context.SaveChanges(); + { + await Context.SaveChangesAsync(); + } } - - /// - /// 清除上下文跟踪(清除缓存) by wyg - /// - /// - /// - public void removeTracking(T entity) where T : class - { - if (context.Entry(entity).State != EntityState.Detached) - context.Entry(entity).State = EntityState.Detached; - } - - /// - /// 获取实体,从缓存中,根据键值获取 - /// - /// - /// - /// - public T GetModel(params object[] keyValues) where T : class - { - return context.Set().Find(keyValues); - } - /// - /// 获取实体,根据条件获取,从数据库获取 - /// - /// 筛选条件 - /// 默认false会进行缓存并跟踪(可按需update字段),true不需要缓存和跟踪(一般只读查询使用) - /// - public T GetModel(System.Linq.Expressions.Expression> where, bool isNoTracking = false) where T : class - { - if (!isNoTracking) - return context.Set().FirstOrDefault(where); //可按需update字段 - return context.Set().AsNoTracking().FirstOrDefault(where); //一般只读查询使用 - } - - /// - /// 获取列表数据,返回IQueryable - /// - /// 筛选条件 - /// 默认false会进行缓存并跟踪(可按需update字段),true不需要缓存和跟踪(一般只读查询使用) - /// - public IQueryable GetList(System.Linq.Expressions.Expression> where, bool isNoTracking = false) where T : class - { - if (!isNoTracking) - return context.Set().Where(where).AsQueryable(); - return context.Set().Where(where).AsNoTracking().AsQueryable(); - } - - public int GetCount(System.Linq.Expressions.Expression> where) where T : class - { - return context.Set().AsNoTracking().Count(where); - } - public bool Exists(System.Linq.Expressions.Expression> where) where T : class - { - //return context.Set().AsNoTracking().Any(where); - return context.Set().AsNoTracking().Count(where) > 0; - } - - } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj b/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj index beb049e..e95534e 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj @@ -4,6 +4,8 @@ net8.0 enable enable + True + True @@ -20,6 +22,7 @@ + diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtManager.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtManager.cs new file mode 100644 index 0000000..bb76c5e --- /dev/null +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtManager.cs @@ -0,0 +1,167 @@ +using HuanMeng.DotNetCore.JwtInfrastructure.Interface; +using HuanMeng.DotNetCore.JwtInfrastructure; +using Microsoft.IdentityModel.Tokens; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.DotNetCore.JwtInfrastructure +{ + /// + /// jwt,不保存 + /// + /// + public class JwtManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager + { + + /// + /// 后面可以存储在数据库或分布式缓存中 + /// + private readonly ConcurrentDictionary _usersRefreshTokens = new(); + /// + /// 获取加密字段 + /// + private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret); + + /// + /// 删除过期token + /// + /// + public void RemoveExpiredRefreshTokens(DateTime now) + { + var expiredTokens = _usersRefreshTokens.Where(x => x.Value.ExpireAt < now).ToList(); + foreach (var expiredToken in expiredTokens) + { + _usersRefreshTokens.TryRemove(expiredToken.Key, out _); + } + } + + /// + /// 根据用户名删除token + /// + /// + public void RemoveRefreshTokenByUserName(string userName) + { + var refreshTokens = _usersRefreshTokens.Where(x => x.Value.UserName == userName).ToList(); + foreach (var refreshToken in refreshTokens) + { + _usersRefreshTokens.TryRemove(refreshToken.Key, out _); + } + } + + /// + /// 创建token + /// + /// 用户名 + /// 用户项 + /// 过期时间 + /// + public JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now) + { + var shouldAddAudienceClaim = string.IsNullOrWhiteSpace(claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud)?.Value); + //创建token + var jwtToken = new JwtSecurityToken( + jwtTokenConfig.Issuer, + shouldAddAudienceClaim ? jwtTokenConfig.Audience : string.Empty, + claims, + expires: now.AddMinutes(jwtTokenConfig.AccessTokenExpiration), + signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature)); + var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken); + + //创建刷新token + var refreshToken = new JwtRefreshToken + { + UserName = username, + TokenString = GenerateRefreshTokenString(), + ExpireAt = now.AddMinutes(jwtTokenConfig.RefreshTokenExpiration) + }; + //_usersRefreshTokens.AddOrUpdate(refreshToken.TokenString, refreshToken, (_, _) => refreshToken); + + return new JwtAuthResult + { + AccessToken = accessToken, + RefreshToken = refreshToken + }; + } + + /// + /// 刷新token + /// + /// + /// + /// + /// + /// + public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now) + { + var (principal, jwtToken) = DecodeJwtToken(accessToken); + if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature)) + { + throw new SecurityTokenException("无效的token"); + } + + var userName = principal.Identity?.Name; + if (!_usersRefreshTokens.TryGetValue(refreshToken, out var existingRefreshToken)) + { + throw new SecurityTokenException("token已失效"); + } + if (existingRefreshToken.UserName != userName || existingRefreshToken.ExpireAt < now) + { + throw new SecurityTokenException("token不匹配"); + } + //创建新的token + return GenerateTokens(userName, principal.Claims.ToArray(), now); + } + /// + /// 解析token + /// + /// + /// + /// + public (ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) + { + throw new SecurityTokenException("token不能为空"); + } + var principal = new JwtSecurityTokenHandler() + .ValidateToken(token, + new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtTokenConfig.Issuer, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(_secret), + ValidAudience = jwtTokenConfig.Audience, + ValidateAudience = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(5) + }, + out var validatedToken); + return (principal, validatedToken as JwtSecurityToken); + } + + + /// + /// 获取刷新的token + /// + /// + private static string GenerateRefreshTokenString() + { + var randomNumber = new byte[32]; + using var randomNumberGenerator = RandomNumberGenerator.Create(); + randomNumberGenerator.GetBytes(randomNumber); + return Convert.ToBase64String(randomNumber); + } + + public IImmutableDictionary UsersRefreshTokensReadOnlyDictionary => throw new NotImplementedException(); + } +} diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/Contract/ITenantInfo.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/Contract/ITenantInfo.cs index 2fe50e6..252e6e6 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/Contract/ITenantInfo.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/Contract/ITenantInfo.cs @@ -20,9 +20,5 @@ namespace HuanMeng.DotNetCore.MultiTenant.Contract /// string? Name { get; set; } - /// - /// 数据库连接字符串 - /// - string? ConnectionString { get; set; } } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/TenantInfo.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/TenantInfo.cs index 04feb94..45a74c7 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/TenantInfo.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MultiTenant/TenantInfo.cs @@ -33,9 +33,6 @@ namespace HuanMeng.DotNetCore.MultiTenant /// public string? Name { get; set; } - /// - /// 租户数据库连接字符串 - /// - public string? ConnectionString { get; set; } + } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/SwaggerUtile/LowercaseParameterFilter.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/SwaggerUtile/LowercaseParameterFilter.cs new file mode 100644 index 0000000..4e2df45 --- /dev/null +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/SwaggerUtile/LowercaseParameterFilter.cs @@ -0,0 +1,59 @@ +using Microsoft.OpenApi.Models; + +using Swashbuckle.AspNetCore.SwaggerGen; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.DotNetCore.SwaggerUtile +{ + /// + /// 自定义参数过滤器 + /// + public class LowercaseParameterFilter : IParameterFilter + { + /// + /// + /// + /// + /// + public void Apply(OpenApiParameter parameter, ParameterFilterContext context) + { + // 将参数名称改为小写开头 + parameter.Name = Char.ToLower(parameter.Name[0]) + parameter.Name.Substring(1); + } + } + + /// + /// 自定义参数过滤器 + /// + public class LowercaseRequestFilter : IRequestBodyFilter + { + public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context) + { + + if (requestBody.Content != null) + { + foreach (var mediaType in requestBody.Content.Values) + { + if (mediaType.Schema?.Properties != null) + { + var propertiesToRename = new Dictionary(mediaType.Schema.Properties); + // 清空旧的属性 + mediaType.Schema.Properties.Clear(); + + foreach (var property in propertiesToRename) + { + // 创建新的属性,并将名称改为小写开头 + var newPropertyName = Char.ToLower(property.Key[0]) + property.Key.Substring(1); + mediaType.Schema.Properties.Add(newPropertyName, property.Value); + } + } + } + } + } + } +} diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfo.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfo.cs index d1d089f..e028cd2 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfo.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfo.cs @@ -14,22 +14,22 @@ namespace HuanMeng.DotNetCore.Utility.AssemblyHelper /// /// 获取或设置程序集的版本。 /// - public string Version { get; set; } + public string? Version { get; set; } /// /// 获取或设置程序集的文件版本。 /// - public string FileVersion { get; set; } + public string? FileVersion { get; set; } /// /// 获取或设置程序集版本。 /// - public string AssemblyVersion { get; set; } + public string? AssemblyVersion { get; set; } /// /// 获取或设置程序集的信息性版本。 /// - public string InformationalVersion { get; set; } + public string? InformationalVersion { get; set; } ///// ///// 获取或设置与程序集关联的公司名称。 @@ -44,12 +44,12 @@ namespace HuanMeng.DotNetCore.Utility.AssemblyHelper /// /// 获取或设置与程序集关联的版权信息。 /// - public string Copyright { get; set; } + public string? Copyright { get; set; } /// /// 获取或设置程序集的描述信息。 /// - public string Description { get; set; } + public string? Description { get; set; } } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs index 3904fb9..65d3311 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs @@ -8,6 +8,9 @@ using System.Threading.Tasks; namespace HuanMeng.DotNetCore.Utility { + /// + /// + /// public static class HttpContextExtensions { /// diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/WeChat/WXBizDataCrypt.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/WeChat/WXBizDataCrypt.cs index 55fde7b..7c21252 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/WeChat/WXBizDataCrypt.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/WeChat/WXBizDataCrypt.cs @@ -98,6 +98,7 @@ namespace HuanMeng.DotNetCore.WeChat public static class ErrorCode { + public const int OK = 0; public const int IllegalAesKey = -41001; public const int IllegalIv = -41002; @@ -106,14 +107,15 @@ namespace HuanMeng.DotNetCore.WeChat public class WXBizDataCryptModel { + public WXBizDataCryptModel() { } /// /// /// - public string EncryptedData; + public string? EncryptedData; /// /// /// - public string Iv; + public string? Iv; public int UserId { get; set; }