2026-08-17 09:10:02 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
|
|
|
|
|
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
|
|
|
|
|
*
|
2026-08-17 09:16:42 +08:00
|
|
|
|
* 对齐 .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()
|
|
|
|
|
|
* ```
|
2026-08-17 09:10:02 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
package simapi
|
2026-08-17 09:16:42 +08:00
|
|
|
|
|
|
|
|
|
|
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 simapi.authsdk.*
|
|
|
|
|
|
import simapi.communications.*
|
|
|
|
|
|
import simapi.configurations.*
|
|
|
|
|
|
import simapi.controllers.*
|
|
|
|
|
|
import simapi.helpers.*
|
2026-08-18 03:19:27 +08:00
|
|
|
|
import simapi.interfaces.*
|
2026-08-17 09:16:42 +08:00
|
|
|
|
import simapi.logger.*
|
|
|
|
|
|
import 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>()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-18 03:19:27 +08:00
|
|
|
|
// ISimApiAuthChecker 自动扫描注册(对齐 C# AddSimApi 中遍历调用者程序集 AddScoped(ISimApiAuthChecker, type))
|
|
|
|
|
|
// 扫描调用者包中的实现类,按【接口】注册;执行时用 getAll<ISimApiAuthChecker>() 一次解析全部实现,
|
|
|
|
|
|
// 无需在 SimApiOptions 里维护类型列表。
|
2026-08-17 09:16:42 +08:00
|
|
|
|
if (options.enableSimApiAuth) {
|
|
|
|
|
|
let checkers = SimApiControllerScanner.scanAuthCheckers()
|
|
|
|
|
|
for (checkerType in checkers) {
|
2026-08-18 03:19:27 +08:00
|
|
|
|
builder.services.addScoped(TypeInfo.of<ISimApiAuthChecker>(), checkerType)
|
2026-08-17 09:16:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存(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 以便注入 IHttpContextAccessor(DI 禁止 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("simapi.controllers", types.toArray())
|
|
|
|
|
|
mvc.addApplicationPart(part)
|
|
|
|
|
|
mvc
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|