/* * Copyright (c) 2025 SimcuTeam. All rights reserved. * 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。 */ package simapi.extensions import std.collection.* import std.convert.* import std.reflect.* import std.time.* import soulsoft_serialization.* import soulsoft_serialization.macros.* import soulsoft_web_http.* import soulsoft_web_hosting.* import soulsoft_web_mvc.* import soulsoft_web_mvc.applicationModels.* import soulsoft_web_mvc.routing.* import soulsoft_web_cors.* import soulsoft_web_routing.* import soulsoft_extensions_injection.* import soulsoft_extensions_logging.* import simapi.authsdk.* import simapi.communications.* import simapi.configurations.* import simapi.controllers.* import simapi.helpers.* import simapi.logger.* import simapi.middlewares.* /** * SimApi 扩展入口:对应 C# 的 SimApiExtensions(AddSimApi + UseSimApi)。 * * 使用方式(仓颉版,与 addLogging 风格一致): * ``` * let builder = WebHost.createBuilder(args) * builder.services.addLogging() * builder.addSimApi { options => * options.enableSimApiAuth = true * } * let host = builder.build() * host.useSimApi() * host.run() * ``` */ public interface SimApiBuilderExtensions { /** * 注册 SimApi 服务到 WebHostBuilder(自动 addRouting + addControllers + 扫描控制器)。 * @param configure 配置回调。 * @return 当前构建器。 */ func addSimApi(configure: (SimApiOptions) -> Unit): WebHostBuilder /** * 注册 SimApi 服务(默认配置,自动扫描控制器)。 * @return 当前构建器。 */ func addSimApi(): WebHostBuilder } extend WebHostBuilder <: SimApiBuilderExtensions { /** * 注册 SimApi 服务(自动 addRouting + addControllers + addLogging + 扫描控制器)。 * @param configure 配置回调。 */ public func addSimApi(configure: (SimApiOptions) -> Unit): WebHostBuilder { // 自动注册路由(对齐 builder.Services.AddRouting()) this.services.addRouting() // 先构造配置,供后续按开关注册服务(对齐 C# AddSimApi 中先读 options 再注册) let options = SimApiOptions() configure(options) // 响应封装(对齐 C# SimApiResponseFilter,受 EnableSimApiResponseFilter 开关控制): // 启用时注册自定义 IRequestDelegateFactory 自动封装响应。 // 必须在 addControllers 之前:soulsoft 用 tryAddSingleton 注册,先到先得,不会被覆盖。 // 未启用时使用 soulsoft 默认派发(String→ContentResult / ISerializable→ObjectResult / 其余→204)。 if (options.enableSimApiResponseFilter) { this.services.addSingleton() } // 自动注册 MVC + 控制器(对齐 builder.Services.AddControllers()) // 自动扫描调用者包中的 Controller 子类(对齐 C# 的 Assembly.GetTypes() 扫描) let controllers = SimApiControllerScanner.scan() this.services.addControllers(controllers) // 注册 SimApi 服务 addSimApiCore(this, options) } /** * 注册 SimApi 服务(默认配置,自动扫描控制器)。 */ public func addSimApi(): WebHostBuilder { addSimApi({_ =>}) } } private func addSimApiCore(builder: WebHostBuilder, options: SimApiOptions): WebHostBuilder { // 注册单例配置(对齐 C# builder.AddSingleton(simApiOptions)) builder.services.addSingleton(options) // 子配置不单独注册:中间件统一注入 SimApiOptions 后访问其属性 // (对齐 C# SimApiExceptionMiddleware(..., SimApiOptions simApiOptions) 风格) // 自定义日志格式(替换默认 console provider) if (options.enableLogger) { builder.services.addLogging { logging => logging.clearProviders() logging.addProvider(SimApiLoggerProvider()) } } // 中间件无需注册:挂载时由 ActivatorUtilities 从 DI 解析构造参数创建 // (对齐 C# builder.UseMiddleware(),其中间件由 UseMiddleware 创建) // 认证(DI 自动注入 SimApiOptions) if (options.enableSimApiAuth) { builder.services.addSingleton() } // ISimApiAuthChecker 自动扫描注册(对齐 C# AddSimApi 中遍历调用者程序集 AddScoped 注册 checker) // 扫描调用者包中的 ISimApiAuthChecker 实现类:填充 options.authCheckers(供 @SimApiAuth 执行时解析) // 并注册到 DI(按实现类类型注册,供 getOrThrow(checkerType) 解析) if (options.enableSimApiAuth) { let checkers = SimApiControllerScanner.scanAuthCheckers() for (checkerType in checkers) { options.authCheckers.add(checkerType) builder.services.addScoped(checkerType) } } // 缓存(DI 自动注入 SimApiOptions) if (options.enableSimApiCache) { builder.services.addSingleton() } // HTTP 客户端(DI 自动注入 SimApiOptions) if (options.enableSimApiHttpClient) { builder.services.addSingleton() } // 存储(S3/MinIO,对齐 C# AddHttpContextAccessor + AddSingleton; // 仓颉版注册为 Scoped 以便注入 IHttpContextAccessor(DI 禁止 singleton 消费 scoped), // 桶初始化由静态守卫保证只执行一次) if (options.enableSimApiStorage) { builder.services.addHttpContextAccessor() builder.services.addScoped() } // AuthGate 认证中心 SDK(对齐 C# 注册 SimApiAuthClient/Center/Iam 单例) if (options.enableSimApiAuthGate) { builder.services.addSingleton() builder.services.addSingleton() builder.services.addSingleton() } // CORS(对齐 C# builder.Services.AddCors(policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader())) if (options.enableCors) { builder.services.addCors { cors => cors.addDefaultPolicy { policy => policy.allowAnyOrigin() policy.allowAnyMethod() policy.allowAnyHeader() } } } return builder } /** * SimApi 主机扩展。 */ public interface SimApiHostExtensions { /** * 应用 SimApi 中间件与内置路由到 WebHost。 */ func useSimApi(): Unit } extend WebHost <: SimApiHostExtensions { /** * 应用 SimApi 中间件与内置路由(日志输出对齐 C# UseSimApi)。 */ public func useSimApi(): Unit { let options = this.services.getOrThrow() let loggerFactory = this.services.getOrThrow() // 对齐 C# ILogger:分类名为 SimApiOptions 的全限定名 let logger = loggerFactory.createLogger() // ===== 基础信息(对齐 C# UseSimApi(IHost) 开头) ===== let now = DateTime.now() logger.info("当前时区: ${now.zoneId}") logger.info("主应用版本: ${SimApiUtil.appVersion}\nSimApi版本: ${SimApiUtil.simApiVersion}") // RedisCache if (!options.redisConfiguration.isEmpty()) { logger.info("开始配置 RedisCache ...") } // SimApiCache if (options.enableSimApiCache) { logger.info("开始配置 SimApiCache...") } // SimApiStorage(已实现:addSimApi 中注册 Scoped,桶初始化惰性执行; // 对齐 C# 的 GetService 预热,但 scoped 服务不能从根解析,故仅输出配置日志) if (options.enableSimApiStorage) { logger.info("开始配置 SimApiStorage...") } // SimApiHttpClient if (options.enableSimApiHttpClient) { logger.info( "开始配置 SimApiHttpClient...\n服务器地址: ${options.simApiHttpClientOptions.server}\nAppId: ${options.simApiHttpClientOptions.appId}\nAppkey: ${options.simApiHttpClientOptions.appKey}") } // Synapse(占位) if (options.enableSynapse) { logger.info("开始配置 SimApiSynapse...") } // SimApiJob(占位) if (options.enableJob) { logger.info("开始配置 SimApiJob ...") } // ===== 中间件与路由(对齐 C# UseSimApi(WebApplication) 的挂载顺序) ===== // C# 挂载顺序(先挂载 = 外层):ForwardedHeaders(L419) → CORS(L425) → AuthGate(L454) → Auth(L462) // → 内置路由(L465-496) → Swagger(L502) → RequestLog(L519) → Exception(L525) → LowerUrl → Job // ForwardedHeaders(占位:soulsoft 暂无内置,对齐 C# 最先挂载) if (options.enableForwardHeaders) { logger.info("开始配置ForwardedHeaders...") } // CORS(对齐 C# builder.UseCors("any")) if (options.enableCors) { logger.info("开始配置 Cors全部允许...") this.useCors() } // AuthGate(对齐 C# UseMiddleware) if (options.enableSimApiAuthGate) { logger.info("开始配置 SimApiAuthGate...") this.use() } // 认证中间件(对齐 C# builder.UseMiddleware()) if (options.enableSimApiAuth) { logger.info("开始配置 SimApiAuth...") this.use() } // 内置路由:RouteOptions 自定义路径时真实注册(默认路径已由内置控制器特性路由覆盖, // 对齐 C# MapControllerRoute 语义;soulsoft 无约定路由 defaults,用 mapGet/mapPost 委托实现) let routeOptions = options.simApiRouteOptions if (let Some(route) <- routeOptions.userInfoRoute) { if (route != "/user/info") { this.mapPost(route, { context => let controller = ActivatorUtilities.createInstance(context.services, TypeInfo.of()) if (let c: SimApiBaseController <- controller) { c.bindRequestContext(context) } if (let c: SimApiCommonController <- controller) { SimApiResultWriter.write(context, c.userInfo()) } }) } logger.info("注册内置Route: UserInfo => ${route}") } if (let Some(route) <- routeOptions.logoutRoute) { if (route != "/auth/logout") { this.mapPost(route, { context => let controller = ActivatorUtilities.createInstance(context.services, TypeInfo.of()) if (let c: SimApiBaseController <- controller) { c.bindRequestContext(context) } if (let c: SimApiAuthController <- controller) { SimApiResultWriter.write(context, c.logout()) } }) } logger.info("注册内置Route: Logout => ${route}") } if (let Some(route) <- routeOptions.webConfigRoute) { if (route != "/config") { this.mapGet(route, { context => let controller = ActivatorUtilities.createInstance(context.services, TypeInfo.of()) if (let c: SimApiBaseController <- controller) { c.bindRequestContext(context) } if (let c: SimApiCommonController <- controller) { SimApiResultWriter.write(context, c.webConfig()) } }) this.mapPost(route, { context => let controller = ActivatorUtilities.createInstance(context.services, TypeInfo.of()) if (let c: SimApiBaseController <- controller) { c.bindRequestContext(context) } if (let c: SimApiCommonController <- controller) { SimApiResultWriter.write(context, c.webConfigPost()) } }) } logger.info("注册内置Route: WebConfig => ${route}") } // SimApiDoc(占位,对齐 C# UseSwagger/UseSwaggerUI) if (options.enableSimApiDoc) { logger.info("开始配置 SimApiDoc...") } // 请求日志中间件(对齐 C# builder.UseMiddleware()) if (options.enableRequestLog) { logger.info("开始配置 SimApiRequestLog...") this.use() } // 异常中间件最后挂载(最内层,对齐 C# builder.UseMiddleware()) if (options.enableSimApiException) { logger.info("开始配置 SimApiException...") this.use() } // URL 小写 if (options.enableLowerUrl) { logger.info("开始配置使用URL小写...") } // SimApiJob Web 控制台(占位) if (options.enableJob && options.simApiJobOptions.dashboardUrl != None) { logger.info("开始配置 SimApiJob Web控制台...") } // 响应封装(已实现:addSimApi 中按开关注册 SimApiRequestDelegateFactory 自动封装, // 对齐 C# SimApiResponseFilter;此处仅输出配置日志) if (options.enableSimApiResponseFilter) { logger.info("开始配置 SimApiResponseFilter...") } // 映射控制器端点(对齐 C# UseSimApi 中的 MapControllers) let callSiteFactory = this.services.getOrThrow() if (callSiteFactory.isService()) { this.mapControllers() } } } /** * SimApi MVC 注册扩展:对齐 .NET 的 builder.Services.AddControllers()。 * 注册 MVC 服务 + SimApi 内置控制器 + 用户控制器。 * * 说明:控制器端点映射使用 soulsoft_web_mvc 的 mapControllers()(对齐 .NET MapControllers()), * 宿主在 WebHost 上直接调用 host.mapControllers() 即可。 */ public interface SimApiMvcBuilderExtensions { /** * 注册 MVC 服务与控制器。 * @param controllerTypes 用户控制器类型列表(可选)。 * @return MVC 构建器。 */ func addControllers(controllerTypes: Array): MvcBuilder } extend ServiceCollection <: SimApiMvcBuilderExtensions { /** * 注册 MVC 服务,并注册 SimApi 内置控制器 + 用户控制器到 ApplicationPartManager。 * 对齐 .NET 的 AddControllers()(含控制器发现)。 * @param controllerTypes 用户控制器类型列表。 * @return MVC 构建器。 */ public func addControllers(controllerTypes: Array): MvcBuilder { // 调用 soulsoft_web_mvc 的无参 addControllers() 注册 MVC 核心服务 let mvc = this.addControllers() let types = ArrayList() // SimApi 内置控制器 types.add(TypeInfo.of()) types.add(TypeInfo.of()) // 用户控制器 for (t in controllerTypes) { types.add(t) } let part = AssemblyPart("simapi.controllers", types.toArray()) mvc.addApplicationPart(part) mvc } }