2026-08-16 12:46:15 +08:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
2026-08-25 13:43:26 +08:00
|
|
|
* 遵循 MIT 许可证。
|
2026-08-16 12:46:15 +08:00
|
|
|
*/
|
|
|
|
|
|
2026-08-18 04:23:07 +08:00
|
|
|
package simcu::simapi.helpers
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
import std.collection.*
|
|
|
|
|
import std.time.*
|
|
|
|
|
import std.random.*
|
|
|
|
|
import stdx.crypto.digest.*
|
|
|
|
|
import stdx.encoding.hex.*
|
|
|
|
|
import stdx.encoding.base64.*
|
|
|
|
|
import std.regex.*
|
2026-08-18 04:26:36 +08:00
|
|
|
import simcu::serialization.*
|
2026-08-18 04:23:07 +08:00
|
|
|
import simcu::simapi.communications.*
|
|
|
|
|
import simcu::simapi.macros.*
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* 工具类。
|
2026-08-16 12:46:15 +08:00
|
|
|
* 提供时间、哈希、Base64、JSON、校验等常用能力。
|
|
|
|
|
*/
|
|
|
|
|
public class SimApiUtil {
|
|
|
|
|
private init() {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 当前 CST 时间(UTC+8)。
|
|
|
|
|
*/
|
|
|
|
|
public static prop cstNow: DateTime {
|
|
|
|
|
get() {
|
|
|
|
|
DateTime.nowUTC().addHours(8)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 当前秒级 Unix 时间戳(Double)。
|
|
|
|
|
*/
|
|
|
|
|
public static prop timestampNow: Float64 {
|
|
|
|
|
get() {
|
|
|
|
|
let ts = DateTime.nowUTC().toUnixTimeStamp()
|
|
|
|
|
Float64(ts / Duration.second)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SimApi 包版本号(编译期从 simapi-cj/cjpm.toml 读取。
|
|
|
|
|
* 宏在调用方项目编译时展开,cwd 为应用根,故用 ../simapi-cj 相对路径)。
|
|
|
|
|
*/
|
|
|
|
|
@ReadTomlVersion[path: "../simapi-cj/cjpm.toml"]
|
|
|
|
|
public static let simApiVersion: String = ""
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 应用版本号(编译期从调用方项目 cjpm.toml 读取)。
|
|
|
|
|
*/
|
|
|
|
|
@ReadTomlVersion[path: "cjpm.toml"]
|
|
|
|
|
public static let appVersion: String = ""
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* MD5 加密字符串。
|
|
|
|
|
* @param source 源字符串。
|
|
|
|
|
* @return 32 位十六进制小写。
|
|
|
|
|
*/
|
2026-08-16 23:44:20 +08:00
|
|
|
public static func md5(source: String, mode!: String = "x2"): String {
|
2026-08-16 12:46:15 +08:00
|
|
|
let md = MD5()
|
|
|
|
|
md.write(source.toArray())
|
2026-08-16 23:44:20 +08:00
|
|
|
formatHex(md.finish(), mode)
|
2026-08-16 12:46:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SHA1 加密字符串。
|
|
|
|
|
* @param source 源字符串。
|
|
|
|
|
* @return 40 位十六进制小写。
|
|
|
|
|
*/
|
2026-08-16 23:44:20 +08:00
|
|
|
public static func sha1(source: String, mode!: String = "x2"): String {
|
2026-08-16 12:46:15 +08:00
|
|
|
let sha = SHA1()
|
|
|
|
|
sha.write(source.toArray())
|
2026-08-16 23:44:20 +08:00
|
|
|
formatHex(sha.finish(), mode)
|
2026-08-16 12:46:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SHA256 加密字符串。
|
|
|
|
|
* @param source 源字符串。
|
|
|
|
|
* @return 64 位十六进制小写。
|
|
|
|
|
*/
|
|
|
|
|
public static func sha256(source: String): String {
|
|
|
|
|
let sha = SHA256()
|
|
|
|
|
sha.write(source.toArray())
|
|
|
|
|
toHexString(sha.finish())
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-25 13:43:26 +08:00
|
|
|
/// 按 Md5/Sha1 的 mode 格式化:x2/x3/x4 → 每个字节 2/3/4 位十六进制
|
2026-08-16 23:44:20 +08:00
|
|
|
private static func formatHex(bytes: Array<Byte>, mode: String): String {
|
|
|
|
|
if (mode == "x2") {
|
|
|
|
|
return toHexString(bytes)
|
|
|
|
|
}
|
|
|
|
|
let two = toHexString(bytes)
|
|
|
|
|
var sb = StringBuilder()
|
|
|
|
|
for (i in 0..bytes.size) {
|
|
|
|
|
let pair = two[i * 2..i * 2 + 2]
|
|
|
|
|
if (mode == "x3") {
|
|
|
|
|
sb.append("0")
|
|
|
|
|
sb.append(pair)
|
|
|
|
|
} else if (mode == "x4") {
|
|
|
|
|
sb.append("00")
|
|
|
|
|
sb.append(pair)
|
|
|
|
|
} else {
|
|
|
|
|
sb.append(pair)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sb.toString()
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
/**
|
|
|
|
|
* 字符串 Base64 编码。
|
|
|
|
|
*/
|
|
|
|
|
public static func base64Encode(str: String): String {
|
|
|
|
|
toBase64String(str.toArray())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从 Base64 解码字符串。
|
|
|
|
|
*/
|
|
|
|
|
public static func base64Decode(base64Str: String): String {
|
|
|
|
|
let decoded = fromBase64String(base64Str).getOrThrow { Exception("Base64 解码失败") }
|
|
|
|
|
String.fromUtf8(decoded)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检测手机号是否正确(中国大陆 11 位手机号)。
|
|
|
|
|
*/
|
|
|
|
|
public static func checkCell(cell: String): Bool {
|
|
|
|
|
Regex("^1[3456789]\\d{9}$").matches(cell)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-16 23:44:20 +08:00
|
|
|
* 判断是否是 Email 地址。
|
2026-08-25 13:43:26 +08:00
|
|
|
* 说明:原版使用 System.Net.Mail.MailAddress 校验,仓颉无等价 API,此处用正则近似。
|
2026-08-16 12:46:15 +08:00
|
|
|
*/
|
|
|
|
|
public static func checkEmail(email: String): Bool {
|
|
|
|
|
Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$").matches(email)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* 生成 UUID v4 字符串。
|
2026-08-16 12:46:15 +08:00
|
|
|
* 仓颉标准库没有 GUID 生成器(stdx 的 GUID 是 stdx.net.http 内部类型),
|
|
|
|
|
* 此处用随机数自行构造:16 字节随机数 + 版本位(4)+ 变体位(10)。
|
|
|
|
|
*/
|
|
|
|
|
public static func newGuid(): String {
|
|
|
|
|
let rnd = Random()
|
|
|
|
|
var bytes = Array<UInt8>(16, repeat: 0u8)
|
|
|
|
|
let h = rnd.nextUInt64()
|
|
|
|
|
let l = rnd.nextUInt64()
|
|
|
|
|
for (i in 0..8) {
|
|
|
|
|
bytes[i] = UInt8((h >> UInt64(i * 8)) & 0xFFu64)
|
|
|
|
|
bytes[8 + i] = UInt8((l >> UInt64(i * 8)) & 0xFFu64)
|
|
|
|
|
}
|
|
|
|
|
// UUID v4:版本位(第 7 字节高 4 位 = 4),变体位(第 9 字节高 2 位 = 10)
|
|
|
|
|
bytes[6] = (bytes[6] & 0x0Fu8) | 0x40u8
|
|
|
|
|
bytes[8] = (bytes[8] & 0x3Fu8) | 0x80u8
|
|
|
|
|
let hex = "0123456789abcdef"
|
|
|
|
|
var sb = StringBuilder()
|
|
|
|
|
for (i in 0..16) {
|
|
|
|
|
if (i == 4 || i == 6 || i == 8 || i == 10) {
|
|
|
|
|
sb.append("-")
|
|
|
|
|
}
|
|
|
|
|
let b = bytes[i]
|
|
|
|
|
// 注意:String 索引返回 UInt8(字节),必须转 Rune 再 append,否则输出十进制 ASCII 码
|
|
|
|
|
sb.append(Rune(UInt32(hex[Int64((b >> 4u8) & 0x0Fu8)])))
|
|
|
|
|
sb.append(Rune(UInt32(hex[Int64(b & 0x0Fu8)])))
|
|
|
|
|
}
|
|
|
|
|
sb.toString()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* 将对象序列化为 JSON 字符串。
|
2026-08-18 01:15:15 +08:00
|
|
|
* @param obj 任意对象(None 输出 null)。
|
2026-08-16 12:46:15 +08:00
|
|
|
*/
|
|
|
|
|
public static func json(obj: ?Any): String {
|
2026-08-18 01:15:15 +08:00
|
|
|
if (let Some(obj) <- obj) {
|
2026-08-25 13:43:26 +08:00
|
|
|
return JsonSerializer.serialize(obj)
|
2026-08-18 01:15:15 +08:00
|
|
|
}
|
|
|
|
|
"null"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* JSON 字符串转义。
|
2026-08-18 01:15:15 +08:00
|
|
|
* @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()
|
2026-08-16 12:46:15 +08:00
|
|
|
}
|
2026-08-16 22:46:57 +08:00
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* 从 JSON 字符串反序列化为 T。
|
2026-08-18 00:07:00 +08:00
|
|
|
* @param T 目标类型(任意类,无需接口/宏约束)。
|
2026-08-16 22:46:57 +08:00
|
|
|
* @param jsonString JSON 字符串。
|
|
|
|
|
* @return 反序列化结果。
|
|
|
|
|
*/
|
2026-08-18 00:07:00 +08:00
|
|
|
public static func fromJson<T>(jsonString: String): T {
|
2026-08-25 13:43:26 +08:00
|
|
|
JsonSerializer.deserialize<T>(jsonString)
|
2026-08-16 22:46:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* 对象 Base64 编码(对象 → JSON → Base64)。
|
2026-08-16 22:46:57 +08:00
|
|
|
* @param obj 任意对象(DTO/基础类型/HashMap 等)。
|
|
|
|
|
* @return Base64 字符串。
|
|
|
|
|
*/
|
|
|
|
|
public static func base64Encode(obj: Any): String {
|
2026-08-18 01:15:15 +08:00
|
|
|
let json = json(Some(obj))
|
2026-08-16 22:46:57 +08:00
|
|
|
base64Encode(json)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* Base64 → JSON → T 反序列化。
|
2026-08-18 00:07:00 +08:00
|
|
|
* @param T 目标类型(任意类,无需接口/宏约束)。
|
2026-08-16 22:46:57 +08:00
|
|
|
* @param base64Str Base64 字符串。
|
|
|
|
|
* @return 反序列化结果。
|
|
|
|
|
*/
|
2026-08-18 00:07:00 +08:00
|
|
|
public static func base64DecodeTo<T>(base64Str: String): T {
|
2026-08-16 22:46:57 +08:00
|
|
|
fromJson<T>(base64Decode(base64Str))
|
|
|
|
|
}
|
2026-08-16 23:44:20 +08:00
|
|
|
|
|
|
|
|
/**
|
2026-08-25 13:43:26 +08:00
|
|
|
* 分页。
|
2026-08-16 23:44:20 +08:00
|
|
|
*/
|
|
|
|
|
public static func paginate<T>(list: Array<T>, page: Int64, count: Int64): Array<T> {
|
|
|
|
|
let p = if (page < 1) { 1 } else { page }
|
|
|
|
|
let c = if (count <= 0) { 10 } else { count }
|
|
|
|
|
let skip = (p - 1) * c
|
|
|
|
|
let total = list.size
|
|
|
|
|
if (skip >= total) {
|
|
|
|
|
return Array<T>()
|
|
|
|
|
}
|
|
|
|
|
let end = if (skip + c > total) { total } else { skip + c }
|
|
|
|
|
list[skip..end]
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-25 13:43:26 +08:00
|
|
|
// 说明:XmlDeserialize<T> 依赖 System.Xml.Serialization,仓颉生态无 XML 序列化库,未移植。
|
2026-08-16 12:46:15 +08:00
|
|
|
}
|