refactor: ai+人工重构优化 framework

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

View File

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

View File

@@ -5,49 +5,101 @@ using Volo.Abp.AspNetCore.Mvc;
namespace Yi.Framework.AspNetCore.Microsoft.AspNetCore.Builder
{
public static class SwaggerBuilderExtensons
/// <summary>
/// Swagger构建器扩展类
/// </summary>
public static class SwaggerBuilderExtensions
{
public static IApplicationBuilder UseYiSwagger(this IApplicationBuilder app, params SwaggerModel[] swaggerModels)
/// <summary>
/// 配置并使用Yi框架的Swagger中间件
/// </summary>
/// <param name="app">应用程序构建器</param>
/// <param name="swaggerConfigs">Swagger配置模型数组</param>
/// <returns>应用程序构建器</returns>
public static IApplicationBuilder UseYiSwagger(
this IApplicationBuilder app,
params SwaggerConfiguration[] swaggerConfigs)
{
var mvcOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value;
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<IOptions<AbpAspNetCoreMvcOptions>>()
.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
/// <summary>
/// Swagger配置模型
/// </summary>
public class SwaggerConfiguration
{
public SwaggerModel(string name)
private const string DefaultSwaggerUrl = "/swagger/v1/swagger.json";
/// <summary>
/// Swagger JSON文档的URL
/// </summary>
public string Url { get; }
/// <summary>
/// Swagger文档的显示名称
/// </summary>
public string Name { get; }
/// <summary>
/// 使用默认URL创建Swagger配置
/// </summary>
/// <param name="name">文档显示名称</param>
public SwaggerConfiguration(string name)
: this(DefaultSwaggerUrl, name)
{
this.Name = name;
this.Url = "/swagger/v1/swagger.json";
}
public SwaggerModel(string url, string name)
/// <summary>
/// 创建自定义Swagger配置
/// </summary>
/// <param name="url">Swagger JSON文档URL</param>
/// <param name="name">文档显示名称</param>
public SwaggerConfiguration(string url, string name)
{
this.Url = url;
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; }
}
}

View File

@@ -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
{
/// <summary>
/// API响应信息处理中间件
/// 主要用于处理特定文件类型的响应头信息
/// </summary>
[DebuggerStepThrough]
public class ApiInfoMiddleware : IMiddleware, ITransientDependency
{
/// <summary>
/// 处理HTTP请求的中间件方法
/// </summary>
/// <param name="context">HTTP上下文</param>
/// <param name="next">请求处理委托</param>
/// <returns>异步任务</returns>
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
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);
}
/// <summary>
/// 处理文件下载响应的响应头信息
/// </summary>
/// <param name="context">HTTP上下文</param>
private static void HandleFileDownloadResponse(HttpContext context)
{
// 仅处理状态码为200的响应
if (context.Response.StatusCode != StatusCodes.Status200OK)
{
return;
}
var contentType = context.Response.Headers["Content-Type"].ToString();
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
// 处理Excel文件下载
if (contentType == "application/vnd.ms-excel")
{
context.FileAttachmentHandle($"{timestamp}.xlsx");
}
// 处理ZIP文件下载
else if (contentType == "application/x-zip-compressed")
{
context.FileAttachmentHandle($"{timestamp}.zip");
}
}
}
}

View File

@@ -9,129 +9,193 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using 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
{
/// <summary>
/// Swagger生成器扩展类
/// </summary>
public static class SwaggerAddExtensions
{
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services,
Action<SwaggerGenOptions>? action = null)
/// <summary>
/// 添加Yi框架的Swagger生成器服务
/// </summary>
/// <typeparam name="TProgram">程序入口类型</typeparam>
/// <param name="services">服务集合</param>
/// <param name="setupAction">自定义配置动作</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddYiSwaggerGen<TProgram>(
this IServiceCollection services,
Action<SwaggerGenOptions>? setupAction = null)
{
// 获取MVC配置选项
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
var 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<TProgram>(options);
options.OperationFilter<AddRequiredHeaderParameter>();
options.SchemaFilter<EnumSchemaFilter>();
// 配置JWT认证
ConfigureJwtAuthentication(options);
// 添加自定义过滤器
ConfigureCustomFilters(options);
}
);
return services;
}
/// <summary>
/// 配置API分组
/// </summary>
private static void ConfigureApiGroups(
SwaggerGenOptions options,
IEnumerable<ConventionalControllerSetting> settings)
{
foreach (var setting in settings.OrderBy(x => x.RemoteServiceName))
{
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo
{
Title = setting.RemoteServiceName,
Version = "v1"
});
}
}
}
/// <summary>
/// 配置API文档过滤器
/// </summary>
private static void ConfigureApiFilter(
SwaggerGenOptions options,
IEnumerable<ConventionalControllerSetting> 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;
});
}
/// <summary>
/// 包含XML注释文档
/// </summary>
private static void IncludeXmlComments<TProgram>(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);
}
}
}
/// <summary>
/// 配置JWT认证
/// </summary>
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<string>()
});
}
/// <summary>
/// 配置自定义过滤器
/// </summary>
private static void ConfigureCustomFilters(SwaggerGenOptions options)
{
options.OperationFilter<TenantHeaderOperationFilter>();
options.SchemaFilter<EnumSchemaFilter>();
}
}
/// <summary>
/// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述
/// Swagger文档枚举字段显示过滤器
/// </summary>
public class EnumSchemaFilter : ISchemaFilter
{
/// <summary>
/// 实现接口
/// 应用枚举架构过滤器
/// </summary>
/// <param name="model"></param>
/// <param name="context"></param>
public void Apply(OpenApiSchema model, SchemaFilterContext context)
/// <param name="schema">OpenAPI架构</param>
/// <param name="context">架构过滤器上下文</param>
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))}】<br />");
});
model.Description = stringBuilder.ToString();
schema.Enum.Add(new OpenApiString(enumName));
enumDescriptions.AppendLine(
$"【枚举:{enumName}{(description is null ? string.Empty : $"({description})")}={enumIntValue}】");
}
schema.Description = enumDescriptions.ToString();
}
/// <summary>
/// 获取枚举描述特性值
/// </summary>
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
/// <summary>
/// 租户头部参数过滤器
/// </summary>
public class TenantHeaderOperationFilter : IOperationFilter
{
public static string HeaderKey { get; set; } = "__tenant";
/// <summary>
/// 租户标识键名
/// </summary>
private const string TenantHeaderKey = "__tenant";
/// <summary>
/// 应用租户头部参数过滤器
/// </summary>
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters ??= new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = HeaderKey,
Name = TenantHeaderKey,
In = ParameterLocation.Header,
Required = false,
AllowEmptyValue = true,
Description = "租户id或者租户名称(可空为默认租户)"
Description = "租户ID或租户名称(留空表示默认租户)"
});
}
}

View File

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

View File

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

View File

@@ -5,32 +5,53 @@ using Volo.Abp.AspNetCore.WebClientInfo;
namespace Yi.Framework.AspNetCore;
/// <summary>
/// 真实IP地址提供程序,支持代理服务器场景
/// </summary>
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
{
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
private const string XForwardedForHeader = "X-Forwarded-For";
/// <summary>
/// 初始化真实IP地址提供程序的新实例
/// </summary>
public RealIpHttpContextWebClientInfoProvider(
ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor)
: base(logger, httpContextAccessor)
{
}
/// <summary>
/// 获取客户端IP地址,优先从X-Forwarded-For头部获取
/// </summary>
/// <returns>客户端IP地址</returns>
protected override string? GetClientIpAddress()
{
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;
}
}

View File

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

View File

@@ -19,15 +19,24 @@ using Yi.Framework.Core;
namespace Yi.Framework.AspNetCore
{
[DependsOn(typeof(YiFrameworkCoreModule)
)]
/// <summary>
/// Yi框架ASP.NET Core模块
/// </summary>
[DependsOn(typeof(YiFrameworkCoreModule))]
public class YiFrameworkAspNetCoreModule : AbpModule
{
/// <summary>
/// 配置服务后的处理
/// </summary>
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));
}
}
}