first version
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
package simapi.communications
|
||||
|
||||
/**
|
||||
* 基础请求 DTO。
|
||||
*/
|
||||
public class SimApiBaseRequest {}
|
||||
|
||||
/**
|
||||
* 仅包含 Id 的请求。
|
||||
*/
|
||||
public class SimApiStringIdOnlyRequest {
|
||||
public var id: String = ""
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(id: String) {
|
||||
this.id = id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单字段请求。
|
||||
* @param T 数据类型。
|
||||
*/
|
||||
public class SimApiOneFieldRequest<T> {
|
||||
public var data: ?T = None
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(data: T) {
|
||||
this.data = Some(data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础分页请求。
|
||||
*/
|
||||
public class SimApiBasePageRequest {
|
||||
public var page: Int64 = 1
|
||||
public var count: Int64 = 20
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(page: Int64, count: Int64) {
|
||||
this.page = page
|
||||
this.count = count
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
package simapi.communications
|
||||
|
||||
import std.collection.*
|
||||
import stdx.encoding.json.*
|
||||
import soulsoft_serialization.*
|
||||
import soulsoft_serialization.macros.*
|
||||
|
||||
/**
|
||||
* 基础响应体:所有接口统一返回该结构。
|
||||
* HTTP 状态码始终 200,业务错误通过 code 字段表达。
|
||||
*/
|
||||
@Serialization
|
||||
public open class SimApiBaseResponse {
|
||||
public var _code: Int64 = 200
|
||||
public var _message: String = "成功"
|
||||
|
||||
public init(code: Int64, message: String) {
|
||||
this._code = code
|
||||
this._message = message
|
||||
}
|
||||
|
||||
public init(code: Int64) {
|
||||
this._code = code
|
||||
this._message = getDefaultMessage(code)
|
||||
}
|
||||
|
||||
public init() {
|
||||
this._code = 200
|
||||
this._message = "成功"
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认错误代码对应提示信息。
|
||||
*/
|
||||
public static func getDefaultMessage(code: Int64): String {
|
||||
match (code) {
|
||||
case 200 => "成功"
|
||||
case 204 => "没有数据"
|
||||
case 400 => "参数错误"
|
||||
case 401 => "需要登录"
|
||||
case 403 => "无权访问"
|
||||
case 404 => "请求资源不存在"
|
||||
case 500 => "服务器错误"
|
||||
case _ => "未知错误代码"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为 JSON 字符串(可选 data 字段)。
|
||||
* @param dataJson 已序列化的 data JSON 字符串(可选)。
|
||||
*/
|
||||
public func toJsonString(dataJson!: String = ""): String {
|
||||
var sb = StringBuilder()
|
||||
sb.append("{\"code\":${_code},\"message\":\"${SimApiJson.escapeJson(_message)}\"")
|
||||
if (!dataJson.isEmpty()) {
|
||||
sb.append(",\"data\":${dataJson}")
|
||||
}
|
||||
sb.append("}")
|
||||
sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页内容返回。
|
||||
* @param T 列表元素类型。
|
||||
* 说明:泛型类暂不通过 @Serialization 宏序列化,可手动转换为 SimApiResponse。
|
||||
*/
|
||||
public class PageResponse<T> {
|
||||
public var _list: Array<T> = Array<T>()
|
||||
public var _page: Int64 = 1
|
||||
public var _count: Int64 = 20
|
||||
public var _total: Int64 = 0
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(list: Array<T>, page: Int64, count: Int64, total: Int64) {
|
||||
this._list = list
|
||||
this._page = page
|
||||
this._count = count
|
||||
this._total = total
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带数据的动态响应。
|
||||
* @param T 数据类型。
|
||||
* 说明:泛型类手写实现 ISerialization<SimApiResponse<T>>(serialize + deserialize),
|
||||
* data 内嵌为对象(对齐 C# SimApiBaseResponse<T>.Data 是 T? 而非字符串)。
|
||||
* 序列化支持动态结构(HashMap<String,Any> 等经 SimApiJson 内嵌);反序列化要求 T <: ISerialization<T>。
|
||||
*/
|
||||
public class SimApiResponse<T> <: SimApiBaseResponse & ISerialization<SimApiResponse<T>> where T <: ISerialization<T> {
|
||||
public var _data: ?T = None
|
||||
|
||||
public init() {
|
||||
super()
|
||||
}
|
||||
|
||||
public init(data: T) {
|
||||
super()
|
||||
this._data = Some(data)
|
||||
}
|
||||
|
||||
public init(code: Int64, message: String) {
|
||||
super(code, message)
|
||||
}
|
||||
|
||||
public init(code: Int64, message: String, data: T) {
|
||||
super(code, message)
|
||||
this._data = Some(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为 DataModel:code + message + data(data 为对象)。
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
*/
|
||||
|
||||
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 字符串"。
|
||||
*/
|
||||
public class SimApiDataResponse <: SimApiBaseResponse & ISerialization<SimApiDataResponse> {
|
||||
public var _data: ?Any = None
|
||||
|
||||
public init() {
|
||||
super()
|
||||
}
|
||||
|
||||
public init(data: Any) {
|
||||
super()
|
||||
this._data = Some(data)
|
||||
}
|
||||
|
||||
public init(code: Int64, message: String) {
|
||||
super(code, message)
|
||||
}
|
||||
|
||||
public init(code: Int64, message: String, data: Any) {
|
||||
super(code, message)
|
||||
this._data = Some(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为 DataModel:code + message + data(data 为对象)。
|
||||
*/
|
||||
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(data 保留原始 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
* JSON 序列化统一工具(对应 C# SimApiUtil.Json)。
|
||||
*/
|
||||
|
||||
package simapi.communications
|
||||
|
||||
import std.collection.*
|
||||
|
||||
/**
|
||||
* JSON 序列化静态工具类。
|
||||
*
|
||||
* 说明:序列化核心放在依赖图最底层的 simapi.communications 包,
|
||||
* SimApiUtil.json(simapi.helpers)委托本类实现,避免循环依赖;
|
||||
* 全框架 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)
|
||||
}
|
||||
"null"
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 字符串转义(统一入口)。
|
||||
* @param s 原始字符串。
|
||||
* @return 转义后可直接放入 JSON 字符串字面量的内容。
|
||||
*/
|
||||
public static func escapeJson(s: String): String {
|
||||
var sb = StringBuilder()
|
||||
for (c in s.runes()) {
|
||||
match (c) {
|
||||
case '"' => sb.append("\\\"")
|
||||
case '\\' => sb.append("\\\\")
|
||||
case '\n' => sb.append("\\n")
|
||||
case '\r' => sb.append("\\r")
|
||||
case '\t' => sb.append("\\t")
|
||||
case _ => sb.append(c)
|
||||
}
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
* Communications/SimApiLoginItem:登录信息项。
|
||||
*/
|
||||
|
||||
package simapi.communications
|
||||
|
||||
import std.collection.*
|
||||
import soulsoft_serialization.*
|
||||
|
||||
/**
|
||||
* 登录信息项:Token 认证通过后注入请求上下文。
|
||||
* 对齐 C# SimApiLoginItem(Id / Type / Meta / Extra,camelCase 输出 id/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)。
|
||||
*/
|
||||
public class SimApiLoginItem <: ISerialization<SimApiLoginItem> {
|
||||
public var _id: String = ""
|
||||
public var _types: Array<String> = []
|
||||
public var _meta: HashMap<String, String> = HashMap<String, String>()
|
||||
public var _extra: HashMap<String, Any> = HashMap<String, Any>()
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(id: String) {
|
||||
this._id = id
|
||||
this._types = ["user"]
|
||||
this._meta = HashMap<String, String>()
|
||||
this._extra = HashMap<String, Any>()
|
||||
}
|
||||
|
||||
public init(id: String, types: Array<String>) {
|
||||
this._id = id
|
||||
this._types = types
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user