Files
simapi-cj/src/helpers/simapi_util.cj
T

252 lines
7.5 KiB
Plaintext

/*
* 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<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()
}
/**
* 字符串 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<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()
}
/**
* 将对象序列化为 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<T>(jsonString: String): T {
JsonSerializer.deserialize<T>(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<T>(base64Str: String): T {
fromJson<T>(base64Decode(base64Str))
}
/**
* 分页。
*/
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]
}
// 说明:XmlDeserialize<T> 依赖 System.Xml.Serialization,仓颉生态无 XML 序列化库,未移植。
}