/* * Copyright (c) 2025 SimcuTeam. All rights reserved. * 遵循 MIT 许可证。 * * 根命名空间 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.* import simcu::simapi.openapi.* import simcu::simapi.openapi.annotations.* /** * SimApi 扩展入口。 */ 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() // 先构造配置,供后续按开关注册服务 let options = SimApiOptions() configure(options) // 响应封装: // 启用时注册自定义 IRequestDelegateFactory 自动封装响应。 // 必须在 addControllers 之前:soulsoft 用 tryAddSingleton 注册,先到先得,不会被覆盖。 // 未启用时使用 soulsoft 默认派发(String→ContentResult / ISerializable→ObjectResult / 其余→204)。 if (options.enableSimApiResponseFilter) { builder.services.addSingleton() } // 自动注册 MVC + 控制器(对齐 builder.Services.AddControllers()) // 自动扫描调用者包中的 Controller 子类 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 中间件与内置路由(日志输出)。 * @param host 构建完成的主机。 */ public static func useSimApi(host: WebHost): Unit { let options = host.services.getOrThrow() let loggerFactory = host.services.getOrThrow() // 分类名为 SimApiOptions 的全限定名 let logger = loggerFactory.createLogger() // ===== 基础信息 ===== 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,桶初始化惰性执行; // 但 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 ...") } // ===== 中间件与路由 ===== // 挂载顺序(先挂载 = 外层):ForwardedHeaders(L419) → CORS(L425) → AuthGate(L454) → Auth(L462) // → 内置路由(L465-496) → Swagger(L502) → RequestLog(L519) → Exception(L525) → LowerUrl → Job // ForwardedHeaders(占位:soulsoft 暂无内置) if (options.enableForwardHeaders) { logger.info("开始配置ForwardedHeaders...") } // CORS if (options.enableCors) { logger.info("开始配置 Cors全部允许...") host.useCors() } // AuthGate if (options.enableSimApiAuthGate) { logger.info("开始配置 SimApiAuthGate...") host.use() } // 认证中间件 if (options.enableSimApiAuth) { logger.info("开始配置 SimApiAuth...") host.use() } // 内置路由:全部动态注册(路径由 SimApiRouteOptions 决定,不写死在控制器特性注解上) let routeOptions = options.simApiRouteOptions if (let Some(route) <- routeOptions.userInfoRoute) { 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()) } }).withOpenApi(SimApiDoc(tags: "认证", summary: "获取登录用户信息")) logger.info("注册内置Route: UserInfo => ${route}") } if (let Some(route) <- routeOptions.logoutRoute) { 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()) } }).withOpenApi(SimApiDoc(tags: "认证", summary: "退出登录")) logger.info("注册内置Route: Logout => ${route}") } if (let Some(route) <- routeOptions.webConfigRoute) { 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()) } }).withOpenApi(SimApiDoc(tags: "公共", summary: "获取公共系统配置")) 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.webConfig()) } }).withOpenApi(SimApiDoc(tags: "公共", summary: "获取公共系统配置")) logger.info("注册内置Route: WebConfig => ${route}") } // 请求日志中间件 if (options.enableRequestLog) { logger.info("开始配置 SimApiRequestLog...") host.use() } // 异常中间件最后挂载(最内层) 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 自动封装, // 此处仅输出配置日志) if (options.enableSimApiResponseFilter) { logger.info("开始配置 SimApiResponseFilter...") } // 映射控制器端点 let callSiteFactory = host.services.getOrThrow() if (callSiteFactory.isService()) { host.mapControllers() } // SimApiDoc(OpenAPI 文档 JSON 路由 + Swagger UI 静态资源,最内层挂载) if (options.enableSimApiDoc) { logger.info("开始配置 SimApiDoc...") host.mapOpenApi() host.useOpenApiUI() } } // ===== 私有辅助 ===== private static func addSimApiCore(builder: WebHostBuilder, options: SimApiOptions): WebHostBuilder { // 注册单例配置 builder.services.addSingleton(options) // 子配置不单独注册:中间件统一注入 SimApiOptions 后访问其属性 // 自定义日志格式(替换默认 console provider) if (options.enableLogger) { builder.services.addLogging { logging => logging.clearProviders() logging.addProvider(SimApiLoggerProvider()) } } // 中间件无需注册:挂载时由 ActivatorUtilities 从 DI 解析构造参数创建 // API 文档(OpenAPI) if (options.enableSimApiDoc) { builder.services.addOpenApi() } // 认证(DI 自动注入 SimApiOptions) if (options.enableSimApiAuth) { builder.services.addSingleton() } // SimApiAuthChecker 自动扫描注册 // 扫描调用者包中的实现类,按【接口】注册;执行时用 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): // 仓颉版注册为 Scoped 以便注入 IHttpContextAccessor(DI 禁止 singleton 消费 scoped), // 桶初始化由静态守卫保证只执行一次。 if (options.enableSimApiStorage) { builder.services.addHttpContextAccessor() builder.services.addScoped() } // AuthGate 认证中心 SDK if (options.enableSimApiAuthGate) { builder.services.addSingleton() builder.services.addSingleton() builder.services.addSingleton() } // CORS if (options.enableCors) { builder.services.addCors { cors => cors.addDefaultPolicy { policy => policy.allowAnyOrigin() policy.allowAnyMethod() policy.allowAnyHeader() } } } return builder } /// 注册 MVC 服务,并注册 SimApi 内置控制器 + 用户控制器到 ApplicationPartManager。 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 } }