221 lines
8.0 KiB
C#
221 lines
8.0 KiB
C#
using Fake.AspNetCore;
|
||
using Fake.Autofac;
|
||
using Fake.Modularity;
|
||
using Fake.Rbac.Application;
|
||
using Fake.Rbac.Infrastructure;
|
||
using Fake.AspNetCore.Auditing;
|
||
using Fake.AspNetCore.ExceptionHandling;
|
||
using Fake.AspNetCore.Newtonsoft;
|
||
using Fake.AspNetCore.Swagger;
|
||
using Fake.EventBus.IntegrationEventLog;
|
||
using Fake.FeiShu;
|
||
using Fake.FileManagement.Application;
|
||
using Fake.FileManagement.Infrastructure;
|
||
using Fake.ObjectStorage;
|
||
using Fake.Security.Claims;
|
||
using Fake.Serilog;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.HttpOverrides;
|
||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using Oms.Application;
|
||
using Oms.SqlSugarCore;
|
||
using SimpleAdmin.Api.Auth;
|
||
using SimpleAdmin.Api.Storage;
|
||
using SimpleAdmin.Api.Swagger;
|
||
using SimpleAdmin.Api.WeChat;
|
||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||
|
||
namespace SimpleAdmin.Api;
|
||
|
||
[DependsOn(
|
||
typeof(FakeAutofacModule),
|
||
typeof(OmsApplicationModule),
|
||
typeof(OmsSqlSugarCoreModule),
|
||
typeof(FakeRbacApplicationModule),
|
||
typeof(FakeRbacInfrastructureModule),
|
||
typeof(FakeFileManagementApplicationModule),
|
||
typeof(FakeFileManagementInfrastructureModule),
|
||
typeof(FakeObjectStorageTencentCosModule),
|
||
// 在 RabbitMQ 物理总线之后 Replace IDistributedEventBus → Outbox
|
||
typeof(FakeEventBusIntegrationEventLogModule),
|
||
typeof(FakeAspNetCoreModule),
|
||
typeof(FakeSerilogModule)
|
||
)]
|
||
public class SimpleAdminApiModule : FakeModule
|
||
{
|
||
private const string DefaultCorsPolicyName = "default";
|
||
|
||
public override void PreConfigureServices(ServiceConfigurationContext context)
|
||
{
|
||
FakeClaimTypes.UserId = "mapId";
|
||
}
|
||
|
||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||
{
|
||
var services = context.Services;
|
||
var configuration = context.Services.GetConfiguration();
|
||
|
||
// 配置动态 API - 扫描 OMS / RBAC / FileManagement 应用服务
|
||
services.Configure<FakeAspNetCoreMvcOptions>(options =>
|
||
{
|
||
options.ApplicationServices2Controller<OmsApplicationModule>();
|
||
options.ApplicationServices2Controller<SimpleAdminApiModule>(settings =>
|
||
{
|
||
settings.RootPath = "rbac";
|
||
settings.TypePredicate = type =>
|
||
type == typeof(AuthService) || type == typeof(WeChatCustomerService);
|
||
});
|
||
options.ApplicationServices2Controller<FakeRbacApplicationModule>(settings =>
|
||
{
|
||
settings.RootPath = "rbac";
|
||
settings.TypePredicate = type =>
|
||
type != typeof(Fake.Rbac.Application.Services.AuthService) &&
|
||
type != typeof(Fake.Rbac.Application.Services.UserService);
|
||
});
|
||
options.ApplicationServices2Controller<FakeFileManagementApplicationModule>(settings =>
|
||
{
|
||
settings.RootPath = "files";
|
||
});
|
||
});
|
||
|
||
services.AddOmsWeChat(configuration);
|
||
services.AddSingleton<IRbacAuthCache, RbacAuthCache>();
|
||
|
||
// Outbox / Inbox 与业务同库(EF),通过 DbTransaction 征用 SqlSugar 事务
|
||
services.AddDbContext<IntegrationEventLogContext>(options =>
|
||
{
|
||
var connectionString = configuration.GetConnectionString("IntegrationEventLog")
|
||
?? configuration.GetConnectionString("Default")
|
||
?? configuration["ConnectionStrings:Default"]
|
||
?? string.Empty;
|
||
var serverVersion = ServerVersion.AutoDetect(connectionString);
|
||
options.UseMySql(connectionString, serverVersion);
|
||
#if DEBUG
|
||
options.EnableSensitiveDataLogging();
|
||
#endif
|
||
});
|
||
|
||
//Json格式化全局处理
|
||
context.Services.AddFakeNewtonsoft();
|
||
|
||
services.AddFakeAspNetCoreAuditing();
|
||
|
||
// 添加 Fake Swagger 支持
|
||
services.AddFakeSwaggerGen(addSecurity: true);
|
||
// Fake.Rbac 菜单/组织/权限/角色接口无 XML 注释,补全 summary
|
||
services.Configure<SwaggerGenOptions>(options =>
|
||
{
|
||
options.OperationFilter<RbacSwaggerOperationFilter>();
|
||
});
|
||
|
||
services.AddCors(options =>
|
||
{
|
||
options.AddPolicy(DefaultCorsPolicyName, builder =>
|
||
{
|
||
builder.WithOrigins(configuration.GetSection("App:CorsOrigins").Get<string[]>() ?? [])
|
||
.AllowAnyHeader()
|
||
.AllowAnyMethod()
|
||
.AllowCredentials();
|
||
});
|
||
});
|
||
|
||
services.Configure<ForwardedHeadersOptions>(options =>
|
||
{
|
||
// 反向代理场景下恢复客户端真实来源信息,供 Swagger、鉴权和重定向等中间件使用
|
||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
|
||
ForwardedHeaders.XForwardedProto |
|
||
ForwardedHeaders.XForwardedHost;
|
||
options.KnownNetworks.Clear();
|
||
options.KnownProxies.Clear();
|
||
});
|
||
|
||
services.AddFeiShuExceptionSubscriber();
|
||
|
||
// 草稿已落库失败:结构化 Details + 清理 msg 机器标记
|
||
services.AddTransient<DefaultException2ResponseConverter>();
|
||
var exceptionConverterDescriptors = services
|
||
.Where(d => d.ServiceType == typeof(IException2ResponseConverter))
|
||
.ToList();
|
||
foreach (var descriptor in exceptionConverterDescriptors)
|
||
{
|
||
services.Remove(descriptor);
|
||
}
|
||
|
||
services.AddTransient<IException2ResponseConverter>(sp =>
|
||
new ExceptionHandling.OmsException2ResponseConverter(
|
||
sp.GetRequiredService<DefaultException2ResponseConverter>()));
|
||
|
||
// 全局授权配置:所有接口默认需要授权
|
||
services.AddMvc(options =>
|
||
{
|
||
var policy = new AuthorizationPolicyBuilder()
|
||
.RequireAuthenticatedUser()
|
||
.Build();
|
||
options.Filters.Add(new AuthorizeFilter(policy));
|
||
});
|
||
// 添加授权服务
|
||
services.AddAuthorization();
|
||
|
||
services.AddHostedService<ObjectStorageStartupLogger>();
|
||
}
|
||
|
||
public override void ConfigureApplication(ApplicationConfigureContext context)
|
||
{
|
||
var app = context.GetWebApplication();
|
||
|
||
// 必须在读取 Scheme/Host/PathBase 的中间件之前执行,否则 Swagger 会生成错误的请求地址
|
||
app.UseForwardedHeaders();
|
||
|
||
// 虚拟路由-网关适配
|
||
app.UsePathBase(app.Configuration["ASPNETCORE_PATHBASE"]);
|
||
|
||
// Swagger
|
||
app.UseFakeSwagger();
|
||
// 异常处理
|
||
app.UseFakeExceptionHandling();
|
||
// CORS
|
||
app.UseCors(DefaultCorsPolicyName);
|
||
|
||
// 静态文件服务(用于头像等上传文件)
|
||
app.UseStaticFiles();
|
||
|
||
// 路由 - 必须在认证和授权之前
|
||
app.UseRouting();
|
||
|
||
// OpenAPI 验签需读取原始 Body,允许二次读流
|
||
app.Use(async (ctx, next) =>
|
||
{
|
||
if (ctx.Request.Path.StartsWithSegments("/openapi"))
|
||
{
|
||
ctx.Request.EnableBuffering();
|
||
}
|
||
|
||
await next();
|
||
});
|
||
|
||
// 认证和授权
|
||
app.UseAuthentication();
|
||
app.UseAuthorization();
|
||
|
||
// 角色/菜单写操作后失效登录用户信息缓存
|
||
app.UseMiddleware<RbacCacheInvalidationMiddleware>();
|
||
|
||
// 端点映射
|
||
app.MapControllers();
|
||
|
||
// FileManagement / Outbox 与业务同库并行(EF)
|
||
using (var scope = app.Services.CreateScope())
|
||
{
|
||
var fileDb = scope.ServiceProvider.GetRequiredService<FileManagementDbContext>();
|
||
fileDb.Database.Migrate();
|
||
|
||
var outboxDb = scope.ServiceProvider.GetRequiredService<IntegrationEventLogContext>();
|
||
IntegrationEventLogSchema.EnsureTablesAsync(outboxDb).GetAwaiter().GetResult();
|
||
}
|
||
|
||
app.Services.GetRequiredService<IFeiShuNotificationService>().Send("Application started");
|
||
}
|
||
}
|