refactor: 全框架 JSON 操作迁移到 simapi_serialization

- 依赖:新增 simapi_serialization path 依赖(保留 soulsoft_serialization 供 soulsoft web 栈传递使用)
- SimApiJson.json / SimApiUtil.json → JsonSerializer.Serialize
- SimApiUtil.fromJson / base64DecodeTo → JsonSerializer.Deserialize(去 ISerialization 约束)
- SimApiHttpClient signQuery/aesQuery/aesSignQuery/query → 去约束,反序列化换新库
- SimApiBaseResponse/SimApiDataResponse/SimApiLoginItem/AesBodyRequest/AuthSDK DTO → 去 @Serialization 宏/去 soulsoft 接口,改纯 POCO(simapi_serialization 反射)
- SimApiBaseResponse 标 @SerializerParent:子类响应序列化包含 _code/_message
- SimApiResultWriter / SimApiExceptionMiddleware → 响应输出走 simapi_serialization(统一 _code/_message 格式)
- SimApiRequestLogMiddleware 截断 → Deserialize<HashMap<String,Any>> + 重序列化
- SimApiAuthDto JsonValue 字段 → HashMap<String,Any>;SimApiCache.get<T> 去约束
- webdemo:AuthController readFromJson 换 Deserialize;cjpm.toml 补依赖
- 验证:webdemo 启动正常,GET/POST 接口响应格式统一,S3 存储全链路 OK
This commit is contained in:
2026-08-18 00:07:00 +08:00
parent 8b0a8f783c
commit 73f60f6f15
17 changed files with 108 additions and 380 deletions
+12 -12
View File
@@ -2,19 +2,19 @@ version = 0
[requires]
soulsoft_extensions_hosting = {version = "1.0.20260528"}
soulsoft_extensions_options_configuration = {version = "1.0.20260528"}
soulsoft_net_http = {version = "1.0.20260528"}
redis = {version = "1.0.20260627"}
soulsoft_web_http = {version = "1.0.20260528"}
soulsoft_identity_claims = {version = "1.0.20260528"}
soulsoft_web_routing = {version = "1.0.20260528"}
soulsoft_web_hosting = {version = "1.0.20260528"}
soulsoft_extensions_logging = {version = "1.0.20260528"}
soulsoft_extensions_configuration = {version = "1.0.20260528"}
soulsoft_web_routing = {version = "1.0.20260528"}
soulsoft_extensions_options_configuration = {version = "1.0.20260528"}
soulsoft_web_mvc = {version = "1.0.20260528"}
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
soulsoft_web_cors = {version = "1.0.20260528"}
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
soulsoft_extensions_injection = {version = "1.0.20260528"}
soulsoft_serialization = {version = "1.0.20260528"}
soulsoft_extensions_options = {version = "1.0.20260528"}
redis = {version = "1.0.20260627"}
soulsoft_web_cors = {version = "1.0.20260528"}
soulsoft_identity_claims = {version = "1.0.20260528"}
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
soulsoft_extensions_logging = {version = "1.0.20260528"}
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
soulsoft_serialization = {version = "1.0.20260528"}
soulsoft_extensions_configuration = {version = "1.0.20260528"}
soulsoft_net_http = {version = "1.0.20260528"}
soulsoft_extensions_injection = {version = "1.0.20260528"}
+1
View File
@@ -24,6 +24,7 @@
soulsoft_serialization = "1.0.20260528"
soulsoft_net_http = "1.0.20260528"
redis = "1.0.20260627"
simapi_serialization = { path = "../simapi-serialization" }
[target]
[target.x86_64-w64-mingw32]
-2
View File
@@ -25,8 +25,6 @@ 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.*
+3 -1
View File
@@ -11,6 +11,7 @@ import stdx.net.tls.*
import stdx.net.tls.common.*
import soulsoft_net_http.{HttpClient, HttpRequestMessage, JsonContent}
import soulsoft_net_http.{HttpMethod as NetHttpMethod}
import simapi_serialization.*
import simapi.communications.*
import simapi.helpers.*
@@ -62,7 +63,8 @@ public class SimApiAuthCenter {
let response = http.send(request)
try {
response.ensureSuccessStatusCode()
let resp = response.content.readFromJson<SimApiBaseResponse>()
let json = response.content.readAsString()
let resp = JsonSerializer.Deserialize<SimApiBaseResponse>(json)
SimApiError.errorWhen(resp._code != 200, code: 400, message: "签名验证失败")
} finally {
response.close()
+5 -13
View File
@@ -6,19 +6,17 @@
* 说明:C# 中这些 DTO 是 SimApiAuthCenterDto / SimApiAuthIamDto 的嵌套类;
* 仓颉不支持在类体内声明嵌套类(unexpected class declaration in class body),
* 故拍平为顶层类,语义与字段保持一致。
*
* 序列化/反序列化由 simapi_serialization 反射处理(免标注、免约束)。
*/
package simapi.authsdk
import std.collection.*
import stdx.encoding.json.*
import soulsoft_serialization.*
import soulsoft_serialization.macros.*
/**
* 应用/Profile 通用项(对齐 C# AppAndProfileItem)。
*/
@Serialization
public class AppAndProfileItem {
public var _id: String = ""
public var _name: String = ""
@@ -30,14 +28,13 @@ public class AppAndProfileItem {
/**
* 安全确认响应(对齐 C# ConfirmResponse)。
* _data 用 ?JsonValue 对齐 C# Dictionary<string,object>?(任意 JSON 对象)。
* _data 用 ?HashMap<String, Any> 对齐 C# Dictionary<string,object>?(任意 JSON 对象)。
*/
@Serialization
public class ConfirmResponse {
public var _applicationId: String = ""
public var _profileId: String = ""
public var _scene: ?String = None
public var _data: ?JsonValue = None
public var _data: ?HashMap<String, Any> = None
public init() {}
}
@@ -45,10 +42,9 @@ public class ConfirmResponse {
/**
* 登录信息响应(对齐 C# LoginInfoResponse)。
*/
@Serialization
public class LoginInfoResponse {
public var _scene: ?String = None
public var _data: ?JsonValue = None
public var _data: ?HashMap<String, Any> = None
public var _profileId: String = ""
public var _name: String = ""
public var _image: ?String = None
@@ -60,7 +56,6 @@ public class LoginInfoResponse {
/**
* 获取授权码响应(对齐 C# GetCodeResponse)。
*/
@Serialization
public class GetCodeResponse {
public var _code: String = ""
public var _server: String = ""
@@ -78,7 +73,6 @@ public class GetCodeResponse {
/**
* 群组关联项(对齐 C# GroupRelatedItem)。
*/
@Serialization
public class GroupRelatedItem {
public var _id: String = ""
public var _name: String = ""
@@ -94,7 +88,6 @@ public class GroupRelatedItem {
/**
* 群组详情树节点(对齐 C# GroupDetailTreeNodechildren 递归)。
*/
@Serialization
public class GroupDetailTreeNode {
public var _id: String = ""
public var _name: String = ""
@@ -109,7 +102,6 @@ public class GroupDetailTreeNode {
/**
* 权限项(对齐 C# PermissionItem)。
*/
@Serialization
public class PermissionItem {
public var _identifier: String = ""
public var _name: String = ""
+11 -53
View File
@@ -6,15 +6,16 @@
package simapi.communications
import std.collection.*
import stdx.encoding.json.*
import soulsoft_serialization.*
import soulsoft_serialization.macros.*
import simapi_serialization.*
import simapi_serialization.macros.*
/**
* 基础响应体:所有接口统一返回该结构。
* HTTP 状态码始终 200,业务错误通过 code 字段表达。
* 序列化/反序列化由 simapi_serialization 反射处理(无需接口/宏);
* @SerializerParent 使子类(SimApiResponse<T>/SimApiDataResponse)序列化时包含 _code/_message。
*/
@Serialization
@SerializerParent
public open class SimApiBaseResponse {
public var _code: Int64 = 200
public var _message: String = "成功"
@@ -68,7 +69,7 @@ public open class SimApiBaseResponse {
/**
* 分页内容返回。
* @param T 列表元素类型。
* 说明:泛型类暂不通过 @Serialization 宏序列化,可手动转换为 SimApiResponse。
* 说明:泛型类暂不通过反射序列化,可手动转换为 SimApiResponse。
*/
public class PageResponse<T> {
public var _list: Array<T> = Array<T>()
@@ -89,11 +90,12 @@ public class PageResponse<T> {
/**
* 带数据的动态响应。
* @param T 数据类型。
* 说明:泛型类手写实现 ISerialization<SimApiResponse<T>>serialize + deserialize),
* data 内嵌为对象(对齐 C# SimApiBaseResponse<T>.Data 是 T? 而非字符串)。
* 序列化支持动态结构(HashMap<String,Any> 等经 SimApiJson 内嵌);反序列化要求 T <: ISerialization<T>。
* 序列化/反序列化由 simapi_serialization 反射处理:
* - data 为对象/数组/标量,反射递归
* - data 支持任意 T(免约束)
* 注:泛型嵌套(SimApiResponse<T>)的反射反序列化已验证可用。
*/
public class SimApiResponse<T> <: SimApiBaseResponse & ISerialization<SimApiResponse<T>> where T <: ISerialization<T> {
public class SimApiResponse<T> <: SimApiBaseResponse {
public var _data: ?T = None
public init() {
@@ -113,48 +115,4 @@ public class SimApiResponse<T> <: SimApiBaseResponse & ISerialization<SimApiResp
super(code, message)
this._data = Some(data)
}
/**
* 序列化为 DataModelcode + message + datadata 为对象)。
*/
public override func serializeObject(options: JsonSerializerOptions): DataModel {
let dms = DataModelStruct()
dms.add(Field("code", DataModelInt(_code)))
dms.add(Field("message", DataModelString(_message)))
if (let Some(data) <- _data) {
if (let ser: ISerializable <- data) {
dms.add(Field("data", ser.serializeObject(options)))
} else {
// 动态结构(HashMap<String, Any> 等):经 SimApiJson 序列化后解析内嵌为对象
let json = SimApiJson.json(Some(data))
dms.add(Field("data", DataModel.fromJson(JsonValue.fromStr(json))))
}
}
dms
}
/**
* 从 DataModel 反序列化:code + message + data(对齐 .NET JsonSerializer.Deserialize<T>)。
*/
public static func deserializeObject(dm: DataModel, options: JsonSerializerOptions): SimApiResponse<T> {
let resp = SimApiResponse<T>()
if (let dms: DataModelStruct <- dm) {
for (field in dms.getFields()) {
match (field.getName()) {
case "code" =>
if (let v: DataModelInt <- field.getData()) {
resp._code = v.getValue()
}
case "message" =>
if (let v: DataModelString <- field.getData()) {
resp._message = v.getValue()
}
case "data" =>
resp._data = Some(T.deserializeObject(field.getData(), options))
case _ => ()
}
}
}
resp
}
}
+4 -54
View File
@@ -5,23 +5,17 @@
package simapi.communications
import std.collection.*
import stdx.encoding.json.*
import soulsoft_serialization.*
/**
* 带任意对象数据的响应(非泛型版,供响应自动封装使用)。
*
* 与 SimApiResponse<T> 的区别:data 为 Any(运行时类型不定),用于
* SimApiRequestDelegateFactory 派发结果时统一包装 DTO/数组/动态结构。
*
* 序列化规则(对齐 SimApiResponse<T>
* - data 实现了 ISerializable@Serialization DTO、Array<T> 等)→ data 内嵌为对象
* - data 为动态结构(HashMap<String, Any>,不满足泛型 ISerialization 约束)
* → 经 SimApiJson 序列化后解析内嵌为对象(而非字符串)
* 因此 data 在 JSON 中始终是对象/数组/标量,不会是"JSON 字符串"。
* 序列化/反序列化由 simapi_serialization 反射处理
* - data 为对象/数组/标量,反射递归
* - data 为动态结构(HashMap<String, Any>)同样反射支持
*/
public class SimApiDataResponse <: SimApiBaseResponse & ISerialization<SimApiDataResponse> {
public class SimApiDataResponse <: SimApiBaseResponse {
public var _data: ?Any = None
public init() {
@@ -41,48 +35,4 @@ public class SimApiDataResponse <: SimApiBaseResponse & ISerialization<SimApiDat
super(code, message)
this._data = Some(data)
}
/**
* 序列化为 DataModelcode + message + datadata 为对象)。
*/
public override func serializeObject(options: JsonSerializerOptions): DataModel {
let dms = DataModelStruct()
dms.add(Field("code", DataModelInt(_code)))
dms.add(Field("message", DataModelString(_message)))
if (let Some(data) <- _data) {
if (let ser: ISerializable <- data) {
dms.add(Field("data", ser.serializeObject(options)))
} else {
// 动态结构(HashMap<String, Any> 等):经 SimApiJson 序列化后解析内嵌为对象
let json = SimApiJson.json(Some(data))
dms.add(Field("data", DataModel.fromJson(JsonValue.fromStr(json))))
}
}
dms
}
/**
* 从 DataModel 反序列化:code + message + datadata 保留原始 DataModel)。
*/
public static func deserializeObject(dm: DataModel, options: JsonSerializerOptions): SimApiDataResponse {
let resp = SimApiDataResponse()
if (let dms: DataModelStruct <- dm) {
for (field in dms.getFields()) {
match (field.getName()) {
case "code" =>
if (let v: DataModelInt <- field.getData()) {
resp._code = v.getValue()
}
case "message" =>
if (let v: DataModelString <- field.getData()) {
resp._message = v.getValue()
}
case "data" =>
resp._data = Some(field.getData())
case _ => ()
}
}
}
resp
}
}
+4 -53
View File
@@ -7,25 +7,24 @@
package simapi.communications
import std.collection.*
import simapi_serialization.*
/**
* JSON 序列化静态工具类。
*
* 说明:序列化核心放在依赖图最底层的 simapi.communications 包
* SimApiUtil.jsonsimapi.helpers)委托本类实现,避免循环依赖;
* 全框架 JSON 输出统一走此处,保证转义与格式一致。
* 说明:序列化核心委托给 simapi_serializationJsonSerializer.Serialize
* 全框架 JSON 输出统一走此处,保证与序列化库行为一致。
*/
public class SimApiJson {
private init() {}
/**
* 对象序列化为 JSON 字符串(统一入口)。
* 支持 String/Int64/Bool/Float64/Array/HashMap,其他类型退化为字符串。
* @param obj 任意对象(None 输出 null)。
*/
public static func json(obj: ?Any): String {
if (let Some(obj) <- obj) {
return jsonValue(obj)
return JsonSerializer.Serialize(obj)
}
"null"
}
@@ -49,52 +48,4 @@ public class SimApiJson {
}
sb.toString()
}
private static func jsonValue(obj: Any): String {
if (let s: String <- obj) {
return "\"${escapeJson(s)}\""
}
if (let i: Int64 <- obj) {
return "${i}"
}
if (let b: Bool <- obj) {
return "${b}"
}
if (let f: Float64 <- obj) {
return "${f}"
}
if (let arr: Array<Any> <- obj) {
var sb = StringBuilder()
sb.append("[")
var first = true
for (item in arr) {
if (!first) { sb.append(",") }
sb.append(jsonValue(item))
first = false
}
sb.append("]")
return sb.toString()
}
if (let map: HashMap<String, Any> <- obj) {
var sb = StringBuilder()
sb.append("{")
var first = true
for ((key, value) in map) {
if (!first) { sb.append(",") }
sb.append("\"${escapeJson(key)}\":${jsonValue(value)}")
first = false
}
sb.append("}")
return sb.toString()
}
// 其他类型退化为字符串
return "\"${escapeJson(describe(obj))}\""
}
private static func describe(obj: Any): String {
if (let s: ToString <- obj) {
return s.toString()
}
"null"
}
}
+4 -132
View File
@@ -7,18 +7,15 @@
package simapi.communications
import std.collection.*
import soulsoft_serialization.*
/**
* 登录信息项:Token 认证通过后注入请求上下文。
* 对齐 C# SimApiLoginItemId / Type / Meta / ExtracamelCase 输出 id/type/meta/extra)。
* 对齐 C# SimApiLoginItemId / Type / Meta / Extra)。
*
* 说明:因 _extra 为 HashMap<String, Any>Any 不满足 soulsoft 的 ISerialization<V> 约束,
* 无法用 @Serialization 宏),故手动实现 ISerialization<SimApiLoginItem>serialize + deserialize);
* 调用方统一通过 JsonSerializer.serializeObject<T>() / deserializeObject<T>() 使用
* (对齐 .NET JsonSerializer.Serialize / Deserialize)。
* 序列化/反序列化由 simapi_serialization 反射处理(免标注、免约束
* 字段 _id/_types/_meta/_extra 全部为受支持类型(String/Array/HashMap)。
*/
public class SimApiLoginItem <: ISerialization<SimApiLoginItem> {
public class SimApiLoginItem {
public var _id: String = ""
public var _types: Array<String> = ["user"]
public var _meta: HashMap<String, String> = HashMap<String, String>()
@@ -39,129 +36,4 @@ public class SimApiLoginItem <: ISerialization<SimApiLoginItem> {
this._meta = HashMap<String, String>()
this._extra = HashMap<String, Any>()
}
/**
* 序列化为 DataModel{"id","type","meta","extra"}。
*/
public func serializeObject(options: JsonSerializerOptions): DataModel {
let dms = DataModelStruct()
dms.add(Field("id", DataModelString(_id)))
// type: string[]
let typesSeq = DataModelSeq()
for (t in _types) {
typesSeq.add(DataModelString(t))
}
dms.add(Field("type", typesSeq))
// meta: object<string,string>
let metaStruct = DataModelStruct()
for ((k, v) in _meta) {
metaStruct.add(Field(k, DataModelString(v)))
}
dms.add(Field("meta", metaStruct))
// extra: object<string,any>
let extraStruct = DataModelStruct()
for ((k, v) in _extra) {
extraStruct.add(Field(k, anyToDataModel(v)))
}
dms.add(Field("extra", extraStruct))
dms
}
/**
* 从 DataModel 反序列化:{"id","type","meta","extra"}。
*/
public static func deserializeObject(dm: DataModel, options: JsonSerializerOptions): SimApiLoginItem {
let item = SimApiLoginItem()
if (let dms: DataModelStruct <- dm) {
for (field in dms.getFields()) {
match (field.getName()) {
case "id" => item._id = field.getData() |> dataModelToString
case "type" =>
let list = ArrayList<String>()
if (let seq: DataModelSeq <- field.getData()) {
for (sub in seq.getItems()) {
list.add(dataModelToString(sub))
}
}
item._types = list.toArray()
case "meta" =>
if (let metaStruct: DataModelStruct <- field.getData()) {
for (sub in metaStruct.getFields()) {
item._meta[sub.getName()] = dataModelToString(sub.getData())
}
}
case "extra" =>
if (let extraStruct: DataModelStruct <- field.getData()) {
for (sub in extraStruct.getFields()) {
item._extra[sub.getName()] = dataModelToAny(sub.getData())
}
}
case _ => ()
}
}
}
item
}
private static func dataModelToString(dm: DataModel): String {
if (let s: DataModelString <- dm) {
return s.getValue()
}
if (let i: DataModelInt <- dm) {
return "${i.getValue()}"
}
if (let b: DataModelBool <- dm) {
return "${b.getValue()}"
}
""
}
private static func dataModelToAny(dm: DataModel): Any {
if (let s: DataModelString <- dm) {
return s.getValue()
}
if (let i: DataModelInt <- dm) {
return i.getValue()
}
if (let b: DataModelBool <- dm) {
return b.getValue()
}
if (let f: DataModelFloat <- dm) {
return f.getValue()
}
if (let dms: DataModelStruct <- dm) {
var map = HashMap<String, Any>()
for (field in dms.getFields()) {
map[field.getName()] = dataModelToAny(field.getData())
}
return map
}
if (let seq: DataModelSeq <- dm) {
let list = ArrayList<Any>()
for (item in seq.getItems()) {
list.add(dataModelToAny(item))
}
return list.toArray()
}
""
}
private static func anyToDataModel(v: Any): DataModel {
if (let s: String <- v) {
return DataModelString(s)
}
if (let i: Int64 <- v) {
return DataModelInt(i)
}
if (let b: Bool <- v) {
return DataModelBool(b)
}
if (let f: Float64 <- v) {
return DataModelFloat(f)
}
if (let s: ToString <- v) {
return DataModelString(s.toString())
}
DataModelNull()
}
}
+3 -5
View File
@@ -7,8 +7,7 @@ package simapi.helpers
import std.collection.*
import std.io.*
import soulsoft_serialization.*
import soulsoft_serialization.macros.*
import simapi_serialization.*
import soulsoft_web_http.*
import simapi.communications.*
import simapi.exceptions.*
@@ -16,7 +15,6 @@ import simapi.exceptions.*
/**
* AES body 请求({"data": "密文"},对齐 C# SimApiOneFieldRequest<string>)。
*/
@Serialization
public class AesBodyRequest {
public var _data: String = ""
@@ -48,7 +46,7 @@ public open class AesBodyProviderBase {
*
* 仓颉无 ModelBinder 机制,按项目惯例由控制器在方法开头调用:
* let jsonStr = SimApiAesBodyChecker.decryptBody(context, provider)
* let request = JsonSerializer.deserializeObject<XxxRequest>(jsonStr)
* let request = JsonSerializer.Deserialize<XxxRequest>(jsonStr)
*
* 流程(与 C# 一致):
* 1. 读取 body 并反序列化为 {"data": "密文"}
@@ -75,7 +73,7 @@ public class SimApiAesBodyChecker {
}
// 2. 反序列化 {"data": "密文"}
let req = JsonSerializer.deserializeObject<AesBodyRequest>(body)
let req = JsonSerializer.Deserialize<AesBodyRequest>(body)
if (req._data.isEmpty()) {
SimApiError.error(code: 400, message: "请求体缺少密文Data字段")
}
+3 -4
View File
@@ -9,8 +9,7 @@ import std.collection.*
import std.collection.concurrent.*
import std.convert.*
import std.time.*
import stdx.encoding.json.*
import soulsoft_serialization.*
import simapi_serialization.*
import redis.client.*
import simapi.communications.*
import simapi.configurations.*
@@ -287,11 +286,11 @@ public class SimApiAuth {
private static func loginItemJson(item: SimApiLoginItem): String {
// 统一 JSON 序列化:对齐 .NET JsonSerializer.Serialize(item)
JsonSerializer.serializeObject<SimApiLoginItem>(item)
JsonSerializer.Serialize(item)
}
private static func parseLoginItem(json: String): SimApiLoginItem {
// 统一 JSON 反序列化:对齐 .NET JsonSerializer.Deserialize<SimApiLoginItem>(json)
JsonSerializer.deserializeObject<SimApiLoginItem>(json)
JsonSerializer.Deserialize<SimApiLoginItem>(json)
}
}
+4 -4
View File
@@ -10,7 +10,7 @@ import std.collection.concurrent.*
import std.convert.*
import std.time.*
import redis.client.*
import soulsoft_serialization.*
import simapi_serialization.*
import simapi.configurations.*
import simapi.exceptions.*
@@ -100,11 +100,11 @@ public class SimApiCache {
}
/**
* 获取特定类型缓存(对齐 C# Get<T>:从 JSON 反序列化)。
* 获取特定类型缓存(对齐 C# Get<T>:从 JSON 反序列化,使用 simapi_serialization)。
*/
public func get<T>(key: String): ?T where T <: ISerialization<T> {
public func get<T>(key: String): ?T {
match (getString(key)) {
case Some(json) => Some(JsonSerializer.deserializeObject<T>(json))
case Some(json) => Some(JsonSerializer.Deserialize<T>(json))
case None => None
}
}
+11 -9
View File
@@ -11,7 +11,7 @@ import stdx.net.tls.*
import stdx.net.tls.common.*
import soulsoft_net_http.{HttpClient, HttpRequestMessage, JsonContent}
import soulsoft_net_http.{HttpMethod as NetHttpMethod}
import soulsoft_serialization.*
import simapi_serialization.*
import simapi.communications.*
import simapi.configurations.*
import simapi.exceptions.*
@@ -21,7 +21,7 @@ import simapi.exceptions.*
* 对齐 C# 的 SimApi.Helpers.SimApiHttpClient
* - 内部使用 soulsoft_net_http 的 HttpClient(等价 .NET 的 System.Net.Http.HttpClient
* - 返回泛型 T(反序列化响应 body 的 data 字段),不再返回 String
* @param T 响应 data 的数据类型(需实现 ISerialization<T>,如 SimApiLoginItem、String、Int64 等)。
* @param T 响应 data 的数据类型(任意类,simapi_serialization 反射反序列化)。
*/
public open class SimApiHttpClient {
public var server: String
@@ -48,7 +48,7 @@ public open class SimApiHttpClient {
* @param queries 额外查询参数(可选)。
* @return 响应 data 字段反序列化后的 T。
*/
public func signQuery<T>(url: String, body!: String = "", queries!: HashMap<String, String> = HashMap<String, String>()): T where T <: ISerialization<T> {
public func signQuery<T>(url: String, body!: String = "", queries!: HashMap<String, String> = HashMap<String, String>()): T {
var queryUrl = StringBuilder()
for (field in signFields) {
queryUrl.append("${field}=")
@@ -80,7 +80,7 @@ public open class SimApiHttpClient {
* @param body 请求体 JSON 字符串。
* @return 响应 data 字段反序列化后的 T。
*/
public func aesQuery<T>(url: String, body: String): T where T <: ISerialization<T> {
public func aesQuery<T>(url: String, body: String): T {
var target = "${server}${url}"
if (let Some(name) <- appIdName) {
target = "${target}?${name}=${appId}"
@@ -98,19 +98,20 @@ public open class SimApiHttpClient {
* @param queries 额外查询参数(可选)。
* @return 响应 data 字段反序列化后的 T。
*/
public func aesSignQuery<T>(url: String, body: String, queries!: HashMap<String, String> = HashMap<String, String>()): T where T <: ISerialization<T> {
public func aesSignQuery<T>(url: String, body: String, queries!: HashMap<String, String> = HashMap<String, String>()): T {
let encrypted = aesEncrypt(body)
let req = "{\"data\":\"${encrypted}\"}"
return signQuery<T>(url, body: req, queries: queries)
}
/**
* 发起 POST 请求并反序列化 SimApiBaseResponse<T>,返回 data 字段。
* 发起 POST 请求并反序列化 SimApiResponse<T>,返回 data 字段。
* 对齐 C# Query<T>
* ErrorWhenFalse(IsSuccessStatusCode) → ReadFromJsonAsync<SimApiBaseResponse<T>> → ErrorWhen(Code != 200) → return Data。
* ErrorWhenFalse(IsSuccessStatusCode) → ReadFromJsonAsync<SimApiResponse<T>> → ErrorWhen(Code != 200) → return Data。
* 注意:必须 noProxy(),否则会走系统代理(192.168.0.250:8118)导致连接被拒。
* 反序列化使用 simapi_serializationDeserialize<T> 免约束)。
*/
private func query<T>(url: String, body: String): T where T <: ISerialization<T> {
private func query<T>(url: String, body: String): T {
let client = HttpClient.create { builder =>
builder.noProxy()
// 支持 https:配置 TLS(信任所有证书 + SNI 域名)
@@ -128,7 +129,8 @@ public open class SimApiHttpClient {
let response = client.send(request)
try {
SimApiError.errorWhenFalse(response.isSuccessStatusCode, code: response.statusCode, message: "HTTP ERROR: ${response.statusCode}")
let result = response.content.readFromJson<SimApiResponse<T>>()
let json = response.content.readAsString()
let result = JsonSerializer.Deserialize<SimApiResponse<T>>(json)
SimApiError.errorWhen(result._code != 200, code: result._code, message: result._message)
return result._data.getOrThrow()
} finally {
+13 -5
View File
@@ -7,6 +7,7 @@ package simapi.helpers
import soulsoft_web_http.*
import soulsoft_web_mvc.core.*
import simapi_serialization.*
import simapi.communications.*
/**
@@ -16,6 +17,8 @@ import simapi.communications.*
* - String → SimApiResponse<String>data 为字符串)
* - Unitvoid)→ SimApiBaseResponse(){code:200, message:成功}
* - 其他对象(DTO/数组/动态结构)→ SimApiDataResponsedata 内嵌为对象)
*
* 输出使用 simapi_serialization 序列化后直接写响应体(不走 soulsoft formatter)。
*/
public class SimApiResultWriter {
private init() {}
@@ -26,17 +29,22 @@ public class SimApiResultWriter {
result.invoke(context)
} else if (let result: SimApiBaseResponse <- actionResult) {
// 已是 SimApiBaseResponse(含子类)→ 原样输出
ObjectResult<Any>(result).invoke(context)
writeJson(context, result)
} else if (let result: String <- actionResult) {
// String → SimApiResponse<String>data 为字符串)
ObjectResult<Any>(SimApiResponse<String>(result)).invoke(context)
writeJson(context, SimApiResponse<String>(result))
} else if (let result: Unit <- actionResult) {
// void/无返回 → SimApiBaseResponse(){code:200, message:成功}
context.response.writeAsJson(SimApiBaseResponse())
writeJson(context, SimApiBaseResponse())
} else {
// 其他对象(DTO/数组/动态结构)→ SimApiDataResponse
// data 由 SimApiDataResponse.serializeObject 内嵌为对象
ObjectResult<Any>(SimApiDataResponse(actionResult)).invoke(context)
writeJson(context, SimApiDataResponse(actionResult))
}
}
/// simapi_serialization 序列化后直接写响应体
private static func writeJson(context: HttpContext, obj: Any): Unit {
context.response.contentType = "application/json; charset=utf-8"
context.response.write(JsonSerializer.Serialize(obj))
}
}
+7 -12
View File
@@ -11,9 +11,8 @@ import std.random.*
import stdx.crypto.digest.*
import stdx.encoding.hex.*
import stdx.encoding.base64.*
import stdx.encoding.json.*
import std.regex.*
import soulsoft_serialization.*
import simapi_serialization.*
import simapi.communications.*
import simapi.macros.*
@@ -181,12 +180,12 @@ public class SimApiUtil {
/**
* 从 JSON 字符串反序列化为 T(对齐 C# SimApiUtil.FromJson<T>)。
* @param T 目标类型(需实现 ISerialization<T>,如 @Serialization DTO、基础类型等)。
* @param T 目标类型(任意类,无需接口/宏约束)。
* @param jsonString JSON 字符串。
* @return 反序列化结果。
*/
public static func fromJson<T>(jsonString: String): T where T <: ISerialization<T> {
JsonSerializer.deserializeObject<T>(jsonString)
public static func fromJson<T>(jsonString: String): T {
JsonSerializer.Deserialize<T>(jsonString)
}
/**
@@ -195,21 +194,17 @@ public class SimApiUtil {
* @return Base64 字符串。
*/
public static func base64Encode(obj: Any): String {
let json = if (let ser: ISerializable <- obj) {
ser.serializeObject().toJson().toString()
} else {
SimApiJson.json(Some(obj))
}
let json = SimApiJson.json(Some(obj))
base64Encode(json)
}
/**
* Base64 → JSON → T 反序列化(对齐 C# Base64Decode<T>)。
* @param T 目标类型(需实现 ISerialization<T>)。
* @param T 目标类型(任意类,无需接口/宏约束)。
* @param base64Str Base64 字符串。
* @return 反序列化结果。
*/
public static func base64DecodeTo<T>(base64Str: String): T where T <: ISerialization<T> {
public static func base64DecodeTo<T>(base64Str: String): T {
fromJson<T>(base64Decode(base64Str))
}
+3 -2
View File
@@ -7,6 +7,7 @@ package simapi.middlewares
import soulsoft_web_http.*
import soulsoft_extensions_logging.*
import simapi_serialization.*
import simapi.communications.*
import simapi.exceptions.*
import simapi.configurations.*
@@ -60,7 +61,7 @@ public class SimApiExceptionMiddleware <: IMiddleware {
}
var response = SimApiBaseResponse(simEx.code, message)
if (context.response.statusCode == 404) {
response.message = "接口不存在"
response._message = "接口不存在"
}
return response
}
@@ -69,6 +70,6 @@ public class SimApiExceptionMiddleware <: IMiddleware {
}
private func responseJson(response: SimApiBaseResponse): String {
response.toJsonString()
JsonSerializer.Serialize(response)
}
}
+20 -19
View File
@@ -8,9 +8,9 @@ package simapi.middlewares
import std.collection.*
import std.io.*
import std.time.*
import stdx.encoding.json.*
import soulsoft_web_http.*
import soulsoft_extensions_logging.*
import simapi_serialization.*
import simapi.communications.*
import simapi.configurations.*
@@ -139,26 +139,27 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
return body + "\n"
}
try {
let jv = JsonValue.fromStr(body)
match (jv.kind()) {
case JsObject =>
let obj = jv.asObject()
let newObj = JsonObject()
for ((k, v) in obj.getFields()) {
match (v.kind()) {
case JsString =>
let str = v.asString().getValue()
if (str.size > maxLen) {
newObj.put(k, JsonString(str[0..maxLen] + "...(${str.size})"))
} else {
newObj.put(k, v)
}
case _ => newObj.put(k, v)
}
// 用 simapi_serialization 解析为动态结构,仅截断超长字符串字段(保持结构)
let map = JsonSerializer.Deserialize<HashMap<String, Any>>(body)
var sb = StringBuilder()
sb.append("{")
var first = true
for ((k, v) in map) {
if (!first) { sb.append(",") }
sb.append("\"${SimApiJson.escapeJson(k)}\":")
if (let s: String <- v) {
if (s.size > maxLen) {
sb.append("\"${SimApiJson.escapeJson(s[0..maxLen])}...(${s.size})\"")
} else {
sb.append("\"${SimApiJson.escapeJson(s)}\"")
}
return newObj.toString() + "\n"
case _ => ()
} else {
sb.append(SimApiJson.json(Some(v)))
}
first = false
}
sb.append("}")
return sb.toString() + "\n"
} catch (_: Exception) {
}
// 非 JSON 或解析失败:整串按长度截断