build-flow.api/src/BuildFlow.Host/BuildFlowHostModule.cs
xiangning ade4beacdb fix(middleware): 解决Swagger文档无法正常显示的问题
- 添加 /swagger 路径到 ApiResponseMiddleware 的 PassthroughPrefixes 列表
- 确保 Swagger/OpenAPI 文档保持原始结构不被包裹成统一响应格式
- 避免 Swagger UI 无法识别 openapi 字段导致的文档加载失败
2026-07-03 09:42:38 +08:00

168 lines
7.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>