- 添加 /swagger 路径到 ApiResponseMiddleware 的 PassthroughPrefixes 列表 - 确保 Swagger/OpenAPI 文档保持原始结构不被包裹成统一响应格式 - 避免 Swagger UI 无法识别 openapi 字段导致的文档加载失败
168 lines
7.8 KiB
C#
168 lines
7.8 KiB
C#
using BuildFlow.Host.Configuration;
|
||
using BuildFlow.Host.Hubs;
|
||
using BuildFlow.Host.Services.BackgroundServices;
|
||
using BuildFlow.Host.Services.Integration;
|
||
using BuildFlow.Host.Services.Notifications;
|
||
using BuildFlow.Host.Services.Users;
|
||
using FastEndpoints.Swagger;
|
||
using OpenTelemetry.Metrics;
|
||
using OpenTelemetry.Resources;
|
||
using OpenTelemetry.Trace;
|
||
using BuildFlow.Shared.Api;
|
||
using BuildFlow.Shared.Infrastructure.Serialization;
|
||
using File.Application;
|
||
using File.Infrastructure;
|
||
using IM.Application;
|
||
using IM.Domain.Common;
|
||
using IM.Infrastructure;
|
||
using BuildFlow.Platform.Application;
|
||
using BuildFlow.Platform.Infrastructure.Persistence;
|
||
using Volo.Abp;
|
||
using Volo.Abp.Modularity;
|
||
using Workflow.Application;
|
||
using Workflow.Application.Engine;
|
||
using Workflow.Application.Integration;
|
||
using Workflow.Application.Notifications;
|
||
using Workflow.Infrastructure;
|
||
|
||
namespace BuildFlow.Host;
|
||
|
||
|
||
[DependsOn(
|
||
typeof(BuildFlow.Application.RAGApplicationModule),
|
||
typeof(BuildFlow.Infrastructure.RAGInfrastructureModule),
|
||
typeof(PlatformApplicationModule),
|
||
typeof(FileApplicationModule),
|
||
typeof(FileInfrastructureModule),
|
||
typeof(IMApplicationModule),
|
||
typeof(IMInfrastructureModule),
|
||
typeof(WorkflowApplicationModule),
|
||
typeof(WorkflowInfrastructureModule))]
|
||
/// <summary>
|
||
/// BuildFlow 主 Host 的 ABP 模块。通过 [DependsOn] 聚合 platform/rag/file/im/workflow 五个业务模块的
|
||
/// Application + Infrastructure 层,统一注册跨切面基础设施(OTel/FastEndpoints/SignalR/JWT/CORS/
|
||
/// 跨模块集成接口)与中间件管道。HTTP 层(Endpoints/Hubs)已全部并入本 Host 程序集。
|
||
/// platform 模块承载用户/角色/权限/菜单/通知(原属 rag,已独立成库)。
|
||
/// </summary>
|
||
public class BuildFlowHostModule : AbpModule
|
||
{
|
||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||
{
|
||
var services = context.Services;
|
||
var config = services.GetConfiguration();
|
||
|
||
// ===== 跨切面基础设施(仅注册一次)=====
|
||
// OpenTelemetry:统一导出 Traces + Metrics
|
||
var otlpEndpoint = config["OpenTelemetry:OtlpEndpoint"] ?? "http://localhost:4316";
|
||
services.AddOpenTelemetry()
|
||
.ConfigureResource(r => r
|
||
.AddService(serviceName: "buildflow", serviceVersion: "1.0.0"))
|
||
.WithTracing(tracing => tracing
|
||
.AddAspNetCoreInstrumentation()
|
||
.AddHttpClientInstrumentation()
|
||
.AddEntityFrameworkCoreInstrumentation()
|
||
.AddOtlpExporter(o => o.Endpoint = new Uri(otlpEndpoint)))
|
||
.WithMetrics(metrics => metrics
|
||
.AddAspNetCoreInstrumentation()
|
||
.AddHttpClientInstrumentation()
|
||
.AddRuntimeInstrumentation()
|
||
.AddOtlpExporter((exporter, reader) => exporter.Endpoint = new Uri(otlpEndpoint)));
|
||
|
||
// FastEndpoints + Swagger(Endpoints 已并入本程序集,默认扫描当前程序集)
|
||
services.AddFastEndpoints();
|
||
services.SwaggerDocument();
|
||
|
||
// SignalR(rag 通知 + im 聊天共用)
|
||
services.AddSignalR();
|
||
// im 的 DomainExceptionHubFilter 注册为全局 hub filter(统一异常信封)
|
||
services.AddSingleton<DomainExceptionHubFilter>();
|
||
|
||
// 当前用户上下文:各模块的 CurrentUserContext 实现
|
||
services.AddHttpContextAccessor();
|
||
services.AddScoped<BuildFlow.Shared.Domain.Common.ICurrentUserContext, CurrentUserContext>();
|
||
services.AddScoped<IUserInfoService, InProcessUserInfoService>();
|
||
|
||
// 串联:IM @AI → RAG 问答(跨模块集成,RAG 实现 IAiBotHandler 供 IM 调用)
|
||
services.AddScoped<BuildFlow.Shared.Integration.IAiBotHandler, BuildFlow.Application.Integration.AiBotHandler>();
|
||
// 串联:AI 对话 → 触发工作流(跨模块集成,Workflow 实现 IWorkflowTriggerService 供 RAG 调用)
|
||
services.AddScoped<BuildFlow.Shared.Integration.IWorkflowTriggerService, WorkflowTriggerService>();
|
||
|
||
// workflow 后台服务:超时调度 + Webhook 投递
|
||
services.AddScoped<ProcessEngine>();
|
||
services.Configure<NotificationOptions>(config.GetSection("Workflow:Notifications"));
|
||
services.AddNotifications(config);
|
||
services.AddHostedService<TimeoutSchedulerService>();
|
||
services.AddHostedService<WebhookDispatcherService>();
|
||
|
||
// 消息中心通知分发器(仅 rag 用)
|
||
services.AddScoped<BuildFlow.Platform.Application.Notifications.INotificationDispatcher, SignalRNotificationDispatcher>();
|
||
|
||
// 进程内文件存储:覆盖 rag 原有的 HTTP 版 FileStorageClient(后注册者胜出)
|
||
services.AddScoped<BuildFlow.Domain.Interfaces.IFileStorageClient, InProcessFileStorageClient>();
|
||
|
||
// JWT 认证 + CORS:使用共享内核扩展方法(消除重复的认证/跨域配置)
|
||
services.AddSharedJwtAuthentication(config);
|
||
services.AddSharedCors();
|
||
|
||
// 响应包裹中间件配置:文件下载为二进制流式响应,必须绕过 JSON 包裹与 MemoryStream 缓冲
|
||
services.Configure<BuildFlow.Shared.Api.ApiResponseMiddlewareOptions>(opts =>
|
||
{
|
||
opts.PassthroughPrefixes.Add("/api/files/download");
|
||
// Swagger/OpenAPI 文档必须保持原始结构,不能包裹成 { code, data, message },否则 Swagger UI 无法识别 openapi 字段。
|
||
opts.PassthroughPrefixes.Add("/swagger");
|
||
});
|
||
}
|
||
|
||
public override void OnApplicationInitialization(ApplicationInitializationContext context)
|
||
{
|
||
var app = context.GetApplicationBuilder();
|
||
|
||
// 中间件管道(只装配一次,避免双重包裹):
|
||
// CORS → Routing → GlobalException → gRPC ×2 → ApiResponse → Auth → AuthZ → FastEndpoints → Swagger
|
||
app.UseCors();
|
||
app.UseRouting();
|
||
|
||
// 全局异常中间件(rag 版本是超集:含流式 HasStarted 守卫)
|
||
app.UseMiddleware<GlobalExceptionMiddleware>();
|
||
|
||
// 响应包裹中间件(rag 版本是超集:含 /api/auth/refresh 排除 + SSE 流式路径排除)
|
||
app.UseMiddleware<ApiResponseMiddleware>();
|
||
|
||
app.UseAuthentication();
|
||
app.UseAuthorization();
|
||
|
||
app.UseFastEndpoints(config =>
|
||
{
|
||
config.Endpoints.RoutePrefix = "api";
|
||
// 统一数据规范:时间字段输出 UTC 毫秒时间戳
|
||
config.Serializer.Options.Converters.Add(new TimestampDateTimeConverter());
|
||
config.Serializer.Options.Converters.Add(new TimestampDateTimeOffsetConverter());
|
||
|
||
// 校验失败统一 { code:400, message, data:null }(使用内核共享信封类型)
|
||
config.Errors.StatusCode = 400;
|
||
config.Errors.ResponseBuilder = (failures, ctx, statusCode) =>
|
||
{
|
||
var message = string.Join("; ", failures.Select(f => f.ErrorMessage));
|
||
return new BuildFlow.Shared.Api.ValidationErrorResponse(400, message);
|
||
};
|
||
|
||
// 全局异常过滤器:扩展为同时识别 rag 与 file-system 两套领域异常
|
||
config.Endpoints.Configurator = ep =>
|
||
{
|
||
ep.PostProcessors(Order.After, typeof(AggregateExceptionHandler<,>));
|
||
};
|
||
});
|
||
|
||
app.UseSwaggerGen();
|
||
|
||
// SignalR Hub(rag 通知 + im 聊天,不同路径共存)
|
||
context.GetEndpointRouteBuilder().MapHub<NotificationHub>("/hubs/notifications");
|
||
context.GetEndpointRouteBuilder().MapHub<ChatHub>("/hubs/chat");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 聚合异常过滤器(FastEndpoints post-processor),同时映射 rag 与 file-system 两套领域异常。
|
||
/// 使用内核共享的 <see cref="ErrorResponse"/> 信封类型(替代之前的私有 ExceptionEnvelope record)。
|
||
/// </summary>
|