/* * Copyright (c) 2025 SimcuTeam. All rights reserved. * 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。 * * 对齐 .NET:根命名空间 SimApi 下的静态类 SimApiExtensions(AddSimApi + UseSimApi)。 * 本文件同时是 simapi 根包的入口锚点:cjpm 要求 src 根目录至少有一个 .cj 文件, * 否则不会扫描 src 子目录(helpers/controllers/...),整个包将编译为空。 * * 使用方式: * ``` * let builder = WebHost.createBuilder(args) * builder.services.addLogging() * SimApiExtensions.addSimApi(builder) { options => * options.enableSimApiAuth = true * } * let host = builder.build() * SimApiExtensions.useSimApi(host) * host.run() * ``` */ package simcu::simapi import std.collection.* import std.convert.* import std.reflect.* import std.time.* 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 simcu::simapi.authsdk.* import simcu::simapi.communications.* import simcu::simapi.configurations.* import simcu::simapi.controllers.* import simcu::simapi.helpers.* import simcu::simapi.interfaces.* import simcu::simapi.logger.* import simcu::simapi.middlewares.* /** * SimApi 扩展入口(对齐 C# 根命名空间 SimApi 的静态类 SimApiExtensions)。 */ public class SimApiExtensions { private init() {} /** * 注册 SimApi 服务到 WebHostBuilder(自动 addRouting + addControllers + 扫描控制器)。 * @param builder 主机构建器。 * @param configure 配置回调。 * @return 当前构建器。 */ public static func addSimApi(builder: WebHostBuilder, configure: (SimApiOptions) -> Unit): WebHostBuilder { // 自动注册路由(对齐 builder.Services.AddRouting()) builder.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) { builder.services.addSingleton() } // 自动注册 MVC + 控制器(对齐 builder.Services.AddControllers()) // 自动扫描调用者包中的 Controller 子类(对齐 C# 的 Assembly.GetTypes() 扫描) let controllers = SimApiControllerScanner.scan() addControllers(builder.services, controllers) // 注册 SimApi 服务 addSimApiCore(builder, options) return builder } /** * 注册 SimApi 服务(默认配置,自动扫描控制器)。 * @param builder 主机构建器。 * @return 当前构建器。 */ public static func addSimApi(builder: WebHostBuilder): WebHostBuilder { addSimApi(builder, {_ =>}) } /** * 应用 SimApi 中间件与内置路由(日志输出对齐 C# UseSimApi)。 * @param host 构建完成的主机。 */ public static func useSimApi(host: WebHost): Unit { let options = host.services.getOrThrow() let loggerFactory = host.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全部允许...") host.useCors() } // AuthGate(对齐 C# UseMiddleware) if (options.enableSimApiAuthGate) { logger.info("开始配置 SimApiAuthGate...") host.use() } // 认证中间件(对齐 C# builder.UseMiddleware()) if (options.enableSimApiAuth) { logger.info("开始配置 SimApiAuth...") host.use() } // 内置路由:RouteOptions 自定义路径时真实注册(默认路径已由内置控制器特性路由覆盖, // 对齐 C# MapControllerRoute 语义;soulsoft 无约定路由 defaults,用 mapGet/mapPost 委托实现) let routeOptions = options.simApiRouteOptions if (let Some(route) <- routeOptions.userInfoRoute) { if (route != "/user/info") { host.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") { host.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") { host.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()) } }) host.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...") host.use() } // 异常中间件最后挂载(最内层,对齐 C# builder.UseMiddleware()) if (options.enableSimApiException) { logger.info("开始配置 SimApiException...") host.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 = host.services.getOrThrow() if (callSiteFactory.isService()) { host.mapControllers() } } // ===== 私有辅助 ===== private static 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(ISimApiAuthChecker, type)) // 扫描调用者包中的实现类,按【接口】注册;执行时用 getAll() 一次解析全部实现, // 无需在 SimApiOptions 里维护类型列表。 if (options.enableSimApiAuth) { let checkers = SimApiControllerScanner.scanAuthCheckers() for (checkerType in checkers) { builder.services.addScoped(TypeInfo.of(), 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 } /// 注册 MVC 服务,并注册 SimApi 内置控制器 + 用户控制器到 ApplicationPartManager(对齐 .NET AddControllers())。 private static func addControllers(services: ServiceCollection, controllerTypes: Array): MvcBuilder { // 调用 soulsoft_web_mvc 的无参 addControllers() 注册 MVC 核心服务 let mvc = services.addControllers() let types = ArrayList() // SimApi 内置控制器 types.add(TypeInfo.of()) types.add(TypeInfo.of()) // 用户控制器 for (t in controllerTypes) { types.add(t) } let part = AssemblyPart("simcu::simapi.controllers", types.toArray()) mvc.addApplicationPart(part) mvc } }