/* * Copyright (c) 2025 SimcuTeam. All rights reserved. * 遵循 MIT 许可证。 */ package simcu::simapi.helpers import std.collection.* import std.time.* import std.random.* import stdx.crypto.digest.* import stdx.encoding.hex.* import stdx.encoding.base64.* import std.regex.* import simcu::serialization.* import simcu::simapi.communications.* import simcu::simapi.macros.* /** * 工具类。 * 提供时间、哈希、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 位十六进制小写。 */ public static func md5(source: String, mode!: String = "x2"): String { let md = MD5() md.write(source.toArray()) formatHex(md.finish(), mode) } /** * SHA1 加密字符串。 * @param source 源字符串。 * @return 40 位十六进制小写。 */ public static func sha1(source: String, mode!: String = "x2"): String { let sha = SHA1() sha.write(source.toArray()) formatHex(sha.finish(), mode) } /** * SHA256 加密字符串。 * @param source 源字符串。 * @return 64 位十六进制小写。 */ public static func sha256(source: String): String { let sha = SHA256() sha.write(source.toArray()) toHexString(sha.finish()) } /// 按 Md5/Sha1 的 mode 格式化:x2/x3/x4 → 每个字节 2/3/4 位十六进制 private static func formatHex(bytes: Array, 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() } /** * 字符串 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) } /** * 判断是否是 Email 地址。 * 说明:原版使用 System.Net.Mail.MailAddress 校验,仓颉无等价 API,此处用正则近似。 */ public static func checkEmail(email: String): Bool { Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$").matches(email) } /** * 生成 UUID v4 字符串。 * 仓颉标准库没有 GUID 生成器(stdx 的 GUID 是 stdx.net.http 内部类型), * 此处用随机数自行构造:16 字节随机数 + 版本位(4)+ 变体位(10)。 */ public static func newGuid(): String { let rnd = Random() var bytes = Array(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() } /** * 将对象序列化为 JSON 字符串。 * @param obj 任意对象(None 输出 null)。 */ public static func json(obj: ?Any): String { if (let Some(obj) <- obj) { return JsonSerializer.serialize(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() } /** * 从 JSON 字符串反序列化为 T。 * @param T 目标类型(任意类,无需接口/宏约束)。 * @param jsonString JSON 字符串。 * @return 反序列化结果。 */ public static func fromJson(jsonString: String): T { JsonSerializer.deserialize(jsonString) } /** * 对象 Base64 编码(对象 → JSON → Base64)。 * @param obj 任意对象(DTO/基础类型/HashMap 等)。 * @return Base64 字符串。 */ public static func base64Encode(obj: Any): String { let json = json(Some(obj)) base64Encode(json) } /** * Base64 → JSON → T 反序列化。 * @param T 目标类型(任意类,无需接口/宏约束)。 * @param base64Str Base64 字符串。 * @return 反序列化结果。 */ public static func base64DecodeTo(base64Str: String): T { fromJson(base64Decode(base64Str)) } /** * 分页。 */ public static func paginate(list: Array, page: Int64, count: Int64): Array { 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() } let end = if (skip + c > total) { total } else { skip + c } list[skip..end] } // 说明:XmlDeserialize 依赖 System.Xml.Serialization,仓颉生态无 XML 序列化库,未移植。 }