refactor: SimApiExtensions 移入根包 simapi 并改为静态类,删除 extensions 子包

- src/extensions/SimApiExtensions.cj 删除,内容合并进 src/SimApiExtensions.cj(package simapi)
- 接口+extend 改为静态类 SimApiExtensions:
  SimApiExtensions.addSimApi(builder, configure) / useSimApi(host)(对齐 .NET 根命名空间静态类)
- 私有辅助 addSimApiCore / addControllers 收敛为私有静态方法
- 用法从 builder.addSimApi{} / host.useSimApi() 改为 SimApiExtensions.addSimApi(builder){} / useSimApi(host)
This commit is contained in:
2026-08-17 09:16:42 +08:00
parent bcac7b1102
commit 8b0a8f783c
3 changed files with 359 additions and 406 deletions
+10 -10
View File
@@ -16,7 +16,7 @@ import soulsoft_web_routing.*
import soulsoft_web_hosting.* import soulsoft_web_hosting.*
import soulsoft_extensions_logging.* import soulsoft_extensions_logging.*
import soulsoft_extensions_injection.* import soulsoft_extensions_injection.*
import simapi.extensions.* import simapi.*
import simapi.communications.* import simapi.communications.*
main(args: Array<String>) { main(args: Array<String>) {
@@ -25,14 +25,14 @@ main(args: Array<String>) {
builder.services.addLogging() builder.services.addLogging()
// 注册 SimApi 服务(与 addLogging 同样式) // 注册 SimApi 服务(与 addLogging 同样式)
builder.addSimApi { options => SimApiExtensions.addSimApi(builder) { options =>
options.enableSimApiAuth = true // Token 认证(未配 Redis 自动用 InMemory options.enableSimApiAuth = true // Token 认证(未配 Redis 自动用 InMemory
options.enableSimApiCache = true // 缓存 options.enableSimApiCache = true // 缓存
options.enableSimApiException = true // 全局异常拦截 options.enableSimApiException = true // 全局异常拦截
} }
let host = builder.build() let host = builder.build()
host.useSimApi() SimApiExtensions.useSimApi(host)
// 业务接口:返回统一响应格式 // 业务接口:返回统一响应格式
host.mapGet("hello") { host.mapGet("hello") {
@@ -74,17 +74,17 @@ main(args: Array<String>) {
simapi-cj/ simapi-cj/
├── cjpm.toml # 包配置 ├── cjpm.toml # 包配置
├── src/ ├── src/
│ ├── SimApiExtensions.cj # 根包入口:SimApiExtensions 静态类(addSimApi / useSimApi + 内置路由 + 响应封装)
│ ├── attributes/ # 声明式注解:@SimApiAuth(鉴权)、@OriginResponse(原样响应) │ ├── attributes/ # 声明式注解:@SimApiAuth(鉴权)、@OriginResponse(原样响应)
│ ├── authsdk/ # 认证中心 SDKSimApiAuthClient/Center/Iam + 网关中间件 + DTO │ ├── authsdk/ # 认证中心 SDKSimApiAuthClient/Center/Iam + 网关中间件 + DTO
│ ├── communications/ # SimApiBaseResponse, PageResponse, SimApiLoginItem, 请求 DTO │ ├── communications/ # SimApiBaseResponse, PageResponse, SimApiLoginItem, 请求 DTO
│ ├── configurations/ # SimApiOptions + 各模块 Option(含 ConfigureSimApiXxx 回调) │ ├── configurations/ # SimApiOptions + 各模块 Option(含 ConfigureSimApiXxx 回调)
│ ├── controllers/ # SimApiBaseController, SimApiCommonController, SimApiAuthControllerMVC 写法) │ ├── controllers/ # SimApiBaseController, SimApiCommonController, SimApiAuthControllerMVC 写法)
│ ├── exceptions/ # SimApiException │ ├── exceptions/ # SimApiException
│ ├── extensions/ # SimApiExtensionsaddSimApi / useSimApi + 内置路由 + 响应封装)
│ ├── helpers/ # SimApiError, SimApiUtil, SimApiAuth, SimApiCache, SimApiHttpClient, │ ├── helpers/ # SimApiError, SimApiUtil, SimApiAuth, SimApiCache, SimApiHttpClient,
│ │ # SimApiAesUtil(AES-256), SimApiSignChecker(验签), SimApiAesBodyChecker(AES body), │ │ # SimApiAesUtil(AES-256), SimApiSignChecker(验签), SimApiAesBodyChecker(AES body),
│ │ # SimApiStorage(S3/MinIO, 自实现 SigV4) │ │ # SimApiStorage(S3/MinIO, 自实现 SigV4), SimApiRequestDelegateFactory, SimApiResultWriter
│ ├── interfaces/ # ISimApiAuthChecker │ ├── interfaces/ # ISimApiAuthChecker, IBindRequestContext
│ ├── logger/ # SimApiLogger, SimApiLoggerProvider(彩色日志) │ ├── logger/ # SimApiLogger, SimApiLoggerProvider(彩色日志)
│ ├── macros/ # ReadTomlVersion(编译期读版本号) │ ├── macros/ # ReadTomlVersion(编译期读版本号)
│ ├── middlewares/ # SimApiExceptionMiddleware, SimApiAuthMiddleware, SimApiRequestLogMiddleware │ ├── middlewares/ # SimApiExceptionMiddleware, SimApiAuthMiddleware, SimApiRequestLogMiddleware
@@ -283,7 +283,7 @@ let resp3 = client.aesSignQuery<SimApiLoginItem>("/api/data", body: "{\"a\":1}")
- 响应体因 soulsoft `HttpResponse.body` 只读不可替换,记录 `Content-Length` 作为替代(C# 用 MemoryStream 捕获) - 响应体因 soulsoft `HttpResponse.body` 只读不可替换,记录 `Content-Length` 作为替代(C# 用 MemoryStream 捕获)
```cangjie ```cangjie
builder.addSimApi { options => SimApiExtensions.addSimApi(builder) { options =>
options.enableRequestLog = true options.enableRequestLog = true
options.simApiRequestLogOptions.showFullHeader = true // 打印完整 Header(默认只打 Token/Query-Id options.simApiRequestLogOptions.showFullHeader = true // 打印完整 Header(默认只打 Token/Query-Id
options.simApiRequestLogOptions.requestStringLogLength = 200 // 请求体字段截断长度(0 不截断) options.simApiRequestLogOptions.requestStringLogLength = 200 // 请求体字段截断长度(0 不截断)
@@ -326,7 +326,7 @@ builder.addSimApi { options =>
`enableSimApiStorage = true` 时注册 `SimApiStorage`Scoped,内部自实现 AWS Signature V4,无需 Minio SDK): `enableSimApiStorage = true` 时注册 `SimApiStorage`Scoped,内部自实现 AWS Signature V4,无需 Minio SDK):
```cangjie ```cangjie
builder.addSimApi { options => SimApiExtensions.addSimApi(builder) { options =>
options.enableSimApiStorage = true options.enableSimApiStorage = true
options.configureSimApiStorage { storage => options.configureSimApiStorage { storage =>
storage.endpoint = "http://192.168.0.2:9000" // 必须 http:// 或 https:// 开头 storage.endpoint = "http://192.168.0.2:9000" // 必须 http:// 或 https:// 开头
@@ -388,7 +388,7 @@ class MyAuthChecker <: ISimApiAuthChecker {
`enableSimApiAuthGate = true` 时注册 `SimApiAuthClient` / `SimApiAuthCenter` / `SimApiAuthIam` 单例并挂载网关透传中间件: `enableSimApiAuthGate = true` 时注册 `SimApiAuthClient` / `SimApiAuthCenter` / `SimApiAuthIam` 单例并挂载网关透传中间件:
```cangjie ```cangjie
builder.addSimApi { options => SimApiExtensions.addSimApi(builder) { options =>
options.enableSimApiAuthGate = true options.enableSimApiAuthGate = true
options.configureSimApiAuthCenter { auth => options.configureSimApiAuthCenter { auth =>
auth.server = "https://auth.example.com" auth.server = "https://auth.example.com"
@@ -420,7 +420,7 @@ iam.checkPermission(profileId, "app:create") // 无权限抛 403
## SimApiOptions 完整配置 ## SimApiOptions 完整配置
```cangjie ```cangjie
builder.addSimApi { options => SimApiExtensions.addSimApi(builder) { options =>
options.redisConfiguration = "localhost:6379" // Redis(可选,支持 ,password=xxx,db=2 options.redisConfiguration = "localhost:6379" // Redis(可选,支持 ,password=xxx,db=2
// 功能开关 // 功能开关
+349 -4
View File
@@ -2,10 +2,355 @@
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。
* *
* 文件名对齐 .NET:根目录 SimApiExtensions.csnamespace SimApi)。 * 对齐 .NET:根命名空间 SimApi 下的静态类 SimApiExtensionsAddSimApi + UseSimApi)。
* 本文件是 simapi 根包的入口锚点:cjpm 要求 src 根目录至少有一个 .cj 文件, * 本文件同时是 simapi 根包的入口锚点:cjpm 要求 src 根目录至少有一个 .cj 文件,
* 否则不会扫描 src 子目录(helpers/extensions/...),整个包将编译为空。 * 否则不会扫描 src 子目录(helpers/controllers/...),整个包将编译为空。
* 实际扩展入口见 src/extensions/SimApiExtensions.cjaddSimApi / useSimApi)。 *
* 使用方式:
* ```
* 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 simapi package simapi
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# 根命名空间 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 注册 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<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("simapi.controllers", types.toArray())
mvc.addApplicationPart(part)
mvc
}
}
-392
View File
@@ -1,392 +0,0 @@
/*
* Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\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# 的 SimApiExtensionsAddSimApi + 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<IRequestDelegateFactory, SimApiRequestDelegateFactory>()
}
// 自动注册 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<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 注册 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<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
}
/**
* SimApi 主机扩展。
*/
public interface SimApiHostExtensions {
/**
* 应用 SimApi 中间件与内置路由到 WebHost。
*/
func useSimApi(): Unit
}
extend WebHost <: SimApiHostExtensions {
/**
* 应用 SimApi 中间件与内置路由(日志输出对齐 C# UseSimApi)。
*/
public func useSimApi(): Unit {
let options = this.services.getOrThrow<SimApiOptions>()
let loggerFactory = this.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全部允许...")
this.useCors()
}
// AuthGate(对齐 C# UseMiddleware<SimApiAuthCenterMiddleware>
if (options.enableSimApiAuthGate) {
logger.info("开始配置 SimApiAuthGate...")
this.use<SimApiAuthCenterMiddleware>()
}
// 认证中间件(对齐 C# builder.UseMiddleware<SimApiAuthMiddleware>()
if (options.enableSimApiAuth) {
logger.info("开始配置 SimApiAuth...")
this.use<SimApiAuthMiddleware>()
}
// 内置路由: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<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") {
this.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") {
this.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())
}
})
this.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...")
this.use<SimApiRequestLogMiddleware>()
}
// 异常中间件最后挂载(最内层,对齐 C# builder.UseMiddleware<SimApiExceptionMiddleware>()
if (options.enableSimApiException) {
logger.info("开始配置 SimApiException...")
this.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 = this.services.getOrThrow<IServiceProviderIsService>()
if (callSiteFactory.isService<ApplicationPartManager>()) {
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<TypeInfo>): MvcBuilder
}
extend ServiceCollection <: SimApiMvcBuilderExtensions {
/**
* 注册 MVC 服务,并注册 SimApi 内置控制器 + 用户控制器到 ApplicationPartManager。
* 对齐 .NET 的 AddControllers()(含控制器发现)。
* @param controllerTypes 用户控制器类型列表。
* @return MVC 构建器。
*/
public func addControllers(controllerTypes: Array<TypeInfo>): MvcBuilder {
// 调用 soulsoft_web_mvc 的无参 addControllers() 注册 MVC 核心服务
let mvc = this.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
}
}