添加jwt

This commit is contained in:
zpc 2024-10-11 01:13:22 +08:00
parent 977972b029
commit b58cc5f229
24 changed files with 1022 additions and 180 deletions

View File

@ -7,15 +7,28 @@
<UserSecretsId>3936c093-f18d-46a7-91d6-96cc43ffe3b8</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Code\CloudGaming.Code\CloudGaming.Code.csproj" />
<ProjectReference Include="..\..\Model\CloudGaming.DtoModel\CloudGaming.DtoModel.csproj" />
<ProjectReference Include="..\..\Model\CloudGaming.GameModel\CloudGaming.GameModel.csproj" />
<ProjectReference Include="..\..\Model\CloudGaming.Model\CloudGaming.Model.csproj" />
<ProjectReference Include="..\..\Utile\HuanMeng.DotNetCore\HuanMeng.DotNetCore.csproj" />
</ItemGroup>

View File

@ -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<string>()}
});
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<LowercaseParameterFilter>();
c.RequestBodyFilter<LowercaseRequestFilter>();
});
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<AssemblyInformationalVersionAttribute>().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();

View File

@ -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}

View File

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.Code.AppExtend
{
/// <summary>
/// 项目配置
/// </summary>
public class AppConfig
{
public AppConfig() { }
/// <summary>
/// 用户数据库连接字符串
/// </summary>
public string UserConnectionString { get; set; }
/// <summary>
/// 游戏
/// </summary>
public string GameConnectionString { get; set; }
/// <summary>
/// 扩展
/// </summary>
public string ExtConnectionString { get; set; }
/// <summary>
/// 手机app配置
/// </summary>
public string PhoneConnectionString { get; set; }
/// <summary>
/// redis连接字符串
/// </summary>
public string RedisConnectionString { get; set; }
/// <summary>
/// 域名
/// </summary>
public string DomainName { get; set; }
/// <summary>
/// 标识
/// </summary>
public string Identifier { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 租户
/// </summary>
public Guid TenantId { get; set; }
/// <summary>
/// 项目支付数据
/// </summary>
//public PaymentModel? Payment { get; set; }
/// <summary>
/// 获取数据库连接字符串
/// </summary>
/// <param name="key">usergameextphone</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
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("数据库连接字符串不存在");
}
}
}
/// <summary>
/// 数据库选项
/// </summary>
public enum AppDataBaseType
{
/// <summary>
/// 用户数据库
/// </summary>
User,
/// <summary>
/// 游戏数据库
/// </summary>
Game,
/// <summary>
/// 扩展数据库
/// </summary>
Ext,
/// <summary>
/// app数据库
/// </summary>
App
}
}

View File

@ -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
{
/// <summary>
/// app配置项扩展
/// </summary>
public static class AppConfigurationExtend
{
/// <summary>
/// 配置数据
/// </summary>
//public static ConfigClient AppConfigClient { get; set; }
/// <summary>
///
/// </summary>
public static IConfigurationManager ConfigurationManager { get; set; }
/// <summary>
///
/// </summary>
public static ConcurrentDictionary<string, AppConfig> AppConfigs { get; set; } = new ConcurrentDictionary<string, AppConfig>();
/// <summary>
/// 获取配置项
/// </summary>
/// <param name="domainName"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 初始化app
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IHostApplicationBuilder AddAppConfigClient(this IHostApplicationBuilder builder)
{
//hostBuilder.
//builder..UseAgileConfig(configClient, ConfigClient_ConfigChanged);
//AppConfigClient = configClient;
ConfigurationManager = builder.Configuration;
AppConfigInit(builder.Configuration);
builder.Services.AddScoped<AppConfig>();
builder.AddDataBase();
return builder;
}
/// <summary>
/// 获取配置项
/// </summary>
/// <param name="identifier"></param>
/// <returns></returns>
public static AppConfig? GetAppConfigIdentifier(string identifier)
{
var app = AppConfigs.Where(it => it.Value.Identifier == identifier).Select(it => it.Value).FirstOrDefault();
return app;
}
/// <summary>
/// 配置版本号
/// </summary>
public static string AppVersion
{
get;
set;
}
#region
/// <summary>
///
/// </summary>
/// <param name="appConfig"></param>
/// <param name="tenantInfo"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="appConfig"></param>
/// <param name="newAppConfig"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 初始化租户数据
/// </summary>
/// <param name="configurationManager"></param>
private static void AppConfigInit(IConfigurationManager configurationManager)
{
var tenants = configurationManager.GetSection("Tenant").Get<List<AppConfig>>();
if (tenants != null)
{
ConcurrentDictionary<string, AppConfig> _AppConfigs = new ConcurrentDictionary<string, AppConfig>();
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
}
}

View File

@ -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
{
/// <summary>
///
/// </summary>
public static class JwtTokenManageExtension
{
/// <summary>
/// 添加jwt安全验证配置
/// </summary>
/// <param name="server"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static void AddJwtConfig(this IHostApplicationBuilder builder)
{
var jwtTokenConfig = builder.Configuration.GetSection("JwtTokenConfig").Get<JwtTokenConfig>()!;
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<IJwtAuthManager, JwtManager>();
}
}
}

View File

@ -4,9 +4,17 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Model\CloudGaming.DtoModel\CloudGaming.DtoModel.csproj" />
<ProjectReference Include="..\..\Model\CloudGaming.GameModel\CloudGaming.GameModel.csproj" />
<ProjectReference Include="..\..\Model\CloudGaming.Model\CloudGaming.Model.csproj" />
<ProjectReference Include="..\..\Utile\HuanMeng.DotNetCore\HuanMeng.DotNetCore.csproj" />

View File

@ -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
/// </summary>
public class DAO : DaoBase
{
//private IMultiTenantProvider _multiTenantProvider;
private ITenantInfo _tenantInfo;
private ITenantInfo? _tenantInfo;
/// <summary>
///
@ -32,7 +18,8 @@ namespace CloudGaming.Code.DataAccess
{
if (_tenantInfo == null)
{
this._tenantInfo = _serviceProvider.GetRequiredService<TenantInfo>();
var app = _serviceProvider.GetRequiredService<AppConfig>();
this._tenantInfo = app.ToITenantInfo();
}
return this._tenantInfo;
}
@ -43,16 +30,16 @@ namespace CloudGaming.Code.DataAccess
/// <param name="serviceProvider"></param>
public DAO(IServiceProvider serviceProvider) : base(serviceProvider)
{
this._tenantInfo = serviceProvider.GetRequiredService<ITenantInfo>();
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
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
/// </summary>
public EfCoreDaoBase<CloudGamingCBTContext> daoExt
public EfCoreDaoBase<CloudGamingCBTContext> DaoExt
{
get
{
@ -69,7 +56,7 @@ namespace CloudGaming.Code.DataAccess
{
var dbContext = _serviceProvider.GetRequiredService<CloudGamingCBTContext>();
_daoExt = new EfCoreDaoBase<CloudGamingCBTContext>(dbContext);
_daoExt.context.TenantInfo = TenantInfo;
_daoExt.Context.TenantInfo = TenantInfo;
}
return _daoExt;
}
@ -81,7 +68,7 @@ namespace CloudGaming.Code.DataAccess
/// 数据库[CloudGamingGameContext]
/// 更新删除尽量只操作本bll实例dao获取到的对象取和存要同一个dao
/// </summary>
public EfCoreDaoBase<CloudGamingGameContext> daoGame
public EfCoreDaoBase<CloudGamingGameContext> DaoGame
{
get
{
@ -89,7 +76,7 @@ namespace CloudGaming.Code.DataAccess
{
var dbContext = _serviceProvider.GetRequiredService<CloudGamingGameContext>();
_daoGame = new EfCoreDaoBase<CloudGamingGameContext>(dbContext);
_daoGame.context.TenantInfo = TenantInfo;
_daoGame.Context.TenantInfo = TenantInfo;
}
return _daoGame;
}
@ -102,7 +89,7 @@ namespace CloudGaming.Code.DataAccess
/// 数据库[CloudGamingGameContext]
/// 更新删除尽量只操作本bll实例dao获取到的对象取和存要同一个dao
/// </summary>
public EfCoreDaoBase<CloudGamingPhoneContext> daoPhone
public EfCoreDaoBase<CloudGamingPhoneContext> DaoPhone
{
get
{
@ -110,7 +97,7 @@ namespace CloudGaming.Code.DataAccess
{
var dbContext = _serviceProvider.GetRequiredService<CloudGamingPhoneContext>();
_daoPhone = new EfCoreDaoBase<CloudGamingPhoneContext>(dbContext);
_daoPhone.context.TenantInfo = TenantInfo;
_daoPhone.Context.TenantInfo = TenantInfo;
}
return _daoPhone;
}
@ -123,7 +110,7 @@ namespace CloudGaming.Code.DataAccess
/// 数据库[CloudGamingGameContext]
/// 更新删除尽量只操作本bll实例dao获取到的对象取和存要同一个dao
/// </summary>
public EfCoreDaoBase<CloudGamingUserContext> daoUser
public EfCoreDaoBase<CloudGamingUserContext> DaoUser
{
get
{
@ -131,7 +118,7 @@ namespace CloudGaming.Code.DataAccess
{
var dbContext = _serviceProvider.GetRequiredService<CloudGamingUserContext>();
_daoUser = new EfCoreDaoBase<CloudGamingUserContext>(dbContext);
_daoUser.context.TenantInfo = TenantInfo;
_daoUser.Context.TenantInfo = TenantInfo;
}
return _daoUser;
}

View File

@ -0,0 +1,63 @@
namespace CloudGaming.Code.DataAccess.MultiTenantUtil
{
/// <summary>
/// 多租户扩展
/// </summary>
public static class MultiTenantExtension
{
/// <summary>
/// 多租户 IServiceCollection 扩展
/// </summary>
/// <param name="builder"></param>
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<CloudGamingUserContext>(builder, userConnectionString, AppDataBaseType.User);
//注册 游戏Db
RegisterDbContext<CloudGamingGameContext>(builder, gameConnectionString, AppDataBaseType.Game);
//注册 app配置Db
RegisterDbContext<CloudGamingPhoneContext>(builder, phoneConnectionString, AppDataBaseType.App);
//注册 扩展Db
RegisterDbContext<CloudGamingCBTContext>(builder, extConnectionString, AppDataBaseType.Ext);
}
/// <summary>
/// 注册 DbContext避免重复代码
/// </summary>
/// <typeparam name="TContext">要注册的 DbContext 类型</typeparam>
/// <param name="builder">应用构建器</param>
/// <param name="defaultConnectionString">默认连接字符串</param>
/// <param name="configKey">AppConfig 中的连接字符串键</param>
private static void RegisterDbContext<TContext>(IHostApplicationBuilder builder, string defaultConnectionString, AppDataBaseType configKey) where TContext : DbContext
{
builder.Services.AddDbContext<TContext>((serviceProvider, options) =>
{
var appConfig = serviceProvider.GetRequiredService<AppConfig>();
string dbConnectionString = appConfig?.GetConnectionString(configKey) ?? defaultConnectionString;
if (string.IsNullOrEmpty(dbConnectionString))
{
dbConnectionString = defaultConnectionString;
}
options.UseSqlServer(dbConnectionString);
}, ServiceLifetime.Scoped);
}
/// <summary>
/// 多租户IApplicationBuilder扩展
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
public static IApplicationBuilder UseMultiTenant(this IApplicationBuilder app)
{
return app.UseMiddleware<MultiTenantTenantMiddleware>();
}
}
}

View File

@ -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
{
/// <summary>
/// 多租户中间件
/// </summary>
public class MultiTenantTenantMiddleware
{
private readonly RequestDelegate _next;
public MultiTenantTenantMiddleware(RequestDelegate next)
{
_next = next;
}
/// <summary>
/// 根据HttpContext获取并设置当前租户ID
/// </summary>
/// <param name="context"></param>
/// <param name="_serviceProvider"></param>
/// <param name="appConfig"></param>
/// <returns></returns>
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);
}
}
}

View File

@ -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;

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CloudGaming.GameModel\CloudGaming.GameModel.csproj" />
<ProjectReference Include="..\CloudGaming.Model\CloudGaming.Model.csproj" />
</ItemGroup>
</Project>

View File

@ -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;

View File

@ -4,6 +4,8 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>

View File

@ -4,6 +4,8 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>

View File

@ -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
{ /// <summary>
/// 基本数据库操作,需要安装
/// Microsoft.EntityFrameworkCore
/// Microsoft.EntityFrameworkCore.Relational
{
/// <summary>
/// 基本数据库操作,需要安装 Microsoft.EntityFrameworkCore 和 Microsoft.EntityFrameworkCore.Relational
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public class EfCoreDaoBase<TDbContext> 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;
/// <summary>
/// 构造函数
@ -37,8 +20,9 @@ namespace HuanMeng.DotNetCore.Base
/// <param name="context"></param>
public EfCoreDaoBase(TDbContext context)
{
_context = context;
_context = context ?? throw new ArgumentNullException(nameof(context));
}
/// <summary>
/// 是否手动提交
/// </summary>
@ -47,149 +31,133 @@ namespace HuanMeng.DotNetCore.Base
/// <summary>
/// SqlQueryRaw
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public T SqlQuery<T>(string sql, params object[] parameters) where T : class
public async Task<T?> SqlQueryAsync<T>(string sql, params object[] parameters) where T : class
{
var quiry = context.Database.SqlQueryRaw<T>(sql, parameters);
return quiry.FirstOrDefault();
return await Context.Database.SqlQueryRaw<T>(sql, parameters).FirstOrDefaultAsync();
}
/// <summary>
/// SqlQueryList
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public List<T> SqlQueryList<T>(string sql, params object[] parameters)
public async Task<List<T>> SqlQueryListAsync<T>(string sql, params object[] parameters)
{
var quiry = context.Database.SqlQueryRaw<T>(sql, parameters);
return quiry.ToList();
return await Context.Database.SqlQueryRaw<T>(sql, parameters).ToListAsync();
}
/// <summary>
/// ExecuteSql
/// </summary>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public int ExecuteSql(string sql, params object[] parameters)
public async Task<int> 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;
}
/// <summary>
/// 添加实体
/// </summary>
/// <param name="entity"></param>
public void Add<T>(T entity) where T : class
public async Task AddAsync<T>(T entity) where T : class
{
context.Set<T>().Add(entity);
if (!IsManualSubmit)
context.SaveChanges();
Context.Set<T>().Add(entity);
await AutoSaveChangesAsync();
}
/// <summary>
/// 批量添加实体
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
public void AddRange<T>(List<T> entity) where T : class
public async Task AddRangeAsync<T>(IEnumerable<T> entities) where T : class
{
context.Set<T>().AddRange(entity);
if (!IsManualSubmit)
context.SaveChanges();
Context.Set<T>().AddRange(entities);
await AutoSaveChangesAsync();
}
/// <summary>
/// 删除某个实体
/// </summary>
/// <param name="entity"></param>
public void Delete<T>(T entity) where T : class
public async Task DeleteAsync<T>(T entity) where T : class
{
context.Entry<T>(entity).State = EntityState.Deleted; //整条更新
//context.Set<T>().Remove(entity);
if (!IsManualSubmit)
context.SaveChanges();
Context.Entry(entity).State = EntityState.Deleted;
await AutoSaveChangesAsync();
}
/// <summary>
/// 更新实体
/// </summary>
/// <param name="entity"></param>
public void Update<T>(T entity) where T : class
public async Task UpdateAsync<T>(T entity) where T : class
{
//一般不需要整条更新,按需更新字段即可
if (context.Entry<T>(entity).State == EntityState.Detached)
if (Context.Entry(entity).State == EntityState.Detached)
{
context.Set<T>().Attach(entity);
context.Entry<T>(entity).State = EntityState.Modified; //整条更新
Context.Set<T>().Attach(entity);
Context.Entry(entity).State = EntityState.Modified;
}
await AutoSaveChangesAsync();
}
/// <summary>
/// 清除上下文跟踪
/// </summary>
public void RemoveTracking<T>(T entity) where T : class
{
if (Context.Entry(entity).State != EntityState.Detached)
{
Context.Entry(entity).State = EntityState.Detached;
}
}
/// <summary>
/// 获取实体,根据主键
/// </summary>
public async Task<T?> GetModelAsync<T>(params object[] keyValues) where T : class
{
return await Context.Set<T>().FindAsync(keyValues);
}
/// <summary>
/// 获取实体,根据条件
/// </summary>
public async Task<T?> GetModelAsync<T>(Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
var query = Context.Set<T>().AsQueryable();
if (isNoTracking)
query = query.AsNoTracking();
return await query.FirstOrDefaultAsync(where);
}
/// <summary>
/// 获取列表数据
/// </summary>
public IQueryable<T> GetList<T>(Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
var query = Context.Set<T>().Where(where);
return isNoTracking ? query.AsNoTracking() : query;
}
/// <summary>
/// 获取记录数量
/// </summary>
public async Task<int> GetCountAsync<T>(Expression<Func<T, bool>> where) where T : class
{
return await Context.Set<T>().AsNoTracking().CountAsync(where);
}
/// <summary>
/// 判断是否存在记录
/// </summary>
public async Task<bool> ExistsAsync<T>(Expression<Func<T, bool>> where) where T : class
{
return await Context.Set<T>().AsNoTracking().AnyAsync(where);
}
/// <summary>
/// 根据提交状态决定是否自动保存更改
/// </summary>
private async Task AutoSaveChangesAsync()
{
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 清除上下文跟踪(清除缓存) by wyg
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
public void removeTracking<T>(T entity) where T : class
{
if (context.Entry<T>(entity).State != EntityState.Detached)
context.Entry<T>(entity).State = EntityState.Detached;
await Context.SaveChangesAsync();
}
/// <summary>
/// 获取实体,从缓存中,根据键值获取
/// </summary>
/// <param name="keyName"></param>
/// <param name="keyValue"></param>
/// <returns></returns>
public T GetModel<T>(params object[] keyValues) where T : class
{
return context.Set<T>().Find(keyValues);
}
/// <summary>
/// 获取实体,根据条件获取,从数据库获取
/// </summary>
/// <param name="where">筛选条件</param>
/// <param name="isNoTracking">默认false会进行缓存并跟踪(可按需update字段)true不需要缓存和跟踪(一般只读查询使用)</param>
/// <returns></returns>
public T GetModel<T>(System.Linq.Expressions.Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
if (!isNoTracking)
return context.Set<T>().FirstOrDefault(where); //可按需update字段
return context.Set<T>().AsNoTracking<T>().FirstOrDefault(where); //一般只读查询使用
}
/// <summary>
/// 获取列表数据返回IQueryable
/// </summary>
/// <param name="where">筛选条件</param>
/// <param name="isNoTracking">默认false会进行缓存并跟踪(可按需update字段)true不需要缓存和跟踪(一般只读查询使用)</param>
/// <returns></returns>
public IQueryable<T> GetList<T>(System.Linq.Expressions.Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
if (!isNoTracking)
return context.Set<T>().Where(where).AsQueryable();
return context.Set<T>().Where(where).AsNoTracking<T>().AsQueryable();
}
public int GetCount<T>(System.Linq.Expressions.Expression<Func<T, bool>> where) where T : class
{
return context.Set<T>().AsNoTracking<T>().Count(where);
}
public bool Exists<T>(System.Linq.Expressions.Expression<Func<T, bool>> where) where T : class
{
//return context.Set<T>().AsNoTracking<T>().Any(where);
return context.Set<T>().AsNoTracking<T>().Count(where) > 0;
}
}
}

View File

@ -4,6 +4,8 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
@ -20,6 +22,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="StackExchange.Redis" Version="2.8.16" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.8.1" />
</ItemGroup>
</Project>

View File

@ -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
{
/// <summary>
/// jwt,不保存
/// </summary>
/// <param name="jwtTokenConfig"></param>
public class JwtManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager
{
/// <summary>
/// 后面可以存储在数据库或分布式缓存中
/// </summary>
private readonly ConcurrentDictionary<string, JwtRefreshToken> _usersRefreshTokens = new();
/// <summary>
/// 获取加密字段
/// </summary>
private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret);
/// <summary>
/// 删除过期token
/// </summary>
/// <param name="now"></param>
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 _);
}
}
/// <summary>
/// 根据用户名删除token
/// </summary>
/// <param name="userName"></param>
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 _);
}
}
/// <summary>
/// 创建token
/// </summary>
/// <param name="username">用户名</param>
/// <param name="claims">用户项</param>
/// <param name="now">过期时间</param>
/// <returns></returns>
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
};
}
/// <summary>
/// 刷新token
/// </summary>
/// <param name="refreshToken"></param>
/// <param name="accessToken"></param>
/// <param name="now"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
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);
}
/// <summary>
/// 解析token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
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);
}
/// <summary>
/// 获取刷新的token
/// </summary>
/// <returns></returns>
private static string GenerateRefreshTokenString()
{
var randomNumber = new byte[32];
using var randomNumberGenerator = RandomNumberGenerator.Create();
randomNumberGenerator.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public IImmutableDictionary<string, JwtRefreshToken> UsersRefreshTokensReadOnlyDictionary => throw new NotImplementedException();
}
}

View File

@ -20,9 +20,5 @@ namespace HuanMeng.DotNetCore.MultiTenant.Contract
/// </summary>
string? Name { get; set; }
/// <summary>
/// 数据库连接字符串
/// </summary>
string? ConnectionString { get; set; }
}
}

View File

@ -33,9 +33,6 @@ namespace HuanMeng.DotNetCore.MultiTenant
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 租户数据库连接字符串
/// </summary>
public string? ConnectionString { get; set; }
}
}

View File

@ -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
{
/// <summary>
/// 自定义参数过滤器
/// </summary>
public class LowercaseParameterFilter : IParameterFilter
{
/// <summary>
///
/// </summary>
/// <param name="parameter"></param>
/// <param name="context"></param>
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
{
// 将参数名称改为小写开头
parameter.Name = Char.ToLower(parameter.Name[0]) + parameter.Name.Substring(1);
}
}
/// <summary>
/// 自定义参数过滤器
/// </summary>
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<string, OpenApiSchema>(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);
}
}
}
}
}
}
}

View File

@ -14,22 +14,22 @@ namespace HuanMeng.DotNetCore.Utility.AssemblyHelper
/// <summary>
/// 获取或设置程序集的版本。
/// </summary>
public string Version { get; set; }
public string? Version { get; set; }
/// <summary>
/// 获取或设置程序集的文件版本。
/// </summary>
public string FileVersion { get; set; }
public string? FileVersion { get; set; }
/// <summary>
/// 获取或设置程序集版本。
/// </summary>
public string AssemblyVersion { get; set; }
public string? AssemblyVersion { get; set; }
/// <summary>
/// 获取或设置程序集的信息性版本。
/// </summary>
public string InformationalVersion { get; set; }
public string? InformationalVersion { get; set; }
///// <summary>
///// 获取或设置与程序集关联的公司名称。
@ -44,12 +44,12 @@ namespace HuanMeng.DotNetCore.Utility.AssemblyHelper
/// <summary>
/// 获取或设置与程序集关联的版权信息。
/// </summary>
public string Copyright { get; set; }
public string? Copyright { get; set; }
/// <summary>
/// 获取或设置程序集的描述信息。
/// </summary>
public string Description { get; set; }
public string? Description { get; set; }
}
}

View File

@ -8,6 +8,9 @@ using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.Utility
{
/// <summary>
///
/// </summary>
public static class HttpContextExtensions
{
/// <summary>

View File

@ -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() { }
/// <summary>
///
/// </summary>
public string EncryptedData;
public string? EncryptedData;
/// <summary>
///
/// </summary>
public string Iv;
public string? Iv;
public int UserId { get; set; }