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:
@@ -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 解析 keyProvider(AesBodyProviderBase 实现)
|
||||
let provider = resolveAesProvider(aes.keyProvider)
|
||||
// 2. 读取并解密 body(SimApiAesBodyChecker.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 解析 keyProvider(SimApiSignProviderBase 实现)
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user