refactor: 对齐 simapi-net 的 20 项差异
- SimApiLoginItem._types 默认 [user] - 新增 SimApiIdOnlyRequest - errorWhenNone 改名 errorWhenNull - SimApiAuth.update 保留 TTL(Redis ttl/expire) - SimApiCache: set 接受 Any + 新增 get<T>/getString - SimApiHttpClient 签名字段移出 Options - SimApiUtil: md5/sha1 支持 mode + 新增 paginate - @SimApiAuth 支持逗号分隔多类型 - Logger: 4 位毫秒 + IsEnabled 恒 true - 删除 /versions 端点;exceptionHandler 改为抛错 - UseSimApi 中间件顺序对齐 .NET - README 同步更新
This commit is contained in:
@@ -114,7 +114,12 @@ public class SimApiAuth {
|
||||
let tokenKey = "${tokenCachePrefix}${token}"
|
||||
let json = loginItemJson(loginItem)
|
||||
if (let Some(redis) <- _redis) {
|
||||
// 保留原过期时间(对齐 C# update 不刷新 TTL):先读旧 TTL,SET 后重新续期
|
||||
let ttl = redis.ttl(tokenKey)
|
||||
redis.set(tokenKey, Blob.fromUtf8(json))
|
||||
if (ttl > 0) {
|
||||
redis.expire(tokenKey, ttl)
|
||||
}
|
||||
} else {
|
||||
// 保留原过期时间(对齐 C# update 不刷新 TTL)
|
||||
let expireAt = match (_tokenStore.get(token)) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import std.collection.concurrent.*
|
||||
import std.convert.*
|
||||
import std.time.*
|
||||
import redis.client.*
|
||||
import soulsoft_serialization.*
|
||||
import simapi.configurations.*
|
||||
import simapi.exceptions.*
|
||||
|
||||
@@ -66,16 +67,17 @@ public class SimApiCache {
|
||||
* @param value 缓存值(不能为 null)。
|
||||
* @param expireSeconds 过期秒数(可选,<=0 表示不过期)。
|
||||
*/
|
||||
public func set(key: String, value: String, expireSeconds!: Int64 = -1): Unit {
|
||||
public func set(key: String, value: Any, expireSeconds!: Int64 = -1): Unit {
|
||||
let json = SimApiUtil.json(Some(value))
|
||||
if (let Some(redis) <- _redis) {
|
||||
if (expireSeconds > 0) {
|
||||
redis.set("${prefix}${key}", Blob.fromUtf8(value), ex: Some(expireSeconds))
|
||||
redis.set("${prefix}${key}", Blob.fromUtf8(json), ex: Some(expireSeconds))
|
||||
} else {
|
||||
redis.set("${prefix}${key}", Blob.fromUtf8(value))
|
||||
redis.set("${prefix}${key}", Blob.fromUtf8(json))
|
||||
}
|
||||
} else {
|
||||
let expireAt = if (expireSeconds > 0) { nowMillis() + expireSeconds * 1000 } else { 0 }
|
||||
_store["${prefix}${key}"] = CacheEntry(value, expireAt)
|
||||
_store["${prefix}${key}"] = CacheEntry(json, expireAt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,13 +96,23 @@ public class SimApiCache {
|
||||
* 缓存 Key 是否存在。
|
||||
*/
|
||||
public func hasKey(key: String): Bool {
|
||||
get(key) != None
|
||||
getString(key) != None
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 string 类型缓存。
|
||||
* 获取特定类型缓存(对齐 C# Get<T>:从 JSON 反序列化)。
|
||||
*/
|
||||
public func get(key: String): ?String {
|
||||
public func get<T>(key: String): ?T where T <: ISerialization<T> {
|
||||
match (getString(key)) {
|
||||
case Some(json) => Some(JsonSerializer.deserializeObject<T>(json))
|
||||
case None => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 string 类型缓存(对应 C# Get(string);Cangjie 不支持按泛型重载,故拆分为 getString/get<T>)。
|
||||
*/
|
||||
public func getString(key: String): ?String {
|
||||
if (let Some(redis) <- _redis) {
|
||||
return match (redis.get("${prefix}${key}")) {
|
||||
case Some(blob) => Some(blob.toUtf8())
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SimApiError {
|
||||
* @param code 错误代码,默认 404。
|
||||
* @param message 错误描述。
|
||||
*/
|
||||
public static func errorWhenNone(condition: ?Any, code!: Int64 = 404, message!: String = ""): Unit {
|
||||
public static func errorWhenNull(condition: ?Any, code!: Int64 = 404, message!: String = ""): Unit {
|
||||
match (condition) {
|
||||
case None => error(code: code, message: message)
|
||||
case _ => ()
|
||||
|
||||
@@ -38,11 +38,6 @@ public open class SimApiHttpClient {
|
||||
server = httpOptions.server
|
||||
appId = httpOptions.appId
|
||||
appKey = httpOptions.appKey
|
||||
signName = httpOptions.signName
|
||||
timestampName = httpOptions.timestampName
|
||||
nonceName = httpOptions.nonceName
|
||||
appIdName = httpOptions.appIdName
|
||||
signFields = httpOptions.signFields
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,10 +61,10 @@ public class SimApiUtil {
|
||||
* @param source 源字符串。
|
||||
* @return 32 位十六进制小写。
|
||||
*/
|
||||
public static func md5(source: String): String {
|
||||
public static func md5(source: String, mode!: String = "x2"): String {
|
||||
let md = MD5()
|
||||
md.write(source.toArray())
|
||||
toHexString(md.finish())
|
||||
formatHex(md.finish(), mode)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,10 +72,10 @@ public class SimApiUtil {
|
||||
* @param source 源字符串。
|
||||
* @return 40 位十六进制小写。
|
||||
*/
|
||||
public static func sha1(source: String): String {
|
||||
public static func sha1(source: String, mode!: String = "x2"): String {
|
||||
let sha = SHA1()
|
||||
sha.write(source.toArray())
|
||||
toHexString(sha.finish())
|
||||
formatHex(sha.finish(), mode)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +89,28 @@ public class SimApiUtil {
|
||||
toHexString(sha.finish())
|
||||
}
|
||||
|
||||
/// 按 .NET 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 编码。
|
||||
*/
|
||||
@@ -112,7 +134,8 @@ public class SimApiUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是 Email 地址(简化校验)。
|
||||
* 判断是否是 Email 地址。
|
||||
* 说明:.NET 使用 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)
|
||||
@@ -189,4 +212,21 @@ public class SimApiUtil {
|
||||
public static func base64DecodeTo<T>(base64Str: String): T where T <: ISerialization<T> {
|
||||
fromJson<T>(base64Decode(base64Str))
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页(对齐 C# Paginate 扩展;仓颉无 IQueryable,改为对 Array<T> 切片)。
|
||||
*/
|
||||
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]
|
||||
}
|
||||
|
||||
// 说明:C# 的 XmlDeserialize<T> 依赖 System.Xml.Serialization,仓颉生态无 XML 序列化库,未移植。
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user