diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/ApiInfoBuilderExtensions.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/ApiInfoBuilderExtensions.cs index e94d369a..5525a2e5 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/ApiInfoBuilderExtensions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/ApiInfoBuilderExtensions.cs @@ -4,13 +4,23 @@ using Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares; namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder { + /// + /// 提供API信息处理的应用程序构建器扩展方法 + /// public static class ApiInfoBuilderExtensions { - public static IApplicationBuilder UseYiApiHandlinge([NotNull] this IApplicationBuilder app) + /// + /// 使用Yi框架的API信息处理中间件 + /// + /// 应用程序构建器实例 + /// 配置后的应用程序构建器实例 + /// 当builder参数为null时抛出 + public static IApplicationBuilder UseApiInfoHandling([NotNull] this IApplicationBuilder builder) { - app.UseMiddleware(); - return app; - + // 添加API信息处理中间件到请求管道 + builder.UseMiddleware(); + + return builder; } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/SwaggerBuilderExtensons.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/SwaggerBuilderExtensons.cs index 7ea50a4c..03fb034f 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/SwaggerBuilderExtensons.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Builder/SwaggerBuilderExtensons.cs @@ -5,49 +5,101 @@ using Volo.Abp.AspNetCore.Mvc; namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder { - public static class SwaggerBuilderExtensons + /// + /// Swagger构建器扩展类 + /// + public static class SwaggerBuilderExtensions { - public static IApplicationBuilder UseYiSwagger(this IApplicationBuilder app, params SwaggerModel[] swaggerModels) + /// + /// 配置并使用Yi框架的Swagger中间件 + /// + /// 应用程序构建器 + /// Swagger配置模型数组 + /// 应用程序构建器 + public static IApplicationBuilder UseYiSwagger( + this IApplicationBuilder app, + params SwaggerConfiguration[] swaggerConfigs) { - var mvcOptions = app.ApplicationServices.GetRequiredService>().Value; - - app.UseSwagger(); - app.UseSwaggerUI(c => + if (app == null) { - foreach (var setting in mvcOptions.ConventionalControllers.ConventionalControllerSettings) + throw new ArgumentNullException(nameof(app)); + } + + var mvcOptions = app.ApplicationServices + .GetRequiredService>() + .Value; + + // 启用Swagger中间件 + app.UseSwagger(); + + // 配置SwaggerUI + app.UseSwaggerUI(options => + { + // 添加约定控制器的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", + setting.RemoteServiceName); } - if (mvcOptions.ConventionalControllers.ConventionalControllerSettings.Count==0&&swaggerModels.Length == 0) + + // 如果没有配置任何终结点,使用默认配置 + if (!conventionalSettings.Any() && (swaggerConfigs == null || !swaggerConfigs.Any())) { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework"); + options.SwaggerEndpoint("/swagger/v1/swagger.json", "Yi.Framework"); + return; } - else + + // 添加自定义Swagger配置的终结点 + if (swaggerConfigs != null) { - foreach (var k in swaggerModels) + foreach (var config in swaggerConfigs) { - c.SwaggerEndpoint(k.Url, k.Name); + options.SwaggerEndpoint(config.Url, config.Name); } } - }); + return app; } - } - public class SwaggerModel + + /// + /// Swagger配置模型 + /// + public class SwaggerConfiguration { - public SwaggerModel(string name) + private const string DefaultSwaggerUrl = "/swagger/v1/swagger.json"; + + /// + /// Swagger JSON文档的URL + /// + public string Url { get; } + + /// + /// Swagger文档的显示名称 + /// + public string Name { get; } + + /// + /// 使用默认URL创建Swagger配置 + /// + /// 文档显示名称 + public SwaggerConfiguration(string name) + : this(DefaultSwaggerUrl, name) { - this.Name = name; - this.Url = "/swagger/v1/swagger.json"; } - public SwaggerModel(string url, string name) + + /// + /// 创建自定义Swagger配置 + /// + /// Swagger JSON文档URL + /// 文档显示名称 + public SwaggerConfiguration(string url, string name) { - this.Url = url; - this.Name = name; + Url = url ?? throw new ArgumentNullException(nameof(url)); + Name = name ?? throw new ArgumentNullException(nameof(name)); } - public string Url { get; set; } - public string Name { get; set; } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Middlewares/ApiInfoMiddleware.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Middlewares/ApiInfoMiddleware.cs index 3d89d729..8a8bb7b7 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Middlewares/ApiInfoMiddleware.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/AspNetCore/Middlewares/ApiInfoMiddleware.cs @@ -1,39 +1,61 @@ using System.Diagnostics; -using System.Net.Http; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; -using Volo.Abp.Json; using Yi.Framework.Core.Extensions; -using static System.Net.WebRequestMethods; namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Middlewares { + /// + /// API响应信息处理中间件 + /// 主要用于处理特定文件类型的响应头信息 + /// [DebuggerStepThrough] public class ApiInfoMiddleware : IMiddleware, ITransientDependency { - + /// + /// 处理HTTP请求的中间件方法 + /// + /// HTTP上下文 + /// 请求处理委托 + /// 异步任务 public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - context.Response.OnStarting([DebuggerStepThrough] () => + // 在响应开始时处理文件下载相关的响应头 + context.Response.OnStarting(() => { - if (context.Response.StatusCode == StatusCodes.Status200OK - && 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"); - } + HandleFileDownloadResponse(context); return Task.CompletedTask; }); + // 继续处理管道中的下一个中间件 await next(context); + } + /// + /// 处理文件下载响应的响应头信息 + /// + /// HTTP上下文 + 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"); + } } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/Extensions/DependencyInjection/SwaggerAddExtensions.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/Extensions/DependencyInjection/SwaggerAddExtensions.cs index 7f446586..b6fe3861 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/Extensions/DependencyInjection/SwaggerAddExtensions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Microsoft/Extensions/DependencyInjection/SwaggerAddExtensions.cs @@ -9,129 +9,193 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.DependencyInjection; using Volo.Abp.Options; namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection { + /// + /// Swagger生成器扩展类 + /// public static class SwaggerAddExtensions { - public static IServiceCollection AddYiSwaggerGen(this IServiceCollection services, - Action? action = null) + /// + /// 添加Yi框架的Swagger生成器服务 + /// + /// 程序入口类型 + /// 服务集合 + /// 自定义配置动作 + /// 服务集合 + public static IServiceCollection AddYiSwaggerGen( + this IServiceCollection services, + Action? setupAction = null) { + // 获取MVC配置选项 var mvcOptions = services.GetPreConfigureActions().Configure(); - var mvcSettings = - mvcOptions.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName); - + // 获取并去重远程服务名称 + var remoteServiceSettings = mvcOptions.ConventionalControllers + .ConventionalControllerSettings + .DistinctBy(x => x.RemoteServiceName); services.AddAbpSwaggerGen( options => { - if (action is not null) - { - action.Invoke(options); - } + // 应用外部配置 + setupAction?.Invoke(options); - // 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准 - foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName)) - { - if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName)) - { - options.SwaggerDoc(setting.RemoteServiceName, - new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" }); - } - } + // 配置API文档分组 + ConfigureApiGroups(options, remoteServiceSettings); - // 根据分组名称过滤 API 文档 - options.DocInclusionPredicate((docName, apiDesc) => - { - if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) - { - var settingOrNull = mvcSettings - .Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly) - .FirstOrDefault(); - if (settingOrNull is not null) - { - return docName == settingOrNull.RemoteServiceName; - } - } - - return false; - }); + // 配置API文档过滤器 + ConfigureApiFilter(options, remoteServiceSettings); + // 配置Schema ID生成规则 options.CustomSchemaIds(type => type.FullName); - var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location); - if (basePath is not null) - { - foreach (var item in Directory.GetFiles(basePath, "*.xml")) - { - options.IncludeXmlComments(item, true); - } - } - options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme() - { - Description = "直接输入Token即可", - Name = "Authorization", - In = ParameterLocation.Header, - Type = SecuritySchemeType.Http, - Scheme = "bearer" - }); - var scheme = new OpenApiSecurityScheme() - { - Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" } - }; - options.AddSecurityRequirement(new OpenApiSecurityRequirement() - { - [scheme] = new string[0] - }); + // 包含XML注释文档 + IncludeXmlComments(options); - options.OperationFilter(); - options.SchemaFilter(); + // 配置JWT认证 + ConfigureJwtAuthentication(options); + + // 添加自定义过滤器 + ConfigureCustomFilters(options); } ); - return services; } + + /// + /// 配置API分组 + /// + private static void ConfigureApiGroups( + SwaggerGenOptions options, + IEnumerable settings) + { + foreach (var setting in settings.OrderBy(x => x.RemoteServiceName)) + { + if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName)) + { + options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo + { + Title = setting.RemoteServiceName, + Version = "v1" + }); + } + } + } + + /// + /// 配置API文档过滤器 + /// + private static void ConfigureApiFilter( + SwaggerGenOptions options, + IEnumerable settings) + { + options.DocInclusionPredicate((docName, apiDesc) => + { + if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerDesc) + { + var matchedSetting = settings + .FirstOrDefault(x => x.Assembly == controllerDesc.ControllerTypeInfo.Assembly); + return matchedSetting?.RemoteServiceName == docName; + } + return false; + }); + } + + /// + /// 包含XML注释文档 + /// + private static void IncludeXmlComments(SwaggerGenOptions options) + { + var basePath = Path.GetDirectoryName(typeof(TProgram).Assembly.Location); + if (basePath is not null) + { + foreach (var xmlFile in Directory.GetFiles(basePath, "*.xml")) + { + options.IncludeXmlComments(xmlFile, true); + } + } + } + + /// + /// 配置JWT认证 + /// + private static void ConfigureJwtAuthentication(SwaggerGenOptions options) + { + options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme + { + Description = "请在此输入JWT Token", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "bearer" + }); + + var scheme = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "JwtBearer" + } + }; + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + [scheme] = Array.Empty() + }); + } + + /// + /// 配置自定义过滤器 + /// + private static void ConfigureCustomFilters(SwaggerGenOptions options) + { + options.OperationFilter(); + options.SchemaFilter(); + } } - /// - /// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述 + /// Swagger文档枚举字段显示过滤器 /// public class EnumSchemaFilter : ISchemaFilter { /// - /// 实现接口 + /// 应用枚举架构过滤器 /// - /// - /// - public void Apply(OpenApiSchema model, SchemaFilterContext context) + /// OpenAPI架构 + /// 架构过滤器上下文 + public void Apply(OpenApiSchema schema, SchemaFilterContext context) { - if (context.Type.IsEnum) + if (!context.Type.IsEnum) return; + + schema.Enum.Clear(); + schema.Type = "string"; + schema.Format = null; + + var enumDescriptions = new StringBuilder(); + foreach (var enumName in Enum.GetNames(context.Type)) { - model.Enum.Clear(); - model.Type = "string"; - model.Format = null; + var enumValue = (Enum)Enum.Parse(context.Type, enumName); + var description = GetEnumDescription(enumValue); + var enumIntValue = Convert.ToInt64(enumValue); - - StringBuilder stringBuilder = new StringBuilder(); - Enum.GetNames(context.Type) - .ToList() - .ForEach(name => - { - Enum e = (Enum)Enum.Parse(context.Type, name); - var descrptionOrNull = GetEnumDescription(e); - model.Enum.Add(new OpenApiString(name)); - stringBuilder.Append( - $"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】
"); - }); - model.Description = stringBuilder.ToString(); + schema.Enum.Add(new OpenApiString(enumName)); + enumDescriptions.AppendLine( + $"【枚举:{enumName}{(description is null ? string.Empty : $"({description})")}={enumIntValue}】"); } + schema.Description = enumDescriptions.ToString(); } + /// + /// 获取枚举描述特性值 + /// private static string? GetEnumDescription(Enum value) { var fieldInfo = value.GetType().GetField(value.ToString()); @@ -140,22 +204,30 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection } } - - public class AddRequiredHeaderParameter : IOperationFilter + /// + /// 租户头部参数过滤器 + /// + public class TenantHeaderOperationFilter : IOperationFilter { - public static string HeaderKey { get; set; } = "__tenant"; + /// + /// 租户标识键名 + /// + private const string TenantHeaderKey = "__tenant"; + /// + /// 应用租户头部参数过滤器 + /// public void Apply(OpenApiOperation operation, OperationFilterContext context) { - if (operation.Parameters == null) - operation.Parameters = new List(); + operation.Parameters ??= new List(); + operation.Parameters.Add(new OpenApiParameter { - Name = HeaderKey, + Name = TenantHeaderKey, In = ParameterLocation.Header, Required = false, AllowEmptyValue = true, - Description = "租户id或者租户名称(可空为默认租户)" + Description = "租户ID或租户名称(留空表示默认租户)" }); } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiConventionalRouteBuilder.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiConventionalRouteBuilder.cs index dc946ea0..47a9a746 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiConventionalRouteBuilder.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiConventionalRouteBuilder.cs @@ -9,65 +9,129 @@ using Volo.Abp.Reflection; namespace Yi.Framework.AspNetCore.Mvc { + /// + /// 自定义路由构建器,用于生成API路由规则 + /// [Dependency(ServiceLifetime.Transient, ReplaceServices = true)] [ExposeServices(typeof(IConventionalRouteBuilder))] public class YiConventionalRouteBuilder : ConventionalRouteBuilder { - public YiConventionalRouteBuilder(IOptions options) : base(options) + /// + /// 构造函数 + /// + /// ABP约定控制器配置选项 + public YiConventionalRouteBuilder(IOptions options) + : base(options) { } + + /// + /// 构建API路由 + /// + /// 根路径 + /// 控制器名称 + /// Action模型 + /// HTTP方法 + /// 控制器配置 + /// 构建的路由URL public override string Build( - string rootPath, - string controllerName, - ActionModel action, - string httpMethod, - [CanBeNull] ConventionalControllerSetting configuration) + string rootPath, + string controllerName, + ActionModel action, + string httpMethod, + [CanBeNull] ConventionalControllerSetting configuration) { - + // 获取API路由前缀 var apiRoutePrefix = GetApiRoutePrefix(action, configuration); - var controllerNameInUrl = - NormalizeUrlControllerName(rootPath, controllerName, action, httpMethod, configuration); + + // 规范化控制器名称 + var normalizedControllerName = NormalizeUrlControllerName( + rootPath, + controllerName, + action, + httpMethod, + configuration); - var url = $"{rootPath}/{NormalizeControllerNameCase(controllerNameInUrl, configuration)}"; + // 构建基础URL + var url = $"{rootPath}/{NormalizeControllerNameCase(normalizedControllerName, configuration)}"; - //Add {id} path if needed - var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id"); - if (idParameterModel != null) - { - if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true)) - { - url += "/{id}"; - } - else - { - var properties = idParameterModel - .ParameterType - .GetProperties(BindingFlags.Instance | BindingFlags.Public); + // 处理ID参数路由 + url = BuildIdParameterRoute(url, action, configuration); - foreach (var property in properties) - { - url += "/{" + NormalizeIdPropertyNameCase(property, configuration) + "}"; - } - } - } - - //Add action name if needed - var actionNameInUrl = NormalizeUrlActionName(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)}}}"; - } - } + // 处理Action名称路由 + url = BuildActionNameRoute(url, rootPath, controllerName, action, httpMethod, configuration); return url; } + /// + /// 构建ID参数路由部分 + /// + 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; + } + + /// + /// 构建Action名称路由部分 + /// + 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; + } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiServiceConvention.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiServiceConvention.cs index 200fcd4b..7f3afc12 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiServiceConvention.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/Mvc/YiServiceConvention.cs @@ -13,24 +13,46 @@ using Volo.Abp.Reflection; namespace Yi.Framework.AspNetCore.Mvc { + /// + /// 自定义服务约定实现,用于处理API路由和HTTP方法约束 + /// [Dependency(ServiceLifetime.Transient, ReplaceServices = true)] [ExposeServices(typeof(IAbpServiceConvention))] public class YiServiceConvention : AbpServiceConvention { - public YiServiceConvention(IOptions options, IConventionalRouteBuilder conventionalRouteBuilder) : base(options, conventionalRouteBuilder) + /// + /// 初始化服务约定的新实例 + /// + /// ABP AspNetCore MVC 配置选项 + /// 约定路由构建器 + public YiServiceConvention( + IOptions options, + IConventionalRouteBuilder conventionalRouteBuilder) + : base(options, conventionalRouteBuilder) { } - protected override void ConfigureSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration) + /// + /// 配置选择器,处理路由和HTTP方法约束 + /// + protected override void ConfigureSelector( + string rootPath, + string controllerName, + ActionModel action, + ConventionalControllerSetting? configuration) { + // 移除空选择器 RemoveEmptySelectors(action.Selectors); - var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault(action.ActionMethod); - if (remoteServiceAtt != null && !remoteServiceAtt.IsEnabledFor(action.ActionMethod)) + // 检查远程服务特性 + var remoteServiceAttr = ReflectionHelper + .GetSingleAttributeOrDefault(action.ActionMethod); + if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(action.ActionMethod)) { return; } + // 根据选择器是否存在执行不同的配置 if (!action.Selectors.Any()) { AddAbpServiceSelector(rootPath, controllerName, action, configuration); @@ -41,56 +63,92 @@ namespace Yi.Framework.AspNetCore.Mvc } } - - protected override void AddAbpServiceSelector(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration) - { - base.AddAbpServiceSelector(rootPath, controllerName, action, configuration); - } - - protected override void NormalizeSelectorRoutes(string rootPath, string controllerName, ActionModel action, ConventionalControllerSetting? configuration) + /// + /// 规范化选择器路由 + /// + protected override void NormalizeSelectorRoutes( + string rootPath, + string controllerName, + ActionModel action, + ConventionalControllerSetting? configuration) { foreach (var selector in action.Selectors) { - var httpMethod = selector.ActionConstraints - .OfType() - .FirstOrDefault()? - .HttpMethods? - .FirstOrDefault(); - - if (httpMethod == null) - { - httpMethod = SelectHttpMethod(action, configuration); - } - - if (selector.AttributeRouteModel == null) - { - selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel(rootPath, controllerName, action, httpMethod, configuration); - } - else - { - - var template = selector.AttributeRouteModel.Template; - if (!template.StartsWith("/")) - { - var route = $"{rootPath}/{template}"; - selector.AttributeRouteModel.Template = route; - - } - - } - - - - if (!selector.ActionConstraints.OfType().Any()) - { - selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod })); - } + // 获取HTTP方法约束 + var httpMethod = GetOrCreateHttpMethod(selector, action, configuration); + // 处理路由模板 + ConfigureRouteTemplate(selector, rootPath, controllerName, action, httpMethod, configuration); + // 确保HTTP方法约束存在 + EnsureHttpMethodConstraint(selector, httpMethod); } } + /// + /// 获取或创建HTTP方法 + /// + private string GetOrCreateHttpMethod( + SelectorModel selector, + ActionModel action, + ConventionalControllerSetting? configuration) + { + return selector.ActionConstraints + .OfType() + .FirstOrDefault()? + .HttpMethods? + .FirstOrDefault() + ?? SelectHttpMethod(action, configuration); + } + /// + /// 配置路由模板 + /// + private void ConfigureRouteTemplate( + SelectorModel selector, + string rootPath, + string controllerName, + ActionModel action, + string httpMethod, + ConventionalControllerSetting? configuration) + { + if (selector.AttributeRouteModel == null) + { + selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel( + rootPath, + controllerName, + action, + httpMethod, + configuration); + } + else + { + NormalizeAttributeRouteTemplate(selector, rootPath); + } + } + /// + /// 规范化特性路由模板 + /// + private void NormalizeAttributeRouteTemplate(SelectorModel selector, string rootPath) + { + var template = selector.AttributeRouteModel.Template; + if (!template.StartsWith("/")) + { + selector.AttributeRouteModel.Template = $"{rootPath}/{template}"; + } + } + + /// + /// 确保HTTP方法约束存在 + /// + private void EnsureHttpMethodConstraint(SelectorModel selector, string httpMethod) + { + if (!selector.ActionConstraints.OfType().Any()) + { + selector.ActionConstraints.Add( + new HttpMethodActionConstraint(new[] { httpMethod })); + } + } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RealIpHttpContextWebClientInfoProvider.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RealIpHttpContextWebClientInfoProvider.cs index d00fd95c..f338509b 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RealIpHttpContextWebClientInfoProvider.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RealIpHttpContextWebClientInfoProvider.cs @@ -5,32 +5,53 @@ using Volo.Abp.AspNetCore.WebClientInfo; namespace Yi.Framework.AspNetCore; +/// +/// 真实IP地址提供程序,支持代理服务器场景 +/// public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider { - public RealIpHttpContextWebClientInfoProvider(ILogger logger, - IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor) + private const string XForwardedForHeader = "X-Forwarded-For"; + + /// + /// 初始化真实IP地址提供程序的新实例 + /// + public RealIpHttpContextWebClientInfoProvider( + ILogger logger, + IHttpContextAccessor httpContextAccessor) + : base(logger, httpContextAccessor) { } + /// + /// 获取客户端IP地址,优先从X-Forwarded-For头部获取 + /// + /// 客户端IP地址 protected override string? GetClientIpAddress() { try { var httpContext = HttpContextAccessor.HttpContext; - - var headers = httpContext?.Request?.Headers; - - if (headers != null && headers.ContainsKey("X-Forwarded-For")) + if (httpContext == null) { - httpContext.Connection.RemoteIpAddress = - IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault()); + return null; } - 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) { - Logger.LogException(ex, LogLevel.Warning); + Logger.LogWarning(ex, "获取客户端IP地址时发生异常"); return null; } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RemoteServiceSuccessInfo.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RemoteServiceSuccessInfo.cs index c008f162..daa9cf35 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RemoteServiceSuccessInfo.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/RemoteServiceSuccessInfo.cs @@ -1,49 +1,55 @@ namespace Yi.Framework.AspNetCore { + /// + /// 远程服务成功响应信息 + /// [Serializable] public class RemoteServiceSuccessInfo { /// - /// Creates a new instance of . + /// 获取或设置响应代码 + /// + public string? Code { get; private set; } + + /// + /// 获取或设置响应消息 + /// + public string? Message { get; private set; } + + /// + /// 获取或设置详细信息 + /// + public string? Details { get; private set; } + + /// + /// 获取或设置响应数据 + /// + public object? Data { get; private set; } + + /// + /// 初始化远程服务成功响应信息的新实例 /// public RemoteServiceSuccessInfo() { } /// - /// Creates a new instance of . + /// 使用指定参数初始化远程服务成功响应信息的新实例 /// - /// Error code - /// Error details - /// Error message - /// Error data - 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; Details = details; Code = code; Data = data; } - - /// - /// code. - /// - public string? Code { get; set; } - - /// - /// message. - /// - public string? Message { get; set; } - - /// - /// details. - /// - public string? Details { get; set; } - - /// - /// data. - /// - public object? Data { get; set; } - } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/YiFrameworkAspNetCoreModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/YiFrameworkAspNetCoreModule.cs index 006a3763..36cdb0d9 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/YiFrameworkAspNetCoreModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.AspNetCore/YiFrameworkAspNetCoreModule.cs @@ -19,15 +19,24 @@ using Yi.Framework.Core; namespace Yi.Framework.AspNetCore { - [DependsOn(typeof(YiFrameworkCoreModule) - )] + /// + /// Yi框架ASP.NET Core模块 + /// + [DependsOn(typeof(YiFrameworkCoreModule))] public class YiFrameworkAspNetCoreModule : AbpModule { + /// + /// 配置服务后的处理 + /// public override void PostConfigureServices(ServiceConfigurationContext context) { 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)); } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/UnitOfWorkHangfireFilter.cs b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/UnitOfWorkHangfireFilter.cs index 47748c22..ad818576 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/UnitOfWorkHangfireFilter.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/UnitOfWorkHangfireFilter.cs @@ -5,40 +5,72 @@ using Volo.Abp.Uow; namespace Yi.Framework.BackgroundWorkers.Hangfire; -public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency +/// +/// Hangfire 工作单元过滤器 +/// 用于管理后台任务的事务处理 +/// +public sealed class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency { - private const string CurrentJobUow = "HangfireUnitOfWork"; + private const string UnitOfWorkItemKey = "HangfireUnitOfWork"; private readonly IUnitOfWorkManager _unitOfWorkManager; + /// + /// 初始化工作单元过滤器 + /// + /// 工作单元管理器 public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager) { _unitOfWorkManager = unitOfWorkManager; } + /// + /// 任务执行前的处理 + /// + /// 执行上下文 public void OnPerforming(PerformingContext context) { + // 开启一个工作单元并存储到上下文中 var uow = _unitOfWorkManager.Begin(); - context.Items.Add(CurrentJobUow, uow); + context.Items.Add(UnitOfWorkItemKey, uow); } + /// + /// 任务执行后的处理 + /// + /// 执行上下文 public void OnPerformed(PerformedContext context) { - AsyncHelper.RunSync(()=>OnPerformedAsync(context)); + AsyncHelper.RunSync(() => OnPerformedAsync(context)); } + /// + /// 任务执行后的异步处理 + /// + /// 执行上下文 private async Task OnPerformedAsync(PerformedContext context) { - if (context.Items.TryGetValue(CurrentJobUow, out var obj) - && obj is IUnitOfWork uow) + if (!context.Items.TryGetValue(UnitOfWorkItemKey, out var obj) || + obj is not IUnitOfWork uow) { + return; + } + + try + { + // 如果没有异常且工作单元未完成,则提交事务 if (context.Exception == null && !uow.IsCompleted) { await uow.CompleteAsync(); } else { + // 否则回滚事务 await uow.RollbackAsync(); } + } + finally + { + // 确保工作单元被释放 uow.Dispose(); } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/Yi.Framework.BackgroundWorkers.Hangfire.csproj b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/Yi.Framework.BackgroundWorkers.Hangfire.csproj index ee6c5ed7..4706bcf7 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/Yi.Framework.BackgroundWorkers.Hangfire.csproj +++ b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/Yi.Framework.BackgroundWorkers.Hangfire.csproj @@ -10,6 +10,7 @@ + diff --git a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs index d0a44ce1..6663358a 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs @@ -2,53 +2,82 @@ using Hangfire; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.BackgroundJobs.Hangfire; using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.DynamicProxy; namespace Yi.Framework.BackgroundWorkers.Hangfire; -[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))] -public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule +/// +/// Hangfire 后台任务模块 +/// +[DependsOn(typeof(AbpBackgroundWorkersHangfireModule), + typeof(AbpBackgroundJobsHangfireModule))] +public sealed class YiFrameworkBackgroundWorkersHangfireModule : AbpModule { + /// + /// 配置服务前的预处理 + /// + /// 服务配置上下文 public override void PreConfigureServices(ServiceConfigurationContext context) { + // 添加 Hangfire 后台任务约定注册器 context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar()); } + /// + /// 应用程序初始化 + /// + /// 应用程序初始化上下文 public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { - //定时任务自动注入,Abp默认只有在Quartz才实现 + // 获取后台任务管理器和所有 Hangfire 后台任务 var backgroundWorkerManager = context.ServiceProvider.GetRequiredService(); - var works = context.ServiceProvider.GetServices(); + var workers = context.ServiceProvider.GetServices(); + // 获取配置 var configuration = context.ServiceProvider.GetRequiredService(); - //【特殊,为了兼容内存模式,由于内存模式任务,不能使用队列】 - bool.TryParse(configuration["Redis:IsEnabled"], out var redisEnabled); - foreach (var work in works) + + // 检查是否启用 Redis + var isRedisEnabled = configuration.GetValue("Redis:IsEnabled"); + + foreach (var worker in workers) { - //如果为空,默认使用服务器本地上海时间 - work.TimeZone = TimeZoneInfo.Local; - if (redisEnabled) + // 设置时区为本地时区(上海) + worker.TimeZone = TimeZoneInfo.Local; + + if (isRedisEnabled) { - await backgroundWorkerManager.AddAsync(work); + // Redis 模式:使用 ABP 后台任务管理器 + await backgroundWorkerManager.AddAsync(worker); } else { - object unProxyWorker = ProxyHelper.UnProxy((object)work); - RecurringJob.AddOrUpdate(work.RecurringJobId, - (Expression>)(() => - ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default(CancellationToken))), - work.CronExpression, new RecurringJobOptions() + // 内存模式:直接使用 Hangfire + var unProxyWorker = ProxyHelper.UnProxy(worker); + + // 添加或更新循环任务 + RecurringJob.AddOrUpdate( + worker.RecurringJobId, + (Expression>)(() => + ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(default)), + worker.CronExpression, + new RecurringJobOptions { - TimeZone = work.TimeZone + TimeZone = worker.TimeZone }); } } } + /// + /// 应用程序初始化前的预处理 + /// + /// 应用程序初始化上下文 public override void OnPreApplicationInitialization(ApplicationInitializationContext context) { + // 添加工作单元过滤器 var services = context.ServiceProvider; GlobalJobFilters.Filters.Add(services.GetRequiredService()); } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiHangfireConventionalRegistrar.cs b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiHangfireConventionalRegistrar.cs index f8f99bad..4560f9a4 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiHangfireConventionalRegistrar.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiHangfireConventionalRegistrar.cs @@ -3,18 +3,32 @@ using Volo.Abp.DependencyInjection; namespace Yi.Framework.BackgroundWorkers.Hangfire; -public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar +/// +/// Hangfire 后台任务约定注册器 +/// +public sealed class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar { + /// + /// 检查类型是否禁用约定注册 + /// + /// 要检查的类型 + /// 如果类型不是 IHangfireBackgroundWorker 或已被禁用则返回 true protected override bool IsConventionalRegistrationDisabled(Type type) { - return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type); + return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || + base.IsConventionalRegistrationDisabled(type); } + /// + /// 获取要暴露的服务类型列表 + /// + /// 实现类型 + /// 服务类型列表 protected override List GetExposedServiceTypes(Type type) { - return new List() - { - typeof(IHangfireBackgroundWorker) - }; + return new List + { + typeof(IHangfireBackgroundWorker) + }; } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiTokenAuthorizationFilter.cs b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiTokenAuthorizationFilter.cs index 4c7c3bb8..f7235a67 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiTokenAuthorizationFilter.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiTokenAuthorizationFilter.cs @@ -6,116 +6,141 @@ using Volo.Abp.Users; namespace Yi.Framework.BackgroundWorkers.Hangfire; -public class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency +/// +/// Hangfire 仪表盘的令牌认证过滤器 +/// +public sealed class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency { - private const string Bearer = "Bearer: "; - private string RequireUser { get; set; } = "cc"; - private TimeSpan ExpiresTime { get; set; } = TimeSpan.FromMinutes(10); - private IServiceProvider _serviceProvider; + private const string BearerPrefix = "Bearer "; + private const string TokenCookieKey = "Token"; + private const string HtmlContentType = "text/html"; + + private readonly IServiceProvider _serviceProvider; + private string _requiredUsername = "cc"; + private TimeSpan _tokenExpiration = TimeSpan.FromMinutes(10); + /// + /// 初始化令牌认证过滤器 + /// + /// 服务提供者 public YiTokenAuthorizationFilter(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } - public YiTokenAuthorizationFilter SetRequireUser(string userName) + /// + /// 设置需要的用户名 + /// + /// 允许访问的用户名 + /// 当前实例,支持链式调用 + public YiTokenAuthorizationFilter SetRequiredUsername(string username) { - RequireUser = userName; + _requiredUsername = username ?? throw new ArgumentNullException(nameof(username)); return this; } - public YiTokenAuthorizationFilter SetExpiresTime(TimeSpan expiresTime) + /// + /// 设置令牌过期时间 + /// + /// 过期时间间隔 + /// 当前实例,支持链式调用 + public YiTokenAuthorizationFilter SetTokenExpiration(TimeSpan expiration) { - ExpiresTime = expiresTime; + _tokenExpiration = expiration; return this; } + /// + /// 授权验证 + /// + /// 仪表盘上下文 + /// 是否通过授权 public bool Authorize(DashboardContext context) { var httpContext = context.GetHttpContext(); - var _currentUser = _serviceProvider.GetRequiredService(); - //如果验证通过,设置cookies - if (_currentUser.IsAuthenticated) + var currentUser = _serviceProvider.GetRequiredService(); + + if (!currentUser.IsAuthenticated) { - var cookieOptions = new CookieOptions - { - Expires = DateTimeOffset.Now + ExpiresTime, // 设置 cookie 过期时间,10分钟 - }; - - - 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); + return false; } - SetChallengeResponse(httpContext); - 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; } + /// + /// 设置认证挑战响应 + /// 当用户未认证时,返回一个包含令牌输入表单的HTML页面 + /// + /// HTTP 上下文 private void SetChallengeResponse(HttpContext httpContext) { httpContext.Response.StatusCode = 401; - httpContext.Response.ContentType = "text/html; charset=utf-8"; - string html = """ - - - - - - Token 输入 - - - -

Yi-hangfire

-

输入您的Token,我们将验证您是否为管理员

- - - - - """; + httpContext.Response.ContentType = HtmlContentType; + + var html = @" + + + Hangfire Dashboard Authorization + + + +
+

Authorization Required

+
+ +
+ +
+ + + "; + httpContext.Response.WriteAsync(html); } + /// + /// 设置令牌 Cookie + /// + /// HTTP 上下文 + /// 令牌值 + 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 AuthorizeAsync(DashboardContext context) { return Task.FromResult(Authorize(context)); diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiDistributedCacheKeyNormalizer.cs b/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiDistributedCacheKeyNormalizer.cs index e3e0e22e..8ead64aa 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiDistributedCacheKeyNormalizer.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiDistributedCacheKeyNormalizer.cs @@ -10,28 +10,43 @@ using Volo.Abp.MultiTenancy; namespace Yi.Framework.Caching.FreeRedis { - [Dependency(ReplaceServices =true)] + /// + /// 缓存键标准化处理器 + /// 用于处理缓存键的格式化和多租户支持 + /// + [Dependency(ReplaceServices = true)] public class YiDistributedCacheKeyNormalizer : IDistributedCacheKeyNormalizer, ITransientDependency { - protected ICurrentTenant CurrentTenant { get; } - - protected AbpDistributedCacheOptions DistributedCacheOptions { get; } + private readonly ICurrentTenant _currentTenant; + private readonly AbpDistributedCacheOptions _distributedCacheOptions; + /// + /// 构造函数 + /// + /// 当前租户服务 + /// 分布式缓存配置选项 public YiDistributedCacheKeyNormalizer( ICurrentTenant currentTenant, IOptions distributedCacheOptions) { - CurrentTenant = currentTenant; - DistributedCacheOptions = distributedCacheOptions.Value; + _currentTenant = currentTenant; + _distributedCacheOptions = distributedCacheOptions.Value; } + /// + /// 标准化缓存键 + /// + /// 缓存键标准化参数 + /// 标准化后的缓存键 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; diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiFrameworkCachingFreeRedisModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiFrameworkCachingFreeRedisModule.cs index 85b65e44..07076abc 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiFrameworkCachingFreeRedisModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Caching.FreeRedis/YiFrameworkCachingFreeRedisModule.cs @@ -1,5 +1,6 @@ using FreeRedis; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Caching; @@ -7,26 +8,57 @@ using Volo.Abp.Caching; namespace Yi.Framework.Caching.FreeRedis { /// - /// 此模块得益于FreeRedis作者支持IDistributedCache,使用湿滑 + /// FreeRedis缓存模块 + /// 提供基于FreeRedis的分布式缓存实现 /// [DependsOn(typeof(AbpCachingModule))] public class YiFrameworkCachingFreeRedisModule : AbpModule { + private const string RedisEnabledKey = "Redis:IsEnabled"; + private const string RedisConfigurationKey = "Redis:Configuration"; + + /// + /// 配置服务 + /// + /// 服务配置上下文 public override void ConfigureServices(ServiceConfigurationContext context) { - var configuration = context.Services.GetConfiguration(); - var redisEnabled = configuration["Redis:IsEnabled"]; - if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled)) + // 检查Redis是否启用 + if (!IsRedisEnabled(configuration)) { - var redisConfiguration = configuration["Redis:Configuration"]; - RedisClient redisClient = new RedisClient(redisConfiguration); - - context.Services.AddSingleton(redisClient); - context.Services.Replace(ServiceDescriptor.Singleton(new - DistributedCache(redisClient))); + return; } + + // 注册Redis服务 + RegisterRedisServices(context, configuration); + } + + /// + /// 检查Redis是否启用 + /// + /// 配置 + /// 是否启用Redis + private static bool IsRedisEnabled(IConfiguration configuration) + { + var redisEnabled = configuration[RedisEnabledKey]; + return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled); + } + + /// + /// 注册Redis相关服务 + /// + /// 服务配置上下文 + /// 配置 + private static void RegisterRedisServices(ServiceConfigurationContext context, IConfiguration configuration) + { + var redisConfiguration = configuration[RedisConfigurationKey]; + var redisClient = new RedisClient(redisConfiguration); + + context.Services.AddSingleton(redisClient); + context.Services.Replace(ServiceDescriptor.Singleton( + new DistributedCache(redisClient))); } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IOrderNum.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IOrderNum.cs index aecb1d6e..e564b440 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IOrderNum.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IOrderNum.cs @@ -6,8 +6,21 @@ using System.Threading.Tasks; namespace Yi.Framework.Core.Data { + /// + /// 排序接口 + /// + /// + /// 实现此接口的实体类将支持排序功能 + /// 通常用于列表数据的展示顺序控制 + /// public interface IOrderNum { + /// + /// 排序号 + /// + /// + /// 数字越小越靠前,默认为0 + /// int OrderNum { get; set; } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IState.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IState.cs index d5abcea9..e910ad5f 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IState.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Data/IState.cs @@ -6,8 +6,21 @@ using System.Threading.Tasks; namespace Yi.Framework.Core.Data { + /// + /// 状态接口 + /// + /// + /// 实现此接口的实体类将支持启用/禁用状态管理 + /// 用于控制数据记录的可用状态 + /// public interface IState { - public bool State { get; set; } + /// + /// 状态标识 + /// + /// + /// true表示启用,false表示禁用 + /// + bool State { get; set; } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs index 193f7909..1eb618cd 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/FileTypeEnum.cs @@ -7,14 +7,37 @@ using System.Threading.Tasks; namespace Yi.Framework.Core.Enums { /// - /// 定义公共文件路径 + /// 文件类型枚举 /// + /// + /// 用于定义系统支持的文件类型分类 + /// 主要用于文件上传和存储时的类型区分 + /// public enum FileTypeEnum { - file, - image, - thumbnail, - excel, - temp + /// + /// 普通文件 + /// + file = 0, + + /// + /// 图片文件 + /// + image = 1, + + /// + /// 缩略图文件 + /// + thumbnail = 2, + + /// + /// Excel文件 + /// + excel = 3, + + /// + /// 临时文件 + /// + temp = 4 } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/OrderByEnum.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/OrderByEnum.cs index b1edac3a..70fbb6b8 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/OrderByEnum.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/OrderByEnum.cs @@ -6,9 +6,23 @@ using System.Threading.Tasks; namespace Yi.Framework.Core.Enums { + /// + /// 排序方向枚举 + /// + /// + /// 用于定义数据查询时的排序方向 + /// 常用于列表数据排序 + /// public enum OrderByEnum { - Asc, - Desc + /// + /// 升序排列 + /// + Asc = 0, + + /// + /// 降序排列 + /// + Desc = 1 } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/QueryOperatorEnum.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/QueryOperatorEnum.cs index 13439675..f2c2613a 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/QueryOperatorEnum.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/QueryOperatorEnum.cs @@ -6,67 +6,91 @@ using System.Threading.Tasks; namespace Yi.Framework.Core.Enums { + /// + /// 查询操作符枚举 + /// + /// + /// 定义查询条件中支持的操作符类型 + /// 用于构建动态查询条件 + /// public enum QueryOperatorEnum { /// - /// 相等 + /// 等于 /// - Equal, + Equal = 0, + /// - /// 匹配 + /// 模糊匹配 /// - Like, + Like = 1, + /// /// 大于 /// - GreaterThan, + GreaterThan = 2, + /// /// 大于或等于 /// - GreaterThanOrEqual, + GreaterThanOrEqual = 3, + /// /// 小于 /// - LessThan, + LessThan = 4, + /// /// 小于或等于 /// - LessThanOrEqual, + LessThanOrEqual = 5, + /// - /// 等于集合 + /// 在指定集合中 /// - In, + In = 6, + /// - /// 不等于集合 + /// 不在指定集合中 /// - NotIn, + NotIn = 7, + /// - /// 左边匹配 + /// 左侧模糊匹配 /// - LikeLeft, + LikeLeft = 8, + /// - /// 右边匹配 + /// 右侧模糊匹配 /// - LikeRight, + LikeRight = 9, + /// - /// 不相等 + /// 不等于 /// - NoEqual, + NoEqual = 10, + /// - /// 为空或空 + /// 为null或空 /// - IsNullOrEmpty, + IsNullOrEmpty = 11, + /// - /// 不为空 + /// 不为null /// - IsNot, + IsNot = 12, + /// /// 不匹配 /// - NoLike, + NoLike = 13, + /// - /// 时间段 值用 "|" 隔开 + /// 日期范围 /// - DateRange + /// + /// 使用"|"分隔起始和结束日期 + /// + DateRange = 14 } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/ResultCodeEnum.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/ResultCodeEnum.cs index e4197867..bb84b128 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/ResultCodeEnum.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Enums/ResultCodeEnum.cs @@ -6,26 +6,33 @@ using System.Threading.Tasks; namespace Yi.Framework.Core.Enums { + /// + /// API返回状态码枚举 + /// + /// + /// 定义API接口统一的返回状态码 + /// 遵循HTTP状态码规范 + /// public enum ResultCodeEnum { /// - /// 操作成功。 + /// 操作成功 /// Success = 200, /// - /// 操作不成功 - /// - NotSuccess = 500, - - /// - /// 无权限 + /// 未授权访问 /// NoPermission = 401, /// - /// 被拒绝 + /// 访问被拒绝 /// - Denied = 403 + Denied = 403, + + /// + /// 操作失败 + /// + NotSuccess = 500 } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs index 293d3a83..7e0b3785 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Extensions/HttpContextExtensions.cs @@ -4,110 +4,131 @@ using Microsoft.AspNetCore.Http; namespace Yi.Framework.Core.Extensions { + /// + /// HttpContext扩展方法类 + /// public static class HttpContextExtensions { /// - /// 设置文件下载名称 + /// 设置内联文件下载响应头 /// - /// - /// + /// HTTP上下文 + /// 文件名 public static void FileInlineHandle(this HttpContext httpContext, string fileName) { - string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8")); - httpContext.Response.Headers.Add("Content-Disposition", "inline;filename=" + encodeFilename); - + var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8); + httpContext.Response.Headers.Add("Content-Disposition", $"inline;filename={encodeFilename}"); } /// - /// 设置文件附件名称 + /// 设置附件下载响应头 /// - /// - /// + /// HTTP上下文 + /// 文件名 public static void FileAttachmentHandle(this HttpContext httpContext, string fileName) { - string encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8")); - httpContext.Response.Headers.Add("Content-Disposition", "attachment;filename=" + encodeFilename); - + var encodeFilename = System.Web.HttpUtility.UrlEncode(fileName, Encoding.UTF8); + httpContext.Response.Headers.Add("Content-Disposition", $"attachment;filename={encodeFilename}"); } /// - /// 获取语言种类 + /// 获取客户端首选语言 /// - /// - /// + /// HTTP上下文 + /// 语言代码,默认返回zh-CN public static string GetLanguage(this HttpContext httpContext) { - string res = "zh-CN"; - var str = httpContext.Request.Headers["Accept-Language"].FirstOrDefault(); - if (str is not null) - { - res = str.Split(",")[0]; - } - return res; - + const string defaultLanguage = "zh-CN"; + var acceptLanguage = httpContext.Request.Headers["Accept-Language"].FirstOrDefault(); + + return string.IsNullOrEmpty(acceptLanguage) + ? defaultLanguage + : acceptLanguage.Split(',')[0]; } /// - /// 判断是否为异步请求 + /// 判断是否为Ajax请求 /// - /// - /// + /// HTTP请求 + /// 是否为Ajax请求 public static bool IsAjaxRequest(this HttpRequest request) { - string header = request.Headers["X-Requested-With"]; - return "XMLHttpRequest".Equals(header); + const string ajaxHeader = "XMLHttpRequest"; + return ajaxHeader.Equals(request.Headers["X-Requested-With"], + StringComparison.OrdinalIgnoreCase); } + /// - /// 获取客户端IP + /// 获取客户端IP地址 /// - /// - /// + /// HTTP上下文 + /// 客户端IP地址 public static string GetClientIp(this HttpContext context) { - if (context == null) return ""; - var result = context.Request.Headers["X-Forwarded-For"].FirstOrDefault(); - if (string.IsNullOrEmpty(result)) + const string localhost = "127.0.0.1"; + if (context == null) return string.Empty; + + // 尝试获取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"); - //如果有端口号,删除端口号 - result = Regex.Replace(result, @":\d{1,5}$", ""); - //Ip规则校验 - 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}$"); + // 处理特殊IP + if (string.IsNullOrEmpty(ip) || ip.Contains("::1")) + { + return localhost; + } - result = regResult ? result : "127.0.0.1"; - return result; + // 清理IPv6格式 + 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; } /// - /// 获取浏览器标识 + /// 获取User-Agent信息 /// - /// - /// + /// HTTP上下文 + /// User-Agent字符串 public static string GetUserAgent(this HttpContext context) { - return context.Request.Headers["User-Agent"]; + return context.Request.Headers["User-Agent"].ToString(); } + /// + /// 获取用户权限声明值 + /// + /// HTTP上下文 + /// 权限声明名称 + /// 权限值数组 public static string[]? GetUserPermissions(this HttpContext context, string permissionsName) { - return context.User.Claims.Where(x => x.Type == permissionsName).Select(x => x.Value).ToArray(); + return context.User.Claims + .Where(x => x.Type == permissionsName) + .Select(x => x.Value) + .ToArray(); } /// - /// 判断是否是 WebSocket 请求 + /// 判断是否为WebSocket请求 /// - /// - /// + /// HTTP上下文 + /// 是否为WebSocket请求 public static bool IsWebSocketRequest(this HttpContext context) { - return context.WebSockets.IsWebSocketRequest || context.Request.Path == "/ws"; + return context.WebSockets.IsWebSocketRequest || + context.Request.Path == "/ws"; } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Json/DatetimeJsonConverter.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Json/DatetimeJsonConverter.cs index e4732065..c46c54d8 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Json/DatetimeJsonConverter.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Json/DatetimeJsonConverter.cs @@ -3,25 +3,48 @@ using System.Text.Json.Serialization; namespace Yi.Framework.Core.Json; +/// +/// DateTime JSON序列化转换器 +/// public class DatetimeJsonConverter : JsonConverter { - private string _format; - public DatetimeJsonConverter(string format="yyyy-MM-dd HH:mm:ss") + private readonly string _dateFormat; + + /// + /// 初始化DateTime转换器 + /// + /// 日期格式化字符串,默认为yyyy-MM-dd HH:mm:ss + public DatetimeJsonConverter(string format = "yyyy-MM-dd HH:mm:ss") { - _format = format; + _dateFormat = format; } + /// + /// 从JSON读取DateTime值 + /// + /// JSON读取器 + /// 目标类型 + /// JSON序列化选项 + /// DateTime值 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(); } + /// + /// 将DateTime写入JSON + /// + /// JSON写入器 + /// DateTime值 + /// JSON序列化选项 public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { - writer.WriteStringValue(value.ToString(_format)); + writer.WriteStringValue(value.ToString(_dateFormat)); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/Modularity/YiModuleManager.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/Modularity/YiModuleManager.cs index e3b2fc4b..b83d3ad3 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/Modularity/YiModuleManager.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/Modularity/YiModuleManager.cs @@ -8,52 +8,81 @@ using Volo.Abp.Modularity; namespace Yi.Framework.Core.Modularity; -[Dependency(ReplaceServices =true)] +/// +/// Yi框架模块管理器 +/// +[Dependency(ReplaceServices = true)] public class YiModuleManager : ModuleManager, IModuleManager, ISingletonDependency { private readonly IModuleContainer _moduleContainer; private readonly IEnumerable _lifecycleContributors; private readonly ILogger _logger; - public YiModuleManager(IModuleContainer moduleContainer, ILogger logger, IOptions options, IServiceProvider serviceProvider) : base(moduleContainer, logger, options, serviceProvider) + /// + /// 初始化模块管理器 + /// + public YiModuleManager( + IModuleContainer moduleContainer, + ILogger logger, + IOptions options, + IServiceProvider serviceProvider) + : base(moduleContainer, logger, options, serviceProvider) { _moduleContainer = moduleContainer; _logger = logger; - _lifecycleContributors = options.Value.Contributors.Select(serviceProvider.GetRequiredService).Cast().ToArray(); + _lifecycleContributors = options.Value.Contributors + .Select(serviceProvider.GetRequiredService) + .Cast() + .ToArray(); } + /// + /// 初始化所有模块 + /// + /// 应用程序初始化上下文 public override async Task InitializeModulesAsync(ApplicationInitializationContext context) { - _logger.LogDebug("==========模块Initialize初始化统计-跳过0ms模块=========="); - var total = 0; - var watch =new Stopwatch(); - long totalTime = 0; + + var moduleCount = 0; + var stopwatch = new Stopwatch(); + var totalTime = 0L; + foreach (var contributor in _lifecycleContributors) { foreach (var module in _moduleContainer.Modules) { try { - watch.Restart(); + stopwatch.Restart(); await contributor.InitializeAsync(context, module.Instance); - watch.Stop(); - totalTime += watch.ElapsedMilliseconds; - total++; - if (watch.ElapsedMilliseconds > 1) + stopwatch.Stop(); + + totalTime += stopwatch.ElapsedMilliseconds; + moduleCount++; + + // 仅记录耗时超过1ms的模块 + if (stopwatch.ElapsedMilliseconds > 1) { - _logger.LogDebug($"耗时-{watch.ElapsedMilliseconds}ms,已加载模块-{module.Assembly.GetName().Name}"); + _logger.LogDebug( + "耗时-{Time}ms,已加载模块-{ModuleName}", + stopwatch.ElapsedMilliseconds, + module.Assembly.GetName().Name); } - } 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); } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Core/YiFrameworkCoreModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.Core/YiFrameworkCoreModule.cs index 653c3911..576c20c8 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Core/YiFrameworkCoreModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Core/YiFrameworkCoreModule.cs @@ -2,8 +2,28 @@ namespace Yi.Framework.Core { - public class YiFrameworkCoreModule:AbpModule + /// + /// Yi框架核心模块 + /// + /// + /// 提供框架的基础功能和核心服务 + /// + public class YiFrameworkCoreModule : AbpModule { + /// + /// 配置服务 + /// + public override void ConfigureServices(ServiceConfigurationContext context) + { + base.ConfigureServices(context); + } + /// + /// 应用程序初始化 + /// + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + base.OnApplicationInitialization(context); + } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IDeletesAppService.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IDeletesAppService.cs index e5e3fc95..a25addd2 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IDeletesAppService.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IDeletesAppService.cs @@ -3,8 +3,17 @@ using Volo.Abp.Application.Services; namespace Yi.Framework.Ddd.Application.Contracts { - public interface IDeletesAppService : IDeleteAppService< TKey> , IApplicationService, IRemoteService + /// + /// 批量删除服务接口 + /// + /// 主键类型 + public interface IDeletesAppService : IDeleteAppService, IApplicationService, IRemoteService { + /// + /// 批量删除实体 + /// + /// 要删除的实体ID集合 + /// 删除操作的异步任务 Task DeleteAsync(IEnumerable ids); } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs index a2e816fd..b0223a39 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs @@ -2,9 +2,19 @@ namespace Yi.Framework.Ddd.Application.Contracts { + /// + /// 带时间范围的分页查询请求接口 + /// public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest { + /// + /// 查询开始时间 + /// DateTime? StartTime { get; set; } + + /// + /// 查询结束时间 + /// DateTime? EndTime { get; set; } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs index 283b4530..2b5ea40a 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs @@ -2,6 +2,9 @@ namespace Yi.Framework.Ddd.Application.Contracts { + /// + /// 分页查询请求接口,包含时间范围和排序功能 + /// public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest { } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IYiCrudAppService.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IYiCrudAppService.cs index 71e1fc89..3afe3988 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IYiCrudAppService.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/IYiCrudAppService.cs @@ -7,24 +7,47 @@ using Volo.Abp.Application.Services; namespace Yi.Framework.Ddd.Application.Contracts { + /// + /// Yi框架CRUD服务基础接口 + /// + /// 实体DTO类型 + /// 主键类型 public interface IYiCrudAppService : ICrudAppService { } + /// + /// Yi框架CRUD服务接口(带查询输入) + /// + /// 实体DTO类型 + /// 主键类型 + /// 查询输入类型 public interface IYiCrudAppService : ICrudAppService { } + /// + /// Yi框架CRUD服务接口(带查询输入和创建输入) + /// + /// 实体DTO类型 + /// 主键类型 + /// 查询输入类型 + /// 创建输入类型 public interface IYiCrudAppService : ICrudAppService { } + /// + /// Yi框架CRUD服务接口(带查询、创建和更新输入) + /// public interface IYiCrudAppService : ICrudAppService { } + /// + /// Yi框架完整CRUD服务接口(包含所有操作和批量删除功能) + /// public interface IYiCrudAppService : ICrudAppService, IDeletesAppService { - } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs index c9757489..24e1c65f 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs @@ -2,48 +2,50 @@ namespace Yi.Framework.Ddd.Application.Contracts { + /// + /// 分页查询请求DTO,包含时间范围和自定义排序功能 + /// public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto { /// - /// 查询开始时间条件 + /// 查询开始时间 /// public DateTime? StartTime { get; set; } /// - /// 查询结束时间条件 + /// 查询结束时间 /// public DateTime? EndTime { get; set; } /// - /// 排序列名,字段名对应前端 + /// 排序列名 /// public string? OrderByColumn { get; set; } /// - /// 是否顺序,字段名对应前端 + /// 排序方向(ascending/descending) /// public string? IsAsc { get; set; } /// - /// 是否顺序 + /// 是否为升序排序 /// - public bool CanAsc => IsAsc?.ToLower() == "ascending" ? true : false; + public bool IsAscending => string.Equals(IsAsc, "ascending", StringComparison.OrdinalIgnoreCase); - private string _sorting; + private string? _sorting; - //排序引用 - public new string? Sorting + /// + /// 排序表达式 + /// + public override string? Sorting { get { - if (!OrderByColumn.IsNullOrWhiteSpace()) + if (!string.IsNullOrWhiteSpace(OrderByColumn)) { - return $"{OrderByColumn} {(CanAsc ? "ASC" : "DESC")}"; - } - else - { - return _sorting; + return $"{OrderByColumn} {(IsAscending ? "ASC" : "DESC")}"; } + return _sorting; } set => _sorting = value; } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/YiFrameworkDddApplicationContractsModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/YiFrameworkDddApplicationContractsModule.cs index 63e7758f..c231fec5 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/YiFrameworkDddApplicationContractsModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application.Contracts/YiFrameworkDddApplicationContractsModule.cs @@ -3,6 +3,9 @@ using Volo.Abp.Modularity; namespace Yi.Framework.Ddd.Application.Contracts { + /// + /// Yi框架DDD应用层契约模块 + /// [DependsOn(typeof(AbpDddApplicationContractsModule))] public class YiFrameworkDddApplicationContractsModule : AbpModule { diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCacheCrudAppService.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCacheCrudAppService.cs index d10cdd82..ed2d54ab 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCacheCrudAppService.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCacheCrudAppService.cs @@ -6,11 +6,19 @@ using Volo.Abp.MultiTenancy; namespace Yi.Framework.Ddd.Application { - public abstract class YiCacheCrudAppService : YiCrudAppService - where TEntity : class, IEntity - where TEntityDto : IEntityDto + /// + /// 带缓存的CRUD应用服务基类 + /// + /// 实体类型 + /// 实体DTO类型 + /// 主键类型 + public abstract class YiCacheCrudAppService + : YiCrudAppService + where TEntity : class, IEntity + where TEntityDto : IEntityDto { - protected YiCacheCrudAppService(IRepository repository) : base(repository) + protected YiCacheCrudAppService(IRepository repository) + : base(repository) { } } @@ -47,73 +55,92 @@ namespace Yi.Framework.Ddd.Application } + /// + /// 完整的带缓存CRUD应用服务实现 + /// public abstract class YiCacheCrudAppService : YiCrudAppService - where TEntity : class, IEntity - where TGetOutputDto : IEntityDto - where TGetListOutputDto : IEntityDto + where TEntity : class, IEntity + where TGetOutputDto : IEntityDto + where TGetListOutputDto : IEntityDto { - protected IDistributedCache Cache => LazyServiceProvider.LazyGetRequiredService>(); + /// + /// 分布式缓存访问器 + /// + private IDistributedCache EntityCache => + LazyServiceProvider.LazyGetRequiredService>(); - protected string GetCacheKey(TKey id) => typeof(TEntity).Name + ":" + CurrentTenant.Id ?? Guid.Empty + ":" + id.ToString(); - protected YiCacheCrudAppService(IRepository repository) : base(repository) + /// + /// 获取缓存键 + /// + protected virtual string GenerateCacheKey(TKey id) => + $"{typeof(TEntity).Name}:{CurrentTenant.Id ?? Guid.Empty}:{id}"; + + protected YiCacheCrudAppService(IRepository repository) + : base(repository) { } - public override async Task UpdateAsync(TKey id, TUpdateInput input) + /// + /// 更新实体并清除缓存 + /// + public override async Task UpdateAsync(TKey id, TUpdateInput input) { - var output = await base.UpdateAsync(id, input); - await Cache.RemoveAsync(GetCacheKey(id)); - return output; + var result = await base.UpdateAsync(id, input); + await EntityCache.RemoveAsync(GenerateCacheKey(id)); + return result; } - public override async Task> GetListAsync(TGetListInput input) + /// + /// 获取实体列表(需要继承实现具体的缓存策略) + /// + public override Task> GetListAsync(TGetListInput input) { - //两种方式: - //1:全表缓存,使用缓存直接查询 - //2:非全部缓存,查询到的数据直接添加到缓存 - - //判断是否该实体为全表缓存 - throw new NotImplementedException(); - - //IDistributedCache 有局限性,条件查询无法进行缓存了 - //if (true) - //{ - // return await GetListByCacheAsync(input); - //} - //else - //{ - // return await GetListByDbAsync(input); - //} - + // 建议实现两种缓存策略: + // 1. 全表缓存: 适用于数据量小且变动不频繁的场景 + // 2. 按需缓存: 仅缓存常用数据,适用于大数据量场景 + throw new NotImplementedException("请实现具体的缓存查询策略"); } - protected virtual async Task> GetListByDbAsync(TGetListInput input) + /// + /// 从数据库获取实体列表 + /// + protected virtual Task> GetListFromDatabaseAsync( + TGetListInput input) { - //如果不是全表缓存,可以走这个啦 - throw new NotImplementedException(); - } - protected virtual async Task> GetListByCacheAsync(TGetListInput input) - { - //如果是全表缓存,可以走这个啦 throw new NotImplementedException(); } + /// + /// 从缓存获取实体列表 + /// + protected virtual Task> GetListFromCacheAsync( + TGetListInput input) + { + throw new NotImplementedException(); + } + /// + /// 获取单个实体(优先从缓存获取) + /// protected override async Task GetEntityByIdAsync(TKey id) { - var output = await Cache.GetOrAddAsync(GetCacheKey(id), async () => await base.GetEntityByIdAsync(id)); - return output!; + return (await EntityCache.GetOrAddAsync( + GenerateCacheKey(id), + async () => await base.GetEntityByIdAsync(id)))!; } - public override async Task DeleteAsync(IEnumerable id) + /// + /// 批量删除实体并清除缓存 + /// + public override async Task DeleteAsync(IEnumerable ids) { - await base.DeleteAsync(id); - foreach (var itemId in id) - { - await Cache.RemoveAsync(GetCacheKey(itemId)); - } - + await base.DeleteAsync(ids); + + // 批量清除缓存 + var tasks = ids.Select(id => + EntityCache.RemoveAsync(GenerateCacheKey(id))); + await Task.WhenAll(tasks); } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs index b2d1475c..3ead8bcc 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiCrudAppService.cs @@ -8,183 +8,244 @@ using Volo.Abp.Domain.Repositories; namespace Yi.Framework.Ddd.Application { - public abstract class - YiCrudAppService : YiCrudAppService + /// + /// CRUD应用服务基类 - 基础版本 + /// + public abstract class YiCrudAppService + : YiCrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { - protected YiCrudAppService(IRepository repository) : base(repository) + protected YiCrudAppService(IRepository repository) + : base(repository) { } } + /// + /// CRUD应用服务基类 - 支持自定义查询输入 + /// public abstract class YiCrudAppService : YiCrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { - protected YiCrudAppService(IRepository repository) : base(repository) + protected YiCrudAppService(IRepository repository) + : base(repository) { } } - + /// + /// CRUD应用服务基类 - 支持自定义创建输入 + /// public abstract class YiCrudAppService : YiCrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { - protected YiCrudAppService(IRepository repository) : base(repository) + protected YiCrudAppService(IRepository repository) + : base(repository) { } } + /// + /// CRUD应用服务基类 - 支持自定义更新输入 + /// public abstract class YiCrudAppService : YiCrudAppService where TEntity : class, IEntity where TEntityDto : IEntityDto { - protected YiCrudAppService(IRepository repository) : base(repository) + protected YiCrudAppService(IRepository repository) + : base(repository) { } } - - public abstract class YiCrudAppService + /// + /// CRUD应用服务基类 - 完整实现 + /// + public abstract class YiCrudAppService : CrudAppService where TEntity : class, IEntity where TGetOutputDto : IEntityDto where TGetListOutputDto : IEntityDto { - protected YiCrudAppService(IRepository repository) : base(repository) + /// + /// 临时文件存储路径 + /// + private const string TempFilePath = "/wwwroot/temp"; + + protected YiCrudAppService(IRepository repository) + : base(repository) { } + /// + /// 更新实体 + /// + /// 实体ID + /// 更新输入 + /// 更新后的实体DTO public override async Task UpdateAsync(TKey id, TUpdateInput input) { + // 检查更新权限 await CheckUpdatePolicyAsync(); + // 获取并验证实体 var entity = await GetEntityByIdAsync(id); - await CheckUpdateInputDtoAsync(entity,input); + + // 检查更新输入 + await CheckUpdateInputDtoAsync(entity, input); + // 映射并更新实体 await MapToEntityAsync(input, entity); await Repository.UpdateAsync(entity, autoSave: true); return await MapToGetOutputDtoAsync(entity); } - protected virtual Task CheckUpdateInputDtoAsync(TEntity entity,TUpdateInput input) + /// + /// 检查更新输入数据的有效性 + /// + protected virtual Task CheckUpdateInputDtoAsync(TEntity entity, TUpdateInput input) { return Task.CompletedTask; } + /// + /// 创建实体 + /// + /// 创建输入 + /// 创建后的实体DTO public override async Task CreateAsync(TCreateInput input) { + // 检查创建权限 await CheckCreatePolicyAsync(); + + // 检查创建输入 await CheckCreateInputDtoAsync(input); + + // 映射到实体 var entity = await MapToEntityAsync(input); + // 设置租户ID TryToSetTenantId(entity); + // 插入实体 await Repository.InsertAsync(entity, autoSave: true); return await MapToGetOutputDtoAsync(entity); } + /// + /// 检查创建输入数据的有效性 + /// protected virtual Task CheckCreateInputDtoAsync(TCreateInput input) { return Task.CompletedTask; } /// - /// 多查 + /// 获取实体列表 /// - /// - /// + /// 查询输入 + /// 分页结果 public override async Task> GetListAsync(TGetListInput input) { - List? entites = null; - //区分多查还是批量查 + List entities; + + // 根据输入类型决定查询方式 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 { - entites = await Repository.GetListAsync(); + // 查询全部 + entities = await Repository.GetListAsync(); } - var total = await Repository.GetCountAsync(); - var output = await MapToGetListOutputDtosAsync(entites); - return new PagedResultDto(total, output); - //throw new NotImplementedException($"【{typeof(TEntity)}】实体的CrudAppService,查询为具体业务,通用查询几乎无实际场景,请重写实现!"); + // 获取总数并映射结果 + var totalCount = await Repository.GetCountAsync(); + var dtos = await MapToGetListOutputDtosAsync(entities); + + return new PagedResultDto(totalCount, dtos); } /// - /// 多删 + /// 批量删除实体 /// - /// - /// + /// 实体ID集合 [RemoteService(isEnabled: true)] - public virtual async Task DeleteAsync(IEnumerable id) + public virtual async Task DeleteAsync(IEnumerable ids) { - await Repository.DeleteManyAsync(id); + await Repository.DeleteManyAsync(ids); } /// - /// 偷梁换柱 + /// 单个删除实体(禁用远程访问) /// - /// - /// [RemoteService(isEnabled: false)] public override Task DeleteAsync(TKey id) { return base.DeleteAsync(id); } - /// - /// 导出excel + /// 导出Excel /// - /// - /// + /// 查询条件 + /// Excel文件 public virtual async Task GetExportExcelAsync(TGetListInput input) { + // 重置分页参数以获取全部数据 if (input is IPagedResultRequest paged) { paged.SkipCount = 0; 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(dirPath)) + // 确保临时目录存在 + if (!Directory.Exists(TempFilePath)) { - 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"); } /// - /// 导入excle + /// 生成Excel文件名 /// - /// - /// - public virtual async Task PostImportExcelAsync(List input) + private string GenerateExcelFileName() { - var entities = input.Select(x => MapToEntity(x)).ToList(); - //安全起见,该接口需要自己实现 - throw new NotImplementedException(); - //await Repository.DeleteManyAsync(entities.Select(x => x.Id)); - //await Repository.InsertManyAsync(entities); + return $"{typeof(TEntity).Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Guid.NewGuid()}.xlsx"; + } + + /// + /// 导入Excel(需要实现类重写此方法) + /// + public virtual Task PostImportExcelAsync(List input) + { + throw new NotImplementedException("请在实现类中重写此方法以支持Excel导入"); } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiFrameworkDddApplicationModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiFrameworkDddApplicationModule.cs index e2781601..d4657568 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiFrameworkDddApplicationModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Ddd.Application/YiFrameworkDddApplicationModule.cs @@ -6,14 +6,34 @@ using Yi.Framework.Ddd.Application.Contracts; namespace Yi.Framework.Ddd.Application { - [DependsOn(typeof(AbpDddApplicationModule), - typeof(YiFrameworkDddApplicationContractsModule))] + /// + /// Yi框架DDD应用层模块 + /// + [DependsOn( + typeof(AbpDddApplicationModule), + typeof(YiFrameworkDddApplicationContractsModule) + )] public class YiFrameworkDddApplicationModule : AbpModule { + /// + /// 应用程序初始化配置 + /// + /// 应用程序初始化上下文 public override void OnApplicationInitialization(ApplicationInitializationContext context) { - //分页限制 + // 配置分页查询的默认值和最大值限制 + ConfigureDefaultPagingSettings(); + } + + /// + /// 配置默认分页设置 + /// + private void ConfigureDefaultPagingSettings() + { + // 设置默认每页显示记录数 LimitedResultRequestDto.DefaultMaxResultCount = 10; + + // 设置最大允许的每页记录数 LimitedResultRequestDto.MaxMaxResultCount = 10000; } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterAutoObjectMappingProvider.cs b/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterAutoObjectMappingProvider.cs index 3c6b73eb..60660b35 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterAutoObjectMappingProvider.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterAutoObjectMappingProvider.cs @@ -8,17 +8,37 @@ using Volo.Abp.ObjectMapping; namespace Yi.Framework.Mapster { + /// + /// Mapster自动对象映射提供程序 + /// 实现IAutoObjectMappingProvider接口,提供对象间的自动映射功能 + /// public class MapsterAutoObjectMappingProvider : IAutoObjectMappingProvider { + /// + /// 将源对象映射到目标类型 + /// + /// 源类型 + /// 目标类型 + /// 源对象 + /// 映射后的目标类型实例 public TDestination Map(object source) { - var sss = typeof(TDestination).Name; + // 使用Mapster的Adapt方法进行对象映射 return source.Adapt(); } + /// + /// 将源对象映射到现有的目标对象 + /// + /// 源类型 + /// 目标类型 + /// 源对象 + /// 目标对象 + /// 映射后的目标对象 public TDestination Map(TSource source, TDestination destination) { - return source.Adapt(destination); + // 使用Mapster的Adapt方法进行对象映射,保留目标对象的实例 + return source.Adapt(destination); } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterObjectMapper.cs b/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterObjectMapper.cs index 7cbe8804..11b9b5e1 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterObjectMapper.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Mapster/MapsterObjectMapper.cs @@ -7,18 +7,51 @@ using Volo.Abp.ObjectMapping; namespace Yi.Framework.Mapster { + /// + /// Mapster对象映射器 + /// 实现IObjectMapper接口,提供对象映射功能 + /// public class MapsterObjectMapper : IObjectMapper { - public IAutoObjectMappingProvider AutoObjectMappingProvider => throw new NotImplementedException(); + private readonly IAutoObjectMappingProvider _autoObjectMappingProvider; - public TDestination Map(TSource source) + /// + /// 构造函数 + /// + /// 自动对象映射提供程序 + public MapsterObjectMapper(IAutoObjectMappingProvider autoObjectMappingProvider) { - throw new NotImplementedException(); + _autoObjectMappingProvider = autoObjectMappingProvider; } + /// + /// 获取自动对象映射提供程序 + /// + public IAutoObjectMappingProvider AutoObjectMappingProvider => _autoObjectMappingProvider; + + /// + /// 将源对象映射到目标类型 + /// + /// 源类型 + /// 目标类型 + /// 源对象 + /// 映射后的目标类型实例 + public TDestination Map(TSource source) + { + return AutoObjectMappingProvider.Map(source); + } + + /// + /// 将源对象映射到现有的目标对象 + /// + /// 源类型 + /// 目标类型 + /// 源对象 + /// 目标对象 + /// 映射后的目标对象 public TDestination Map(TSource source, TDestination destination) { - throw new NotImplementedException(); + return AutoObjectMappingProvider.Map(source, destination); } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.Mapster/YiFrameworkMapsterModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.Mapster/YiFrameworkMapsterModule.cs index 5df08b25..78f93dd1 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.Mapster/YiFrameworkMapsterModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.Mapster/YiFrameworkMapsterModule.cs @@ -1,21 +1,31 @@ -using MapsterMapper; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; using Volo.Abp.ObjectMapping; using Yi.Framework.Core; namespace Yi.Framework.Mapster { - [DependsOn(typeof(YiFrameworkCoreModule), - + /// + /// Yi框架Mapster模块 + /// 用于配置和注册Mapster相关服务 + /// + [DependsOn( + typeof(YiFrameworkCoreModule), typeof(AbpObjectMappingModule) - )] + )] public class YiFrameworkMapsterModule : AbpModule { + /// + /// 配置服务 + /// + /// 服务配置上下文 public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddTransient(); + var services = context.Services; + + // 注册Mapster相关服务 + services.AddTransient(); + services.AddTransient(); } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs index 6533950c..6f48f14d 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs @@ -3,10 +3,14 @@ using ArgumentException = System.ArgumentException; namespace Yi.Framework.SqlSugarCore.Abstractions { + /// + /// 数据库连接配置选项 + /// public class DbConnOptions { /// - /// 连接字符串(如果开启多租户,也就是默认库了),必填 + /// 主数据库连接字符串 + /// 如果开启多租户,此为默认租户数据库 /// public string? Url { get; set; } @@ -16,42 +20,42 @@ namespace Yi.Framework.SqlSugarCore.Abstractions public DbType? DbType { get; set; } /// - /// 开启种子数据 + /// 是否启用种子数据初始化 /// public bool EnabledDbSeed { get; set; } = false; /// - /// 开启驼峰转下划线 + /// 是否启用驼峰命名转下划线命名 /// public bool EnableUnderLine { get; set; } = false; /// - /// 开启codefirst + /// 是否启用Code First模式 /// public bool EnabledCodeFirst { get; set; } = false; /// - /// 开启sql日志 + /// 是否启用SQL日志记录 /// public bool EnabledSqlLog { get; set; } = true; /// - /// 实体程序集 + /// 实体类所在程序集名称列表 /// public List? EntityAssembly { get; set; } /// - /// 开启读写分离 + /// 是否启用读写分离 /// public bool EnabledReadWrite { get; set; } = false; /// - /// 读写分离 + /// 只读数据库连接字符串列表 /// public List? ReadUrl { get; set; } /// - /// 开启Saas多租户 + /// 是否启用SaaS多租户 /// public bool EnabledSaasMultiTenancy { get; set; } = false; } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DefaultTenantTableAttribute.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DefaultTenantTableAttribute.cs index 2b2519ef..85684833 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DefaultTenantTableAttribute.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DefaultTenantTableAttribute.cs @@ -4,10 +4,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Yi.Framework.SqlSugarCore.Abstractions +namespace Yi.Framework.SqlSugarCore.Abstractions; + +/// +/// 默认租户表特性 +/// 标记此特性的实体类将在默认租户数据库中创建表 +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public sealed class DefaultTenantTableAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class)] - public class DefaultTenantTableAttribute : Attribute - { - } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs index cb736291..e1a509b1 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs @@ -8,15 +8,18 @@ using Volo.Abp.DependencyInjection; namespace Yi.Framework.SqlSugarCore.Abstractions { + /// + /// SqlSugar数据库上下文接口 + /// public interface ISqlSugarDbContext { /// - /// SqlSugarDb + /// 获取SqlSugar客户端实例 /// ISqlSugarClient SqlSugarClient { get; } /// - /// 数据库备份 + /// 执行数据库备份 /// void BackupDataBase(); } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContextDependencies.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContextDependencies.cs index f41bb88d..5d65e90c 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContextDependencies.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContextDependencies.cs @@ -3,19 +3,55 @@ using SqlSugar; namespace Yi.Framework.SqlSugarCore.Abstractions; +/// +/// SqlSugar数据库上下文依赖接口 +/// 定义数据库操作的各个生命周期钩子 +/// public interface ISqlSugarDbContextDependencies { /// - /// 执行顺序 + /// 获取执行顺序 /// int ExecutionOrder { get; } + /// + /// SqlSugar客户端配置时触发 + /// + /// SqlSugar客户端实例 void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient); + + /// + /// 数据执行后触发 + /// + /// 原始值 + /// 实体信息 void DataExecuted(object oldValue, DataAfterModel entityInfo); + + /// + /// 数据执行前触发 + /// + /// 原始值 + /// 实体信息 void DataExecuting(object oldValue, DataFilterModel entityInfo); - void OnLogExecuting(string sql, SugarParameter[] pars); - void OnLogExecuted(string sql, SugarParameter[] pars); + /// + /// SQL执行前触发 + /// + /// SQL语句 + /// SQL参数 + void OnLogExecuting(string sql, SugarParameter[] parameters); + + /// + /// SQL执行后触发 + /// + /// SQL语句 + /// SQL参数 + void OnLogExecuted(string sql, SugarParameter[] parameters); + /// + /// 实体服务配置 + /// + /// 属性信息 + /// 实体列信息 void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo); } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarRepository.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarRepository.cs index d581f813..06c00009 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarRepository.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarRepository.cs @@ -6,84 +6,242 @@ using Volo.Abp.Uow; namespace Yi.Framework.SqlSugarCore.Abstractions { - - public interface ISqlSugarRepository:IRepository,IUnitOfWorkEnabled where TEntity : class, IEntity,new () + /// + /// SqlSugar仓储接口 + /// + /// 实体类型 + public interface ISqlSugarRepository : IRepository, IUnitOfWorkEnabled + where TEntity : class, IEntity, new() { + #region 数据库访问器 + + /// + /// 获取SqlSugar客户端实例 + /// ISqlSugarClient _Db { get; } + + /// + /// 获取查询构造器 + /// ISugarQueryable _DbQueryable { get; } + /// + /// 异步获取数据库上下文 + /// Task GetDbContextAsync(); + + /// + /// 获取删除操作构造器 + /// Task> AsDeleteable(); - Task> AsInsertable(List insertObjs); - Task> AsInsertable(TEntity insertObj); - Task> AsInsertable(TEntity[] insertObjs); + + /// + /// 获取插入操作构造器 + /// + Task> AsInsertable(TEntity entity); + + /// + /// 获取批量插入操作构造器 + /// + Task> AsInsertable(List entities); + + /// + /// 获取查询构造器 + /// Task> AsQueryable(); + + /// + /// 获取SqlSugar客户端 + /// Task AsSugarClient(); + + /// + /// 获取租户操作接口 + /// Task AsTenant(); - Task> AsUpdateable(List updateObjs); - Task> AsUpdateable(TEntity updateObj); + + /// + /// 获取更新操作构造器 + /// Task> AsUpdateable(); - Task> AsUpdateable(TEntity[] updateObjs); - #region 单查 - //单查 + /// + /// 获取实体更新操作构造器 + /// + Task> AsUpdateable(TEntity entity); + + /// + /// 获取批量更新操作构造器 + /// + Task> AsUpdateable(List entities); + + #endregion + + #region 查询操作 + + /// + /// 根据主键获取实体 + /// Task GetByIdAsync(dynamic id); - Task GetSingleAsync(Expression> whereExpression); - Task GetFirstAsync(Expression> whereExpression); - Task IsAnyAsync(Expression> whereExpression); - Task CountAsync(Expression> whereExpression); - #endregion + /// + /// 获取满足条件的单个实体 + /// + Task GetSingleAsync(Expression> predicate); + /// + /// 获取满足条件的第一个实体 + /// + Task GetFirstAsync(Expression> predicate); - #region 多查 - //多查 + /// + /// 判断是否存在满足条件的实体 + /// + Task IsAnyAsync(Expression> predicate); + + /// + /// 获取满足条件的实体数量 + /// + Task CountAsync(Expression> predicate); + + /// + /// 获取所有实体 + /// Task> GetListAsync(); - Task> GetListAsync(Expression> whereExpression); + + /// + /// 获取满足条件的所有实体 + /// + Task> GetListAsync(Expression> predicate); + #endregion + #region 分页查询 + + /// + /// 获取分页数据 + /// + Task> GetPageListAsync( + Expression> predicate, + int pageIndex, + int pageSize); + + /// + /// 获取排序的分页数据 + /// + Task> GetPageListAsync( + Expression> predicate, + int pageIndex, + int pageSize, + Expression>? orderByExpression = null, + OrderByType orderByType = OrderByType.Asc); - #region 分页查 - //分页查 - Task> GetPageListAsync(Expression> whereExpression, int pageNum, int pageSize); - Task> GetPageListAsync(Expression> whereExpression, int pageNum, int pageSize, Expression>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc); #endregion - #region 插入 - //插入 - Task InsertAsync(TEntity insertObj); - Task InsertOrUpdateAsync(TEntity data); - Task InsertOrUpdateAsync(List datas); - Task InsertReturnIdentityAsync(TEntity insertObj); - Task InsertReturnBigIdentityAsync(TEntity insertObj); - Task InsertReturnSnowflakeIdAsync(TEntity insertObj); - Task InsertReturnEntityAsync(TEntity insertObj); - Task InsertRangeAsync(List insertObjs); + #region 插入操作 + + /// + /// 插入实体 + /// + Task InsertAsync(TEntity entity); + + /// + /// 插入或更新实体 + /// + Task InsertOrUpdateAsync(TEntity entity); + + /// + /// 批量插入或更新实体 + /// + Task InsertOrUpdateAsync(List entities); + + /// + /// 插入实体并返回自增主键 + /// + Task InsertReturnIdentityAsync(TEntity entity); + + /// + /// 插入实体并返回长整型自增主键 + /// + Task InsertReturnBigIdentityAsync(TEntity entity); + + /// + /// 插入实体并返回雪花ID + /// + Task InsertReturnSnowflakeIdAsync(TEntity entity); + + /// + /// 插入实体并返回实体 + /// + Task InsertReturnEntityAsync(TEntity entity); + + /// + /// 批量插入实体 + /// + Task InsertRangeAsync(List entities); + #endregion + #region 更新操作 + + /// + /// 更新实体 + /// + Task UpdateAsync(TEntity entity); + + /// + /// 批量更新实体 + /// + Task UpdateRangeAsync(List entities); + + /// + /// 条件更新指定列 + /// + Task UpdateAsync( + Expression> columns, + Expression> predicate); - #region 更新 - //更新 - Task UpdateAsync(TEntity updateObj); - Task UpdateRangeAsync(List updateObjs); - Task UpdateAsync(Expression> columns, Expression> whereExpression); #endregion - #region 删除 - //删除 - Task DeleteAsync(TEntity deleteObj); - Task DeleteAsync(List deleteObjs); - Task DeleteAsync(Expression> whereExpression); + #region 删除操作 + + /// + /// 删除实体 + /// + Task DeleteAsync(TEntity entity); + + /// + /// 批量删除实体 + /// + Task DeleteAsync(List entities); + + /// + /// 条件删除 + /// + Task DeleteAsync(Expression> predicate); + + /// + /// 根据主键删除 + /// Task DeleteByIdAsync(dynamic id); - Task DeleteByIdsAsync(dynamic[] ids); - #endregion + /// + /// 根据主键批量删除 + /// + Task DeleteByIdsAsync(dynamic[] ids); + + #endregion } - - public interface ISqlSugarRepository : ISqlSugarRepository,IRepository where TEntity : class, IEntity, new() - { - - + /// + /// SqlSugar仓储接口(带主键) + /// + /// 实体类型 + /// 主键类型 + public interface ISqlSugarRepository : + ISqlSugarRepository, + IRepository + where TEntity : class, IEntity, new() + { } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISugarDbContextProvider.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISugarDbContextProvider.cs index 49c18b08..19e671ee 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISugarDbContextProvider.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISugarDbContextProvider.cs @@ -6,12 +6,17 @@ using System.Threading.Tasks; namespace Yi.Framework.SqlSugarCore.Abstractions { + /// + /// SqlSugar数据库上下文提供者接口 + /// + /// 数据库上下文类型 public interface ISugarDbContextProvider where TDbContext : ISqlSugarDbContext { - + /// + /// 异步获取数据库上下文实例 + /// + /// 数据库上下文实例 Task GetDbContextAsync(); - } - } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/IgnoreCodeFirstAttribute.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/IgnoreCodeFirstAttribute.cs index c7e11169..673710ea 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/IgnoreCodeFirstAttribute.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/IgnoreCodeFirstAttribute.cs @@ -6,6 +6,10 @@ using System.Threading.Tasks; namespace Yi.Framework.SqlSugarCore.Abstractions { + /// + /// 忽略CodeFirst特性 + /// 标记此特性的实体类将不会被CodeFirst功能扫描 + /// [AttributeUsage(AttributeTargets.Class)] public class IgnoreCodeFirstAttribute : Attribute { diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/YiFrameworkSqlSugarCoreAbstractionsModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/YiFrameworkSqlSugarCoreAbstractionsModule.cs index 1bb1e91f..f662aa3a 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/YiFrameworkSqlSugarCoreAbstractionsModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/YiFrameworkSqlSugarCoreAbstractionsModule.cs @@ -3,9 +3,13 @@ using Yi.Framework.Core; namespace Yi.Framework.SqlSugarCore.Abstractions { + /// + /// SqlSugar Core抽象层模块 + /// 提供SqlSugar ORM的基础抽象接口和类型定义 + /// [DependsOn(typeof(YiFrameworkCoreModule))] public class YiFrameworkSqlSugarCoreAbstractionsModule : AbpModule { - + // 模块配置方法可在此添加 } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/AsyncLocalDbContextAccessor .cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/AsyncLocalDbContextAccessor .cs index 09b214cc..f4d717b2 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/AsyncLocalDbContextAccessor .cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/AsyncLocalDbContextAccessor .cs @@ -2,18 +2,34 @@ namespace Yi.Framework.SqlSugarCore { - public class AsyncLocalDbContextAccessor + /// + /// 异步本地数据库上下文访问器 + /// 用于在异步流中保存和访问数据库上下文 + /// + public sealed class AsyncLocalDbContextAccessor { + private readonly AsyncLocal _currentScope; + + /// + /// 获取单例实例 + /// public static AsyncLocalDbContextAccessor Instance { get; } = new(); + + /// + /// 获取或设置当前数据库上下文 + /// public ISqlSugarDbContext? Current { get => _currentScope.Value; set => _currentScope.Value = value; } - public AsyncLocalDbContextAccessor() + + /// + /// 初始化异步本地数据库上下文访问器 + /// + private AsyncLocalDbContextAccessor() { _currentScope = new AsyncLocal(); } - private readonly AsyncLocal _currentScope; } -} +} \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/DefaultSqlSugarDbContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/DefaultSqlSugarDbContext.cs index 85f71a55..394d26de 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/DefaultSqlSugarDbContext.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/DefaultSqlSugarDbContext.cs @@ -18,218 +18,233 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore; +/// +/// 默认SqlSugar数据库上下文实现 +/// public class DefaultSqlSugarDbContext : SqlSugarDbContext { - protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService>().Value; - protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService(); - protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService(); - protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService(); - protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); - protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService(); - public IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService(); - protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled() ?? false; - protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled() ?? false; + #region Protected Properties - protected IEntityChangeEventHelper EntityChangeEventHelper => + /// + /// 数据库连接配置选项 + /// + protected DbConnOptions DbOptions => LazyServiceProvider.LazyGetRequiredService>().Value; + + /// + /// 当前用户服务 + /// + protected ICurrentUser CurrentUserService => LazyServiceProvider.GetRequiredService(); + + /// + /// GUID生成器 + /// + protected IGuidGenerator GuidGeneratorService => LazyServiceProvider.LazyGetRequiredService(); + + /// + /// 日志工厂 + /// + protected ILoggerFactory LoggerFactory => LazyServiceProvider.LazyGetRequiredService(); + + /// + /// 当前租户服务 + /// + protected ICurrentTenant CurrentTenantService => LazyServiceProvider.LazyGetRequiredService(); + + /// + /// 数据过滤服务 + /// + protected IDataFilter DataFilterService => LazyServiceProvider.LazyGetRequiredService(); + + /// + /// 工作单元管理器 + /// + protected IUnitOfWorkManager UnitOfWorkManagerService => LazyServiceProvider.LazyGetRequiredService(); + + /// + /// 实体变更事件帮助类 + /// + protected IEntityChangeEventHelper EntityChangeEventHelperService => LazyServiceProvider.LazyGetService(NullEntityChangeEventHelper.Instance); - public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) + /// + /// 是否启用多租户过滤 + /// + protected virtual bool IsMultiTenantFilterEnabled => DataFilterService?.IsEnabled() ?? false; + + /// + /// 是否启用软删除过滤 + /// + protected virtual bool IsSoftDeleteFilterEnabled => DataFilterService?.IsEnabled() ?? false; + + #endregion + + /// + /// 构造函数 + /// + public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) + : base(lazyServiceProvider) { } + /// + /// 自定义数据过滤器 + /// protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient) { + // 配置软删除过滤器 if (IsSoftDeleteFilterEnabled) { - sqlSugarClient.QueryFilter.AddTableFilter(u => u.IsDeleted == false); + sqlSugarClient.QueryFilter.AddTableFilter(entity => !entity.IsDeleted); } + // 配置多租户过滤器 if (IsMultiTenantFilterEnabled) { - //表达式里只能有具体值,不能运算 - var expressionCurrentTenant = CurrentTenant.Id ?? null; - sqlSugarClient.QueryFilter.AddTableFilter(u => u.TenantId == expressionCurrentTenant); + var currentTenantId = CurrentTenantService.Id; + sqlSugarClient.QueryFilter.AddTableFilter(entity => entity.TenantId == currentTenantId); } } + /// + /// 数据执行前的处理 + /// public override void DataExecuting(object oldValue, DataFilterModel entityInfo) { - //审计日志 + HandleAuditFields(oldValue, entityInfo); + HandleEntityEvents(entityInfo); + HandleDomainEvents(entityInfo); + } + + #region Private Methods + + /// + /// 处理审计字段 + /// + private void HandleAuditFields(object oldValue, DataFilterModel entityInfo) + { switch (entityInfo.OperationType) { case DataFilterType.UpdateByObject: - - 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); - } - } - } - + HandleUpdateAuditFields(oldValue, entityInfo); break; case DataFilterType.InsertByObject: - - if (entityInfo.PropertyName.Equals(nameof(IEntity.Id))) - { - //类型为guid - if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType) - { - //主键为空或者为默认最小值 - if (Guid.Empty.Equals(oldValue)) - { - entityInfo.SetValue(GuidGenerator.Create()); - } - } - } - - else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime))) - { - //为空或者为默认最小值 - if (DateTime.MinValue.Equals(oldValue)) - { - entityInfo.SetValue(DateTime.Now); - } - } - else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId))) - { - //类型为guid - if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType) - { - if (CurrentUser.Id is not null) - { - entityInfo.SetValue(CurrentUser.Id); - } - } - } - - else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId))) - { - if (CurrentTenant.Id is not null) - { - entityInfo.SetValue(CurrentTenant.Id); - } - } - + HandleInsertAuditFields(oldValue, entityInfo); break; } + } + /// + /// 处理更新时的审计字段 + /// + 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); + } + } - //实体变更领域事件 + /// + /// 处理插入时的审计字段 + /// + private void HandleInsertAuditFields(object oldValue, DataFilterModel entityInfo) + { + if (entityInfo.PropertyName.Equals(nameof(IEntity.Id))) + { + if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType) + { + if (Guid.Empty.Equals(oldValue)) + { + entityInfo.SetValue(GuidGeneratorService.Create()); + } + } + } + else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime))) + { + if (DateTime.MinValue.Equals(oldValue)) + { + entityInfo.SetValue(DateTime.Now); + } + } + else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId))) + { + if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType) + { + if (CurrentUserService.Id is not null) + { + entityInfo.SetValue(CurrentUserService.Id); + } + } + } + else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId))) + { + if (CurrentTenantService.Id is not null) + { + entityInfo.SetValue(CurrentTenantService.Id); + } + } + } + + /// + /// 处理实体变更事件 + /// + private void HandleEntityEvents(DataFilterModel entityInfo) + { + // 实体变更领域事件 switch (entityInfo.OperationType) { case DataFilterType.InsertByObject: if (entityInfo.PropertyName == nameof(IEntity.Id)) { - EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue); + EntityChangeEventHelperService.PublishEntityCreatedEvent(entityInfo.EntityValue); } - break; case DataFilterType.UpdateByObject: if (entityInfo.PropertyName == nameof(IEntity.Id)) { - //软删除,发布的是删除事件 if (entityInfo.EntityValue is ISoftDelete softDelete) { if (softDelete.IsDeleted == true) { - EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue); + EntityChangeEventHelperService.PublishEntityDeletedEvent(entityInfo.EntityValue); } } else { - EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue); + EntityChangeEventHelperService.PublishEntityUpdatedEvent(entityInfo.EntityValue); } } - break; case DataFilterType.DeleteByObject: - // if (entityInfo.PropertyName == nameof(IEntity.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; } - - - //实体领域事件-所有操作类型 + } + + /// + /// 处理领域事件 + /// + private void HandleDomainEvents(DataFilterModel entityInfo) + { + // 实体领域事件-所有操作类型 if (entityInfo.PropertyName == nameof(IEntity.Id)) { - var eventReport = CreateEventReport(entityInfo.EntityValue); + var eventReport = CreateEventReport(entityInfo.EntityValue); PublishEntityEvents(eventReport); } } - 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().LogDebug(sb.ToString()); - } - } - - public override void OnLogExecuted(string sql, SugarParameter[] pars) - { - if (Options.EnabledSqlLog) - { - var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒====="; - Logger.CreateLogger().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.Id)) - { - entityColumnInfo.IsPrimarykey = true; - } - } - - /// /// 创建领域事件报告 /// @@ -286,16 +301,58 @@ public class DefaultSqlSugarDbContext : SqlSugarDbContext { foreach (var localEvent in changeReport.DomainEvents) { - UnitOfWorkManager.Current?.AddOrReplaceLocalEvent( + UnitOfWorkManagerService.Current?.AddOrReplaceLocalEvent( new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder) ); } foreach (var distributedEvent in changeReport.DistributedEvents) { - UnitOfWorkManager.Current?.AddOrReplaceDistributedEvent( + UnitOfWorkManagerService.Current?.AddOrReplaceDistributedEvent( 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().LogDebug(sb.ToString()); + } + } + + public override void OnLogExecuted(string sql, SugarParameter[] pars) + { + if (DbOptions.EnabledSqlLog) + { + var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒====="; + LoggerFactory.CreateLogger().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.Id)) + { + entityColumnInfo.IsPrimarykey = true; + } + } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs index 082afb35..7468ee9b 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs @@ -21,34 +21,39 @@ namespace Yi.Framework.SqlSugarCore.Repositories public ISugarQueryable _DbQueryable => _Db.Queryable(); - private ISugarDbContextProvider _sugarDbContextProvider; + private readonly ISugarDbContextProvider _dbContextProvider; + + /// + /// 异步查询执行器 + /// public IAsyncQueryableExecuter AsyncExecuter { get; } + /// + /// 是否启用变更追踪 + /// public bool? IsChangeTrackingEnabled => false; - public SqlSugarRepository(ISugarDbContextProvider sugarDbContextProvider) + public SqlSugarRepository(ISugarDbContextProvider dbContextProvider) { - _sugarDbContextProvider = sugarDbContextProvider; + _dbContextProvider = dbContextProvider; } /// - /// 获取DB + /// 获取数据库上下文 /// - /// public virtual async Task GetDbContextAsync() { - var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient; - return db; + var dbContext = await _dbContextProvider.GetDbContextAsync(); + return dbContext.SqlSugarClient; } /// - /// 获取简单Db + /// 获取简单数据库客户端 /// - /// public virtual async Task> GetDbSimpleClientAsync() { - var db = await GetDbContextAsync(); - return new SimpleClient(db); + var dbContext = await GetDbContextAsync(); + return new SimpleClient(dbContext); } #region Abp模块 @@ -166,7 +171,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories { return (await GetDbSimpleClientAsync()).AsInsertable(insertObj); } - + public virtual async Task> AsInsertable(TEntity[] insertObjs) { return (await GetDbSimpleClientAsync()).AsInsertable(insertObjs); diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs index 0c5057f8..535cd3ff 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs @@ -7,35 +7,47 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Yi.Framework.SqlSugarCore.Abstractions; -namespace Yi.Framework.SqlSugarCore +namespace Yi.Framework.SqlSugarCore; + +/// +/// SqlSugar Core扩展方法 +/// +public static class SqlSugarCoreExtensions { - public static class SqlSugarCoreExtensions + /// + /// 添加数据库上下文 + /// + /// 数据库上下文类型 + /// 服务集合 + /// 服务生命周期 + /// 服务集合 + public static IServiceCollection AddYiDbContext( + this IServiceCollection services, + ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + where TDbContext : class, ISqlSugarDbContextDependencies { - /// - /// 新增db对象,可支持多个 - /// - /// - /// - /// - /// - public static IServiceCollection AddYiDbContext(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies - { - service.AddTransient(); - return service; - } - - /// - /// 新增db对象,可支持多个 - /// - /// - /// - /// - /// - public static IServiceCollection AddYiDbContext(this IServiceCollection service, Action options) where TDbContext : class, ISqlSugarDbContextDependencies - { - service.Configure(options.Invoke); - service.AddYiDbContext(); - return service; - } + services.TryAdd(new ServiceDescriptor( + typeof(ISqlSugarDbContextDependencies), + typeof(TDbContext), + serviceLifetime)); + + return services; + } + + /// + /// 添加数据库上下文并配置选项 + /// + /// 数据库上下文类型 + /// 服务集合 + /// 配置选项委托 + /// 服务集合 + public static IServiceCollection AddYiDbContext( + this IServiceCollection services, + Action configureOptions) + where TDbContext : class, ISqlSugarDbContextDependencies + { + services.Configure(configureOptions); + services.AddYiDbContext(); + return services; } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs index 579dba20..b5ea612e 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs @@ -5,44 +5,78 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore; +/// +/// SqlSugar数据库上下文基类 +/// public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies { + /// + /// 服务提供者 + /// protected IAbpLazyServiceProvider LazyServiceProvider { get; } - public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) + /// + /// 数据库客户端实例 + /// + protected ISqlSugarClient SqlSugarClient { get; private set; } + + /// + /// 执行顺序 + /// + public virtual int ExecutionOrder => 0; + + protected SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) { - this.LazyServiceProvider = lazyServiceProvider; + LazyServiceProvider = lazyServiceProvider; } - - protected ISqlSugarClient SqlSugarClient { get;private set; } - public int ExecutionOrder => 0; - - public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient) + /// + /// 配置SqlSugar客户端 + /// + public virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient) { SqlSugarClient = sqlSugarClient; CustomDataFilter(sqlSugarClient); } + + /// + /// 自定义数据过滤器 + /// protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient) { } - + + /// + /// 数据执行后事件 + /// public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo) { } + /// + /// 数据执行前事件 + /// public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo) { } + /// + /// SQL执行前事件 + /// public virtual void OnLogExecuting(string sql, SugarParameter[] pars) { } + /// + /// SQL执行后事件 + /// public virtual void OnLogExecuted(string sql, SugarParameter[] pars) { } + /// + /// 实体服务配置 + /// public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo) { } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextCreationContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextCreationContext.cs index 17032ad4..d03c96af 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextCreationContext.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextCreationContext.cs @@ -4,26 +4,52 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore; +/// +/// SqlSugar数据库上下文创建上下文 +/// public class SqlSugarDbContextCreationContext { - public static SqlSugarDbContextCreationContext Current => _current.Value; - private static readonly AsyncLocal _current = new AsyncLocal(); + private static readonly AsyncLocal CurrentContextHolder = + new AsyncLocal(); + + /// + /// 获取当前上下文 + /// + public static SqlSugarDbContextCreationContext Current => CurrentContextHolder.Value!; + + /// + /// 连接字符串名称 + /// public string ConnectionStringName { get; } + /// + /// 连接字符串 + /// public string ConnectionString { get; } - public DbConnection ExistingConnection { get; internal set; } + /// + /// 现有数据库连接 + /// + public DbConnection? ExistingConnection { get; internal set; } - public SqlSugarDbContextCreationContext(string connectionStringName, string connectionString) + /// + /// 构造函数 + /// + public SqlSugarDbContextCreationContext( + string connectionStringName, + string connectionString) { ConnectionStringName = connectionStringName; ConnectionString = connectionString; } + /// + /// 使用指定的上下文 + /// public static IDisposable Use(SqlSugarDbContextCreationContext context) { - var previousValue = Current; - _current.Value = context; - return new DisposeAction(() => _current.Value = previousValue); + var previousContext = Current; + CurrentContextHolder.Value = context; + return new DisposeAction(() => CurrentContextHolder.Value = previousContext); } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextFactory.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextFactory.cs index 67bdb261..ae41573f 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextFactory.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextFactory.cs @@ -13,189 +13,226 @@ using Check = Volo.Abp.Check; namespace Yi.Framework.SqlSugarCore { + /// + /// SqlSugar数据库上下文工厂类 + /// 负责创建和配置SqlSugar客户端实例 + /// public class SqlSugarDbContextFactory : ISqlSugarDbContext { + #region Properties + /// - /// SqlSugar 客户端 + /// SqlSugar客户端实例 /// public ISqlSugarClient SqlSugarClient { get; private set; } + /// + /// 延迟服务提供者 + /// private IAbpLazyServiceProvider LazyServiceProvider { get; } - private TenantConfigurationWrapper TenantConfigurationWrapper=> LazyServiceProvider.LazyGetRequiredService(); - private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); + /// + /// 租户配置包装器 + /// + private TenantConfigurationWrapper TenantConfigurationWrapper => + LazyServiceProvider.LazyGetRequiredService(); - private DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService>().Value; + /// + /// 当前租户信息 + /// + private ICurrentTenant CurrentTenant => + LazyServiceProvider.LazyGetRequiredService(); - private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService(); + /// + /// 数据库连接配置选项 + /// + private DbConnOptions DbConnectionOptions => + LazyServiceProvider.LazyGetRequiredService>().Value; + /// + /// 序列化服务 + /// + private ISerializeService SerializeService => + LazyServiceProvider.LazyGetRequiredService(); + + /// + /// SqlSugar上下文依赖项集合 + /// private IEnumerable SqlSugarDbContextDependencies => LazyServiceProvider.LazyGetRequiredService>(); + /// + /// 连接配置缓存字典 + /// private static readonly ConcurrentDictionary ConnectionConfigCache = new(); + #endregion + + /// + /// 构造函数 + /// + /// 延迟服务提供者 public SqlSugarDbContextFactory(IAbpLazyServiceProvider 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()); }); - SqlSugarClient = new SqlSugarClient(connectionConfig); - //生命周期,以下都可以直接使用sqlsugardb了 - // Aop及多租户连接字符串和类型,需要单独设置 - // Aop操作不能进行缓存 - SetDbAop(SqlSugarClient); + // 创建SqlSugar客户端实例 + SqlSugarClient = new SqlSugarClient(connectionConfig); + + // 配置数据库AOP + ConfigureDbAop(SqlSugarClient); } /// - /// 构建Aop-sqlsugaraop在多租户模式中,需单独设置 + /// 配置数据库AOP操作 /// - /// - protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient) + /// SqlSugar客户端实例 + protected virtual void ConfigureDbAop(ISqlSugarClient sqlSugarClient) { - //替换默认序列化器 + // 配置序列化服务 sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService; - //将所有,ISqlSugarDbContextDependencies进行累加 + // 初始化AOP事件处理器 Action onLogExecuting = null; Action onLogExecuted = null; Action dataExecuting = null; Action dataExecuted = null; - Action onSqlSugarClientConfig = null; + Action onClientConfig = null; + // 按执行顺序聚合所有依赖项的AOP处理器 foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder)) { onLogExecuting += dependency.OnLogExecuting; onLogExecuted += dependency.OnLogExecuted; dataExecuting += dependency.DataExecuting; dataExecuted += dependency.DataExecuted; - - onSqlSugarClientConfig += dependency.OnSqlSugarClientConfig; + onClientConfig += dependency.OnSqlSugarClientConfig; } - //最先存放db操作 - onSqlSugarClientConfig(sqlSugarClient); + // 配置SqlSugar客户端 + onClientConfig?.Invoke(sqlSugarClient); - sqlSugarClient.Aop.OnLogExecuting =onLogExecuting; + // 设置AOP事件 + sqlSugarClient.Aop.OnLogExecuting = onLogExecuting; sqlSugarClient.Aop.OnLogExecuted = onLogExecuted; - - sqlSugarClient.Aop.DataExecuting =dataExecuting; - sqlSugarClient.Aop.DataExecuted =dataExecuted; + sqlSugarClient.Aop.DataExecuting = dataExecuting; + sqlSugarClient.Aop.DataExecuted = dataExecuted; } /// - /// 构建连接配置 + /// 构建数据库连接配置 /// - /// - /// - protected virtual ConnectionConfig BuildConnectionConfig(Action? action = null) + /// 配置操作委托 + /// 连接配置对象 + protected virtual ConnectionConfig BuildConnectionConfig(Action configAction = null) { - var dbConnOptions = Options; - - #region 组装options + var dbConnOptions = DbConnectionOptions; + // 验证数据库类型配置 if (dbConnOptions.DbType is null) { - throw new ArgumentException("DbType配置为空"); + throw new ArgumentException("未配置数据库类型(DbType)"); } - var slavaConFig = new List(); + // 配置读写分离 + var slaveConfigs = new List(); if (dbConnOptions.EnabledReadWrite) { if (dbConnOptions.ReadUrl is null) { - throw new ArgumentException("读写分离为空"); + throw new ArgumentException("启用读写分离但未配置读库连接字符串"); } - var readCon = dbConnOptions.ReadUrl; - - readCon.ForEach(s => - { - //如果是动态saas分库,这里的连接串都不能写死,需要动态添加,这里只配置共享库的连接 - slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s }); - }); + slaveConfigs.AddRange(dbConnOptions.ReadUrl.Select(url => + new SlaveConnectionConfig { ConnectionString = url })); } - #endregion - - #region 组装连接config - - var connectionConfig = new ConnectionConfig() + // 创建连接配置 + var connectionConfig = new ConnectionConfig { ConfigId = ConnectionStrings.DefaultConnectionStringName, DbType = dbConnOptions.DbType ?? DbType.Sqlite, ConnectionString = dbConnOptions.Url, IsAutoCloseConnection = true, - SlaveConnectionConfigs = slavaConFig, - //设置codefirst非空值判断 - ConfigureExternalServices = new ConfigureExternalServices - { - // 处理表 - EntityNameService = (type, entity) => - { - if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_')) - entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线 - }, - EntityService = (c, p) => - { - if (new NullabilityInfoContext() - .Create(c).WriteState is NullabilityState.Nullable) - { - p.IsNullable = true; - } - - if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_')) - p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName); // 驼峰转下划线 - - //将所有,ISqlSugarDbContextDependencies的EntityService进行累加 - //额外的实体服务需要这里配置, - - Action entityService = null; - foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder)) - { - entityService += dependency.EntityService; - } - - entityService(c, p); - } - }, - //这里多租户有个坑,这里配置是无效的 - // AopEvents = new AopEvents - // { - // DataExecuted = DataExecuted, - // DataExecuting = DataExecuting, - // OnLogExecuted = OnLogExecuted, - // OnLogExecuting = OnLogExecuting - // } + SlaveConnectionConfigs = slaveConfigs, + ConfigureExternalServices = CreateExternalServices(dbConnOptions) }; - if (action is not null) - { - action.Invoke(connectionConfig); - } - - #endregion + // 应用额外配置 + configAction?.Invoke(connectionConfig); return connectionConfig; } - - protected virtual DbType GetCurrentDbType(string tenantName) + + /// + /// 创建外部服务配置 + /// + private ConfigureExternalServices CreateExternalServices(DbConnOptions dbConnOptions) { - if (tenantName == ConnectionStrings.DefaultConnectionStringName) + return new ConfigureExternalServices { - return Options.DbType!.Value; - } - var dbTypeFromTenantName = GetDbTypeFromTenantName(tenantName); - return dbTypeFromTenantName!.Value; + EntityNameService = (type, entity) => + { + if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_')) + { + entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); + } + }, + EntityService = (propertyInfo, columnInfo) => + { + // 配置空值处理 + if (new NullabilityInfoContext().Create(propertyInfo).WriteState + is NullabilityState.Nullable) + { + columnInfo.IsNullable = true; + } + + // 处理下划线命名 + if (dbConnOptions.EnableUnderLine && !columnInfo.IsIgnore + && !columnInfo.DbColumnName.Contains('_')) + { + columnInfo.DbColumnName = UtilMethods.ToUnderLine(columnInfo.DbColumnName); + } + + // 聚合所有依赖项的实体服务 + Action entityService = null; + foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder)) + { + entityService += dependency.EntityService; + } + + entityService?.Invoke(propertyInfo, columnInfo); + } + }; } - //根据租户name进行匹配db类型: Test@Sqlite,[form:AI] + /// + /// 获取当前数据库类型 + /// + /// 租户名称 + /// 数据库类型 + protected virtual DbType GetCurrentDbType(string tenantName) + { + return tenantName == ConnectionStrings.DefaultConnectionStringName + ? DbConnectionOptions.DbType!.Value + : GetDbTypeFromTenantName(tenantName) + ?? throw new ArgumentException($"无法从租户名称{tenantName}中解析数据库类型"); + } + + /// + /// 从租户名称解析数据库类型 + /// 格式:TenantName@DbType + /// private DbType? GetDbTypeFromTenantName(string name) { if (string.IsNullOrWhiteSpace(name)) @@ -203,61 +240,50 @@ namespace Yi.Framework.SqlSugarCore return null; } - // 查找@符号的位置 - int atIndex = name.LastIndexOf('@'); - + var atIndex = name.LastIndexOf('@'); if (atIndex == -1 || atIndex == name.Length - 1) { return null; } - // 提取 枚举 部分 - string enumString = name.Substring(atIndex + 1); - - // 尝试将 尾缀 转换为枚举 - if (Enum.TryParse(enumString, out DbType result)) - { - return result; - } - else - { - throw new ArgumentException($"数据库{name}db类型错误或不支持:无法匹配{enumString}数据库类型"); - } + var dbTypeString = name[(atIndex + 1)..]; + return Enum.TryParse(dbTypeString, out var dbType) + ? dbType + : throw new ArgumentException($"不支持的数据库类型: {dbTypeString}"); } + /// + /// 备份数据库 + /// public virtual void BackupDataBase() { - string directoryName = "database_backup"; - string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}"; - if (!Directory.Exists(directoryName)) - { - Directory.CreateDirectory(directoryName); - } + const string backupDirectory = "database_backup"; + var fileName = $"{DateTime.Now:yyyyMMdd_HHmmss}_{SqlSugarClient.Ado.Connection.Database}"; + + Directory.CreateDirectory(backupDirectory); - switch (Options.DbType) + switch (DbConnectionOptions.DbType) { case DbType.MySql: - //MySql - SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, - $"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core + SqlSugarClient.DbMaintenance.BackupDataBase( + SqlSugarClient.Ado.Connection.Database, + Path.Combine(backupDirectory, $"{fileName}.sql")); break; - case DbType.Sqlite: - //Sqlite - SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core + SqlSugarClient.DbMaintenance.BackupDataBase( + null, + $"{fileName}.db"); break; - case DbType.SqlServer: - //SqlServer - SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database, - $"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名 + SqlSugarClient.DbMaintenance.BackupDataBase( + SqlSugarClient.Ado.Connection.Database, + Path.Combine(backupDirectory, $"{fileName}.bak")); break; - default: - throw new NotImplementedException("其他数据库备份未实现"); + throw new NotImplementedException($"数据库类型 {DbConnectionOptions.DbType} 的备份操作尚未实现"); } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarNonPublicSerializer.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarNonPublicSerializer.cs index 74a1bee5..72638909 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarNonPublicSerializer.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarNonPublicSerializer.cs @@ -63,14 +63,14 @@ public class SqlSugarNonPublicSerializer : ISerializeService // 调用 SerializeObject 方法序列化对象 T json = (T)methods.MakeGenericMethod(typeof(T)) - .Invoke(null, new object[] { value, null }); - return json; + .Invoke(null, new object[] { value, null! }); + return json!; } var jSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, ContractResolver =new NonPublicPropertiesResolver() //替换默认解析器使能支持protect }; - return JsonConvert.DeserializeObject(value, jSetting); + return JsonConvert.DeserializeObject(value, jSetting)!; } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/TenantConfigurationWrapper.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/TenantConfigurationWrapper.cs index 420ef296..e4590faa 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/TenantConfigurationWrapper.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/TenantConfigurationWrapper.cs @@ -7,56 +7,68 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore; /// -/// 租户配置 +/// 租户配置包装器 /// public class TenantConfigurationWrapper : ITransientDependency { private readonly IAbpLazyServiceProvider _serviceProvider; - private ICurrentTenant CurrentTenant => _serviceProvider.LazyGetRequiredService(); - private ITenantStore TenantStore => _serviceProvider.LazyGetRequiredService(); - private DbConnOptions DbConnOptions => _serviceProvider.LazyGetRequiredService>().Value; + + private ICurrentTenant CurrentTenantService => + _serviceProvider.LazyGetRequiredService(); + + private ITenantStore TenantStoreService => + _serviceProvider.LazyGetRequiredService(); + + private DbConnOptions DbConnectionOptions => + _serviceProvider.LazyGetRequiredService>().Value; + /// + /// 构造函数 + /// public TenantConfigurationWrapper(IAbpLazyServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// - /// 获取租户信息 - /// [from:ai] + /// 获取租户配置信息 /// - /// public async Task GetAsync() { - //未开启多租户 - if (!DbConnOptions.EnabledSaasMultiTenancy) + if (!DbConnectionOptions.EnabledSaasMultiTenancy) { - return await TenantStore.FindAsync(ConnectionStrings.DefaultConnectionStringName); + return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName); } - - TenantConfiguration? tenantConfiguration = null; - - if (CurrentTenant.Id is not null) + + return await GetTenantConfigurationByCurrentTenant(); + } + + private async Task GetTenantConfigurationByCurrentTenant() + { + // 通过租户ID查找 + if (CurrentTenantService.Id.HasValue) { - tenantConfiguration = await TenantStore.FindAsync(CurrentTenant.Id.Value); - if (tenantConfiguration == null) + var config = await TenantStoreService.FindAsync(CurrentTenantService.Id.Value); + if (config == null) { - throw new ApplicationException($"未找到租户信息,租户Id:{CurrentTenant.Id}"); + throw new ApplicationException($"未找到租户信息,租户Id:{CurrentTenantService.Id}"); } - return tenantConfiguration; + return config; } - - if (!string.IsNullOrEmpty(CurrentTenant.Name)) + + // 通过租户名称查找 + if (!string.IsNullOrEmpty(CurrentTenantService.Name)) { - tenantConfiguration = await TenantStore.FindAsync(CurrentTenant.Name); - if (tenantConfiguration == null) + var config = await TenantStoreService.FindAsync(CurrentTenantService.Name); + if (config == null) { - throw new ApplicationException($"未找到租户信息,租户名称:{CurrentTenant.Name}"); + throw new ApplicationException($"未找到租户信息,租户名称:{CurrentTenantService.Name}"); } - return tenantConfiguration; + return config; } - - return await TenantStore.FindAsync(ConnectionStrings.DefaultConnectionStringName); + + // 返回默认配置 + return await TenantStoreService.FindAsync(ConnectionStrings.DefaultConnectionStringName); } /// diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarDatabaseApi.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarDatabaseApi.cs index 5cd02410..4dcb24fd 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarDatabaseApi.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarDatabaseApi.cs @@ -8,10 +8,20 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore.Uow { + /// + /// SqlSugar数据库API实现 + /// public class SqlSugarDatabaseApi : IDatabaseApi { + /// + /// 数据库上下文 + /// public ISqlSugarDbContext DbContext { get; } + /// + /// 初始化SqlSugar数据库API + /// + /// 数据库上下文 public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext) { DbContext = dbContext; diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs index 05197e93..ff0de559 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/SqlSugarTransactionApi.cs @@ -3,33 +3,48 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore.Uow { + /// + /// SqlSugar事务API实现 + /// public class SqlSugarTransactionApi : ITransactionApi, ISupportsRollback { - private ISqlSugarDbContext _sqlsugarDbContext; + private readonly ISqlSugarDbContext _dbContext; - public SqlSugarTransactionApi(ISqlSugarDbContext sqlsugarDbContext) + public SqlSugarTransactionApi(ISqlSugarDbContext dbContext) { - _sqlsugarDbContext = sqlsugarDbContext; + _dbContext = dbContext; } + /// + /// 获取数据库上下文 + /// public ISqlSugarDbContext GetDbContext() { - return _sqlsugarDbContext; + return _dbContext; } + /// + /// 提交事务 + /// public async Task CommitAsync(CancellationToken cancellationToken = default) { - await _sqlsugarDbContext.SqlSugarClient.Ado.CommitTranAsync(); - } - - public void Dispose() - { - _sqlsugarDbContext.SqlSugarClient.Ado.Dispose(); + await _dbContext.SqlSugarClient.Ado.CommitTranAsync(); } + /// + /// 回滚事务 + /// public async Task RollbackAsync(CancellationToken cancellationToken = default) { - await _sqlsugarDbContext.SqlSugarClient.Ado.RollbackTranAsync(); + await _dbContext.SqlSugarClient.Ado.RollbackTranAsync(); + } + + /// + /// 释放资源 + /// + public void Dispose() + { + _dbContext.SqlSugarClient.Ado.Dispose(); } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs index c235ffd8..397cbbfd 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs @@ -13,112 +13,121 @@ namespace Yi.Framework.SqlSugarCore.Uow { public class UnitOfWorkSqlsugarDbContextProvider : ISugarDbContextProvider where TDbContext : ISqlSugarDbContext { + /// + /// 日志记录器 + /// public ILogger> Logger { get; set; } + + /// + /// 服务提供者 + /// public IServiceProvider ServiceProvider { get; set; } + + /// + /// 数据库上下文访问器实例 + /// private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance; - protected readonly TenantConfigurationWrapper _tenantConfigurationWrapper; - protected readonly IUnitOfWorkManager UnitOfWorkManager; - protected readonly IConnectionStringResolver ConnectionStringResolver; - protected readonly ICancellationTokenProvider CancellationTokenProvider; - protected readonly ICurrentTenant CurrentTenant; + + private readonly TenantConfigurationWrapper _tenantConfigurationWrapper; + private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly IConnectionStringResolver _connectionStringResolver; + private readonly ICancellationTokenProvider _cancellationTokenProvider; + private readonly ICurrentTenant _currentTenant; public UnitOfWorkSqlsugarDbContextProvider( IUnitOfWorkManager unitOfWorkManager, IConnectionStringResolver connectionStringResolver, ICancellationTokenProvider cancellationTokenProvider, - ICurrentTenant currentTenant, TenantConfigurationWrapper tenantConfigurationWrapper) + ICurrentTenant currentTenant, + TenantConfigurationWrapper tenantConfigurationWrapper) { - UnitOfWorkManager = unitOfWorkManager; - ConnectionStringResolver = connectionStringResolver; - CancellationTokenProvider = cancellationTokenProvider; - CurrentTenant = currentTenant; + _unitOfWorkManager = unitOfWorkManager; + _connectionStringResolver = connectionStringResolver; + _cancellationTokenProvider = cancellationTokenProvider; + _currentTenant = currentTenant; _tenantConfigurationWrapper = tenantConfigurationWrapper; Logger = NullLogger>.Instance; } - + + /// + /// 获取数据库上下文 + /// public virtual async Task GetDbContextAsync() { - //获取当前连接字符串,未多租户时,默认为空 - var tenantConfiguration= await _tenantConfigurationWrapper.GetAsync(); - //由于sqlsugar的特殊性,没有db区分,不再使用连接字符串解析器 + // 获取当前租户配置 + var tenantConfiguration = await _tenantConfigurationWrapper.GetAsync(); + + // 获取连接字符串信息 var connectionStringName = tenantConfiguration.GetCurrentConnectionName(); var connectionString = tenantConfiguration.GetCurrentConnectionString(); var dbContextKey = $"{this.GetType().Name}_{connectionString}"; - - var unitOfWork = UnitOfWorkManager.Current; - if (unitOfWork == null ) - { - //var dbContext = (TDbContext)ServiceProvider.GetRequiredService(); - //如果不启用工作单元,创建一个新的db,不开启事务即可 - //return dbContext; - - //2024-11-30,改回强制性使用工作单元,否则容易造成歧义 - throw new AbpException("DbContext 只能在工作单元内工作,当前DbContext没有工作单元,如需创建新线程并发操作,请手动创建工作单元"); + var unitOfWork = _unitOfWorkManager.Current; + if (unitOfWork == null) + { + throw new AbpException( + "DbContext 只能在工作单元内工作,当前DbContext没有工作单元,如需创建新线程并发操作,请手动创建工作单元"); } - //尝试当前工作单元获取db + + // 尝试从当前工作单元获取数据库API var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey); - //当前没有db创建一个新的db + // 当前没有数据库API则创建新的 if (databaseApi == null) { - //db根据连接字符串来创建 databaseApi = new SqlSugarDatabaseApi( - await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString) + await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString) ); - //创建的db加入到当前工作单元中 unitOfWork.AddDatabaseApi(dbContextKey, databaseApi); - } + return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext; } - - - protected virtual async Task CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) + /// + /// 创建数据库上下文 + /// + protected virtual async Task CreateDbContextAsync( + IUnitOfWork unitOfWork, + string connectionStringName, + string connectionString) { var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString); - //将连接key进行传值 using (SqlSugarDbContextCreationContext.Use(creationContext)) { - var dbContext = await CreateDbContextAsync(unitOfWork); - return dbContext; + return await CreateDbContextAsync(unitOfWork); } } + /// + /// 根据工作单元创建数据库上下文 + /// protected virtual async Task CreateDbContextAsync(IUnitOfWork unitOfWork) { return unitOfWork.Options.IsTransactional ? await CreateDbContextWithTransactionAsync(unitOfWork) : unitOfWork.ServiceProvider.GetRequiredService(); } + + /// + /// 创建带事务的数据库上下文 + /// protected virtual async Task CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork) { - //事务key var transactionApiKey = $"SqlSugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}"; - - //尝试查找事务 var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi; - //该db还没有进行开启事务 if (activeTransaction == null) { - //获取到db添加事务即可 var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); - var transaction = new SqlSugarTransactionApi( - dbContext - ); + var transaction = new SqlSugarTransactionApi(dbContext); unitOfWork.AddTransactionApi(transactionApiKey, transaction); await dbContext.SqlSugarClient.Ado.BeginTranAsync(); return dbContext; } - else - { - return (TDbContext)activeTransaction.GetDbContext(); - } - + return (TDbContext)activeTransaction.GetDbContext(); } } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs index d299b91b..6d37f31e 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs @@ -18,142 +18,177 @@ using Yi.Framework.SqlSugarCore.Uow; namespace Yi.Framework.SqlSugarCore { + /// + /// SqlSugar Core模块 + /// [DependsOn(typeof(AbpDddDomainModule))] public class YiFrameworkSqlSugarCoreModule : AbpModule { public override Task ConfigureServicesAsync(ServiceConfigurationContext context) { - var service = context.Services; - var configuration = service.GetConfiguration(); + var services = context.Services; + 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"); Configure(section); + var dbConnOptions = new DbConnOptions(); section.Bind(dbConnOptions); - //很多人遗漏了这一点,不同的数据库,对于主键的使用规约不一样,需要根据数据库进行判断 - SequentialGuidType guidType; - switch (dbConnOptions.DbType) - { - case DbType.MySql: - case DbType.PostgreSQL: - guidType= SequentialGuidType.SequentialAsString; - break; - case DbType.SqlServer: - guidType = SequentialGuidType.SequentialAtEnd; - break; - case DbType.Oracle: - guidType = SequentialGuidType.SequentialAsBinary; - break; - default: - guidType = SequentialGuidType.SequentialAtEnd; - break; - } + // 配置默认连接字符串 + Configure(options => + { + options.ConnectionStrings.Default = dbConnOptions.Url; + }); + + // 配置默认租户 + ConfigureDefaultTenant(services, dbConnOptions); + } + + private void ConfigureGuidGenerator(IServiceCollection services) + { + var dbConnOptions = services.GetConfiguration() + .GetSection("DbConnOptions") + .Get(); + + var guidType = GetSequentialGuidType(dbConnOptions?.DbType); Configure(options => { options.DefaultSequentialGuidType = guidType; }); - - service.TryAddTransient(); - - //不开放sqlsugarClient - //service.AddTransient(x => x.GetRequiredService().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(); - - var dbConfig = section.Get(); - //将默认db传递给abp连接字符串模块 - Configure(x => { x.ConnectionStrings.Default = dbConfig.Url; }); - //配置abp默认租户,对接abp模块 - Configure(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 - { - Id = Guid.Empty, - Name = $"{ConnectionStrings.DefaultConnectionStringName}", - NormalizedName = ConnectionStrings.DefaultConnectionStringName, - ConnectionStrings = new ConnectionStrings() { { ConnectionStrings.DefaultConnectionStringName, dbConfig.Url } }, - IsActive = true - }); - x.Tenants = tenantList.ToArray(); - }); - context.Services.AddYiDbContext(); - return Task.CompletedTask; } + private void RegisterRepositories(IServiceCollection services) + { + services.TryAddTransient(); + 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(); + services.AddYiDbContext(); + } + + private void ConfigureDefaultTenant(IServiceCollection services, DbConnOptions dbConfig) + { + Configure(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, + Name = ConnectionStrings.DefaultConnectionStringName, + NormalizedName = ConnectionStrings.DefaultConnectionStringName, + ConnectionStrings = new ConnectionStrings + { + { ConnectionStrings.DefaultConnectionStringName, dbConfig.Url } + }, + IsActive = true + }); + + options.Tenants = tenants.ToArray(); + }); + } + + 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) { - //进行CodeFirst - var service = context.ServiceProvider; - var options = service.GetRequiredService>().Value; + var serviceProvider = context.ServiceProvider; + var options = serviceProvider.GetRequiredService>().Value; + var logger = serviceProvider.GetRequiredService>(); - var logger = service.GetRequiredService>(); - - - 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()); + // 记录配置信息 + LogConfiguration(logger, options); + // 初始化数据库 if (options.EnabledCodeFirst) { - CodeFirst(service); + await InitializeDatabase(serviceProvider); } + // 初始化种子数据 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(); - var db = service.GetRequiredService().SqlSugarClient; + var logMessage = new StringBuilder() + .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(); + var db = serviceProvider.GetRequiredService().SqlSugarClient; + + // 创建数据库 db.DbMaintenance.CreateDatabase(); - List types = new List(); - foreach (var module in moduleContainer.Modules) - { - types.AddRange(module.Assembly.GetTypes() - .Where(x => x.GetCustomAttribute() == null) - .Where(x => x.GetCustomAttribute() != null) - .Where(x => x.GetCustomAttribute() is null)); - } + // 获取需要创建表的实体类型 + var entityTypes = moduleContainer.Modules + .SelectMany(m => m.Assembly.GetTypes()) + .Where(t => t.GetCustomAttribute() == null + && t.GetCustomAttribute() != null + && t.GetCustomAttribute() == 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(); + var dataSeeder = serviceProvider.GetRequiredService(); await dataSeeder.SeedAsync(); } } diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs index 9a116727..d32dc1d0 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs @@ -360,7 +360,7 @@ namespace Yi.Abp.Web app.UseAccessLog(); //请求处理 - app.UseYiApiHandlinge(); + app.UseApiInfoHandling(); //静态资源 app.UseStaticFiles(new StaticFileOptions diff --git a/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/YiAbpToolWebModule.cs b/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/YiAbpToolWebModule.cs index 25cdd73a..ee778fd9 100644 --- a/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/YiAbpToolWebModule.cs +++ b/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/YiAbpToolWebModule.cs @@ -150,7 +150,7 @@ namespace Yi.Abp.Tool.Web app.UseYiSwagger(); //请求处理 - app.UseYiApiHandlinge(); + app.UseApiInfoHandling(); //静态资源 app.UseStaticFiles("/api/app/wwwroot"); app.UseDefaultFiles();