feat: 新增 @SimApiSign 与 @AesBody 注解(对齐 C# SimApiSignAttribute / AesBodyAttribute)

- src/annotations/SimApiSign.cj:方法/类级声明式验签注解,keyProvider 用类型名(DI 解析)
- src/annotations/AesBody.cj:参数级声明式 AES body 解密注解
- SimApiRequestDelegateFactory 自动执行:
  - checkSimApiSign:解析 @SimApiSign → DI 取 SimApiSignProviderBase → SimApiSignChecker.verify
  - bindAesBody:@AesBody 参数 → DI 取 AesBodyProviderBase → decryptBody → 按类型反序列化
- 注解未指定类型名时用默认空实现(未配置即报错,对齐 C# 行为)
This commit is contained in:
2026-08-18 00:35:59 +08:00
parent d76b08a542
commit ef9765de5d
4 changed files with 158 additions and 10 deletions
+82 -1
View File
@@ -26,7 +26,7 @@ import soulsoft_web_mvc.abstractions.*
import soulsoft_extensions_options.*
import soulsoft_extensions_injection.*
import simapi_serialization.*
import simapi.annotations.{SimApiAuth as SimApiAuthAttribute, OriginResponse}
import simapi.annotations.{SimApiAuth as SimApiAuthAttribute, OriginResponse, SimApiSign, AesBody}
import simapi.communications.*
import simapi.configurations.*
import simapi.interfaces.*
@@ -63,6 +63,7 @@ struct SimApiActionInvoker {
public func apply(): Unit {
let controller = createControllerInstance()
checkSimApiAuth()
checkSimApiSign()
// 预读并缓存请求体(body 流不可重读;若请求日志中间件已读,直接用其缓存)
if (!context.items.contains(BODY_CACHE_KEY)) {
context.items[BODY_CACHE_KEY] = readBody()
@@ -102,6 +103,9 @@ struct SimApiActionInvoker {
if (isExplicitlyBound(parameter)) {
// Query/Form/Route/Header/Services → soulsoft
bound[index] = soulsoftBound[index]
} else if (let Some(aes) <- parameter.findAnnotation<AesBody>()) {
// @AesBody → 解密 body 后按参数类型反序列化(对齐 C# AesBodyModelBinder
bound[index] = bindAesBody(context, parameter, aes)
} else {
// FromBody → simapi_serialization 按运行时类型反序列化(免 @Serialization 宏)
bound[index] = bindFromBody(context, parameter)
@@ -136,6 +140,83 @@ struct SimApiActionInvoker {
()
}
/// @AesBody 参数绑定:解密 body 后按参数类型反序列化(对齐 C# AesBodyModelBinder
private func bindAesBody(context: ActionBindingContext, parameter: ParameterInfo, aes: AesBody): Any {
// 1. 从 DI 解析 keyProviderAesBodyProviderBase 实现)
let provider = resolveAesProvider(aes.keyProvider)
// 2. 读取并解密 bodySimApiAesBodyChecker.decryptBody 内部读取原始 body 流)
let plain = SimApiAesBodyChecker.decryptBody(context.httpContext, provider)
// 3. 按参数类型反序列化明文 JSON
try {
return JsonSerializer.Deserialize(parameter.typeInfo, plain)
} catch (ex: Exception) {
SimApiError.error(code: 400, message: "AES body 反序列化失败: ${ex.message}")
}
()
}
/// 从 DI 解析 AesBodyProviderBase 实现(注解未指定类型名时返回默认空实现)
private func resolveAesProvider(keyProvider: String): AesBodyProviderBase {
if (keyProvider.isEmpty()) {
return AesBodyProviderBase()
}
var typeInfo: ?TypeInfo = None
try {
typeInfo = Some(TypeInfo.get(keyProvider))
} catch (_: Exception) {
SimApiError.error(code: 400, message: "未找到 AES 密钥提供器 ${keyProvider}")
}
let instance = context.services.getOrThrow(typeInfo.getOrThrow())
if (let p: AesBodyProviderBase <- instance) {
return p
}
SimApiError.error(code: 400, message: "密钥提供器 ${keyProvider} 未实现 AesBodyProviderBase")
AesBodyProviderBase()
}
/// 检查 @SimApiSign 注解并执行验签(对齐 C# SimApiSignAttribute.OnActionExecuting
private func checkSimApiSign() {
var sign: ?SimApiSign = None
for (item in actionDescriptor.endpointMetadata) {
if (let s: SimApiSign <- item) {
sign = Some(s)
break
}
}
if (let Some(sign) <- sign) {
// 1. 从 DI 解析 keyProviderSimApiSignProviderBase 实现)
let provider = resolveSignProvider(sign.keyProvider)
// 2. 解析缓存(nonce 去重;DI 有 SimApiCache 则用)
var cache: ?SimApiCache = None
try {
cache = Some(context.services.getOrThrow<SimApiCache>())
} catch (_: Exception) {
// 未注册缓存 → 跳过 nonce 去重
}
// 3. 执行验签
SimApiSignChecker.verify(context, provider, cache)
}
}
/// 从 DI 解析 SimApiSignProviderBase 实现(注解未指定类型名时返回默认空实现)
private func resolveSignProvider(keyProvider: String): SimApiSignProviderBase {
if (keyProvider.isEmpty()) {
return SimApiSignProviderBase()
}
var typeInfo: ?TypeInfo = None
try {
typeInfo = Some(TypeInfo.get(keyProvider))
} catch (_: Exception) {
SimApiError.error(code: 400, message: "未找到签名提供器 ${keyProvider}")
}
let instance = context.services.getOrThrow(typeInfo.getOrThrow())
if (let p: SimApiSignProviderBase <- instance) {
return p
}
SimApiError.error(code: 400, message: "签名提供器 ${keyProvider} 未实现 SimApiSignProviderBase")
SimApiSignProviderBase()
}
/// 读取并重置请求体流(供后续业务读取)
private func readBody(): String {
try {