refactor: ai+人工重构优化 framework

This commit is contained in:
橙子
2025-02-23 03:06:06 +08:00
parent f9341fd2ac
commit 3e07ca822a
61 changed files with 2604 additions and 1260 deletions

View File

@@ -4,13 +4,23 @@ using Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
{ {
/// <summary>
/// 提供API信息处理的应用程序构建器扩展方法
/// </summary>
public static class ApiInfoBuilderExtensions public static class ApiInfoBuilderExtensions
{ {
public static IApplicationBuilder UseYiApiHandlinge([NotNull] this IApplicationBuilder app) /// <summary>
/// 使用Yi框架的API信息处理中间件
/// </summary>
/// <param name="builder">应用程序构建器实例</param>
/// <returns>配置后的应用程序构建器实例</returns>
/// <exception cref="ArgumentNullException">当builder参数为null时抛出</exception>
public static IApplicationBuilder UseApiInfoHandling([NotNull] this IApplicationBuilder builder)
{ {
app.UseMiddleware<ApiInfoMiddleware>(); // 添加API信息处理中间件到请求管道
return app; builder.UseMiddleware<ApiInfoMiddleware>();
return builder;
} }
} }
} }

View File

@@ -5,49 +5,101 @@ using Volo.Abp.AspNetCore.Mvc;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
{ {
public static class SwaggerBuilderExtensons /// <summary>
/// Swagger构建器扩展类
/// </summary>
public static class SwaggerBuilderExtensions
{ {
public static IApplicationBuilder UseYiSwagger(this IApplicationBuilder app, params SwaggerModel[] swaggerModels) /// <summary>
/// 配置并使用Yi框架的Swagger中间件
/// </summary>
/// <param name="app">应用程序构建器</param>
/// <param name="swaggerConfigs">Swagger配置模型数组</param>
/// <returns>应用程序构建器</returns>
public static IApplicationBuilder UseYiSwagger(
this IApplicationBuilder app,
params SwaggerConfiguration[] swaggerConfigs)
{ {
var mvcOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value; if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
var mvcOptions = app.ApplicationServices
.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>()
.Value;
// 启用Swagger中间件
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(c =>
// 配置SwaggerUI
app.UseSwaggerUI(options =>
{ {
foreach (var setting in mvcOptions.ConventionalControllers.ConventionalControllerSettings) // 添加约定控制器的Swagger终结点
var conventionalSettings = mvcOptions.ConventionalControllers.ConventionalControllerSettings;
foreach (var setting in conventionalSettings)
{ {
c.SwaggerEndpoint($"/swagger/{setting.RemoteServiceName}/swagger.json", setting.RemoteServiceName); options.SwaggerEndpoint(
} $"/swagger/{setting.RemoteServiceName}/swagger.json",
if (mvcOptions.ConventionalControllers.ConventionalControllerSettings.Count==0&&swaggerModels.Length == 0) setting.RemoteServiceName);
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework");
}
else
{
foreach (var k in swaggerModels)
{
c.SwaggerEndpoint(k.Url, k.Name);
}
} }
// 如果没有配置任何终结点,使用默认配置
if (!conventionalSettings.Any() && (swaggerConfigs == null || !swaggerConfigs.Any()))
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework");
return;
}
// 添加自定义Swagger配置的终结点
if (swaggerConfigs != null)
{
foreach (var config in swaggerConfigs)
{
options.SwaggerEndpoint(config.Url, config.Name);
}
}
}); });
return app; return app;
} }
}
} /// <summary>
public class SwaggerModel /// Swagger配置模型
/// </summary>
public class SwaggerConfiguration
{ {
public SwaggerModel(string name) private const string DefaultSwaggerUrl = "/swagger/v1/swagger.json";
/// <summary>
/// Swagger JSON文档的URL
/// </summary>
public string Url { get; }
/// <summary>
/// Swagger文档的显示名称
/// </summary>
public string Name { get; }
/// <summary>
/// 使用默认URL创建Swagger配置
/// </summary>
/// <param name="name">文档显示名称</param>
public SwaggerConfiguration(string name)
: this(DefaultSwaggerUrl, name)
{ {
this.Name = name;
this.Url = "/swagger/v1/swagger.json";
} }
public SwaggerModel(string url, string name)
/// <summary>
/// 创建自定义Swagger配置
/// </summary>
/// <param name="url">Swagger JSON文档URL</param>
/// <param name="name">文档显示名称</param>
public SwaggerConfiguration(string url, string name)
{ {
this.Url = url; Url = url ?? throw new ArgumentNullException(nameof(url));
this.Name = name; Name = name ?? throw new ArgumentNullException(nameof(name));
} }
public string Url { get; set; }
public string Name { get; set; }
} }
} }

View File

@@ -1,39 +1,61 @@
using System.Diagnostics; using System.Diagnostics;
using System.Net.Http;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
using Yi.Framework.Core.Extensions; using Yi.Framework.Core.Extensions;
using static System.Net.WebRequestMethods;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares
{ {
/// <summary>
/// API响应信息处理中间件
/// 主要用于处理特定文件类型的响应头信息
/// </summary>
[DebuggerStepThrough] [DebuggerStepThrough]
public class ApiInfoMiddleware : IMiddleware, ITransientDependency public class ApiInfoMiddleware : IMiddleware, ITransientDependency
{ {
/// <summary>
/// 处理HTTP请求的中间件方法
/// </summary>
/// <param name="context">HTTP上下文</param>
/// <param name="next">请求处理委托</param>
/// <returns>异步任务</returns>
public async Task InvokeAsync(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{ {
context.Response.OnStarting([DebuggerStepThrough] () => // 在响应开始时处理文件下载相关的响应头
context.Response.OnStarting(() =>
{ {
if (context.Response.StatusCode == StatusCodes.Status200OK HandleFileDownloadResponse(context);
&& context.Response.Headers["Content-Type"].ToString() == "application/vnd.ms-excel")
{
context.FileAttachmentHandle($"{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.xlsx");
}
if (context.Response.StatusCode == StatusCodes.Status200OK &&
context.Response.Headers["Content-Type"].ToString() == "application/x-zip-compressed")
{
context.FileAttachmentHandle($"{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
}
return Task.CompletedTask; return Task.CompletedTask;
}); });
// 继续处理管道中的下一个中间件
await next(context); await next(context);
}
/// <summary>
/// 处理文件下载响应的响应头信息
/// </summary>
/// <param name="context">HTTP上下文</param>
private static void HandleFileDownloadResponse(HttpContext context)
{
// 仅处理状态码为200的响应
if (context.Response.StatusCode != StatusCodes.Status200OK)
{
return;
}
var contentType = context.Response.Headers["Content-Type"].ToString();
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
// 处理Excel文件下载
if (contentType == "application/vnd.ms-excel")
{
context.FileAttachmentHandle($"{timestamp}.xlsx");
}
// 处理ZIP文件下载
else if (contentType == "application/x-zip-compressed")
{
context.FileAttachmentHandle($"{timestamp}.zip");
}
} }
} }
} }

View File

@@ -9,129 +9,193 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Options; using Volo.Abp.Options;
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{ {
/// <summary>
/// Swagger生成器扩展类
/// </summary>
public static class SwaggerAddExtensions public static class SwaggerAddExtensions
{ {
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services, /// <summary>
Action<SwaggerGenOptions>? action = null) /// 添加Yi框架的Swagger生成器服务
/// </summary>
/// <typeparam name="TProgram">程序入口类型</typeparam>
/// <param name="services">服务集合</param>
/// <param name="setupAction">自定义配置动作</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddYiSwaggerGen<TProgram>(
this IServiceCollection services,
Action<SwaggerGenOptions>? setupAction = null)
{ {
// 获取MVC配置选项
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure(); var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
var mvcSettings = // 获取并去重远程服务名称
mvcOptions.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName); var remoteServiceSettings = mvcOptions.ConventionalControllers
.ConventionalControllerSettings
.DistinctBy(x => x.RemoteServiceName);
services.AddAbpSwaggerGen( services.AddAbpSwaggerGen(
options => options =>
{ {
if (action is not null) // 应用外部配置
{ setupAction?.Invoke(options);
action.Invoke(options);
// 配置API文档分组
ConfigureApiGroups(options, remoteServiceSettings);
// 配置API文档过滤器
ConfigureApiFilter(options, remoteServiceSettings);
// 配置Schema ID生成规则
options.CustomSchemaIds(type => type.FullName);
// 包含XML注释文档
IncludeXmlComments<TProgram>(options);
// 配置JWT认证
ConfigureJwtAuthentication(options);
// 添加自定义过滤器
ConfigureCustomFilters(options);
}
);
return services;
} }
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准 /// <summary>
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName)) /// 配置API分组
/// </summary>
private static void ConfigureApiGroups(
SwaggerGenOptions options,
IEnumerable<ConventionalControllerSetting> settings)
{
foreach (var setting in settings.OrderBy(x => x.RemoteServiceName))
{ {
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName)) if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{ {
options.SwaggerDoc(setting.RemoteServiceName, options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo
new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" }); {
Title = setting.RemoteServiceName,
Version = "v1"
});
}
} }
} }
// 根据分组名称过滤 API 文档 /// <summary>
/// 配置API文档过滤器
/// </summary>
private static void ConfigureApiFilter(
SwaggerGenOptions options,
IEnumerable<ConventionalControllerSetting> settings)
{
options.DocInclusionPredicate((docName, apiDesc) => options.DocInclusionPredicate((docName, apiDesc) =>
{ {
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerDesc)
{ {
var settingOrNull = mvcSettings var matchedSetting = settings
.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly) .FirstOrDefault(x => x.Assembly == controllerDesc.ControllerTypeInfo.Assembly);
.FirstOrDefault(); return matchedSetting?.RemoteServiceName == docName;
if (settingOrNull is not null)
{
return docName == settingOrNull.RemoteServiceName;
} }
}
return false; return false;
}); });
}
options.CustomSchemaIds(type => type.FullName); /// <summary>
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location); /// 包含XML注释文档
/// </summary>
private static void IncludeXmlComments<TProgram>(SwaggerGenOptions options)
{
var basePath = Path.GetDirectoryName(typeof(TProgram).Assembly.Location);
if (basePath is not null) if (basePath is not null)
{ {
foreach (var item in Directory.GetFiles(basePath, "*.xml")) foreach (var xmlFile in Directory.GetFiles(basePath, "*.xml"))
{ {
options.IncludeXmlComments(item, true); options.IncludeXmlComments(xmlFile, true);
}
} }
} }
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme() /// <summary>
/// 配置JWT认证
/// </summary>
private static void ConfigureJwtAuthentication(SwaggerGenOptions options)
{ {
Description = "直接输入Token即可", options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme
{
Description = "请在此输入JWT Token",
Name = "Authorization", Name = "Authorization",
In = ParameterLocation.Header, In = ParameterLocation.Header,
Type = SecuritySchemeType.Http, Type = SecuritySchemeType.Http,
Scheme = "bearer" Scheme = "bearer"
}); });
var scheme = new OpenApiSecurityScheme()
var scheme = new OpenApiSecurityScheme
{ {
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" } Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "JwtBearer"
}
}; };
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{ {
[scheme] = new string[0] [scheme] = Array.Empty<string>()
}); });
options.OperationFilter<AddRequiredHeaderParameter>();
options.SchemaFilter<EnumSchemaFilter>();
} }
);
return services;
}
}
/// <summary> /// <summary>
/// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述 /// 配置自定义过滤器
/// </summary>
private static void ConfigureCustomFilters(SwaggerGenOptions options)
{
options.OperationFilter<TenantHeaderOperationFilter>();
options.SchemaFilter<EnumSchemaFilter>();
}
}
/// <summary>
/// Swagger文档枚举字段显示过滤器
/// </summary> /// </summary>
public class EnumSchemaFilter : ISchemaFilter public class EnumSchemaFilter : ISchemaFilter
{ {
/// <summary> /// <summary>
/// 实现接口 /// 应用枚举架构过滤器
/// </summary> /// </summary>
/// <param name="model"></param> /// <param name="schema">OpenAPI架构</param>
/// <param name="context"></param> /// <param name="context">架构过滤器上下文</param>
public void Apply(OpenApiSchema model, SchemaFilterContext context) public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{ {
if (context.Type.IsEnum) if (!context.Type.IsEnum) return;
{
model.Enum.Clear();
model.Type = "string";
model.Format = null;
schema.Enum.Clear();
schema.Type = "string";
schema.Format = null;
StringBuilder stringBuilder = new StringBuilder(); var enumDescriptions = new StringBuilder();
Enum.GetNames(context.Type) foreach (var enumName in Enum.GetNames(context.Type))
.ToList()
.ForEach(name =>
{ {
Enum e = (Enum)Enum.Parse(context.Type, name); var enumValue = (Enum)Enum.Parse(context.Type, enumName);
var descrptionOrNull = GetEnumDescription(e); var description = GetEnumDescription(enumValue);
model.Enum.Add(new OpenApiString(name)); var enumIntValue = Convert.ToInt64(enumValue);
stringBuilder.Append(
$"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />"); schema.Enum.Add(new OpenApiString(enumName));
}); enumDescriptions.AppendLine(
model.Description = stringBuilder.ToString(); $"【枚举:{enumName}{(description is null ? string.Empty : $"({description})")}={enumIntValue}】");
} }
schema.Description = enumDescriptions.ToString();
} }
/// <summary>
/// 获取枚举描述特性值
/// </summary>
private static string? GetEnumDescription(Enum value) private static string? GetEnumDescription(Enum value)
{ {
var fieldInfo = value.GetType().GetField(value.ToString()); var fieldInfo = value.GetType().GetField(value.ToString());
@@ -140,22 +204,30 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
} }
} }
/// <summary>
public class AddRequiredHeaderParameter : IOperationFilter /// 租户头部参数过滤器
/// </summary>
public class TenantHeaderOperationFilter : IOperationFilter
{ {
public static string HeaderKey { get; set; } = "__tenant"; /// <summary>
/// 租户标识键名
/// </summary>
private const string TenantHeaderKey = "__tenant";
/// <summary>
/// 应用租户头部参数过滤器
/// </summary>
public void Apply(OpenApiOperation operation, OperationFilterContext context) public void Apply(OpenApiOperation operation, OperationFilterContext context)
{ {
if (operation.Parameters == null) operation.Parameters ??= new List<OpenApiParameter>();
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter operation.Parameters.Add(new OpenApiParameter
{ {
Name = HeaderKey, Name = TenantHeaderKey,
In = ParameterLocation.Header, In = ParameterLocation.Header,
Required = false, Required = false,
AllowEmptyValue = true, AllowEmptyValue = true,
Description = "租户id或者租户名称(可空为默认租户)" Description = "租户ID或租户名称(留空表示默认租户)"
}); });
} }
} }

View File

@@ -9,13 +9,31 @@ using Volo.Abp.Reflection;
namespace Yi.Framework.AspNetCore.Mvc namespace Yi.Framework.AspNetCore.Mvc
{ {
/// <summary>
/// 自定义路由构建器用于生成API路由规则
/// </summary>
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
[ExposeServices(typeof(IConventionalRouteBuilder))] [ExposeServices(typeof(IConventionalRouteBuilder))]
public class YiConventionalRouteBuilder : ConventionalRouteBuilder public class YiConventionalRouteBuilder : ConventionalRouteBuilder
{ {
public YiConventionalRouteBuilder(IOptions<AbpConventionalControllerOptions> options) : base(options) /// <summary>
/// 构造函数
/// </summary>
/// <param name="options">ABP约定控制器配置选项</param>
public YiConventionalRouteBuilder(IOptions<AbpConventionalControllerOptions> options)
: base(options)
{ {
} }
/// <summary>
/// 构建API路由
/// </summary>
/// <param name="rootPath">根路径</param>
/// <param name="controllerName">控制器名称</param>
/// <param name="action">Action模型</param>
/// <param name="httpMethod">HTTP方法</param>
/// <param name="configuration">控制器配置</param>
/// <returns>构建的路由URL</returns>
public override string Build( public override string Build(
string rootPath, string rootPath,
string controllerName, string controllerName,
@@ -23,51 +41,97 @@ namespace Yi.Framework.AspNetCore.Mvc
string httpMethod, string httpMethod,
[CanBeNull] ConventionalControllerSetting configuration) [CanBeNull] ConventionalControllerSetting configuration)
{ {
// 获取API路由前缀
var apiRoutePrefix = GetApiRoutePrefix(action, configuration); var apiRoutePrefix = GetApiRoutePrefix(action, configuration);
var controllerNameInUrl =
NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration);
var url = $"{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}"; // 规范化控制器名称
var normalizedControllerName = NormalizeUrlControllerName(
rootPath,
controllerName,
action,
httpMethod,
configuration);
//Add {id} path if needed // 构建基础URL
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); var url = $"{rootPath}/{NormalizeControllerNameCase(normalizedControllerName, configuration)}";
if (idParameterModel != null)
{
if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
{
url += "/{id}";
}
else
{
var properties = idParameterModel
.ParameterType
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties) // 处理ID参数路由
{ url = BuildIdParameterRoute(url, action, configuration);
url += "/{" + NormalizeIdPropertyNameCase(property, configuration) + "}";
}
}
}
//Add action name if needed // 处理Action名称路由
var actionNameInUrl = NormalizeUrlActionName(rootPath, controllerName, action, httpMethod, configuration); url = BuildActionNameRoute(url, rootPath, controllerName, action, httpMethod, configuration);
if (!actionNameInUrl.IsNullOrEmpty())
{
url += $"/{NormalizeActionNameCase(actionNameInUrl, configuration)}";
//Add secondary Id
var secondaryIds = action.Parameters
.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal)).ToList();
if (secondaryIds.Count == 1)
{
url += $"/{{{NormalizeSecondaryIdNameCase(secondaryIds[0], configuration)}}}";
}
}
return url; return url;
} }
/// <summary>
/// 构建ID参数路由部分
/// </summary>
private string BuildIdParameterRoute(
string baseUrl,
ActionModel action,
ConventionalControllerSetting configuration)
{
var idParameter = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
if (idParameter == null)
{
return baseUrl;
}
// 处理原始类型ID
if (TypeHelper.IsPrimitiveExtended(idParameter.ParameterType, includeEnums: true))
{
return $"{baseUrl}/{{id}}";
}
// 处理复杂类型ID
var properties = idParameter.ParameterType
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
baseUrl += $"/{{{NormalizeIdPropertyNameCase(property, configuration)}}}";
}
return baseUrl;
}
/// <summary>
/// 构建Action名称路由部分
/// </summary>
private string BuildActionNameRoute(
string baseUrl,
string rootPath,
string controllerName,
ActionModel action,
string httpMethod,
ConventionalControllerSetting configuration)
{
var actionNameInUrl = NormalizeUrlActionName(
rootPath,
controllerName,
action,
httpMethod,
configuration);
if (actionNameInUrl.IsNullOrEmpty())
{
return baseUrl;
}
baseUrl += $"/{NormalizeActionNameCase(actionNameInUrl, configuration)}";
// 处理次要ID参数
var secondaryIds = action.Parameters
.Where(p => p.ParameterName.EndsWith("Id", StringComparison.Ordinal))
.ToList();
if (secondaryIds.Count == 1)
{
baseUrl += $"/{{{NormalizeSecondaryIdNameCase(secondaryIds[0], configuration)}}}";
}
return baseUrl;
}
} }
} }

View File

@@ -13,24 +13,46 @@ using Volo.Abp.Reflection;
namespace Yi.Framework.AspNetCore.Mvc namespace Yi.Framework.AspNetCore.Mvc
{ {
/// <summary>
/// 自定义服务约定实现,用于处理API路由和HTTP方法约束
/// </summary>
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)] [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
[ExposeServices(typeof(IAbpServiceConvention))] [ExposeServices(typeof(IAbpServiceConvention))]
public class YiServiceConvention : AbpServiceConvention public class YiServiceConvention : AbpServiceConvention
{ {
public YiServiceConvention(IOptions<AbpAspNetCoreMvcOptions> options, IConventionalRouteBuilder conventionalRouteBuilder) : base(options, conventionalRouteBuilder) /// <summary>
/// 初始化服务约定的新实例
/// </summary>
/// <param name="options">ABP AspNetCore MVC 配置选项</param>
/// <param name="conventionalRouteBuilder">约定路由构建器</param>
public YiServiceConvention(
IOptions<AbpAspNetCoreMvcOptions> options,
IConventionalRouteBuilder conventionalRouteBuilder)
: base(options, conventionalRouteBuilder)
{ {
} }
protected override void ConfigureSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration) /// <summary>
/// 配置选择器,处理路由和HTTP方法约束
/// </summary>
protected override void ConfigureSelector(
string rootPath,
string controllerName,
ActionModel action,
ConventionalControllerSetting? configuration)
{ {
// 移除空选择器
RemoveEmptySelectors(action.Selectors); RemoveEmptySelectors(action.Selectors);
var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod); // 检查远程服务特性
if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(action.ActionMethod)) var remoteServiceAttr = ReflectionHelper
.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod);
if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(action.ActionMethod))
{ {
return; return;
} }
// 根据选择器是否存在执行不同的配置
if (!action.Selectors.Any()) if (!action.Selectors.Any())
{ {
AddAbpServiceSelector(rootPath, controllerName, action, configuration); AddAbpServiceSelector(rootPath, controllerName, action, configuration);
@@ -41,56 +63,92 @@ namespace Yi.Framework.AspNetCore.Mvc
} }
} }
/// <summary>
protected override void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration) /// 规范化选择器路由
{ /// </summary>
base.AddAbpServiceSelector(rootPath, controllerName, action, configuration); protected override void NormalizeSelectorRoutes(
} string rootPath,
string controllerName,
protected override void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration) ActionModel action,
ConventionalControllerSetting? configuration)
{ {
foreach (var selector in action.Selectors) foreach (var selector in action.Selectors)
{ {
var httpMethod = selector.ActionConstraints // 获取HTTP方法约束
var httpMethod = GetOrCreateHttpMethod(selector, action, configuration);
// 处理路由模板
ConfigureRouteTemplate(selector, rootPath, controllerName, action, httpMethod, configuration);
// 确保HTTP方法约束存在
EnsureHttpMethodConstraint(selector, httpMethod);
}
}
/// <summary>
/// 获取或创建HTTP方法
/// </summary>
private string GetOrCreateHttpMethod(
SelectorModel selector,
ActionModel action,
ConventionalControllerSetting? configuration)
{
return selector.ActionConstraints
.OfType<HttpMethodActionConstraint>() .OfType<HttpMethodActionConstraint>()
.FirstOrDefault()? .FirstOrDefault()?
.HttpMethods? .HttpMethods?
.FirstOrDefault(); .FirstOrDefault()
?? SelectHttpMethod(action, configuration);
if (httpMethod == null)
{
httpMethod = SelectHttpMethod(action, configuration);
} }
/// <summary>
/// 配置路由模板
/// </summary>
private void ConfigureRouteTemplate(
SelectorModel selector,
string rootPath,
string controllerName,
ActionModel action,
string httpMethod,
ConventionalControllerSetting? configuration)
{
if (selector.AttributeRouteModel == null) if (selector.AttributeRouteModel == null)
{ {
selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration); selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(
rootPath,
controllerName,
action,
httpMethod,
configuration);
} }
else else
{ {
NormalizeAttributeRouteTemplate(selector, rootPath);
}
}
/// <summary>
/// 规范化特性路由模板
/// </summary>
private void NormalizeAttributeRouteTemplate(SelectorModel selector, string rootPath)
{
var template = selector.AttributeRouteModel.Template; var template = selector.AttributeRouteModel.Template;
if (!template.StartsWith("/")) if (!template.StartsWith("/"))
{ {
var route = $"{rootPath}/{template}"; selector.AttributeRouteModel.Template = $"{rootPath}/{template}";
selector.AttributeRouteModel.Template = route; }
} }
} /// <summary>
/// 确保HTTP方法约束存在
/// </summary>
private void EnsureHttpMethodConstraint(SelectorModel selector, string httpMethod)
{
if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any()) if (!selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Any())
{ {
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod })); selector.ActionConstraints.Add(
} new HttpMethodActionConstraint(new[] { httpMethod }));
} }
} }
} }
} }

View File

@@ -5,32 +5,53 @@ using Volo.Abp.AspNetCore.WebClientInfo;
namespace Yi.Framework.AspNetCore; namespace Yi.Framework.AspNetCore;
/// <summary>
/// 真实IP地址提供程序,支持代理服务器场景
/// </summary>
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
{ {
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger, private const string XForwardedForHeader = "X-Forwarded-For";
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
/// <summary>
/// 初始化真实IP地址提供程序的新实例
/// </summary>
public RealIpHttpContextWebClientInfoProvider(
ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor)
: base(logger, httpContextAccessor)
{ {
} }
/// <summary>
/// 获取客户端IP地址,优先从X-Forwarded-For头部获取
/// </summary>
/// <returns>客户端IP地址</returns>
protected override string? GetClientIpAddress() protected override string? GetClientIpAddress()
{ {
try try
{ {
var httpContext = HttpContextAccessor.HttpContext; var httpContext = HttpContextAccessor.HttpContext;
if (httpContext == null)
var headers = httpContext?.Request?.Headers;
if (headers != null && headers.ContainsKey("X-Forwarded-For"))
{ {
httpContext.Connection.RemoteIpAddress = return null;
IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault());
} }
return httpContext?.Connection?.RemoteIpAddress?.ToString(); var headers = httpContext.Request?.Headers;
if (headers != null && headers.ContainsKey(XForwardedForHeader))
{
// 从X-Forwarded-For获取真实客户端IP
var forwardedIp = headers[XForwardedForHeader].FirstOrDefault();
if (!string.IsNullOrEmpty(forwardedIp))
{
httpContext.Connection.RemoteIpAddress = IPAddress.Parse(forwardedIp);
}
}
return httpContext.Connection?.RemoteIpAddress?.ToString();
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogException(ex, LogLevel.Warning); Logger.LogWarning(ex, "获取客户端IP地址时发生异常");
return null; return null;
} }
} }

View File

@@ -1,49 +1,55 @@
namespace Yi.Framework.AspNetCore namespace Yi.Framework.AspNetCore
{ {
/// <summary>
/// 远程服务成功响应信息
/// </summary>
[Serializable] [Serializable]
public class RemoteServiceSuccessInfo public class RemoteServiceSuccessInfo
{ {
/// <summary> /// <summary>
/// Creates a new instance of <see cref="RemoteServiceSuccessInfo"/>. /// 获取或设置响应代码
/// </summary>
public string? Code { get; private set; }
/// <summary>
/// 获取或设置响应消息
/// </summary>
public string? Message { get; private set; }
/// <summary>
/// 获取或设置详细信息
/// </summary>
public string? Details { get; private set; }
/// <summary>
/// 获取或设置响应数据
/// </summary>
public object? Data { get; private set; }
/// <summary>
/// 初始化远程服务成功响应信息的新实例
/// </summary> /// </summary>
public RemoteServiceSuccessInfo() public RemoteServiceSuccessInfo()
{ {
} }
/// <summary> /// <summary>
/// Creates a new instance of <see cref="RemoteServiceSuccessInfo"/>. /// 使用指定参数初始化远程服务成功响应信息的新实例
/// </summary> /// </summary>
/// <param name="code">Error code</param> /// <param name="message">响应消息</param>
/// <param name="details">Error details</param> /// <param name="details">详细信息</param>
/// <param name="message">Error message</param> /// <param name="code">响应代码</param>
/// <param name="data">Error data</param> /// <param name="data">响应数据</param>
public RemoteServiceSuccessInfo(string message, string? details = null, string? code = null, object? data = null) public RemoteServiceSuccessInfo(
string message,
string? details = null,
string? code = null,
object? data = null)
{ {
Message = message; Message = message;
Details = details; Details = details;
Code = code; Code = code;
Data = data; Data = data;
} }
/// <summary>
/// code.
/// </summary>
public string? Code { get; set; }
/// <summary>
/// message.
/// </summary>
public string? Message { get; set; }
/// <summary>
/// details.
/// </summary>
public string? Details { get; set; }
/// <summary>
/// data.
/// </summary>
public object? Data { get; set; }
} }
} }

View File

@@ -19,15 +19,24 @@ using Yi.Framework.Core;
namespace Yi.Framework.AspNetCore namespace Yi.Framework.AspNetCore
{ {
[DependsOn(typeof(YiFrameworkCoreModule) /// <summary>
)] /// Yi框架ASP.NET Core模块
/// </summary>
[DependsOn(typeof(YiFrameworkCoreModule))]
public class YiFrameworkAspNetCoreModule : AbpModule public class YiFrameworkAspNetCoreModule : AbpModule
{ {
/// <summary>
/// 配置服务后的处理
/// </summary>
public override void PostConfigureServices(ServiceConfigurationContext context) public override void PostConfigureServices(ServiceConfigurationContext context)
{ {
var services = context.Services; var services = context.Services;
services.Replace(new ServiceDescriptor(typeof(IWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider), ServiceLifetime.Transient)); // 替换默认的WebClientInfoProvider为支持代理的实现
services.Replace(new ServiceDescriptor(
typeof(IWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider),
ServiceLifetime.Transient));
} }
} }
} }

View File

@@ -5,40 +5,72 @@ using Volo.Abp.Uow;
namespace Yi.Framework.BackgroundWorkers.Hangfire; namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency /// <summary>
/// Hangfire 工作单元过滤器
/// 用于管理后台任务的事务处理
/// </summary>
public sealed class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
{ {
private const string CurrentJobUow = "HangfireUnitOfWork"; private const string UnitOfWorkItemKey = "HangfireUnitOfWork";
private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IUnitOfWorkManager _unitOfWorkManager;
/// <summary>
/// 初始化工作单元过滤器
/// </summary>
/// <param name="unitOfWorkManager">工作单元管理器</param>
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager) public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
{ {
_unitOfWorkManager = unitOfWorkManager; _unitOfWorkManager = unitOfWorkManager;
} }
/// <summary>
/// 任务执行前的处理
/// </summary>
/// <param name="context">执行上下文</param>
public void OnPerforming(PerformingContext context) public void OnPerforming(PerformingContext context)
{ {
// 开启一个工作单元并存储到上下文中
var uow = _unitOfWorkManager.Begin(); var uow = _unitOfWorkManager.Begin();
context.Items.Add(CurrentJobUow, uow); context.Items.Add(UnitOfWorkItemKey, uow);
} }
/// <summary>
/// 任务执行后的处理
/// </summary>
/// <param name="context">执行上下文</param>
public void OnPerformed(PerformedContext context) public void OnPerformed(PerformedContext context)
{ {
AsyncHelper.RunSync(()=>OnPerformedAsync(context)); AsyncHelper.RunSync(() => OnPerformedAsync(context));
} }
/// <summary>
/// 任务执行后的异步处理
/// </summary>
/// <param name="context">执行上下文</param>
private async Task OnPerformedAsync(PerformedContext context) private async Task OnPerformedAsync(PerformedContext context)
{ {
if (context.Items.TryGetValue(CurrentJobUow, out var obj) if (!context.Items.TryGetValue(UnitOfWorkItemKey, out var obj) ||
&& obj is IUnitOfWork uow) obj is not IUnitOfWork uow)
{ {
return;
}
try
{
// 如果没有异常且工作单元未完成,则提交事务
if (context.Exception == null && !uow.IsCompleted) if (context.Exception == null && !uow.IsCompleted)
{ {
await uow.CompleteAsync(); await uow.CompleteAsync();
} }
else else
{ {
// 否则回滚事务
await uow.RollbackAsync(); await uow.RollbackAsync();
} }
}
finally
{
// 确保工作单元被释放
uow.Dispose(); uow.Dispose();
} }
} }

View File

@@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Volo.Abp.BackgroundJobs.Hangfire" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" /> <PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
</ItemGroup> </ItemGroup>

View File

@@ -2,53 +2,82 @@
using Hangfire; using Hangfire;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.DynamicProxy; using Volo.Abp.DynamicProxy;
namespace Yi.Framework.BackgroundWorkers.Hangfire; namespace Yi.Framework.BackgroundWorkers.Hangfire;
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))] /// <summary>
public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule /// Hangfire 后台任务模块
/// </summary>
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule),
typeof(AbpBackgroundJobsHangfireModule))]
public sealed class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{ {
/// <summary>
/// 配置服务前的预处理
/// </summary>
/// <param name="context">服务配置上下文</param>
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
// 添加 Hangfire 后台任务约定注册器
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar()); context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
} }
/// <summary>
/// 应用程序初始化
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{ {
//定时任务自动注入Abp默认只有在Quartz才实现 // 获取后台任务管理器和所有 Hangfire 后台任务
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>(); var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>(); var workers = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
// 获取配置
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>(); var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
//【特殊,为了兼容内存模式,由于内存模式任务,不能使用队列】
bool.TryParse(configuration["Redis:IsEnabled"], out var redisEnabled); // 检查是否启用 Redis
foreach (var work in works) var isRedisEnabled = configuration.GetValue<bool>("Redis:IsEnabled");
foreach (var worker in workers)
{ {
//如果为空,默认使用服务器本地上海时间 // 设置时区为本地时区(上海)
work.TimeZone = TimeZoneInfo.Local; worker.TimeZone = TimeZoneInfo.Local;
if (redisEnabled)
if (isRedisEnabled)
{ {
await backgroundWorkerManager.AddAsync(work); // Redis 模式:使用 ABP 后台任务管理器
await backgroundWorkerManager.AddAsync(worker);
} }
else else
{ {
object unProxyWorker = ProxyHelper.UnProxy((object)work); // 内存模式:直接使用 Hangfire
RecurringJob.AddOrUpdate(work.RecurringJobId, var unProxyWorker = ProxyHelper.UnProxy(worker);
// 添加或更新循环任务
RecurringJob.AddOrUpdate(
worker.RecurringJobId,
(Expression<Func<Task>>)(() => (Expression<Func<Task>>)(() =>
((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default(CancellationToken))), ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default)),
work.CronExpression, new RecurringJobOptions() worker.CronExpression,
new RecurringJobOptions
{ {
TimeZone = work.TimeZone TimeZone = worker.TimeZone
}); });
} }
} }
} }
/// <summary>
/// 应用程序初始化前的预处理
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override void OnPreApplicationInitialization(ApplicationInitializationContext context) public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{ {
// 添加工作单元过滤器
var services = context.ServiceProvider; var services = context.ServiceProvider;
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>()); GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
} }

View File

@@ -3,16 +3,30 @@ using Volo.Abp.DependencyInjection;
namespace Yi.Framework.BackgroundWorkers.Hangfire; namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar /// <summary>
/// Hangfire 后台任务约定注册器
/// </summary>
public sealed class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
{ {
/// <summary>
/// 检查类型是否禁用约定注册
/// </summary>
/// <param name="type">要检查的类型</param>
/// <returns>如果类型不是 IHangfireBackgroundWorker 或已被禁用则返回 true</returns>
protected override bool IsConventionalRegistrationDisabled(Type type) protected override bool IsConventionalRegistrationDisabled(Type type)
{ {
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type); return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) ||
base.IsConventionalRegistrationDisabled(type);
} }
/// <summary>
/// 获取要暴露的服务类型列表
/// </summary>
/// <param name="type">实现类型</param>
/// <returns>服务类型列表</returns>
protected override List<Type> GetExposedServiceTypes(Type type) protected override List<Type> GetExposedServiceTypes(Type type)
{ {
return new List<Type>() return new List<Type>
{ {
typeof(IHangfireBackgroundWorker) typeof(IHangfireBackgroundWorker)
}; };

View File

@@ -6,116 +6,141 @@ using Volo.Abp.Users;
namespace Yi.Framework.BackgroundWorkers.Hangfire; namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency /// <summary>
/// Hangfire 仪表盘的令牌认证过滤器
/// </summary>
public sealed class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
{ {
private const string Bearer = "Bearer: "; private const string BearerPrefix = "Bearer ";
private string RequireUser { get; set; } = "cc"; private const string TokenCookieKey = "Token";
private TimeSpan ExpiresTime { get; set; } = TimeSpan.FromMinutes(10); private const string HtmlContentType = "text/html";
private IServiceProvider _serviceProvider;
private readonly IServiceProvider _serviceProvider;
private string _requiredUsername = "cc";
private TimeSpan _tokenExpiration = TimeSpan.FromMinutes(10);
/// <summary>
/// 初始化令牌认证过滤器
/// </summary>
/// <param name="serviceProvider">服务提供者</param>
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider) public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public YiTokenAuthorizationFilter SetRequireUser(string userName) /// <summary>
/// 设置需要的用户名
/// </summary>
/// <param name="username">允许访问的用户名</param>
/// <returns>当前实例,支持链式调用</returns>
public YiTokenAuthorizationFilter SetRequiredUsername(string username)
{ {
RequireUser = userName; _requiredUsername = username ?? throw new ArgumentNullException(nameof(username));
return this; return this;
} }
public YiTokenAuthorizationFilter SetExpiresTime(TimeSpan expiresTime) /// <summary>
/// 设置令牌过期时间
/// </summary>
/// <param name="expiration">过期时间间隔</param>
/// <returns>当前实例,支持链式调用</returns>
public YiTokenAuthorizationFilter SetTokenExpiration(TimeSpan expiration)
{ {
ExpiresTime = expiresTime; _tokenExpiration = expiration;
return this; return this;
} }
/// <summary>
/// 授权验证
/// </summary>
/// <param name="context">仪表盘上下文</param>
/// <returns>是否通过授权</returns>
public bool Authorize(DashboardContext context) public bool Authorize(DashboardContext context)
{ {
var httpContext = context.GetHttpContext(); var httpContext = context.GetHttpContext();
var _currentUser = _serviceProvider.GetRequiredService<ICurrentUser>(); var currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
//如果验证通过设置cookies
if (_currentUser.IsAuthenticated)
{
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.Now + ExpiresTime, // 设置 cookie 过期时间,10分钟
};
if (!currentUser.IsAuthenticated)
var authorization = httpContext.Request.Headers["Authorization"].ToString();
if (!string.IsNullOrWhiteSpace(authorization))
{ {
var token = httpContext.Request.Headers["Authorization"].ToString().Substring(Bearer.Length - 1);
httpContext.Response.Cookies.Append("Token", token, cookieOptions);
}
if (_currentUser.UserName == RequireUser)
{
return true;
}
}
SetChallengeResponse(httpContext); SetChallengeResponse(httpContext);
return false; return false;
} }
// 如果验证通过,设置 cookie
var authorization = httpContext.Request.Headers.Authorization.ToString();
if (!string.IsNullOrWhiteSpace(authorization) && authorization.StartsWith(BearerPrefix))
{
var token = authorization[BearerPrefix.Length..];
SetTokenCookie(httpContext, token);
}
return currentUser.UserName == _requiredUsername;
}
/// <summary>
/// 设置认证挑战响应
/// 当用户未认证时返回一个包含令牌输入表单的HTML页面
/// </summary>
/// <param name="httpContext">HTTP 上下文</param>
private void SetChallengeResponse(HttpContext httpContext) private void SetChallengeResponse(HttpContext httpContext)
{ {
httpContext.Response.StatusCode = 401; httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "text/html; charset=utf-8"; httpContext.Response.ContentType = HtmlContentType;
string html = """
<!DOCTYPE html> var html = @"
<html lang="zh"> <html>
<head> <head>
<meta charset="UTF-8"> <title>Hangfire Dashboard Authorization</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <style>
<title>Token </title> body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 400px; margin: 0 auto; }
.form-group { margin-bottom: 15px; }
input[type='text'] { width: 100%; padding: 8px; }
button { background: #337ab7; color: white; border: none; padding: 10px 15px; cursor: pointer; }
button:hover { background: #286090; }
</style>
</head>
<body>
<div class='container'>
<h2>Authorization Required</h2>
<div class='form-group'>
<input type='text' id='token' placeholder='Enter your Bearer token...' />
</div>
<button onclick='authorize()'>Authorize</button>
</div>
<script> <script>
function sendToken() { function authorize() {
// 获取输入的 token var token = document.getElementById('token').value;
var token = document.getElementById("tokenInput").value; if (token) {
token = token.replace('Bearer ',''); document.cookie = 'Token=' + token + '; path=/';
// 构建请求 URL window.location.reload();
var url = "/hangfire";
// 发送 GET 请求
fetch(url,{
headers: {
'Content-Type': 'application/json', // 设置内容类型为 JSON
'Authorization': 'Bearer '+encodeURIComponent(token), // 设置授权头,例如使用 Bearer token
},
})
.then(response => {
if (response.ok) {
return response.text(); // 或使用 response.json() 如果返回的是 JSON
} }
throw new Error('Network response was not ok.');
})
.then(data => {
// 处理成功返回的数据
document.open();
document.write(data);
document.close();
})
.catch(error => {
// 处理错误
console.error('There has been a problem with your fetch operation:', error);
alert("请求失败: " + error.message);
});
} }
</script> </script>
</head>
<body style="text-align: center;">
<h1>Yi-hangfire</h1>
<h1>Token</h1>
<textarea id="tokenInput" placeholder="请输入 token" style="width: 80%;height: 120px;margin: 0 10%;"></textarea>
<button onclick="sendToken()"></button>
</body> </body>
</html> </html>";
""";
httpContext.Response.WriteAsync(html); httpContext.Response.WriteAsync(html);
} }
/// <summary>
/// 设置令牌 Cookie
/// </summary>
/// <param name="httpContext">HTTP 上下文</param>
/// <param name="token">令牌值</param>
private void SetTokenCookie(HttpContext httpContext, string token)
{
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.Now.Add(_tokenExpiration),
HttpOnly = true,
Secure = httpContext.Request.IsHttps,
SameSite = SameSiteMode.Lax
};
httpContext.Response.Cookies.Append(TokenCookieKey, token, cookieOptions);
}
public Task<bool> AuthorizeAsync(DashboardContext context) public Task<bool> AuthorizeAsync(DashboardContext context)
{ {
return Task.FromResult(Authorize(context)); return Task.FromResult(Authorize(context));

View File

@@ -10,28 +10,43 @@ using Volo.Abp.MultiTenancy;
namespace Yi.Framework.Caching.FreeRedis namespace Yi.Framework.Caching.FreeRedis
{ {
[Dependency(ReplaceServices =true)] /// <summary>
/// 缓存键标准化处理器
/// 用于处理缓存键的格式化和多租户支持
/// </summary>
[Dependency(ReplaceServices = true)]
public class YiDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency public class YiDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency
{ {
protected ICurrentTenant CurrentTenant { get; } private readonly ICurrentTenant _currentTenant;
private readonly AbpDistributedCacheOptions _distributedCacheOptions;
protected AbpDistributedCacheOptions DistributedCacheOptions { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="currentTenant">当前租户服务</param>
/// <param name="distributedCacheOptions">分布式缓存配置选项</param>
public YiDistributedCacheKeyNormalizer( public YiDistributedCacheKeyNormalizer(
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IOptions<AbpDistributedCacheOptions> distributedCacheOptions) IOptions<AbpDistributedCacheOptions> distributedCacheOptions)
{ {
CurrentTenant = currentTenant; _currentTenant = currentTenant;
DistributedCacheOptions = distributedCacheOptions.Value; _distributedCacheOptions = distributedCacheOptions.Value;
} }
/// <summary>
/// 标准化缓存键
/// </summary>
/// <param name="args">缓存键标准化参数</param>
/// <returns>标准化后的缓存键</returns>
public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args) public virtual string NormalizeKey(DistributedCacheKeyNormalizeArgs args)
{ {
var normalizedKey = $"{DistributedCacheOptions.KeyPrefix}{args.Key}"; // 添加全局缓存前缀
var normalizedKey = $"{_distributedCacheOptions.KeyPrefix}{args.Key}";
//if (!args.IgnoreMultiTenancy && CurrentTenant.Id.HasValue) //todo 多租户支持已注释,如需启用取消注释即可
//if (!args.IgnoreMultiTenancy && _currentTenant.Id.HasValue)
//{ //{
// normalizedKey = $"t:{CurrentTenant.Id.Value},{normalizedKey}"; // normalizedKey = $"t:{_currentTenant.Id.Value},{normalizedKey}";
//} //}
return normalizedKey; return normalizedKey;

View File

@@ -1,5 +1,6 @@
using FreeRedis; using FreeRedis;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Caching; using Volo.Abp.Caching;
@@ -7,26 +8,57 @@ using Volo.Abp.Caching;
namespace Yi.Framework.Caching.FreeRedis namespace Yi.Framework.Caching.FreeRedis
{ {
/// <summary> /// <summary>
/// 此模块得益于FreeRedis作者支持IDistributedCache使用湿滑 /// FreeRedis缓存模块
/// 提供基于FreeRedis的分布式缓存实现
/// </summary> /// </summary>
[DependsOn(typeof(AbpCachingModule))] [DependsOn(typeof(AbpCachingModule))]
public class YiFrameworkCachingFreeRedisModule : AbpModule public class YiFrameworkCachingFreeRedisModule : AbpModule
{ {
private const string RedisEnabledKey = "Redis:IsEnabled";
private const string RedisConfigurationKey = "Redis:Configuration";
/// <summary>
/// 配置服务
/// </summary>
/// <param name="context">服务配置上下文</param>
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
var configuration = context.Services.GetConfiguration(); var configuration = context.Services.GetConfiguration();
var redisEnabled = configuration["Redis:IsEnabled"]; // 检查Redis是否启用
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled)) if (!IsRedisEnabled(configuration))
{ {
var redisConfiguration = configuration["Redis:Configuration"]; return;
RedisClient redisClient = new RedisClient(redisConfiguration); }
// 注册Redis服务
RegisterRedisServices(context, configuration);
}
/// <summary>
/// 检查Redis是否启用
/// </summary>
/// <param name="configuration">配置</param>
/// <returns>是否启用Redis</returns>
private static bool IsRedisEnabled(IConfiguration configuration)
{
var redisEnabled = configuration[RedisEnabledKey];
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
}
/// <summary>
/// 注册Redis相关服务
/// </summary>
/// <param name="context">服务配置上下文</param>
/// <param name="configuration">配置</param>
private static void RegisterRedisServices(ServiceConfigurationContext context, IConfiguration configuration)
{
var redisConfiguration = configuration[RedisConfigurationKey];
var redisClient = new RedisClient(redisConfiguration);
context.Services.AddSingleton<IRedisClient>(redisClient); context.Services.AddSingleton<IRedisClient>(redisClient);
context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache>(new context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache>(
DistributedCache(redisClient))); new DistributedCache(redisClient)));
}
} }
} }
} }

View File

@@ -6,8 +6,21 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Data namespace Yi.Framework.Core.Data
{ {
/// <summary>
/// 排序接口
/// </summary>
/// <remarks>
/// 实现此接口的实体类将支持排序功能
/// 通常用于列表数据的展示顺序控制
/// </remarks>
public interface IOrderNum public interface IOrderNum
{ {
/// <summary>
/// 排序号
/// </summary>
/// <remarks>
/// 数字越小越靠前,默认为0
/// </remarks>
int OrderNum { get; set; } int OrderNum { get; set; }
} }
} }

View File

@@ -6,8 +6,21 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Data namespace Yi.Framework.Core.Data
{ {
/// <summary>
/// 状态接口
/// </summary>
/// <remarks>
/// 实现此接口的实体类将支持启用/禁用状态管理
/// 用于控制数据记录的可用状态
/// </remarks>
public interface IState public interface IState
{ {
public bool State { get; set; } /// <summary>
/// 状态标识
/// </summary>
/// <remarks>
/// true表示启用,false表示禁用
/// </remarks>
bool State { get; set; }
} }
} }

View File

@@ -7,14 +7,37 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums namespace Yi.Framework.Core.Enums
{ {
/// <summary> /// <summary>
/// 定义公共文件路径 /// 文件类型枚举
/// </summary> /// </summary>
/// <remarks>
/// 用于定义系统支持的文件类型分类
/// 主要用于文件上传和存储时的类型区分
/// </remarks>
public enum FileTypeEnum public enum FileTypeEnum
{ {
file, /// <summary>
image, /// 普通文件
thumbnail, /// </summary>
excel, file = 0,
temp
/// <summary>
/// 图片文件
/// </summary>
image = 1,
/// <summary>
/// 缩略图文件
/// </summary>
thumbnail = 2,
/// <summary>
/// Excel文件
/// </summary>
excel = 3,
/// <summary>
/// 临时文件
/// </summary>
temp = 4
} }
} }

View File

@@ -6,9 +6,23 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums namespace Yi.Framework.Core.Enums
{ {
/// <summary>
/// 排序方向枚举
/// </summary>
/// <remarks>
/// 用于定义数据查询时的排序方向
/// 常用于列表数据排序
/// </remarks>
public enum OrderByEnum public enum OrderByEnum
{ {
Asc, /// <summary>
Desc /// 升序排列
/// </summary>
Asc = 0,
/// <summary>
/// 降序排列
/// </summary>
Desc = 1
} }
} }

View File

@@ -6,67 +6,91 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums namespace Yi.Framework.Core.Enums
{ {
/// <summary>
/// 查询操作符枚举
/// </summary>
/// <remarks>
/// 定义查询条件中支持的操作符类型
/// 用于构建动态查询条件
/// </remarks>
public enum QueryOperatorEnum public enum QueryOperatorEnum
{ {
/// <summary> /// <summary>
/// /// 等
/// </summary> /// </summary>
Equal, Equal = 0,
/// <summary> /// <summary>
/// 匹配 /// 模糊匹配
/// </summary> /// </summary>
Like, Like = 1,
/// <summary> /// <summary>
/// 大于 /// 大于
/// </summary> /// </summary>
GreaterThan, GreaterThan = 2,
/// <summary> /// <summary>
/// 大于或等于 /// 大于或等于
/// </summary> /// </summary>
GreaterThanOrEqual, GreaterThanOrEqual = 3,
/// <summary> /// <summary>
/// 小于 /// 小于
/// </summary> /// </summary>
LessThan, LessThan = 4,
/// <summary> /// <summary>
/// 小于或等于 /// 小于或等于
/// </summary> /// </summary>
LessThanOrEqual, LessThanOrEqual = 5,
/// <summary> /// <summary>
/// 等于集合 /// 在指定集合
/// </summary> /// </summary>
In, In = 6,
/// <summary> /// <summary>
/// 不等于集合 /// 不在指定集合
/// </summary> /// </summary>
NotIn, NotIn = 7,
/// <summary> /// <summary>
/// 左匹配 /// 左侧模糊匹配
/// </summary> /// </summary>
LikeLeft, LikeLeft = 8,
/// <summary> /// <summary>
/// 右匹配 /// 右侧模糊匹配
/// </summary> /// </summary>
LikeRight, LikeRight = 9,
/// <summary> /// <summary>
/// 不 /// 不等
/// </summary> /// </summary>
NoEqual, NoEqual = 10,
/// <summary> /// <summary>
/// 为或空 /// 为null或空
/// </summary> /// </summary>
IsNullOrEmpty, IsNullOrEmpty = 11,
/// <summary> /// <summary>
/// 不为 /// 不为null
/// </summary> /// </summary>
IsNot, IsNot = 12,
/// <summary> /// <summary>
/// 不匹配 /// 不匹配
/// </summary> /// </summary>
NoLike, NoLike = 13,
/// <summary> /// <summary>
/// 时间段 值用 "|" 隔开 /// 日期范围
/// </summary> /// </summary>
DateRange /// <remarks>
/// 使用"|"分隔起始和结束日期
/// </remarks>
DateRange = 14
} }
} }

View File

@@ -6,26 +6,33 @@ using System.Threading.Tasks;
namespace Yi.Framework.Core.Enums namespace Yi.Framework.Core.Enums
{ {
/// <summary>
/// API返回状态码枚举
/// </summary>
/// <remarks>
/// 定义API接口统一的返回状态码
/// 遵循HTTP状态码规范
/// </remarks>
public enum ResultCodeEnum public enum ResultCodeEnum
{ {
/// <summary> /// <summary>
/// 操作成功 /// 操作成功
/// </summary> /// </summary>
Success = 200, Success = 200,
/// <summary> /// <summary>
/// 操作不成功 /// 未授权访问
/// </summary>
NotSuccess = 500,
/// <summary>
/// 无权限
/// </summary> /// </summary>
NoPermission = 401, NoPermission = 401,
/// <summary> /// <summary>
/// 被拒绝 /// 访问被拒绝
/// </summary> /// </summary>
Denied = 403 Denied = 403,
/// <summary>
/// 操作失败
/// </summary>
NotSuccess = 500
} }
} }

View File

@@ -4,110 +4,131 @@ using Microsoft.AspNetCore.Http;
namespace Yi.Framework.Core.Extensions namespace Yi.Framework.Core.Extensions
{ {
/// <summary>
/// HttpContext扩展方法类
/// </summary>
public static class HttpContextExtensions public static class HttpContextExtensions
{ {
/// <summary> /// <summary>
/// 设置文件下载名称 /// 设置内联文件下载响应头
/// </summary> /// </summary>
/// <param name="httpContext"></param> /// <param name="httpContext">HTTP上下文</param>
/// <param name="fileName"></param> /// <param name="fileName">文件名</param>
public static void FileInlineHandle(this HttpContext httpContext, string fileName) public static void FileInlineHandle(this HttpContext httpContext, string fileName)
{ {
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8")); var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
httpContext.Response.Headers.Add("Content-Disposition", "inline;filename=" + encodeFilename); httpContext.Response.Headers.Add("Content-Disposition", $"inline;filename={encodeFilename}");
} }
/// <summary> /// <summary>
/// 设置文件附件名称 /// 设置附件下载响应头
/// </summary> /// </summary>
/// <param name="httpContext"></param> /// <param name="httpContext">HTTP上下文</param>
/// <param name="fileName"></param> /// <param name="fileName">文件名</param>
public static void FileAttachmentHandle(this HttpContext httpContext, string fileName) public static void FileAttachmentHandle(this HttpContext httpContext, string fileName)
{ {
string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8")); var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8);
httpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=" + encodeFilename); httpContext.Response.Headers.Add("Content-Disposition", $"attachment;filename={encodeFilename}");
} }
/// <summary> /// <summary>
/// 获取语言种类 /// 获取客户端首选语言
/// </summary> /// </summary>
/// <param name="httpContext"></param> /// <param name="httpContext">HTTP上下文</param>
/// <returns></returns> /// <returns>语言代码,默认返回zh-CN</returns>
public static string GetLanguage(this HttpContext httpContext) public static string GetLanguage(this HttpContext httpContext)
{ {
string res = "zh-CN"; const string defaultLanguage = "zh-CN";
var str = httpContext.Request.Headers["Accept-Language"].FirstOrDefault(); var acceptLanguage = httpContext.Request.Headers["Accept-Language"].FirstOrDefault();
if (str is not null)
{
res = str.Split(",")[0];
}
return res;
return string.IsNullOrEmpty(acceptLanguage)
? defaultLanguage
: acceptLanguage.Split(',')[0];
} }
/// <summary> /// <summary>
/// 判断是否为异步请求 /// 判断是否为Ajax请求
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request">HTTP请求</param>
/// <returns></returns> /// <returns>是否为Ajax请求</returns>
public static bool IsAjaxRequest(this HttpRequest request) public static bool IsAjaxRequest(this HttpRequest request)
{ {
string header = request.Headers["X-Requested-With"]; const string ajaxHeader = "XMLHttpRequest";
return "XMLHttpRequest".Equals(header); return ajaxHeader.Equals(request.Headers["X-Requested-With"],
StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>
/// 获取客户端IP /// 获取客户端IP地址
/// </summary> /// </summary>
/// <param name="context"></param> /// <param name="context">HTTP上下文</param>
/// <returns></returns> /// <returns>客户端IP地址</returns>
public static string GetClientIp(this HttpContext context) public static string GetClientIp(this HttpContext context)
{ {
if (context == null) return ""; const string localhost = "127.0.0.1";
var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault(); if (context == null) return string.Empty;
if (string.IsNullOrEmpty(result))
// 尝试获取X-Forwarded-For头
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
// 如果没有代理头,则获取远程IP
if (string.IsNullOrEmpty(ip))
{ {
result = context.Connection.RemoteIpAddress?.ToString(); ip = context.Connection.RemoteIpAddress?.ToString();
} }
if (string.IsNullOrEmpty(result) || result.Contains("::1"))
result = "127.0.0.1";
result = result.Replace("::ffff:", "127.0.0.1"); // 处理特殊IP
//如果有端口号,删除端口号 if (string.IsNullOrEmpty(ip) || ip.Contains("::1"))
result = Regex.Replace(result, @":\d{1,5}$", ""); {
//Ip规则校验 return localhost;
var regResult = }
Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$")
|| Regex.IsMatch(result, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?):\d{1,5}$");
result = regResult ? result : "127.0.0.1"; // 清理IPv6格式
return result; ip = ip.Replace("::ffff:", localhost);
// 移除端口号
ip = Regex.Replace(ip, @":\d{1,5}$", "");
// 验证IP格式
var isValidIp = Regex.IsMatch(ip, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$") ||
Regex.IsMatch(ip, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?):\d{1,5}$");
return isValidIp ? ip : localhost;
} }
/// <summary> /// <summary>
/// 获取浏览器标识 /// 获取User-Agent信息
/// </summary> /// </summary>
/// <param name="context"></param> /// <param name="context">HTTP上下文</param>
/// <returns></returns> /// <returns>User-Agent字符串</returns>
public static string GetUserAgent(this HttpContext context) public static string GetUserAgent(this HttpContext context)
{ {
return context.Request.Headers["User-Agent"]; return context.Request.Headers["User-Agent"].ToString();
}
public static string[]? GetUserPermissions(this HttpContext context, string permissionsName)
{
return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray();
} }
/// <summary> /// <summary>
/// 判断是否是 WebSocket 请求 /// 获取用户权限声明值
/// </summary> /// </summary>
/// <param name="context"></param> /// <param name="context">HTTP上下文</param>
/// <returns></returns> /// <param name="permissionsName">权限声明名称</param>
/// <returns>权限值数组</returns>
public static string[]? GetUserPermissions(this HttpContext context, string permissionsName)
{
return context.User.Claims
.Where(x => x.Type == permissionsName)
.Select(x => x.Value)
.ToArray();
}
/// <summary>
/// 判断是否为WebSocket请求
/// </summary>
/// <param name="context">HTTP上下文</param>
/// <returns>是否为WebSocket请求</returns>
public static bool IsWebSocketRequest(this HttpContext context) public static bool IsWebSocketRequest(this HttpContext context)
{ {
return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws"; return context.WebSockets.IsWebSocketRequest ||
context.Request.Path == "/ws";
} }
} }
} }

View File

@@ -3,25 +3,48 @@ using System.Text.Json.Serialization;
namespace Yi.Framework.Core.Json; namespace Yi.Framework.Core.Json;
/// <summary>
/// DateTime JSON序列化转换器
/// </summary>
public class DatetimeJsonConverter : JsonConverter<DateTime> public class DatetimeJsonConverter : JsonConverter<DateTime>
{ {
private string _format; private readonly string _dateFormat;
public DatetimeJsonConverter(string format="yyyy-MM-dd HH:mm:ss")
/// <summary>
/// 初始化DateTime转换器
/// </summary>
/// <param name="format">日期格式化字符串,默认为yyyy-MM-dd HH:mm:ss</param>
public DatetimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss")
{ {
_format = format; _dateFormat = format;
} }
/// <summary>
/// 从JSON读取DateTime值
/// </summary>
/// <param name="reader">JSON读取器</param>
/// <param name="typeToConvert">目标类型</param>
/// <param name="options">JSON序列化选项</param>
/// <returns>DateTime值</returns>
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if(reader.TokenType==JsonTokenType.String) if (reader.TokenType == JsonTokenType.String)
{ {
if (DateTime.TryParse(reader.GetString(), out DateTime dateTime)) return dateTime; return DateTime.TryParse(reader.GetString(), out DateTime dateTime)
? dateTime
: reader.GetDateTime();
} }
return reader.GetDateTime(); return reader.GetDateTime();
} }
/// <summary>
/// 将DateTime写入JSON
/// </summary>
/// <param name="writer">JSON写入器</param>
/// <param name="value">DateTime值</param>
/// <param name="options">JSON序列化选项</param>
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{ {
writer.WriteStringValue(value.ToString(_format)); writer.WriteStringValue(value.ToString(_dateFormat));
} }
} }

View File

@@ -8,52 +8,81 @@ using Volo.Abp.Modularity;
namespace Yi.Framework.Core.Modularity; namespace Yi.Framework.Core.Modularity;
[Dependency(ReplaceServices =true)] /// <summary>
/// Yi框架模块管理器
/// </summary>
[Dependency(ReplaceServices = true)]
public class YiModuleManager : ModuleManager, IModuleManager, ISingletonDependency public class YiModuleManager : ModuleManager, IModuleManager, ISingletonDependency
{ {
private readonly IModuleContainer _moduleContainer; private readonly IModuleContainer _moduleContainer;
private readonly IEnumerable<IModuleLifecycleContributor> _lifecycleContributors; private readonly IEnumerable<IModuleLifecycleContributor> _lifecycleContributors;
private readonly ILogger<YiModuleManager> _logger; private readonly ILogger<YiModuleManager> _logger;
public YiModuleManager(IModuleContainer moduleContainer, ILogger<YiModuleManager> logger, IOptions<AbpModuleLifecycleOptions> options, IServiceProvider serviceProvider) : base(moduleContainer, logger, options, serviceProvider) /// <summary>
/// 初始化模块管理器
/// </summary>
public YiModuleManager(
IModuleContainer moduleContainer,
ILogger<YiModuleManager> logger,
IOptions<AbpModuleLifecycleOptions> options,
IServiceProvider serviceProvider)
: base(moduleContainer, logger, options, serviceProvider)
{ {
_moduleContainer = moduleContainer; _moduleContainer = moduleContainer;
_logger = logger; _logger = logger;
_lifecycleContributors = options.Value.Contributors.Select(serviceProvider.GetRequiredService).Cast<IModuleLifecycleContributor>().ToArray(); _lifecycleContributors = options.Value.Contributors
.Select(serviceProvider.GetRequiredService)
.Cast<IModuleLifecycleContributor>()
.ToArray();
} }
/// <summary>
/// 初始化所有模块
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override async Task InitializeModulesAsync(ApplicationInitializationContext context) public override async Task InitializeModulesAsync(ApplicationInitializationContext context)
{ {
_logger.LogDebug("==========模块Initialize初始化统计-跳过0ms模块=========="); _logger.LogDebug("==========模块Initialize初始化统计-跳过0ms模块==========");
var total = 0;
var watch =new Stopwatch(); var moduleCount = 0;
long totalTime = 0; var stopwatch = new Stopwatch();
var totalTime = 0L;
foreach (var contributor in _lifecycleContributors) foreach (var contributor in _lifecycleContributors)
{ {
foreach (var module in _moduleContainer.Modules) foreach (var module in _moduleContainer.Modules)
{ {
try try
{ {
watch.Restart(); stopwatch.Restart();
await contributor.InitializeAsync(context, module.Instance); await contributor.InitializeAsync(context, module.Instance);
watch.Stop(); stopwatch.Stop();
totalTime += watch.ElapsedMilliseconds;
total++;
if (watch.ElapsedMilliseconds > 1)
{
_logger.LogDebug($"耗时-{watch.ElapsedMilliseconds}ms,已加载模块-{module.Assembly.GetName().Name}");
}
totalTime += stopwatch.ElapsedMilliseconds;
moduleCount++;
// 仅记录耗时超过1ms的模块
if (stopwatch.ElapsedMilliseconds > 1)
{
_logger.LogDebug(
"耗时-{Time}ms,已加载模块-{ModuleName}",
stopwatch.ElapsedMilliseconds,
module.Assembly.GetName().Name);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new AbpInitializationException($"An error occurred during the initialize {contributor.GetType().FullName} phase of the module {module.Type.AssemblyQualifiedName}: {ex.Message}. See the inner exception for details.", ex); throw new AbpInitializationException(
$"模块 {module.Type.AssemblyQualifiedName} 在 {contributor.GetType().FullName} 阶段初始化失败: {ex.Message}",
ex);
} }
} }
} }
_logger.LogInformation($"==========【{total}】个模块初始化执行完毕,总耗时【{totalTime}ms】=========="); _logger.LogInformation(
"==========【{Count}】个模块初始化执行完毕,总耗时【{Time}ms】==========",
moduleCount,
totalTime);
} }
} }

View File

@@ -2,8 +2,28 @@
namespace Yi.Framework.Core namespace Yi.Framework.Core
{ {
public class YiFrameworkCoreModule:AbpModule /// <summary>
/// Yi框架核心模块
/// </summary>
/// <remarks>
/// 提供框架的基础功能和核心服务
/// </remarks>
public class YiFrameworkCoreModule : AbpModule
{ {
/// <summary>
/// 配置服务
/// </summary>
public override void ConfigureServices(ServiceConfigurationContext context)
{
base.ConfigureServices(context);
}
/// <summary>
/// 应用程序初始化
/// </summary>
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
base.OnApplicationInitialization(context);
}
} }
} }

View File

@@ -3,8 +3,17 @@ using Volo.Abp.Application.Services;
namespace Yi.Framework.Ddd.Application.Contracts namespace Yi.Framework.Ddd.Application.Contracts
{ {
public interface IDeletesAppService<in TKey> : IDeleteAppService< TKey> , IApplicationService, IRemoteService /// <summary>
/// 批量删除服务接口
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
public interface IDeletesAppService<in TKey> : IDeleteAppService<TKey>, IApplicationService, IRemoteService
{ {
/// <summary>
/// 批量删除实体
/// </summary>
/// <param name="ids">要删除的实体ID集合</param>
/// <returns>删除操作的异步任务</returns>
Task DeleteAsync(IEnumerable<TKey> ids); Task DeleteAsync(IEnumerable<TKey> ids);
} }
} }

View File

@@ -2,9 +2,19 @@
namespace Yi.Framework.Ddd.Application.Contracts namespace Yi.Framework.Ddd.Application.Contracts
{ {
/// <summary>
/// 带时间范围的分页查询请求接口
/// </summary>
public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest
{ {
/// <summary>
/// 查询开始时间
/// </summary>
DateTime? StartTime { get; set; } DateTime? StartTime { get; set; }
/// <summary>
/// 查询结束时间
/// </summary>
DateTime? EndTime { get; set; } DateTime? EndTime { get; set; }
} }
} }

View File

@@ -2,6 +2,9 @@
namespace Yi.Framework.Ddd.Application.Contracts namespace Yi.Framework.Ddd.Application.Contracts
{ {
/// <summary>
/// 分页查询请求接口,包含时间范围和排序功能
/// </summary>
public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest
{ {
} }

View File

@@ -7,24 +7,47 @@ using Volo.Abp.Application.Services;
namespace Yi.Framework.Ddd.Application.Contracts namespace Yi.Framework.Ddd.Application.Contracts
{ {
/// <summary>
/// Yi框架CRUD服务基础接口
/// </summary>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public interface IYiCrudAppService<TEntityDto, in TKey> : ICrudAppService<TEntityDto, TKey> public interface IYiCrudAppService<TEntityDto, in TKey> : ICrudAppService<TEntityDto, TKey>
{ {
} }
/// <summary>
/// Yi框架CRUD服务接口带查询输入
/// </summary>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TGetListInput">查询输入类型</typeparam>
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput> : ICrudAppService<TEntityDto, TKey, TGetListInput> public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput> : ICrudAppService<TEntityDto, TKey, TGetListInput>
{ {
} }
/// <summary>
/// Yi框架CRUD服务接口带查询输入和创建输入
/// </summary>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TGetListInput">查询输入类型</typeparam>
/// <typeparam name="TCreateInput">创建输入类型</typeparam>
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput> public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput>
{ {
} }
/// <summary>
/// Yi框架CRUD服务接口带查询、创建和更新输入
/// </summary>
public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> public interface IYiCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
{ {
} }
/// <summary>
/// Yi框架完整CRUD服务接口包含所有操作和批量删除功能
/// </summary>
public interface IYiCrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>, IDeletesAppService<TKey> public interface IYiCrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>, IDeletesAppService<TKey>
{ {
} }
} }

View File

@@ -2,49 +2,51 @@
namespace Yi.Framework.Ddd.Application.Contracts namespace Yi.Framework.Ddd.Application.Contracts
{ {
/// <summary>
/// 分页查询请求DTO包含时间范围和自定义排序功能
/// </summary>
public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto
{ {
/// <summary> /// <summary>
/// 查询开始时间条件 /// 查询开始时间
/// </summary> /// </summary>
public DateTime? StartTime { get; set; } public DateTime? StartTime { get; set; }
/// <summary> /// <summary>
/// 查询结束时间条件 /// 查询结束时间
/// </summary> /// </summary>
public DateTime? EndTime { get; set; } public DateTime? EndTime { get; set; }
/// <summary> /// <summary>
/// 排序列名,字段名对应前端 /// 排序列名
/// </summary> /// </summary>
public string? OrderByColumn { get; set; } public string? OrderByColumn { get; set; }
/// <summary> /// <summary>
/// 是否顺序,字段名对应前端 /// 排序方向ascending/descending
/// </summary> /// </summary>
public string? IsAsc { get; set; } public string? IsAsc { get; set; }
/// <summary> /// <summary>
/// 是否 /// 是否为升序排
/// </summary> /// </summary>
public bool CanAsc => IsAsc?.ToLower() == "ascending" ? true : false; public bool IsAscending => string.Equals(IsAsc, "ascending", StringComparison.OrdinalIgnoreCase);
private string _sorting; private string? _sorting;
//排序引用 /// <summary>
public new string? Sorting /// 排序表达式
/// </summary>
public override string? Sorting
{ {
get get
{ {
if (!OrderByColumn.IsNullOrWhiteSpace()) if (!string.IsNullOrWhiteSpace(OrderByColumn))
{ {
return $"{OrderByColumn} {(CanAsc ? "ASC" : "DESC")}"; return $"{OrderByColumn} {(IsAscending ? "ASC" : "DESC")}";
} }
else
{
return _sorting; return _sorting;
} }
}
set => _sorting = value; set => _sorting = value;
} }
} }

View File

@@ -3,6 +3,9 @@ using Volo.Abp.Modularity;
namespace Yi.Framework.Ddd.Application.Contracts namespace Yi.Framework.Ddd.Application.Contracts
{ {
/// <summary>
/// Yi框架DDD应用层契约模块
/// </summary>
[DependsOn(typeof(AbpDddApplicationContractsModule))] [DependsOn(typeof(AbpDddApplicationContractsModule))]
public class YiFrameworkDddApplicationContractsModule : AbpModule public class YiFrameworkDddApplicationContractsModule : AbpModule
{ {

View File

@@ -6,11 +6,19 @@ using Volo.Abp.MultiTenancy;
namespace Yi.Framework.Ddd.Application namespace Yi.Framework.Ddd.Application
{ {
public abstract class YiCacheCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto> /// <summary>
/// 带缓存的CRUD应用服务基类
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TEntityDto">实体DTO类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public abstract class YiCacheCrudAppService<TEntity, TEntityDto, TKey>
: YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey> where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey> where TEntityDto : IEntityDto<TKey>
{ {
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{ {
} }
} }
@@ -47,73 +55,92 @@ namespace Yi.Framework.Ddd.Application
} }
/// <summary>
/// 完整的带缓存CRUD应用服务实现
/// </summary>
public abstract class YiCacheCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput> public abstract class YiCacheCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput> : YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey> where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey> where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey> where TGetListOutputDto : IEntityDto<TKey>
{ {
protected IDistributedCache<TEntity> Cache => LazyServiceProvider.LazyGetRequiredService<IDistributedCache<TEntity>>(); /// <summary>
/// 分布式缓存访问器
/// </summary>
private IDistributedCache<TEntity> EntityCache =>
LazyServiceProvider.LazyGetRequiredService<IDistributedCache<TEntity>>();
protected string GetCacheKey(TKey id) => typeof(TEntity).Name + ":" + CurrentTenant.Id ?? Guid.Empty + ":" + id.ToString(); /// <summary>
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) /// 获取缓存键
/// </summary>
protected virtual string GenerateCacheKey(TKey id) =>
$"{typeof(TEntity).Name}:{CurrentTenant.Id ?? Guid.Empty}:{id}";
protected YiCacheCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{ {
} }
/// <summary>
/// 更新实体并清除缓存
/// </summary>
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input) public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{ {
var output = await base.UpdateAsync(id, input); var result = await base.UpdateAsync(id, input);
await Cache.RemoveAsync(GetCacheKey(id)); await EntityCache.RemoveAsync(GenerateCacheKey(id));
return output; return result;
} }
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input) /// <summary>
/// 获取实体列表(需要继承实现具体的缓存策略)
/// </summary>
public override Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{ {
//两种方式: // 建议实现两种缓存策略:
//1全表缓存使用缓存直接查询 // 1. 全表缓存: 适用于数据量小且变动不频繁的场景
//2非全部缓存查询到的数据直接添加到缓存 // 2. 按需缓存: 仅缓存常用数据,适用于大数据量场景
throw new NotImplementedException("请实现具体的缓存查询策略");
//判断是否该实体为全表缓存
throw new NotImplementedException();
//IDistributedCache 有局限性,条件查询无法进行缓存了
//if (true)
//{
// return await GetListByCacheAsync(input);
//}
//else
//{
// return await GetListByDbAsync(input);
//}
} }
protected virtual async Task<PagedResultDto<TGetListOutputDto>> GetListByDbAsync(TGetListInput input) /// <summary>
/// 从数据库获取实体列表
/// </summary>
protected virtual Task<PagedResultDto<TGetListOutputDto>> GetListFromDatabaseAsync(
TGetListInput input)
{ {
//如果不是全表缓存,可以走这个啦
throw new NotImplementedException();
}
protected virtual async Task<PagedResultDto<TGetListOutputDto>> GetListByCacheAsync(TGetListInput input)
{
//如果是全表缓存,可以走这个啦
throw new NotImplementedException(); throw new NotImplementedException();
} }
/// <summary>
/// 从缓存获取实体列表
/// </summary>
protected virtual Task<PagedResultDto<TGetListOutputDto>> GetListFromCacheAsync(
TGetListInput input)
{
throw new NotImplementedException();
}
/// <summary>
/// 获取单个实体(优先从缓存获取)
/// </summary>
protected override async Task<TEntity> GetEntityByIdAsync(TKey id) protected override async Task<TEntity> GetEntityByIdAsync(TKey id)
{ {
var output = await Cache.GetOrAddAsync(GetCacheKey(id), async () => await base.GetEntityByIdAsync(id)); return (await EntityCache.GetOrAddAsync(
return output!; GenerateCacheKey(id),
async () => await base.GetEntityByIdAsync(id)))!;
} }
public override async Task DeleteAsync(IEnumerable<TKey> id) /// <summary>
/// 批量删除实体并清除缓存
/// </summary>
public override async Task DeleteAsync(IEnumerable<TKey> ids)
{ {
await base.DeleteAsync(id); await base.DeleteAsync(ids);
foreach (var itemId in id)
{
await Cache.RemoveAsync(GetCacheKey(itemId));
}
// 批量清除缓存
var tasks = ids.Select(id =>
EntityCache.RemoveAsync(GenerateCacheKey(id)));
await Task.WhenAll(tasks);
} }
} }
} }

View File

@@ -8,183 +8,244 @@ using Volo.Abp.Domain.Repositories;
namespace Yi.Framework.Ddd.Application namespace Yi.Framework.Ddd.Application
{ {
public abstract class /// <summary>
YiCrudAppService<TEntity, TEntityDto, TKey> : YiCrudAppService<TEntity, TEntityDto, TKey, /// CRUD应用服务基类 - 基础版本
PagedAndSortedResultRequestDto> /// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey>
: YiCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey> where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey> where TEntityDto : IEntityDto<TKey>
{ {
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{ {
} }
} }
/// <summary>
/// CRUD应用服务基类 - 支持自定义查询输入
/// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput> public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto> : YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
where TEntity : class, IEntity<TKey> where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey> where TEntityDto : IEntityDto<TKey>
{ {
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{ {
} }
} }
/// <summary>
/// CRUD应用服务基类 - 支持自定义创建输入
/// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput> public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
: YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput> : YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntity : class, IEntity<TKey> where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey> where TEntityDto : IEntityDto<TKey>
{ {
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{ {
} }
} }
/// <summary>
/// CRUD应用服务基类 - 支持自定义更新输入
/// </summary>
public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> public abstract class YiCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: YiCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> : YiCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey> where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey> where TEntityDto : IEntityDto<TKey>
{ {
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{ {
} }
} }
/// <summary>
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, /// CRUD应用服务基类 - 完整实现
TUpdateInput> /// </summary>
public abstract class YiCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput> : CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey> where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey> where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey> where TGetListOutputDto : IEntityDto<TKey>
{ {
protected YiCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) /// <summary>
/// 临时文件存储路径
/// </summary>
private const string TempFilePath = "/wwwroot/temp";
protected YiCrudAppService(IRepository<TEntity, TKey> repository)
: base(repository)
{ {
} }
/// <summary>
/// 更新实体
/// </summary>
/// <param name="id">实体ID</param>
/// <param name="input">更新输入</param>
/// <returns>更新后的实体DTO</returns>
public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input) public override async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{ {
// 检查更新权限
await CheckUpdatePolicyAsync(); await CheckUpdatePolicyAsync();
// 获取并验证实体
var entity = await GetEntityByIdAsync(id); var entity = await GetEntityByIdAsync(id);
await CheckUpdateInputDtoAsync(entity,input);
// 检查更新输入
await CheckUpdateInputDtoAsync(entity, input);
// 映射并更新实体
await MapToEntityAsync(input, entity); await MapToEntityAsync(input, entity);
await Repository.UpdateAsync(entity, autoSave: true); await Repository.UpdateAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity); return await MapToGetOutputDtoAsync(entity);
} }
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity,TUpdateInput input) /// <summary>
/// 检查更新输入数据的有效性
/// </summary>
protected virtual Task CheckUpdateInputDtoAsync(TEntity entity, TUpdateInput input)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// 创建实体
/// </summary>
/// <param name="input">创建输入</param>
/// <returns>创建后的实体DTO</returns>
public override async Task<TGetOutputDto> CreateAsync(TCreateInput input) public override async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{ {
// 检查创建权限
await CheckCreatePolicyAsync(); await CheckCreatePolicyAsync();
// 检查创建输入
await CheckCreateInputDtoAsync(input); await CheckCreateInputDtoAsync(input);
// 映射到实体
var entity = await MapToEntityAsync(input); var entity = await MapToEntityAsync(input);
// 设置租户ID
TryToSetTenantId(entity); TryToSetTenantId(entity);
// 插入实体
await Repository.InsertAsync(entity, autoSave: true); await Repository.InsertAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity); return await MapToGetOutputDtoAsync(entity);
} }
/// <summary>
/// 检查创建输入数据的有效性
/// </summary>
protected virtual Task CheckCreateInputDtoAsync(TCreateInput input) protected virtual Task CheckCreateInputDtoAsync(TCreateInput input)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary> /// <summary>
/// 多查 /// 获取实体列表
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input">查询输入</param>
/// <returns></returns> /// <returns>分页结果</returns>
public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input) public override async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{ {
List<TEntity>? entites = null; List<TEntity> entities;
//区分多查还是批量查
// 根据输入类型决定查询方式
if (input is IPagedResultRequest pagedInput) if (input is IPagedResultRequest pagedInput)
{ {
entites = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount, // 分页查询
string.Empty); entities = await Repository.GetPagedListAsync(
pagedInput.SkipCount,
pagedInput.MaxResultCount,
string.Empty
);
} }
else else
{ {
entites = await Repository.GetListAsync(); // 查询全部
entities = await Repository.GetListAsync();
} }
var total = await Repository.GetCountAsync(); // 获取总数并映射结果
var output = await MapToGetListOutputDtosAsync(entites); var totalCount = await Repository.GetCountAsync();
return new PagedResultDto<TGetListOutputDto>(total, output); var dtos = await MapToGetListOutputDtosAsync(entities);
//throw new NotImplementedException($"【{typeof(TEntity)}】实体的CrudAppService查询为具体业务通用查询几乎无实际场景请重写实现");
return new PagedResultDto<TGetListOutputDto>(totalCount, dtos);
} }
/// <summary> /// <summary>
/// 多删 /// 批量删除实体
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="ids">实体ID集合</param>
/// <returns></returns>
[RemoteService(isEnabled: true)] [RemoteService(isEnabled: true)]
public virtual async Task DeleteAsync(IEnumerable<TKey> id) public virtual async Task DeleteAsync(IEnumerable<TKey> ids)
{ {
await Repository.DeleteManyAsync(id); await Repository.DeleteManyAsync(ids);
} }
/// <summary> /// <summary>
/// 偷梁换柱 /// 单个删除实体(禁用远程访问)
/// </summary> /// </summary>
/// <param name="id"></param>
/// <returns></returns>
[RemoteService(isEnabled: false)] [RemoteService(isEnabled: false)]
public override Task DeleteAsync(TKey id) public override Task DeleteAsync(TKey id)
{ {
return base.DeleteAsync(id); return base.DeleteAsync(id);
} }
/// <summary> /// <summary>
/// 导出excel /// 导出Excel
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input">查询条件</param>
/// <returns></returns> /// <returns>Excel文件</returns>
public virtual async Task<IActionResult> GetExportExcelAsync(TGetListInput input) public virtual async Task<IActionResult> GetExportExcelAsync(TGetListInput input)
{ {
// 重置分页参数以获取全部数据
if (input is IPagedResultRequest paged) if (input is IPagedResultRequest paged)
{ {
paged.SkipCount = 0; paged.SkipCount = 0;
paged.MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount; paged.MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount;
} }
var output = await this.GetListAsync(input); // 获取数据
var dirPath = $"/wwwroot/temp"; var output = await GetListAsync(input);
var fileName = $"{typeof(TEntity).Name}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}_{Guid.NewGuid()}"; // 确保临时目录存在
var filePath = $"{dirPath}/{fileName}.xlsx"; if (!Directory.Exists(TempFilePath))
if (!Directory.Exists(dirPath))
{ {
Directory.CreateDirectory(dirPath); Directory.CreateDirectory(TempFilePath);
} }
MiniExcel.SaveAs(filePath, output.Items); // 生成文件名和路径
var fileName = GenerateExcelFileName();
var filePath = Path.Combine(TempFilePath, fileName);
// 保存Excel文件
await MiniExcel.SaveAsAsync(filePath, output.Items);
return new PhysicalFileResult(filePath, "application/vnd.ms-excel"); return new PhysicalFileResult(filePath, "application/vnd.ms-excel");
} }
/// <summary> /// <summary>
/// 导入excle /// 生成Excel文件名
/// </summary> /// </summary>
/// <param name="input"></param> private string GenerateExcelFileName()
/// <returns></returns>
public virtual async Task PostImportExcelAsync(List<TCreateInput> input)
{ {
var entities = input.Select(x => MapToEntity(x)).ToList(); return $"{typeof(TEntity).Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Guid.NewGuid()}.xlsx";
//安全起见,该接口需要自己实现 }
throw new NotImplementedException();
//await Repository.DeleteManyAsync(entities.Select(x => x.Id)); /// <summary>
//await Repository.InsertManyAsync(entities); /// 导入Excel(需要实现类重写此方法)
/// </summary>
public virtual Task PostImportExcelAsync(List<TCreateInput> input)
{
throw new NotImplementedException("请在实现类中重写此方法以支持Excel导入");
} }
} }
} }

View File

@@ -6,14 +6,34 @@ using Yi.Framework.Ddd.Application.Contracts;
namespace Yi.Framework.Ddd.Application namespace Yi.Framework.Ddd.Application
{ {
[DependsOn(typeof(AbpDddApplicationModule), /// <summary>
typeof(YiFrameworkDddApplicationContractsModule))] /// Yi框架DDD应用层模块
/// </summary>
[DependsOn(
typeof(AbpDddApplicationModule),
typeof(YiFrameworkDddApplicationContractsModule)
)]
public class YiFrameworkDddApplicationModule : AbpModule public class YiFrameworkDddApplicationModule : AbpModule
{ {
/// <summary>
/// 应用程序初始化配置
/// </summary>
/// <param name="context">应用程序初始化上下文</param>
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)
{ {
//分页限制 // 配置分页查询的默认值和最大值限制
ConfigureDefaultPagingSettings();
}
/// <summary>
/// 配置默认分页设置
/// </summary>
private void ConfigureDefaultPagingSettings()
{
// 设置默认每页显示记录数
LimitedResultRequestDto.DefaultMaxResultCount = 10; LimitedResultRequestDto.DefaultMaxResultCount = 10;
// 设置最大允许的每页记录数
LimitedResultRequestDto.MaxMaxResultCount = 10000; LimitedResultRequestDto.MaxMaxResultCount = 10000;
} }
} }

View File

@@ -8,17 +8,37 @@ using Volo.Abp.ObjectMapping;
namespace Yi.Framework.Mapster namespace Yi.Framework.Mapster
{ {
/// <summary>
/// Mapster自动对象映射提供程序
/// 实现IAutoObjectMappingProvider接口提供对象间的自动映射功能
/// </summary>
public class MapsterAutoObjectMappingProvider : IAutoObjectMappingProvider public class MapsterAutoObjectMappingProvider : IAutoObjectMappingProvider
{ {
/// <summary>
/// 将源对象映射到目标类型
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <returns>映射后的目标类型实例</returns>
public TDestination Map<TSource, TDestination>(object source) public TDestination Map<TSource, TDestination>(object source)
{ {
var sss = typeof(TDestination).Name; // 使用Mapster的Adapt方法进行对象映射
return source.Adapt<TDestination>(); return source.Adapt<TDestination>();
} }
/// <summary>
/// 将源对象映射到现有的目标对象
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="destination">目标对象</param>
/// <returns>映射后的目标对象</returns>
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination) public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{ {
return source.Adapt<TSource, TDestination>(destination); // 使用Mapster的Adapt方法进行对象映射保留目标对象的实例
return source.Adapt(destination);
} }
} }
} }

View File

@@ -7,18 +7,51 @@ using Volo.Abp.ObjectMapping;
namespace Yi.Framework.Mapster namespace Yi.Framework.Mapster
{ {
/// <summary>
/// Mapster对象映射器
/// 实现IObjectMapper接口提供对象映射功能
/// </summary>
public class MapsterObjectMapper : IObjectMapper public class MapsterObjectMapper : IObjectMapper
{ {
public IAutoObjectMappingProvider AutoObjectMappingProvider => throw new NotImplementedException(); private readonly IAutoObjectMappingProvider _autoObjectMappingProvider;
public TDestination Map<TSource, TDestination>(TSource source) /// <summary>
/// 构造函数
/// </summary>
/// <param name="autoObjectMappingProvider">自动对象映射提供程序</param>
public MapsterObjectMapper(IAutoObjectMappingProvider autoObjectMappingProvider)
{ {
throw new NotImplementedException(); _autoObjectMappingProvider = autoObjectMappingProvider;
} }
/// <summary>
/// 获取自动对象映射提供程序
/// </summary>
public IAutoObjectMappingProvider AutoObjectMappingProvider => _autoObjectMappingProvider;
/// <summary>
/// 将源对象映射到目标类型
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <returns>映射后的目标类型实例</returns>
public TDestination Map<TSource, TDestination>(TSource source)
{
return AutoObjectMappingProvider.Map<TSource, TDestination>(source);
}
/// <summary>
/// 将源对象映射到现有的目标对象
/// </summary>
/// <typeparam name="TSource">源类型</typeparam>
/// <typeparam name="TDestination">目标类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="destination">目标对象</param>
/// <returns>映射后的目标对象</returns>
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination) public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{ {
throw new NotImplementedException(); return AutoObjectMappingProvider.Map(source, destination);
} }
} }
} }

View File

@@ -1,21 +1,31 @@
using MapsterMapper; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping; using Volo.Abp.ObjectMapping;
using Yi.Framework.Core; using Yi.Framework.Core;
namespace Yi.Framework.Mapster namespace Yi.Framework.Mapster
{ {
[DependsOn(typeof(YiFrameworkCoreModule), /// <summary>
/// Yi框架Mapster模块
/// 用于配置和注册Mapster相关服务
/// </summary>
[DependsOn(
typeof(YiFrameworkCoreModule),
typeof(AbpObjectMappingModule) typeof(AbpObjectMappingModule)
)] )]
public class YiFrameworkMapsterModule : AbpModule public class YiFrameworkMapsterModule : AbpModule
{ {
/// <summary>
/// 配置服务
/// </summary>
/// <param name="context">服务配置上下文</param>
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>(); var services = context.Services;
// 注册Mapster相关服务
services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
services.AddTransient<IObjectMapper, MapsterObjectMapper>();
} }
} }
} }

View File

@@ -3,10 +3,14 @@ using ArgumentException = System.ArgumentException;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
/// <summary>
/// 数据库连接配置选项
/// </summary>
public class DbConnOptions public class DbConnOptions
{ {
/// <summary> /// <summary>
/// 连接字符串(如果开启多租户,也就是默认库了),必填 /// 主数据库连接字符串
/// 如果开启多租户,此为默认租户数据库
/// </summary> /// </summary>
public string? Url { get; set; } public string? Url { get; set; }
@@ -16,42 +20,42 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
public DbType? DbType { get; set; } public DbType? DbType { get; set; }
/// <summary> /// <summary>
/// 开启种子数据 /// 是否启用种子数据初始化
/// </summary> /// </summary>
public bool EnabledDbSeed { get; set; } = false; public bool EnabledDbSeed { get; set; } = false;
/// <summary> /// <summary>
/// 开启驼峰转下划线 /// 是否启用驼峰命名转下划线命名
/// </summary> /// </summary>
public bool EnableUnderLine { get; set; } = false; public bool EnableUnderLine { get; set; } = false;
/// <summary> /// <summary>
/// 开启codefirst /// 是否启用Code First模式
/// </summary> /// </summary>
public bool EnabledCodeFirst { get; set; } = false; public bool EnabledCodeFirst { get; set; } = false;
/// <summary> /// <summary>
/// 开启sql日志 /// 是否启用SQL日志记录
/// </summary> /// </summary>
public bool EnabledSqlLog { get; set; } = true; public bool EnabledSqlLog { get; set; } = true;
/// <summary> /// <summary>
/// 实体程序集 /// 实体类所在程序集名称列表
/// </summary> /// </summary>
public List<string>? EntityAssembly { get; set; } public List<string>? EntityAssembly { get; set; }
/// <summary> /// <summary>
/// 开启读写分离 /// 是否启用读写分离
/// </summary> /// </summary>
public bool EnabledReadWrite { get; set; } = false; public bool EnabledReadWrite { get; set; } = false;
/// <summary> /// <summary>
/// 读写分离 /// 只读数据库连接字符串列表
/// </summary> /// </summary>
public List<string>? ReadUrl { get; set; } public List<string>? ReadUrl { get; set; }
/// <summary> /// <summary>
/// 开启Saas多租户 /// 是否启用SaaS多租户
/// </summary> /// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false; public bool EnabledSaasMultiTenancy { get; set; } = false;
} }

View File

@@ -4,10 +4,13 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions;
/// <summary>
/// 默认租户表特性
/// 标记此特性的实体类将在默认租户数据库中创建表
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class DefaultTenantTableAttribute : Attribute
{ {
[AttributeUsage(AttributeTargets.Class)]
public class DefaultTenantTableAttribute : Attribute
{
}
} }

View File

@@ -8,15 +8,18 @@ using Volo.Abp.DependencyInjection;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
/// <summary>
/// SqlSugar数据库上下文接口
/// </summary>
public interface ISqlSugarDbContext public interface ISqlSugarDbContext
{ {
/// <summary> /// <summary>
/// SqlSugarDb /// 获取SqlSugar客户端实例
/// </summary> /// </summary>
ISqlSugarClient SqlSugarClient { get; } ISqlSugarClient SqlSugarClient { get; }
/// <summary> /// <summary>
/// 数据库备份 /// 执行数据库备份
/// </summary> /// </summary>
void BackupDataBase(); void BackupDataBase();
} }

View File

@@ -3,19 +3,55 @@ using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore.Abstractions;
/// <summary>
/// SqlSugar数据库上下文依赖接口
/// 定义数据库操作的各个生命周期钩子
/// </summary>
public interface ISqlSugarDbContextDependencies public interface ISqlSugarDbContextDependencies
{ {
/// <summary> /// <summary>
/// 执行顺序 /// 获取执行顺序
/// </summary> /// </summary>
int ExecutionOrder { get; } int ExecutionOrder { get; }
/// <summary>
/// SqlSugar客户端配置时触发
/// </summary>
/// <param name="sqlSugarClient">SqlSugar客户端实例</param>
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient); void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
/// <summary>
/// 数据执行后触发
/// </summary>
/// <param name="oldValue">原始值</param>
/// <param name="entityInfo">实体信息</param>
void DataExecuted(object oldValue, DataAfterModel entityInfo); void DataExecuted(object oldValue, DataAfterModel entityInfo);
/// <summary>
/// 数据执行前触发
/// </summary>
/// <param name="oldValue">原始值</param>
/// <param name="entityInfo">实体信息</param>
void DataExecuting(object oldValue, DataFilterModel entityInfo); void DataExecuting(object oldValue, DataFilterModel entityInfo);
void OnLogExecuting(string sql, SugarParameter[] pars); /// <summary>
void OnLogExecuted(string sql, SugarParameter[] pars); /// SQL执行前触发
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="parameters">SQL参数</param>
void OnLogExecuting(string sql, SugarParameter[] parameters);
/// <summary>
/// SQL执行后触发
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="parameters">SQL参数</param>
void OnLogExecuted(string sql, SugarParameter[] parameters);
/// <summary>
/// 实体服务配置
/// </summary>
/// <param name="propertyInfo">属性信息</param>
/// <param name="entityColumnInfo">实体列信息</param>
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo); void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
} }

View File

@@ -6,84 +6,242 @@ using Volo.Abp.Uow;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
/// <summary>
public interface ISqlSugarRepository<TEntity>:IRepository<TEntity>,IUnitOfWorkEnabled where TEntity : class, IEntity,new () /// SqlSugar仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
public interface ISqlSugarRepository<TEntity> : IRepository<TEntity>, IUnitOfWorkEnabled
where TEntity : class, IEntity, new()
{ {
#region 访
/// <summary>
/// 获取SqlSugar客户端实例
/// </summary>
ISqlSugarClient _Db { get; } ISqlSugarClient _Db { get; }
/// <summary>
/// 获取查询构造器
/// </summary>
ISugarQueryable<TEntity> _DbQueryable { get; } ISugarQueryable<TEntity> _DbQueryable { get; }
/// <summary>
/// 异步获取数据库上下文
/// </summary>
Task<ISqlSugarClient> GetDbContextAsync(); Task<ISqlSugarClient> GetDbContextAsync();
/// <summary>
/// 获取删除操作构造器
/// </summary>
Task<IDeleteable<TEntity>> AsDeleteable(); Task<IDeleteable<TEntity>> AsDeleteable();
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> insertObjs);
Task<IInsertable<TEntity>> AsInsertable(TEntity insertObj); /// <summary>
Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs); /// 获取插入操作构造器
/// </summary>
Task<IInsertable<TEntity>> AsInsertable(TEntity entity);
/// <summary>
/// 获取批量插入操作构造器
/// </summary>
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> entities);
/// <summary>
/// 获取查询构造器
/// </summary>
Task<ISugarQueryable<TEntity>> AsQueryable(); Task<ISugarQueryable<TEntity>> AsQueryable();
/// <summary>
/// 获取SqlSugar客户端
/// </summary>
Task<ISqlSugarClient> AsSugarClient(); Task<ISqlSugarClient> AsSugarClient();
/// <summary>
/// 获取租户操作接口
/// </summary>
Task<ITenant> AsTenant(); Task<ITenant> AsTenant();
Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> updateObjs);
Task<IUpdateable<TEntity>> AsUpdateable(TEntity updateObj); /// <summary>
/// 获取更新操作构造器
/// </summary>
Task<IUpdateable<TEntity>> AsUpdateable(); Task<IUpdateable<TEntity>> AsUpdateable();
Task<IUpdateable<TEntity>> AsUpdateable(TEntity[] updateObjs);
#region /// <summary>
//单查 /// 获取实体更新操作构造器
/// </summary>
Task<IUpdateable<TEntity>> AsUpdateable(TEntity entity);
/// <summary>
/// 获取批量更新操作构造器
/// </summary>
Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> entities);
#endregion
#region
/// <summary>
/// 根据主键获取实体
/// </summary>
Task<TEntity> GetByIdAsync(dynamic id); Task<TEntity> GetByIdAsync(dynamic id);
Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression);
#endregion /// <summary>
/// 获取满足条件的单个实体
/// </summary>
Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 获取满足条件的第一个实体
/// </summary>
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> predicate);
#region /// <summary>
//多查 /// 判断是否存在满足条件的实体
/// </summary>
Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 获取满足条件的实体数量
/// </summary>
Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 获取所有实体
/// </summary>
Task<List<TEntity>> GetListAsync(); Task<List<TEntity>> GetListAsync();
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> whereExpression);
/// <summary>
/// 获取满足条件的所有实体
/// </summary>
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate);
#endregion #endregion
#region
/// <summary>
/// 获取分页数据
/// </summary>
Task<List<TEntity>> GetPageListAsync(
Expression<Func<TEntity, bool>> predicate,
int pageIndex,
int pageSize);
/// <summary>
/// 获取排序的分页数据
/// </summary>
Task<List<TEntity>> GetPageListAsync(
Expression<Func<TEntity, bool>> predicate,
int pageIndex,
int pageSize,
Expression<Func<TEntity, object>>? orderByExpression = null,
OrderByType orderByType = OrderByType.Asc);
#region
//分页查
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize);
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize, Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
#endregion #endregion
#region #region
//插入
Task<bool> InsertAsync(TEntity insertObj); /// <summary>
Task<bool> InsertOrUpdateAsync(TEntity data); /// 插入实体
Task<bool> InsertOrUpdateAsync(List<TEntity> datas); /// </summary>
Task<int> InsertReturnIdentityAsync(TEntity insertObj); Task<bool> InsertAsync(TEntity entity);
Task<long> InsertReturnBigIdentityAsync(TEntity insertObj);
Task<long> InsertReturnSnowflakeIdAsync(TEntity insertObj); /// <summary>
Task<TEntity> InsertReturnEntityAsync(TEntity insertObj); /// 插入或更新实体
Task<bool> InsertRangeAsync(List<TEntity> insertObjs); /// </summary>
Task<bool> InsertOrUpdateAsync(TEntity entity);
/// <summary>
/// 批量插入或更新实体
/// </summary>
Task<bool> InsertOrUpdateAsync(List<TEntity> entities);
/// <summary>
/// 插入实体并返回自增主键
/// </summary>
Task<int> InsertReturnIdentityAsync(TEntity entity);
/// <summary>
/// 插入实体并返回长整型自增主键
/// </summary>
Task<long> InsertReturnBigIdentityAsync(TEntity entity);
/// <summary>
/// 插入实体并返回雪花ID
/// </summary>
Task<long> InsertReturnSnowflakeIdAsync(TEntity entity);
/// <summary>
/// 插入实体并返回实体
/// </summary>
Task<TEntity> InsertReturnEntityAsync(TEntity entity);
/// <summary>
/// 批量插入实体
/// </summary>
Task<bool> InsertRangeAsync(List<TEntity> entities);
#endregion #endregion
#region
/// <summary>
/// 更新实体
/// </summary>
Task<bool> UpdateAsync(TEntity entity);
/// <summary>
/// 批量更新实体
/// </summary>
Task<bool> UpdateRangeAsync(List<TEntity> entities);
/// <summary>
/// 条件更新指定列
/// </summary>
Task<bool> UpdateAsync(
Expression<Func<TEntity, TEntity>> columns,
Expression<Func<TEntity, bool>> predicate);
#region
//更新
Task<bool> UpdateAsync(TEntity updateObj);
Task<bool> UpdateRangeAsync(List<TEntity> updateObjs);
Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression);
#endregion #endregion
#region #region
//删除
Task<bool> DeleteAsync(TEntity deleteObj); /// <summary>
Task<bool> DeleteAsync(List<TEntity> deleteObjs); /// 删除实体
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> whereExpression); /// </summary>
Task<bool> DeleteAsync(TEntity entity);
/// <summary>
/// 批量删除实体
/// </summary>
Task<bool> DeleteAsync(List<TEntity> entities);
/// <summary>
/// 条件删除
/// </summary>
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 根据主键删除
/// </summary>
Task<bool> DeleteByIdAsync(dynamic id); Task<bool> DeleteByIdAsync(dynamic id);
Task<bool> DeleteByIdsAsync(dynamic[] ids);
#endregion
/// <summary>
/// 根据主键批量删除
/// </summary>
Task<bool> DeleteByIdsAsync(dynamic[] ids);
#endregion
} }
/// <summary>
public interface ISqlSugarRepository<TEntity, TKey> : ISqlSugarRepository<TEntity>,IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new() /// SqlSugar仓储接口(带主键)
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public interface ISqlSugarRepository<TEntity, TKey> :
ISqlSugarRepository<TEntity>,
IRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey>, new()
{ {
} }
} }

View File

@@ -6,12 +6,17 @@ using System.Threading.Tasks;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
/// <summary>
/// SqlSugar数据库上下文提供者接口
/// </summary>
/// <typeparam name="TDbContext">数据库上下文类型</typeparam>
public interface ISugarDbContextProvider<TDbContext> public interface ISugarDbContextProvider<TDbContext>
where TDbContext : ISqlSugarDbContext where TDbContext : ISqlSugarDbContext
{ {
/// <summary>
/// 异步获取数据库上下文实例
/// </summary>
/// <returns>数据库上下文实例</returns>
Task<TDbContext> GetDbContextAsync(); Task<TDbContext> GetDbContextAsync();
} }
} }

View File

@@ -6,6 +6,10 @@ using System.Threading.Tasks;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
/// <summary>
/// 忽略CodeFirst特性
/// 标记此特性的实体类将不会被CodeFirst功能扫描
/// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class IgnoreCodeFirstAttribute : Attribute public class IgnoreCodeFirstAttribute : Attribute
{ {

View File

@@ -3,9 +3,13 @@ using Yi.Framework.Core;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
/// <summary>
/// SqlSugar Core抽象层模块
/// 提供SqlSugar ORM的基础抽象接口和类型定义
/// </summary>
[DependsOn(typeof(YiFrameworkCoreModule))] [DependsOn(typeof(YiFrameworkCoreModule))]
public class YiFrameworkSqlSugarCoreAbstractionsModule : AbpModule public class YiFrameworkSqlSugarCoreAbstractionsModule : AbpModule
{ {
// 模块配置方法可在此添加
} }
} }

View File

@@ -2,18 +2,34 @@
namespace Yi.Framework.SqlSugarCore namespace Yi.Framework.SqlSugarCore
{ {
public class AsyncLocalDbContextAccessor /// <summary>
/// 异步本地数据库上下文访问器
/// 用于在异步流中保存和访问数据库上下文
/// </summary>
public sealed class AsyncLocalDbContextAccessor
{ {
private readonly AsyncLocal<ISqlSugarDbContext?> _currentScope;
/// <summary>
/// 获取单例实例
/// </summary>
public static AsyncLocalDbContextAccessor Instance { get; } = new(); public static AsyncLocalDbContextAccessor Instance { get; } = new();
/// <summary>
/// 获取或设置当前数据库上下文
/// </summary>
public ISqlSugarDbContext? Current public ISqlSugarDbContext? Current
{ {
get => _currentScope.Value; get => _currentScope.Value;
set => _currentScope.Value = value; set => _currentScope.Value = value;
} }
public AsyncLocalDbContextAccessor()
/// <summary>
/// 初始化异步本地数据库上下文访问器
/// </summary>
private AsyncLocalDbContextAccessor()
{ {
_currentScope = new AsyncLocal<ISqlSugarDbContext?>(); _currentScope = new AsyncLocal<ISqlSugarDbContext?>();
} }
private readonly AsyncLocal<ISqlSugarDbContext> _currentScope;
} }
} }

View File

@@ -18,94 +18,154 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore; namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// 默认SqlSugar数据库上下文实现
/// </summary>
public class DefaultSqlSugarDbContext : SqlSugarDbContext public class DefaultSqlSugarDbContext : SqlSugarDbContext
{ {
protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value; #region Protected Properties
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
public IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
protected IEntityChangeEventHelper EntityChangeEventHelper => /// <summary>
/// 数据库连接配置选项
/// </summary>
protected DbConnOptions DbOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 当前用户服务
/// </summary>
protected ICurrentUser CurrentUserService => LazyServiceProvider.GetRequiredService<ICurrentUser>();
/// <summary>
/// GUID生成器
/// </summary>
protected IGuidGenerator GuidGeneratorService => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
/// <summary>
/// 日志工厂
/// </summary>
protected ILoggerFactory LoggerFactory => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
/// <summary>
/// 当前租户服务
/// </summary>
protected ICurrentTenant CurrentTenantService => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
/// <summary>
/// 数据过滤服务
/// </summary>
protected IDataFilter DataFilterService => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
/// <summary>
/// 工作单元管理器
/// </summary>
protected IUnitOfWorkManager UnitOfWorkManagerService => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();
/// <summary>
/// 实体变更事件帮助类
/// </summary>
protected IEntityChangeEventHelper EntityChangeEventHelperService =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance); LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) /// <summary>
/// 是否启用多租户过滤
/// </summary>
protected virtual bool IsMultiTenantFilterEnabled => DataFilterService?.IsEnabled<IMultiTenant>() ?? false;
/// <summary>
/// 是否启用软删除过滤
/// </summary>
protected virtual bool IsSoftDeleteFilterEnabled => DataFilterService?.IsEnabled<ISoftDelete>() ?? false;
#endregion
/// <summary>
/// 构造函数
/// </summary>
public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
: base(lazyServiceProvider)
{ {
} }
/// <summary>
/// 自定义数据过滤器
/// </summary>
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient) protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{ {
// 配置软删除过滤器
if (IsSoftDeleteFilterEnabled) if (IsSoftDeleteFilterEnabled)
{ {
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false); sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(entity => !entity.IsDeleted);
} }
// 配置多租户过滤器
if (IsMultiTenantFilterEnabled) if (IsMultiTenantFilterEnabled)
{ {
//表达式里只能有具体值,不能运算 var currentTenantId = CurrentTenantService.Id;
var expressionCurrentTenant = CurrentTenant.Id ?? null; sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(entity => entity.TenantId == currentTenantId);
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
} }
} }
/// <summary>
/// 数据执行前的处理
/// </summary>
public override void DataExecuting(object oldValue, DataFilterModel entityInfo) public override void DataExecuting(object oldValue, DataFilterModel entityInfo)
{ {
//审计日志 HandleAuditFields(oldValue, entityInfo);
HandleEntityEvents(entityInfo);
HandleDomainEvents(entityInfo);
}
#region Private Methods
/// <summary>
/// 处理审计字段
/// </summary>
private void HandleAuditFields(object oldValue, DataFilterModel entityInfo)
{
switch (entityInfo.OperationType) switch (entityInfo.OperationType)
{ {
case DataFilterType.UpdateByObject: case DataFilterType.UpdateByObject:
HandleUpdateAuditFields(oldValue, entityInfo);
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
//最后更新时间,已经是最小值,忽略
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(null);
}
else
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
//最后更新者已经是空guid忽略
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(null);
}
else if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
break; break;
case DataFilterType.InsertByObject: case DataFilterType.InsertByObject:
HandleInsertAuditFields(oldValue, entityInfo);
break;
}
}
/// <summary>
/// 处理更新时的审计字段
/// </summary>
private void HandleUpdateAuditFields(object oldValue, DataFilterModel entityInfo)
{
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
entityInfo.SetValue(DateTime.MinValue.Equals(oldValue) ? null : DateTime.Now);
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId))
&& entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(Guid?))
{
entityInfo.SetValue(Guid.Empty.Equals(oldValue) ? null : CurrentUserService.Id);
}
}
/// <summary>
/// 处理插入时的审计字段
/// </summary>
private void HandleInsertAuditFields(object oldValue, DataFilterModel entityInfo)
{
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id))) if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{ {
//类型为guid
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType) if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{ {
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue)) if (Guid.Empty.Equals(oldValue))
{ {
entityInfo.SetValue(GuidGenerator.Create()); entityInfo.SetValue(GuidGeneratorService.Create());
} }
} }
} }
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime))) else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{ {
//为空或者为默认最小值
if (DateTime.MinValue.Equals(oldValue)) if (DateTime.MinValue.Equals(oldValue))
{ {
entityInfo.SetValue(DateTime.Now); entityInfo.SetValue(DateTime.Now);
@@ -113,75 +173,71 @@ public class DefaultSqlSugarDbContext : SqlSugarDbContext
} }
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId))) else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{ {
//类型为guid
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType) if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{ {
if (CurrentUser.Id is not null) if (CurrentUserService.Id is not null)
{ {
entityInfo.SetValue(CurrentUser.Id); entityInfo.SetValue(CurrentUserService.Id);
} }
} }
} }
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId))) else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{ {
if (CurrentTenant.Id is not null) if (CurrentTenantService.Id is not null)
{ {
entityInfo.SetValue(CurrentTenant.Id); entityInfo.SetValue(CurrentTenantService.Id);
}
} }
} }
break; /// <summary>
} /// 处理实体变更事件
/// </summary>
private void HandleEntityEvents(DataFilterModel entityInfo)
//实体变更领域事件 {
// 实体变更领域事件
switch (entityInfo.OperationType) switch (entityInfo.OperationType)
{ {
case DataFilterType.InsertByObject: case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id)) if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{ {
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue); EntityChangeEventHelperService.PublishEntityCreatedEvent(entityInfo.EntityValue);
} }
break; break;
case DataFilterType.UpdateByObject: case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id)) if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{ {
//软删除,发布的是删除事件
if (entityInfo.EntityValue is ISoftDelete softDelete) if (entityInfo.EntityValue is ISoftDelete softDelete)
{ {
if (softDelete.IsDeleted == true) if (softDelete.IsDeleted == true)
{ {
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue); EntityChangeEventHelperService.PublishEntityDeletedEvent(entityInfo.EntityValue);
} }
} }
else else
{ {
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue); EntityChangeEventHelperService.PublishEntityUpdatedEvent(entityInfo.EntityValue);
} }
} }
break; break;
case DataFilterType.DeleteByObject: case DataFilterType.DeleteByObject:
// if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
// {
//这里sqlsugar有个特殊删除会返回批量的结果
//这里sqlsugar有第二个特殊删除事件是行级事件
if (entityInfo.EntityValue is IEnumerable entityValues) if (entityInfo.EntityValue is IEnumerable entityValues)
{ {
foreach (var entityValue in entityValues) foreach (var entityValue in entityValues)
{ {
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue); EntityChangeEventHelperService.PublishEntityDeletedEvent(entityValue);
} }
} }
// }
break; break;
} }
}
/// <summary>
//实体领域事件-所有操作类型 /// 处理领域事件
/// </summary>
private void HandleDomainEvents(DataFilterModel entityInfo)
{
// 实体领域事件-所有操作类型
if (entityInfo.PropertyName == nameof(IEntity<object>.Id)) if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{ {
var eventReport = CreateEventReport(entityInfo.EntityValue); var eventReport = CreateEventReport(entityInfo.EntityValue);
@@ -189,47 +245,6 @@ public class DefaultSqlSugarDbContext : SqlSugarDbContext
} }
} }
public override void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
Logger.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
}
}
public override void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
entityColumnInfo.IsEnableUpdateVersionValidation = true;
}
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
{
entityColumnInfo.IsIgnore = true;
}
if (propertyInfo.Name == nameof(Entity<object>.Id))
{
entityColumnInfo.IsPrimarykey = true;
}
}
/// <summary> /// <summary>
/// 创建领域事件报告 /// 创建领域事件报告
/// </summary> /// </summary>
@@ -286,16 +301,58 @@ public class DefaultSqlSugarDbContext : SqlSugarDbContext
{ {
foreach (var localEvent in changeReport.DomainEvents) foreach (var localEvent in changeReport.DomainEvents)
{ {
UnitOfWorkManager.Current?.AddOrReplaceLocalEvent( UnitOfWorkManagerService.Current?.AddOrReplaceLocalEvent(
new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder) new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder)
); );
} }
foreach (var distributedEvent in changeReport.DistributedEvents) foreach (var distributedEvent in changeReport.DistributedEvents)
{ {
UnitOfWorkManager.Current?.AddOrReplaceDistributedEvent( UnitOfWorkManagerService.Current?.AddOrReplaceDistributedEvent(
new UnitOfWorkEventRecord(distributedEvent.EventData.GetType(), distributedEvent.EventData, distributedEvent.EventOrder) new UnitOfWorkEventRecord(distributedEvent.EventData.GetType(), distributedEvent.EventData, distributedEvent.EventOrder)
); );
} }
} }
#endregion
public override void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (DbOptions.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
LoggerFactory.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
}
}
public override void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (DbOptions.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
LoggerFactory.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
entityColumnInfo.IsEnableUpdateVersionValidation = true;
}
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
{
entityColumnInfo.IsIgnore = true;
}
if (propertyInfo.Name == nameof(Entity<object>.Id))
{
entityColumnInfo.IsPrimarykey = true;
}
}
} }

View File

@@ -21,34 +21,39 @@ namespace Yi.Framework.SqlSugarCore.Repositories
public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>(); public ISugarQueryable<TEntity> _DbQueryable => _Db.Queryable<TEntity>();
private ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider; private readonly ISugarDbContextProvider<ISqlSugarDbContext> _dbContextProvider;
/// <summary>
/// 异步查询执行器
/// </summary>
public IAsyncQueryableExecuter AsyncExecuter { get; } public IAsyncQueryableExecuter AsyncExecuter { get; }
/// <summary>
/// 是否启用变更追踪
/// </summary>
public bool? IsChangeTrackingEnabled => false; public bool? IsChangeTrackingEnabled => false;
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> dbContextProvider)
{ {
_sugarDbContextProvider = sugarDbContextProvider; _dbContextProvider = dbContextProvider;
} }
/// <summary> /// <summary>
/// 获取DB /// 获取数据库上下文
/// </summary> /// </summary>
/// <returns></returns>
public virtual async Task<ISqlSugarClient> GetDbContextAsync() public virtual async Task<ISqlSugarClient> GetDbContextAsync()
{ {
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient; var dbContext = await _dbContextProvider.GetDbContextAsync();
return db; return dbContext.SqlSugarClient;
} }
/// <summary> /// <summary>
/// 获取简单Db /// 获取简单数据库客户端
/// </summary> /// </summary>
/// <returns></returns>
public virtual async Task<SimpleClient<TEntity>> GetDbSimpleClientAsync() public virtual async Task<SimpleClient<TEntity>> GetDbSimpleClientAsync()
{ {
var db = await GetDbContextAsync(); var dbContext = await GetDbContextAsync();
return new SimpleClient<TEntity>(db); return new SimpleClient<TEntity>(dbContext);
} }
#region Abp模块 #region Abp模块

View File

@@ -7,35 +7,47 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// SqlSugar Core扩展方法
/// </summary>
public static class SqlSugarCoreExtensions
{ {
public static class SqlSugarCoreExtensions
{
/// <summary> /// <summary>
/// 新增db对象可支持多个 /// 添加数据库上下文
/// </summary> /// </summary>
/// <param name="service"></param> /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
/// <param name="serviceLifetime"></param> /// <param name="services">服务集合</param>
/// <typeparam name="TDbContext"></typeparam> /// <param name="serviceLifetime">服务生命周期</param>
/// <returns></returns> /// <returns>服务集合</returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies public static IServiceCollection AddYiDbContext<TDbContext>(
this IServiceCollection services,
ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
where TDbContext : class, ISqlSugarDbContextDependencies
{ {
service.AddTransient<ISqlSugarDbContextDependencies, TDbContext>(); services.TryAdd(new ServiceDescriptor(
return service; typeof(ISqlSugarDbContextDependencies),
typeof(TDbContext),
serviceLifetime));
return services;
} }
/// <summary> /// <summary>
/// 新增db对象可支持多个 /// 添加数据库上下文并配置选项
/// </summary> /// </summary>
/// <param name="service"></param> /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
/// <param name="options"></param> /// <param name="services">服务集合</param>
/// <typeparam name="TDbContext"></typeparam> /// <param name="configureOptions">配置选项委托</param>
/// <returns></returns> /// <returns>服务集合</returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, Action<DbConnOptions> options) where TDbContext : class, ISqlSugarDbContextDependencies public static IServiceCollection AddYiDbContext<TDbContext>(
this IServiceCollection services,
Action<DbConnOptions> configureOptions)
where TDbContext : class, ISqlSugarDbContextDependencies
{ {
service.Configure<DbConnOptions>(options.Invoke); services.Configure(configureOptions);
service.AddYiDbContext<TDbContext>(); services.AddYiDbContext<TDbContext>();
return service; return services;
}
} }
} }

View File

@@ -5,44 +5,78 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore; namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// SqlSugar数据库上下文基类
/// </summary>
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
{ {
/// <summary>
/// 服务提供者
/// </summary>
protected IAbpLazyServiceProvider LazyServiceProvider { get; } protected IAbpLazyServiceProvider LazyServiceProvider { get; }
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) /// <summary>
/// 数据库客户端实例
/// </summary>
protected ISqlSugarClient SqlSugarClient { get; private set; }
/// <summary>
/// 执行顺序
/// </summary>
public virtual int ExecutionOrder => 0;
protected SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{ {
this.LazyServiceProvider = lazyServiceProvider; LazyServiceProvider = lazyServiceProvider;
} }
/// <summary>
protected ISqlSugarClient SqlSugarClient { get;private set; } /// 配置SqlSugar客户端
public int ExecutionOrder => 0; /// </summary>
public virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{ {
SqlSugarClient = sqlSugarClient; SqlSugarClient = sqlSugarClient;
CustomDataFilter(sqlSugarClient); CustomDataFilter(sqlSugarClient);
} }
/// <summary>
/// 自定义数据过滤器
/// </summary>
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient) protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{ {
} }
/// <summary>
/// 数据执行后事件
/// </summary>
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo) public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{ {
} }
/// <summary>
/// 数据执行前事件
/// </summary>
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo) public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{ {
} }
/// <summary>
/// SQL执行前事件
/// </summary>
public virtual void OnLogExecuting(string sql, SugarParameter[] pars) public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{ {
} }
/// <summary>
/// SQL执行后事件
/// </summary>
public virtual void OnLogExecuted(string sql, SugarParameter[] pars) public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{ {
} }
/// <summary>
/// 实体服务配置
/// </summary>
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo) public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{ {
} }

View File

@@ -4,26 +4,52 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore; namespace Yi.Framework.SqlSugarCore;
/// <summary>
/// SqlSugar数据库上下文创建上下文
/// </summary>
public class SqlSugarDbContextCreationContext public class SqlSugarDbContextCreationContext
{ {
public static SqlSugarDbContextCreationContext Current => _current.Value; private static readonly AsyncLocal<SqlSugarDbContextCreationContext> CurrentContextHolder =
private static readonly AsyncLocal<SqlSugarDbContextCreationContext> _current = new AsyncLocal<SqlSugarDbContextCreationContext>(); new AsyncLocal<SqlSugarDbContextCreationContext>();
/// <summary>
/// 获取当前上下文
/// </summary>
public static SqlSugarDbContextCreationContext Current => CurrentContextHolder.Value!;
/// <summary>
/// 连接字符串名称
/// </summary>
public string ConnectionStringName { get; } public string ConnectionStringName { get; }
/// <summary>
/// 连接字符串
/// </summary>
public string ConnectionString { get; } public string ConnectionString { get; }
public DbConnection ExistingConnection { get; internal set; } /// <summary>
/// 现有数据库连接
/// </summary>
public DbConnection? ExistingConnection { get; internal set; }
public SqlSugarDbContextCreationContext(string connectionStringName, string connectionString) /// <summary>
/// 构造函数
/// </summary>
public SqlSugarDbContextCreationContext(
string connectionStringName,
string connectionString)
{ {
ConnectionStringName = connectionStringName; ConnectionStringName = connectionStringName;
ConnectionString = connectionString; ConnectionString = connectionString;
} }
/// <summary>
/// 使用指定的上下文
/// </summary>
public static IDisposable Use(SqlSugarDbContextCreationContext context) public static IDisposable Use(SqlSugarDbContextCreationContext context)
{ {
var previousValue = Current; var previousContext = Current;
_current.Value = context; CurrentContextHolder.Value = context;
return new DisposeAction(() => _current.Value = previousValue); return new DisposeAction(() => CurrentContextHolder.Value = previousContext);
} }
} }

View File

@@ -13,189 +13,226 @@ using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore namespace Yi.Framework.SqlSugarCore
{ {
/// <summary>
/// SqlSugar数据库上下文工厂类
/// 负责创建和配置SqlSugar客户端实例
/// </summary>
public class SqlSugarDbContextFactory : ISqlSugarDbContext public class SqlSugarDbContextFactory : ISqlSugarDbContext
{ {
#region Properties
/// <summary> /// <summary>
/// SqlSugar 客户端 /// SqlSugar客户端实例
/// </summary> /// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; } public ISqlSugarClient SqlSugarClient { get; private set; }
/// <summary>
/// 延迟服务提供者
/// </summary>
private IAbpLazyServiceProvider LazyServiceProvider { get; } private IAbpLazyServiceProvider LazyServiceProvider { get; }
private TenantConfigurationWrapper TenantConfigurationWrapper=> LazyServiceProvider.LazyGetRequiredService<TenantConfigurationWrapper>(); /// <summary>
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>(); /// 租户配置包装器
/// </summary>
private TenantConfigurationWrapper TenantConfigurationWrapper =>
LazyServiceProvider.LazyGetRequiredService<TenantConfigurationWrapper>();
private DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value; /// <summary>
/// 当前租户信息
/// </summary>
private ICurrentTenant CurrentTenant =>
LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>(); /// <summary>
/// 数据库连接配置选项
/// </summary>
private DbConnOptions DbConnectionOptions =>
LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 序列化服务
/// </summary>
private ISerializeService SerializeService =>
LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
/// <summary>
/// SqlSugar上下文依赖项集合
/// </summary>
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies => private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>(); LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
/// <summary>
/// 连接配置缓存字典
/// </summary>
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new(); private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
#endregion
/// <summary>
/// 构造函数
/// </summary>
/// <param name="lazyServiceProvider">延迟服务提供者</param>
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider) public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
{ {
LazyServiceProvider = lazyServiceProvider; LazyServiceProvider = lazyServiceProvider;
var tenantConfiguration= AsyncHelper.RunSync(async () =>await TenantConfigurationWrapper.GetAsync()); // 异步获取租户配置
var tenantConfiguration = AsyncHelper.RunSync(async () => await TenantConfigurationWrapper.GetAsync());
var connectionConfig =BuildConnectionConfig(action: options => // 构建数据库连接配置
var connectionConfig = BuildConnectionConfig(options =>
{ {
options.ConnectionString =tenantConfiguration.GetCurrentConnectionString(); options.ConnectionString = tenantConfiguration.GetCurrentConnectionString();
options.DbType = GetCurrentDbType(tenantConfiguration.GetCurrentConnectionName()); options.DbType = GetCurrentDbType(tenantConfiguration.GetCurrentConnectionName());
}); });
SqlSugarClient = new SqlSugarClient(connectionConfig);
//生命周期以下都可以直接使用sqlsugardb了
// Aop及多租户连接字符串和类型需要单独设置 // 创建SqlSugar客户端实例
// Aop操作不能进行缓存 SqlSugarClient = new SqlSugarClient(connectionConfig);
SetDbAop(SqlSugarClient);
// 配置数据库AOP
ConfigureDbAop(SqlSugarClient);
} }
/// <summary> /// <summary>
/// 构建Aop-sqlsugaraop在多租户模式中需单独设置 /// 配置数据库AOP操作
/// </summary> /// </summary>
/// <param name="sqlSugarClient"></param> /// <param name="sqlSugarClient">SqlSugar客户端实例</param>
protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient) protected virtual void ConfigureDbAop(ISqlSugarClient sqlSugarClient)
{ {
//替换默认序列化 // 配置序列化服务
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService; sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
//将所有ISqlSugarDbContextDependencies进行累加 // 初始化AOP事件处理器
Action<string, SugarParameter[]> onLogExecuting = null; Action<string, SugarParameter[]> onLogExecuting = null;
Action<string, SugarParameter[]> onLogExecuted = null; Action<string, SugarParameter[]> onLogExecuted = null;
Action<object, DataFilterModel> dataExecuting = null; Action<object, DataFilterModel> dataExecuting = null;
Action<object, DataAfterModel> dataExecuted = null; Action<object, DataAfterModel> dataExecuted = null;
Action<ISqlSugarClient> onSqlSugarClientConfig = null; Action<ISqlSugarClient> onClientConfig = null;
// 按执行顺序聚合所有依赖项的AOP处理器
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder)) foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{ {
onLogExecuting += dependency.OnLogExecuting; onLogExecuting += dependency.OnLogExecuting;
onLogExecuted += dependency.OnLogExecuted; onLogExecuted += dependency.OnLogExecuted;
dataExecuting += dependency.DataExecuting; dataExecuting += dependency.DataExecuting;
dataExecuted += dependency.DataExecuted; dataExecuted += dependency.DataExecuted;
onClientConfig += dependency.OnSqlSugarClientConfig;
onSqlSugarClientConfig += dependency.OnSqlSugarClientConfig;
} }
//最先存放db操作 // 配置SqlSugar客户端
onSqlSugarClientConfig(sqlSugarClient); onClientConfig?.Invoke(sqlSugarClient);
sqlSugarClient.Aop.OnLogExecuting =onLogExecuting; // 设置AOP事件
sqlSugarClient.Aop.OnLogExecuting = onLogExecuting;
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted; sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
sqlSugarClient.Aop.DataExecuting = dataExecuting;
sqlSugarClient.Aop.DataExecuting =dataExecuting; sqlSugarClient.Aop.DataExecuted = dataExecuted;
sqlSugarClient.Aop.DataExecuted =dataExecuted;
} }
/// <summary> /// <summary>
/// 构建连接配置 /// 构建数据库连接配置
/// </summary> /// </summary>
/// <returns></returns> /// <param name="configAction">配置操作委托</param>
/// <exception cref="ArgumentException"></exception> /// <returns>连接配置对象</returns>
protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig>? action = null) protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig> configAction = null)
{ {
var dbConnOptions = Options; var dbConnOptions = DbConnectionOptions;
#region options
// 验证数据库类型配置
if (dbConnOptions.DbType is null) if (dbConnOptions.DbType is null)
{ {
throw new ArgumentException("DbType配置为空"); throw new ArgumentException("未配置数据库类型(DbType)");
} }
var slavaConFig = new List<SlaveConnectionConfig>(); // 配置读写分离
var slaveConfigs = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite) if (dbConnOptions.EnabledReadWrite)
{ {
if (dbConnOptions.ReadUrl is null) if (dbConnOptions.ReadUrl is null)
{ {
throw new ArgumentException("读写分离为空"); throw new ArgumentException("启用读写分离但未配置读库连接字符串");
} }
var readCon = dbConnOptions.ReadUrl; slaveConfigs.AddRange(dbConnOptions.ReadUrl.Select(url =>
new SlaveConnectionConfig { ConnectionString = url }));
readCon.ForEach(s =>
{
//如果是动态saas分库这里的连接串都不能写死需要动态添加这里只配置共享库的连接
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
});
} }
#endregion // 创建连接配置
var connectionConfig = new ConnectionConfig
#region config
var connectionConfig = new ConnectionConfig()
{ {
ConfigId = ConnectionStrings.DefaultConnectionStringName, ConfigId = ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite, DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url, ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true, IsAutoCloseConnection = true,
SlaveConnectionConfigs = slavaConFig, SlaveConnectionConfigs = slaveConfigs,
//设置codefirst非空值判断 ConfigureExternalServices = CreateExternalServices(dbConnOptions)
ConfigureExternalServices = new ConfigureExternalServices };
// 应用额外配置
configAction?.Invoke(connectionConfig);
return connectionConfig;
}
/// <summary>
/// 创建外部服务配置
/// </summary>
private ConfigureExternalServices CreateExternalServices(DbConnOptions dbConnOptions)
{
return new ConfigureExternalServices
{ {
// 处理表
EntityNameService = (type, entity) => EntityNameService = (type, entity) =>
{ {
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_')) if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线 {
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);
}
}, },
EntityService = (c, p) => EntityService = (propertyInfo, columnInfo) =>
{ {
if (new NullabilityInfoContext() // 配置空值处理
.Create(c).WriteState is NullabilityState.Nullable) if (new NullabilityInfoContext().Create(propertyInfo).WriteState
is NullabilityState.Nullable)
{ {
p.IsNullable = true; columnInfo.IsNullable = true;
} }
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_')) // 处理下划线命名
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName); // 驼峰转下划线 if (dbConnOptions.EnableUnderLine && !columnInfo.IsIgnore
&& !columnInfo.DbColumnName.Contains('_'))
//将所有ISqlSugarDbContextDependencies的EntityService进行累加 {
//额外的实体服务需要这里配置, columnInfo.DbColumnName = UtilMethods.ToUnderLine(columnInfo.DbColumnName);
}
// 聚合所有依赖项的实体服务
Action<PropertyInfo, EntityColumnInfo> entityService = null; Action<PropertyInfo, EntityColumnInfo> entityService = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder)) foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{ {
entityService += dependency.EntityService; entityService += dependency.EntityService;
} }
entityService(c, p); entityService?.Invoke(propertyInfo, columnInfo);
} }
},
//这里多租户有个坑,这里配置是无效的
// AopEvents = new AopEvents
// {
// DataExecuted = DataExecuted,
// DataExecuting = DataExecuting,
// OnLogExecuted = OnLogExecuted,
// OnLogExecuting = OnLogExecuting
// }
}; };
if (action is not null)
{
action.Invoke(connectionConfig);
}
#endregion
return connectionConfig;
} }
/// <summary>
/// 获取当前数据库类型
/// </summary>
/// <param name="tenantName">租户名称</param>
/// <returns>数据库类型</returns>
protected virtual DbType GetCurrentDbType(string tenantName) protected virtual DbType GetCurrentDbType(string tenantName)
{ {
if (tenantName == ConnectionStrings.DefaultConnectionStringName) return tenantName == ConnectionStrings.DefaultConnectionStringName
{ ? DbConnectionOptions.DbType!.Value
return Options.DbType!.Value; : GetDbTypeFromTenantName(tenantName)
} ?? throw new ArgumentException($"无法从租户名称{tenantName}中解析数据库类型");
var dbTypeFromTenantName = GetDbTypeFromTenantName(tenantName);
return dbTypeFromTenantName!.Value;
} }
//根据租户name进行匹配db类型: Test@Sqlite[form:AI] /// <summary>
/// 从租户名称解析数据库类型
/// 格式TenantName@DbType
/// </summary>
private DbType? GetDbTypeFromTenantName(string name) private DbType? GetDbTypeFromTenantName(string name)
{ {
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
@@ -203,61 +240,50 @@ namespace Yi.Framework.SqlSugarCore
return null; return null;
} }
// 查找@符号的位置 var atIndex = name.LastIndexOf('@');
int atIndex = name.LastIndexOf('@');
if (atIndex == -1 || atIndex == name.Length - 1) if (atIndex == -1 || atIndex == name.Length - 1)
{ {
return null; return null;
} }
// 提取 枚举 部分 var dbTypeString = name[(atIndex + 1)..];
string enumString = name.Substring(atIndex + 1); return Enum.TryParse<DbType>(dbTypeString, out var dbType)
? dbType
// 尝试将 尾缀 转换为枚举 : throw new ArgumentException($"不支持的数据库类型: {dbTypeString}");
if (Enum.TryParse<DbType>(enumString, out DbType result))
{
return result;
}
else
{
throw new ArgumentException($"数据库{name}db类型错误或不支持无法匹配{enumString}数据库类型");
}
} }
/// <summary>
/// 备份数据库
/// </summary>
public virtual void BackupDataBase() public virtual void BackupDataBase()
{ {
string directoryName = "database_backup"; const string backupDirectory = "database_backup";
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}"; var fileName = $"{DateTime.Now:yyyyMMdd_HHmmss}_{SqlSugarClient.Ado.Connection.Database}";
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
switch (Options.DbType) Directory.CreateDirectory(backupDirectory);
switch (DbConnectionOptions.DbType)
{ {
case DbType.MySql: case DbType.MySql:
//MySql SqlSugarClient.DbMaintenance.BackupDataBase(
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core Path.Combine(backupDirectory, $"{fileName}.sql"));
break; break;
case DbType.Sqlite: case DbType.Sqlite:
//Sqlite SqlSugarClient.DbMaintenance.BackupDataBase(
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core null,
$"{fileName}.db");
break; break;
case DbType.SqlServer: case DbType.SqlServer:
//SqlServer SqlSugarClient.DbMaintenance.BackupDataBase(
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名 Path.Combine(backupDirectory, $"{fileName}.bak"));
break; break;
default: default:
throw new NotImplementedException("其他数据库备份未实现"); throw new NotImplementedException($"数据库类型 {DbConnectionOptions.DbType} 的备份操作尚未实现");
} }
} }
} }

View File

@@ -63,14 +63,14 @@ public class SqlSugarNonPublicSerializer : ISerializeService
// 调用 SerializeObject 方法序列化对象 // 调用 SerializeObject 方法序列化对象
T json = (T)methods.MakeGenericMethod(typeof(T)) T json = (T)methods.MakeGenericMethod(typeof(T))
.Invoke(null, new object[] { value, null }); .Invoke(null, new object[] { value, null! });
return json; return json!;
} }
var jSetting = new JsonSerializerSettings var jSetting = new JsonSerializerSettings
{ {
NullValueHandling = NullValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore,
ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect
}; };
return JsonConvert.DeserializeObject<T>(value, jSetting); return JsonConvert.DeserializeObject<T>(value, jSetting)!;
} }
} }

View File

@@ -7,56 +7,68 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore; namespace Yi.Framework.SqlSugarCore;
/// <summary> /// <summary>
/// 租户配置 /// 租户配置包装器
/// </summary> /// </summary>
public class TenantConfigurationWrapper : ITransientDependency public class TenantConfigurationWrapper : ITransientDependency
{ {
private readonly IAbpLazyServiceProvider _serviceProvider; private readonly IAbpLazyServiceProvider _serviceProvider;
private ICurrentTenant CurrentTenant => _serviceProvider.LazyGetRequiredService<ICurrentTenant>();
private ITenantStore TenantStore => _serviceProvider.LazyGetRequiredService<ITenantStore>();
private DbConnOptions DbConnOptions => _serviceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
private ICurrentTenant CurrentTenantService =>
_serviceProvider.LazyGetRequiredService<ICurrentTenant>();
private ITenantStore TenantStoreService =>
_serviceProvider.LazyGetRequiredService<ITenantStore>();
private DbConnOptions DbConnectionOptions =>
_serviceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
/// <summary>
/// 构造函数
/// </summary>
public TenantConfigurationWrapper(IAbpLazyServiceProvider serviceProvider) public TenantConfigurationWrapper(IAbpLazyServiceProvider serviceProvider)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
/// <summary> /// <summary>
/// 获取租户信息 /// 获取租户配置信息
/// [from:ai]
/// </summary> /// </summary>
/// <returns></returns>
public async Task<TenantConfiguration?> GetAsync() public async Task<TenantConfiguration?> GetAsync()
{ {
//未开启多租户 if (!DbConnectionOptions.EnabledSaasMultiTenancy)
if (!DbConnOptions.EnabledSaasMultiTenancy)
{ {
return await TenantStore.FindAsync(ConnectionStrings.DefaultConnectionStringName); return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName);
} }
TenantConfiguration? tenantConfiguration = null; return await GetTenantConfigurationByCurrentTenant();
if (CurrentTenant.Id is not null)
{
tenantConfiguration = await TenantStore.FindAsync(CurrentTenant.Id.Value);
if (tenantConfiguration == null)
{
throw new ApplicationException($"未找到租户信息,租户Id:{CurrentTenant.Id}");
}
return tenantConfiguration;
} }
if (!string.IsNullOrEmpty(CurrentTenant.Name)) private async Task<TenantConfiguration?> GetTenantConfigurationByCurrentTenant()
{ {
tenantConfiguration = await TenantStore.FindAsync(CurrentTenant.Name); // 通过租户ID查找
if (tenantConfiguration == null) if (CurrentTenantService.Id.HasValue)
{ {
throw new ApplicationException($"未找到租户信息,租户名称:{CurrentTenant.Name}"); var config = await TenantStoreService.FindAsync(CurrentTenantService.Id.Value);
if (config == null)
{
throw new ApplicationException($"未找到租户信息,租户Id:{CurrentTenantService.Id}");
} }
return tenantConfiguration; return config;
} }
return await TenantStore.FindAsync(ConnectionStrings.DefaultConnectionStringName); // 通过租户名称查找
if (!string.IsNullOrEmpty(CurrentTenantService.Name))
{
var config = await TenantStoreService.FindAsync(CurrentTenantService.Name);
if (config == null)
{
throw new ApplicationException($"未找到租户信息,租户名称:{CurrentTenantService.Name}");
}
return config;
}
// 返回默认配置
return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName);
} }
/// <summary> /// <summary>

View File

@@ -8,10 +8,20 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore.Uow namespace Yi.Framework.SqlSugarCore.Uow
{ {
/// <summary>
/// SqlSugar数据库API实现
/// </summary>
public class SqlSugarDatabaseApi : IDatabaseApi public class SqlSugarDatabaseApi : IDatabaseApi
{ {
/// <summary>
/// 数据库上下文
/// </summary>
public ISqlSugarDbContext DbContext { get; } public ISqlSugarDbContext DbContext { get; }
/// <summary>
/// 初始化SqlSugar数据库API
/// </summary>
/// <param name="dbContext">数据库上下文</param>
public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext) public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext)
{ {
DbContext = dbContext; DbContext = dbContext;

View File

@@ -3,33 +3,48 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore.Uow namespace Yi.Framework.SqlSugarCore.Uow
{ {
/// <summary>
/// SqlSugar事务API实现
/// </summary>
public class SqlSugarTransactionApi : ITransactionApi, ISupportsRollback public class SqlSugarTransactionApi : ITransactionApi, ISupportsRollback
{ {
private ISqlSugarDbContext _sqlsugarDbContext; private readonly ISqlSugarDbContext _dbContext;
public SqlSugarTransactionApi(ISqlSugarDbContext sqlsugarDbContext) public SqlSugarTransactionApi(ISqlSugarDbContext dbContext)
{ {
_sqlsugarDbContext = sqlsugarDbContext; _dbContext = dbContext;
} }
/// <summary>
/// 获取数据库上下文
/// </summary>
public ISqlSugarDbContext GetDbContext() public ISqlSugarDbContext GetDbContext()
{ {
return _sqlsugarDbContext; return _dbContext;
} }
/// <summary>
/// 提交事务
/// </summary>
public async Task CommitAsync(CancellationToken cancellationToken = default) public async Task CommitAsync(CancellationToken cancellationToken = default)
{ {
await _sqlsugarDbContext.SqlSugarClient.Ado.CommitTranAsync(); await _dbContext.SqlSugarClient.Ado.CommitTranAsync();
}
public void Dispose()
{
_sqlsugarDbContext.SqlSugarClient.Ado.Dispose();
} }
/// <summary>
/// 回滚事务
/// </summary>
public async Task RollbackAsync(CancellationToken cancellationToken = default) public async Task RollbackAsync(CancellationToken cancellationToken = default)
{ {
await _sqlsugarDbContext.SqlSugarClient.Ado.RollbackTranAsync(); await _dbContext.SqlSugarClient.Ado.RollbackTranAsync();
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
_dbContext.SqlSugarClient.Ado.Dispose();
} }
} }
} }

View File

@@ -13,112 +13,121 @@ namespace Yi.Framework.SqlSugarCore.Uow
{ {
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
{ {
/// <summary>
/// 日志记录器
/// </summary>
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; } public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
/// <summary>
/// 服务提供者
/// </summary>
public IServiceProvider ServiceProvider { get; set; } public IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// 数据库上下文访问器实例
/// </summary>
private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance; private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance;
protected readonly TenantConfigurationWrapper _tenantConfigurationWrapper;
protected readonly IUnitOfWorkManager UnitOfWorkManager; private readonly TenantConfigurationWrapper _tenantConfigurationWrapper;
protected readonly IConnectionStringResolver ConnectionStringResolver; private readonly IUnitOfWorkManager _unitOfWorkManager;
protected readonly ICancellationTokenProvider CancellationTokenProvider; private readonly IConnectionStringResolver _connectionStringResolver;
protected readonly ICurrentTenant CurrentTenant; private readonly ICancellationTokenProvider _cancellationTokenProvider;
private readonly ICurrentTenant _currentTenant;
public UnitOfWorkSqlsugarDbContextProvider( public UnitOfWorkSqlsugarDbContextProvider(
IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver, IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant, TenantConfigurationWrapper tenantConfigurationWrapper) ICurrentTenant currentTenant,
TenantConfigurationWrapper tenantConfigurationWrapper)
{ {
UnitOfWorkManager = unitOfWorkManager; _unitOfWorkManager = unitOfWorkManager;
ConnectionStringResolver = connectionStringResolver; _connectionStringResolver = connectionStringResolver;
CancellationTokenProvider = cancellationTokenProvider; _cancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant; _currentTenant = currentTenant;
_tenantConfigurationWrapper = tenantConfigurationWrapper; _tenantConfigurationWrapper = tenantConfigurationWrapper;
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance; Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
} }
/// <summary>
/// 获取数据库上下文
/// </summary>
public virtual async Task<TDbContext> GetDbContextAsync() public virtual async Task<TDbContext> GetDbContextAsync()
{ {
//获取当前连接字符串,未多租户时,默认为空 // 获取当前租户配置
var tenantConfiguration= await _tenantConfigurationWrapper.GetAsync(); var tenantConfiguration = await _tenantConfigurationWrapper.GetAsync();
//由于sqlsugar的特殊性没有db区分不再使用连接字符串解析器
// 获取连接字符串信息
var connectionStringName = tenantConfiguration.GetCurrentConnectionName(); var connectionStringName = tenantConfiguration.GetCurrentConnectionName();
var connectionString = tenantConfiguration.GetCurrentConnectionString(); var connectionString = tenantConfiguration.GetCurrentConnectionString();
var dbContextKey = $"{this.GetType().Name}_{connectionString}"; var dbContextKey = $"{this.GetType().Name}_{connectionString}";
var unitOfWork = UnitOfWorkManager.Current; var unitOfWork = _unitOfWorkManager.Current;
if (unitOfWork == null ) if (unitOfWork == null)
{ {
//var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>(); throw new AbpException(
//如果不启用工作单元创建一个新的db不开启事务即可 "DbContext 只能在工作单元内工作当前DbContext没有工作单元如需创建新线程并发操作请手动创建工作单元");
//return dbContext;
//2024-11-30改回强制性使用工作单元否则容易造成歧义
throw new AbpException("DbContext 只能在工作单元内工作当前DbContext没有工作单元如需创建新线程并发操作请手动创建工作单元");
} }
//尝试当前工作单元获取db
// 尝试从当前工作单元获取数据库API
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey); var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
//当前没有db创建一个新的db // 当前没有数据库API则创建新的
if (databaseApi == null) if (databaseApi == null)
{ {
//db根据连接字符串来创建
databaseApi = new SqlSugarDatabaseApi( databaseApi = new SqlSugarDatabaseApi(
await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString) await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
); );
//创建的db加入到当前工作单元中
unitOfWork.AddDatabaseApi(dbContextKey, databaseApi); unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
} }
return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext; return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext;
} }
/// <summary>
/// 创建数据库上下文
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) /// </summary>
protected virtual async Task<TDbContext> CreateDbContextAsync(
IUnitOfWork unitOfWork,
string connectionStringName,
string connectionString)
{ {
var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString); var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString);
//将连接key进行传值
using (SqlSugarDbContextCreationContext.Use(creationContext)) using (SqlSugarDbContextCreationContext.Use(creationContext))
{ {
var dbContext = await CreateDbContextAsync(unitOfWork); return await CreateDbContextAsync(unitOfWork);
return dbContext;
} }
} }
/// <summary>
/// 根据工作单元创建数据库上下文
/// </summary>
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork) protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
{ {
return unitOfWork.Options.IsTransactional return unitOfWork.Options.IsTransactional
? await CreateDbContextWithTransactionAsync(unitOfWork) ? await CreateDbContextWithTransactionAsync(unitOfWork)
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>(); : unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
} }
/// <summary>
/// 创建带事务的数据库上下文
/// </summary>
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork) protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
{ {
//事务key
var transactionApiKey = $"SqlSugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}"; var transactionApiKey = $"SqlSugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
//尝试查找事务
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi; var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
//该db还没有进行开启事务
if (activeTransaction == null) if (activeTransaction == null)
{ {
//获取到db添加事务即可
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>(); var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
var transaction = new SqlSugarTransactionApi( var transaction = new SqlSugarTransactionApi(dbContext);
dbContext
);
unitOfWork.AddTransactionApi(transactionApiKey, transaction); unitOfWork.AddTransactionApi(transactionApiKey, transaction);
await dbContext.SqlSugarClient.Ado.BeginTranAsync(); await dbContext.SqlSugarClient.Ado.BeginTranAsync();
return dbContext; return dbContext;
} }
else
{
return (TDbContext)activeTransaction.GetDbContext(); return (TDbContext)activeTransaction.GetDbContext();
} }
}
} }
} }

View File

@@ -18,142 +18,177 @@ using Yi.Framework.SqlSugarCore.Uow;
namespace Yi.Framework.SqlSugarCore namespace Yi.Framework.SqlSugarCore
{ {
/// <summary>
/// SqlSugar Core模块
/// </summary>
[DependsOn(typeof(AbpDddDomainModule))] [DependsOn(typeof(AbpDddDomainModule))]
public class YiFrameworkSqlSugarCoreModule : AbpModule public class YiFrameworkSqlSugarCoreModule : AbpModule
{ {
public override Task ConfigureServicesAsync(ServiceConfigurationContext context) public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
{ {
var service = context.Services; var services = context.Services;
var configuration = service.GetConfiguration(); var configuration = services.GetConfiguration();
// 配置数据库连接选项
ConfigureDbOptions(services, configuration);
// 配置GUID生成器
ConfigureGuidGenerator(services);
// 注册仓储和服务
RegisterRepositories(services);
return Task.CompletedTask;
}
private void ConfigureDbOptions(IServiceCollection services, IConfiguration configuration)
{
var section = configuration.GetSection("DbConnOptions"); var section = configuration.GetSection("DbConnOptions");
Configure<DbConnOptions>(section); Configure<DbConnOptions>(section);
var dbConnOptions = new DbConnOptions(); var dbConnOptions = new DbConnOptions();
section.Bind(dbConnOptions); section.Bind(dbConnOptions);
//很多人遗漏了这一点,不同的数据库,对于主键的使用规约不一样,需要根据数据库进行判断 // 配置默认连接字符串
SequentialGuidType guidType; Configure<AbpDbConnectionOptions>(options =>
switch (dbConnOptions.DbType)
{ {
case DbType.MySql: options.ConnectionStrings.Default = dbConnOptions.Url;
case DbType.PostgreSQL: });
guidType= SequentialGuidType.SequentialAsString;
break; // 配置默认租户
case DbType.SqlServer: ConfigureDefaultTenant(services, dbConnOptions);
guidType = SequentialGuidType.SequentialAtEnd;
break;
case DbType.Oracle:
guidType = SequentialGuidType.SequentialAsBinary;
break;
default:
guidType = SequentialGuidType.SequentialAtEnd;
break;
} }
private void ConfigureGuidGenerator(IServiceCollection services)
{
var dbConnOptions = services.GetConfiguration()
.GetSection("DbConnOptions")
.Get<DbConnOptions>();
var guidType = GetSequentialGuidType(dbConnOptions?.DbType);
Configure<AbpSequentialGuidGeneratorOptions>(options => Configure<AbpSequentialGuidGeneratorOptions>(options =>
{ {
options.DefaultSequentialGuidType = guidType; options.DefaultSequentialGuidType = guidType;
}); });
service.TryAddTransient<ISqlSugarDbContext, SqlSugarDbContextFactory>();
//不开放sqlsugarClient
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
service.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
service.AddTransient(typeof(IRepository<,>), typeof(SqlSugarRepository<,>));
service.AddTransient(typeof(ISqlSugarRepository<>), typeof(SqlSugarRepository<>));
service.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
service.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
//替换Sqlsugar默认序列化器用来解决.Select()不支持嵌套对象/匿名对象的非公有访问器 值无法绑定,如Id属性
context.Services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
var dbConfig = section.Get<DbConnOptions>();
//将默认db传递给abp连接字符串模块
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
//配置abp默认租户对接abp模块
Configure<AbpDefaultTenantStoreOptions>(x => {
var tenantList = x.Tenants.ToList();
foreach(var tenant in tenantList)
{
tenant.NormalizedName = tenant.Name.Contains("@") ?
tenant.Name.Substring(0, tenant.Name.LastIndexOf("@")) :
tenant.Name;
} }
tenantList.Insert(0, new TenantConfiguration
private void RegisterRepositories(IServiceCollection services)
{
services.TryAddTransient<ISqlSugarDbContext, SqlSugarDbContextFactory>();
services.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
services.AddTransient(typeof(IRepository<,>), typeof(SqlSugarRepository<,>));
services.AddTransient(typeof(ISqlSugarRepository<>), typeof(SqlSugarRepository<>));
services.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
services.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlsugarDbContextProvider<>));
services.AddSingleton<ISerializeService, SqlSugarNonPublicSerializer>();
services.AddYiDbContext<DefaultSqlSugarDbContext>();
}
private void ConfigureDefaultTenant(IServiceCollection services, DbConnOptions dbConfig)
{
Configure<AbpDefaultTenantStoreOptions>(options =>
{
var tenants = options.Tenants.ToList();
// 规范化租户名称
foreach (var tenant in tenants)
{
tenant.NormalizedName = tenant.Name.Contains("@")
? tenant.Name.Substring(0, tenant.Name.LastIndexOf("@"))
: tenant.Name;
}
// 添加默认租户
tenants.Insert(0, new TenantConfiguration
{ {
Id = Guid.Empty, Id = Guid.Empty,
Name = $"{ConnectionStrings.DefaultConnectionStringName}", Name = ConnectionStrings.DefaultConnectionStringName,
NormalizedName = ConnectionStrings.DefaultConnectionStringName, NormalizedName = ConnectionStrings.DefaultConnectionStringName,
ConnectionStrings = new ConnectionStrings() { { ConnectionStrings.DefaultConnectionStringName, dbConfig.Url } }, ConnectionStrings = new ConnectionStrings
{
{ ConnectionStrings.DefaultConnectionStringName, dbConfig.Url }
},
IsActive = true IsActive = true
}); });
x.Tenants = tenantList.ToArray();
options.Tenants = tenants.ToArray();
}); });
context.Services.AddYiDbContext<DefaultSqlSugarDbContext>();
return Task.CompletedTask;
} }
private SequentialGuidType GetSequentialGuidType(DbType? dbType)
{
return dbType switch
{
DbType.MySql or DbType.PostgreSQL => SequentialGuidType.SequentialAsString,
DbType.SqlServer => SequentialGuidType.SequentialAtEnd,
DbType.Oracle => SequentialGuidType.SequentialAsBinary,
_ => SequentialGuidType.SequentialAtEnd
};
}
public override async Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) public override async Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
{ {
//进行CodeFirst var serviceProvider = context.ServiceProvider;
var service = context.ServiceProvider; var options = serviceProvider.GetRequiredService<IOptions<DbConnOptions>>().Value;
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value; var logger = serviceProvider.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>();
var logger = service.GetRequiredService<ILogger<YiFrameworkSqlSugarCoreModule>>(); // 记录配置信息
LogConfiguration(logger, options);
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL配置:==========");
sb.AppendLine($"数据库连接字符串:{options.Url}");
sb.AppendLine($"数据库类型:{options.DbType.ToString()}");
sb.AppendLine($"是否开启种子数据:{options.EnabledDbSeed}");
sb.AppendLine($"是否开启CodeFirst{options.EnabledCodeFirst}");
sb.AppendLine($"是否开启Saas多租户{options.EnabledSaasMultiTenancy}");
sb.AppendLine("===============================");
logger.LogInformation(sb.ToString());
// 初始化数据库
if (options.EnabledCodeFirst) if (options.EnabledCodeFirst)
{ {
CodeFirst(service); await InitializeDatabase(serviceProvider);
} }
// 初始化种子数据
if (options.EnabledDbSeed) if (options.EnabledDbSeed)
{ {
await DataSeedAsync(service); await InitializeSeedData(serviceProvider);
} }
} }
private void CodeFirst(IServiceProvider service) private void LogConfiguration(ILogger logger, DbConnOptions options)
{ {
var moduleContainer = service.GetRequiredService<IModuleContainer>(); var logMessage = new StringBuilder()
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient; .AppendLine()
.AppendLine("==========Yi-SQL配置:==========")
.AppendLine($"数据库连接字符串:{options.Url}")
.AppendLine($"数据库类型:{options.DbType}")
.AppendLine($"是否开启种子数据:{options.EnabledDbSeed}")
.AppendLine($"是否开启CodeFirst{options.EnabledCodeFirst}")
.AppendLine($"是否开启Saas多租户{options.EnabledSaasMultiTenancy}")
.AppendLine("===============================")
.ToString();
//尝试创建数据库 logger.LogInformation(logMessage);
}
private async Task InitializeDatabase(IServiceProvider serviceProvider)
{
var moduleContainer = serviceProvider.GetRequiredService<IModuleContainer>();
var db = serviceProvider.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
// 创建数据库
db.DbMaintenance.CreateDatabase(); db.DbMaintenance.CreateDatabase();
List<Type> types = new List<Type>(); // 获取需要创建表的实体类型
foreach (var module in moduleContainer.Modules) var entityTypes = moduleContainer.Modules
{ .SelectMany(m => m.Assembly.GetTypes())
types.AddRange(module.Assembly.GetTypes() .Where(t => t.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null) && t.GetCustomAttribute<SugarTable>() != null
.Where(x => x.GetCustomAttribute<SugarTable>() != null) && t.GetCustomAttribute<SplitTableAttribute>() == null)
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null)); .ToList();
}
if (types.Count > 0) if (entityTypes.Any())
{ {
db.CopyNew().CodeFirst.InitTables(types.ToArray()); db.CopyNew().CodeFirst.InitTables(entityTypes.ToArray());
} }
} }
private async Task DataSeedAsync(IServiceProvider service) private async Task InitializeSeedData(IServiceProvider serviceProvider)
{ {
var dataSeeder = service.GetRequiredService<IDataSeeder>(); var dataSeeder = serviceProvider.GetRequiredService<IDataSeeder>();
await dataSeeder.SeedAsync(); await dataSeeder.SeedAsync();
} }
} }

View File

@@ -360,7 +360,7 @@ namespace Yi.Abp.Web
app.UseAccessLog(); app.UseAccessLog();
//请求处理 //请求处理
app.UseYiApiHandlinge(); app.UseApiInfoHandling();
//静态资源 //静态资源
app.UseStaticFiles(new StaticFileOptions app.UseStaticFiles(new StaticFileOptions

View File

@@ -150,7 +150,7 @@ namespace Yi.Abp.Tool.Web
app.UseYiSwagger(); app.UseYiSwagger();
//请求处理 //请求处理
app.UseYiApiHandlinge(); app.UseApiInfoHandling();
//静态资源 //静态资源
app.UseStaticFiles("/api/app/wwwroot"); app.UseStaticFiles("/api/app/wwwroot");
app.UseDefaultFiles(); app.UseDefaultFiles();