Files
simapi-cj/src/SimApiExtensions.cj
T

355 lines
15 KiB
Plaintext
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.
/*
* Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。
*
* 对齐 .NET:根命名空间 SimApi 下的静态类 SimApiExtensionsAddSimApi + 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<IRequestDelegateFactory, SimApiRequestDelegateFactory>()
}
// 自动注册 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<SimApiOptions>()
let loggerFactory = host.services.getOrThrow<ILoggerFactory>()
// 对齐 C# ILogger<SimApiOptions>:分类名为 SimApiOptions 的全限定名
let logger = loggerFactory.createLogger<SimApiOptions>()
// ===== 基础信息(对齐 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<SimApiAuthCenterMiddleware>
if (options.enableSimApiAuthGate) {
logger.info("开始配置 SimApiAuthGate...")
host.use<SimApiAuthCenterMiddleware>()
}
// 认证中间件(对齐 C# builder.UseMiddleware<SimApiAuthMiddleware>()
if (options.enableSimApiAuth) {
logger.info("开始配置 SimApiAuth...")
host.use<SimApiAuthMiddleware>()
}
// 内置路由: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<SimApiCommonController>())
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<SimApiAuthController>())
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<SimApiCommonController>())
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<SimApiCommonController>())
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<SimApiRequestLogMiddleware>()
if (options.enableRequestLog) {
logger.info("开始配置 SimApiRequestLog...")
host.use<SimApiRequestLogMiddleware>()
}
// 异常中间件最后挂载(最内层,对齐 C# builder.UseMiddleware<SimApiExceptionMiddleware>()
if (options.enableSimApiException) {
logger.info("开始配置 SimApiException...")
host.use<SimApiExceptionMiddleware>()
}
// 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<IServiceProviderIsService>()
if (callSiteFactory.isService<ApplicationPartManager>()) {
host.mapControllers()
}
}
// ===== 私有辅助 =====
private static func addSimApiCore(builder: WebHostBuilder, options: SimApiOptions): WebHostBuilder {
// 注册单例配置(对齐 C# builder.AddSingleton(simApiOptions)
builder.services.addSingleton<SimApiOptions>(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<T>(),其中间件由 UseMiddleware 创建)
// 认证(DI 自动注入 SimApiOptions
if (options.enableSimApiAuth) {
builder.services.addSingleton<SimApiAuth, SimApiAuth>()
}
// ISimApiAuthChecker 自动扫描注册(对齐 C# AddSimApi 中遍历调用者程序集 AddScoped(ISimApiAuthChecker, type)
// 扫描调用者包中的实现类,按【接口】注册;执行时用 getAll<ISimApiAuthChecker>() 一次解析全部实现,
// 无需在 SimApiOptions 里维护类型列表。
if (options.enableSimApiAuth) {
let checkers = SimApiControllerScanner.scanAuthCheckers()
for (checkerType in checkers) {
builder.services.addScoped(TypeInfo.of<ISimApiAuthChecker>(), checkerType)
}
}
// 缓存(DI 自动注入 SimApiOptions
if (options.enableSimApiCache) {
builder.services.addSingleton<SimApiCache, SimApiCache>()
}
// HTTP 客户端(DI 自动注入 SimApiOptions
if (options.enableSimApiHttpClient) {
builder.services.addSingleton<SimApiHttpClient, SimApiHttpClient>()
}
// 存储(S3/MinIO,对齐 C# AddHttpContextAccessor + AddSingleton<SimApiStorage>
// 仓颉版注册为 Scoped 以便注入 IHttpContextAccessorDI 禁止 singleton 消费 scoped),
// 桶初始化由静态守卫保证只执行一次)
if (options.enableSimApiStorage) {
builder.services.addHttpContextAccessor()
builder.services.addScoped<SimApiStorage, SimApiStorage>()
}
// AuthGate 认证中心 SDK(对齐 C# 注册 SimApiAuthClient/Center/Iam 单例)
if (options.enableSimApiAuthGate) {
builder.services.addSingleton<SimApiAuthClient, SimApiAuthClient>()
builder.services.addSingleton<SimApiAuthCenter, SimApiAuthCenter>()
builder.services.addSingleton<SimApiAuthIam, SimApiAuthIam>()
}
// 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<TypeInfo>): MvcBuilder {
// 调用 soulsoft_web_mvc 的无参 addControllers() 注册 MVC 核心服务
let mvc = services.addControllers()
let types = ArrayList<TypeInfo>()
// SimApi 内置控制器
types.add(TypeInfo.of<SimApiCommonController>())
types.add(TypeInfo.of<SimApiAuthController>())
// 用户控制器
for (t in controllerTypes) {
types.add(t)
}
let part = AssemblyPart("simcu::simapi.controllers", types.toArray())
mvc.addApplicationPart(part)
mvc
}
}