Files
simapi-cj/src/extensions/SimApiExtensions.cj
T
xrain eb488bcd5a feat: 实现 SimApiStorage(S3/MinIO 存储)
- 自实现 AWS Signature V4(HMAC-SHA256 基于 stdx SHA256),无需 Minio SDK
- getUploadUrl / getDownloadUrl(预签名 PUT/GET)
- uploadFile(直接 PUT 上传)、fullUrl / getUrl / getPath
- 构造时检测并自动创建桶(静态守卫只执行一次)
- 注册为 Scoped 以注入 IHttpContextAccessor(soulsoft DI 禁止 singleton 消费 scoped)
- 签名核心已用 AWS 官方测试向量验证(最终签名 5d672d79... 一致)
- README 同步(未实现表移除、新增 5.3 存储章节)
2026-08-17 00:00:15 +08:00

393 lines
16 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 许可证。
*/
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
}
}