refactor: ai+人工重构优化 framework
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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或租户名称(留空表示默认租户)"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user