添加jwt
This commit is contained in:
parent
977972b029
commit
b58cc5f229
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
109
src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs
Normal file
109
src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs
Normal 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">user,game,ext,phone</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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs
Normal file
15
src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs
Normal 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;
|
||||
|
|
@ -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>
|
||||
10
src/CloudGaming/Model/CloudGaming.DtoModel/GlobalUsings.cs
Normal file
10
src/CloudGaming/Model/CloudGaming.DtoModel/GlobalUsings.cs
Normal 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;
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -20,9 +20,5 @@ namespace HuanMeng.DotNetCore.MultiTenant.Contract
|
|||
/// </summary>
|
||||
string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库连接字符串
|
||||
/// </summary>
|
||||
string? ConnectionString { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,9 +33,6 @@ namespace HuanMeng.DotNetCore.MultiTenant
|
|||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户数据库连接字符串
|
||||
/// </summary>
|
||||
public string? ConnectionString { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace HuanMeng.DotNetCore.Utility
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user