2026-08-16 22:46:40 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
|
|
|
|
|
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
package simapi.helpers
|
|
|
|
|
|
|
|
|
|
|
|
import std.collection.*
|
|
|
|
|
|
import std.io.*
|
2026-08-18 00:07:00 +08:00
|
|
|
|
import simapi_serialization.*
|
2026-08-16 22:46:40 +08:00
|
|
|
|
import soulsoft_web_http.*
|
|
|
|
|
|
import simapi.communications.*
|
|
|
|
|
|
import simapi.exceptions.*
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* AES body 请求({"data": "密文"},对齐 C# SimApiOneFieldRequest<string>)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class AesBodyRequest {
|
|
|
|
|
|
public var _data: String = ""
|
|
|
|
|
|
|
|
|
|
|
|
public init() {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* AES body 密钥提供器(对齐 C# ModelBinders/AesBodyProviderBase):
|
|
|
|
|
|
* 应用继承本类并实现 getKey(appId),返回 appId 对应的 AES 密钥。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public open class AesBodyProviderBase {
|
|
|
|
|
|
/// appId 字段名(None 表示不带 appId)
|
|
|
|
|
|
public var appIdName: ?String = Some("appId")
|
|
|
|
|
|
|
|
|
|
|
|
public init() {}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据 appId 获取密钥。
|
|
|
|
|
|
* @param appId 应用 ID(未配置 appIdName 时为 None)。
|
|
|
|
|
|
* @return 密钥;返回 None 表示获取失败。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public open func getKey(appId: ?String): ?String {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 服务端 AES body 解密校验器(对齐 C# ModelBinders/AesBodyModelBinder)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 仓颉无 ModelBinder 机制,按项目惯例由控制器在方法开头调用:
|
|
|
|
|
|
* let jsonStr = SimApiAesBodyChecker.decryptBody(context, provider)
|
2026-08-18 00:07:00 +08:00
|
|
|
|
* let request = JsonSerializer.Deserialize<XxxRequest>(jsonStr)
|
2026-08-16 22:46:40 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 流程(与 C# 一致):
|
|
|
|
|
|
* 1. 读取 body 并反序列化为 {"data": "密文"}
|
|
|
|
|
|
* 2. 校验 Data 非空
|
|
|
|
|
|
* 3. 提取 appId(Query/Header)
|
|
|
|
|
|
* 4. provider.getKey(appId) 获取密钥
|
|
|
|
|
|
* 5. SimApiAesUtil.decrypt 解密得到明文 JSON 字符串
|
|
|
|
|
|
* 返回解密后的 JSON 字符串,由控制器按目标类型反序列化。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public class SimApiAesBodyChecker {
|
|
|
|
|
|
private init() {}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解密请求体,返回明文 JSON 字符串。
|
|
|
|
|
|
* @param context 当前请求上下文。
|
|
|
|
|
|
* @param provider AES 密钥提供器。
|
|
|
|
|
|
* @return 解密后的 JSON 字符串。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static func decryptBody(context: HttpContext, provider: AesBodyProviderBase): String {
|
|
|
|
|
|
// 1. 读取 body
|
|
|
|
|
|
let body = readBody(context)
|
|
|
|
|
|
if (body.isEmpty()) {
|
|
|
|
|
|
SimApiError.error(code: 400, message: "请求体不能为空")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 反序列化 {"data": "密文"}
|
2026-08-18 00:07:00 +08:00
|
|
|
|
let req = JsonSerializer.Deserialize<AesBodyRequest>(body)
|
2026-08-16 22:46:40 +08:00
|
|
|
|
if (req._data.isEmpty()) {
|
|
|
|
|
|
SimApiError.error(code: 400, message: "请求体缺少密文Data字段")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 提取 appId
|
|
|
|
|
|
var appId: ?String = None
|
|
|
|
|
|
if (let Some(name) <- provider.appIdName) {
|
|
|
|
|
|
if (!name.isEmpty()) {
|
|
|
|
|
|
appId = getParam(context, name)
|
|
|
|
|
|
if (appId == None || appId == Some("")) {
|
|
|
|
|
|
SimApiError.error(code: 400, message: "未找到${name}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 获取密钥
|
|
|
|
|
|
let key = provider.getKey(appId)
|
|
|
|
|
|
if (key == None || key == Some("")) {
|
|
|
|
|
|
SimApiError.error(code: 400, message: "获取密钥失败(应用不存在或密钥未配置)")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 解密
|
|
|
|
|
|
let jsonStr = SimApiAesUtil.decrypt(req._data, key.getOrThrow())
|
|
|
|
|
|
if (jsonStr.isEmpty()) {
|
|
|
|
|
|
SimApiError.error(code: 400, message: "解密失败")
|
|
|
|
|
|
}
|
|
|
|
|
|
jsonStr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static func readBody(context: HttpContext): String {
|
|
|
|
|
|
try {
|
|
|
|
|
|
context.request.enableBuffering()
|
|
|
|
|
|
var buffer = Array<Byte>(4096, repeat: 0)
|
|
|
|
|
|
var sb = StringBuilder()
|
|
|
|
|
|
var read = context.request.body.read(buffer)
|
|
|
|
|
|
while (read > 0) {
|
|
|
|
|
|
sb.appendFromUtf8(buffer.slice(0, read))
|
|
|
|
|
|
read = context.request.body.read(buffer)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 重置流位置,供后续业务读取
|
|
|
|
|
|
if (let seekable: Seekable <- context.request.body) {
|
|
|
|
|
|
seekable.seek(SeekPosition.Begin(0))
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.toString()
|
|
|
|
|
|
} catch (ex: Exception) {
|
|
|
|
|
|
SimApiError.error(code: 400, message: "读取请求体失败: ${ex.message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static func getParam(context: HttpContext, name: String): ?String {
|
|
|
|
|
|
let q = context.request.query.get(name)
|
|
|
|
|
|
if (q != None && q != Some("")) {
|
|
|
|
|
|
return q
|
|
|
|
|
|
}
|
|
|
|
|
|
context.request.headers.get(name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|