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:
@@ -1 +1,2 @@
|
||||
target/
|
||||
*.cj.macrocall
|
||||
@@ -102,7 +102,7 @@ import simapi.helpers.*
|
||||
SimApiError.error(500, "服务器内部错误") // 直接抛错
|
||||
SimApiError.errorWhen(amount <= 0, 400, "金额无效") // 条件为 true 时抛错
|
||||
SimApiError.errorWhenFalse(hasPermission, 403, "无权操作")
|
||||
SimApiError.errorWhenNone(someOptional, 404, "用户不存在")
|
||||
SimApiError.errorWhenNull(someOptional, 404, "用户不存在")
|
||||
```
|
||||
|
||||
### 2. 认证 — SimApiAuth
|
||||
@@ -143,7 +143,7 @@ public class MyController <: SimApiBaseController {
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:仓颉注解参数须为编译期常量,`@SimApiAuth` 支持单类型参数(`@SimApiAuth["admin"]`);空参数表示任意已登录用户。多个 `ISimApiAuthChecker` 通过 `SimApiOptions.authCheckers` 注册(由 addSimApi 扫描调用者包填充)。
|
||||
> 说明:仓颉注解参数须为编译期常量,`@SimApiAuth` 支持单个类型参数(`@SimApiAuth["admin"]`)或逗号分隔多类型(`@SimApiAuth["admin,user"]`,对齐 C# `type.Split(",")`);空参数表示任意已登录用户。多个 `ISimApiAuthChecker` 通过 `SimApiOptions.authCheckers` 注册(由 addSimApi 扫描调用者包填充)。
|
||||
|
||||
### 2.2 原样响应 — @OriginResponse
|
||||
|
||||
@@ -272,7 +272,7 @@ let resp2 = client.aesQuery<SimApiLoginItem>("/api/data", body: "{\"a\":1}")
|
||||
let resp3 = client.aesSignQuery<SimApiLoginItem>("/api/data", body: "{\"a\":1}")
|
||||
```
|
||||
|
||||
签名参数名可配置(`simApiHttpClientOptions.signName / timestampName / nonceName / appIdName / signFields`,C# 侧为硬编码)。
|
||||
签名参数名可配置(`SimApiHttpClient` 实例属性 `signName / timestampName / nonceName / appIdName / signFields`,对齐 C# 的 virtual 属性)。
|
||||
|
||||
### 5.1 请求日志 — enableRequestLog
|
||||
|
||||
@@ -324,10 +324,9 @@ builder.addSimApi { options =>
|
||||
|
||||
| 路由 | 方法 | 条件 | 说明 |
|
||||
| ----------------- | -------- | ---------------------------- | -------------------------- |
|
||||
| `/versions` | GET/POST | 始终 | 返回 SimApi/App 版本 |
|
||||
| `/user/info` | POST | `enableSimApiAuth` | 需登录,返回 LoginInfo |
|
||||
| `/auth/logout` | POST | `enableSimApiAuth` | 退出登录 |
|
||||
| `/exception/{code}` | GET | 始终 | 错误反馈 |
|
||||
| `/exception/{code}` | GET | 始终 | 错误反馈(抛 SimApiException) |
|
||||
|
||||
路由路径可自定义(`configureSimApiRoute`,自定义值通过 `mapGet/mapPost` 真实注册,默认值由内置控制器特性路由覆盖):
|
||||
|
||||
@@ -434,7 +433,7 @@ simapi 提供 Spire MVC 控制器(继承 `SimApiBaseController`),`addSimAp
|
||||
|
||||
| 控制器 | 路由 | 说明 |
|
||||
|--------|------|------|
|
||||
| `SimApiCommonController` | `/exception/{code}`、`/config`、`/versions`、`/user/info` | 通用内置路由 |
|
||||
| `SimApiCommonController` | `/exception/{code}`、`/config`、`/user/info` | 通用内置路由 |
|
||||
| `SimApiAuthController` | `/auth/logout` | 退出登录 |
|
||||
| `SimApiBaseController` | — | 基类:`loginInfo` / `loginToken` / `requireLogin()` / `getLogin()` |
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@ package simapi.attributes
|
||||
* 用法:
|
||||
* @SimApiAuth // 任意已登录用户
|
||||
* @SimApiAuth["admin"] // 仅 admin 类型
|
||||
* @SimApiAuth["admin,user"] // admin 或 user 类型(对齐 C# type.Split(","))
|
||||
*
|
||||
* 说明:仓颉注解参数须为编译期常量,且 String 无法作为 const 值数组元素
|
||||
* (内部为 Array<UInt8>),故与 C# 的 string[] 不同,这里支持单个类型参数。
|
||||
* 说明:仓颉注解参数须为编译期常量,String 无法作为 const 值数组元素,
|
||||
* 故与 C# 的 string[] 不同,这里用逗号分隔字符串对齐 C# 多类型。
|
||||
*/
|
||||
@Annotation[target: [MemberFunction, Type]]
|
||||
public class SimApiAuth {
|
||||
|
||||
@@ -193,6 +193,8 @@ public class SimApiAuthCenter {
|
||||
public func getLoginInfo(code: String, scene!: ?String = None): LoginInfoResponse {
|
||||
var body = HashMap<String, Any>()
|
||||
body["code"] = code
|
||||
// 说明:C# 的 ErrorWhenNull(resp, 400232, "登录信息获取失败") 对应 signQuery 内部 _data.getOrThrow() 的
|
||||
// None 分支;仓颉版 signQuery 返回非空 T(data 缺失即抛异常),故此处无需重复判空。
|
||||
let resp = _client.signQuery<LoginInfoResponse>("/api/auth/login/get", body: SimApiJson.json(Some(body)))
|
||||
SimApiError.errorWhen(resp._scene != scene, code: 403003, message: "登录场景不匹配")
|
||||
resp
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
* Copyright (c) 2025 SimcuTeam. All rights reserved.
|
||||
* 移植自 C# 项目 SimApi(E:\simcu\simapi-net),遵循 MIT 许可证。
|
||||
* AuthSDK 用到的 DTO(对齐 C# SimApiAuthCenterDto / SimApiAuthIamDto)。
|
||||
*
|
||||
* 说明:C# 中这些 DTO 是 SimApiAuthCenterDto / SimApiAuthIamDto 的嵌套类;
|
||||
* 仓颉不支持在类体内声明嵌套类(unexpected class declaration in class body),
|
||||
* 故拍平为顶层类,语义与字段保持一致。
|
||||
*/
|
||||
|
||||
package simapi.authsdk
|
||||
@@ -26,6 +30,7 @@ public class AppAndProfileItem {
|
||||
|
||||
/**
|
||||
* 安全确认响应(对齐 C# ConfirmResponse)。
|
||||
* _data 用 ?JsonValue 对齐 C# Dictionary<string,object>?(任意 JSON 对象)。
|
||||
*/
|
||||
@Serialization
|
||||
public class ConfirmResponse {
|
||||
|
||||
@@ -13,6 +13,19 @@ public class SimApiBaseRequest {}
|
||||
/**
|
||||
* 仅包含 Id 的请求。
|
||||
*/
|
||||
public class SimApiIdOnlyRequest {
|
||||
public var id: Int64 = 0
|
||||
|
||||
public init() {}
|
||||
|
||||
public init(id: Int64) {
|
||||
this.id = id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅包含 Id 的请求(字符串)。
|
||||
*/
|
||||
public class SimApiStringIdOnlyRequest {
|
||||
public var id: String = ""
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import soulsoft_serialization.*
|
||||
*/
|
||||
public class SimApiLoginItem <: ISerialization<SimApiLoginItem> {
|
||||
public var _id: String = ""
|
||||
public var _types: Array<String> = []
|
||||
public var _types: Array<String> = ["user"]
|
||||
public var _meta: HashMap<String, String> = HashMap<String, String>()
|
||||
public var _extra: HashMap<String, Any> = HashMap<String, Any>()
|
||||
|
||||
|
||||
@@ -13,11 +13,6 @@ public class SimApiHttpClientOptions {
|
||||
public var server: String = ""
|
||||
public var appId: String = ""
|
||||
public var appKey: String = ""
|
||||
public var signName: String = "sign"
|
||||
public var timestampName: String = "timestamp"
|
||||
public var nonceName: String = "nonce"
|
||||
public var appIdName: ?String = Some("appId")
|
||||
public var signFields: Array<String> = []
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
@@ -26,24 +26,11 @@ public class SimApiCommonController <: SimApiBaseController {
|
||||
|
||||
/**
|
||||
* GET /exception/{code}:错误反馈页面(始终注册)。
|
||||
* 返回 SimApiBaseResponse(已是响应体,原样输出)。
|
||||
* 对齐 C# ExceptionHandler:抛 SimApiException,由异常中间件统一输出。
|
||||
*/
|
||||
@HttpGet["exception/{code}"]
|
||||
public func exceptionHandler(@FromRoute code: Int64): SimApiBaseResponse {
|
||||
SimApiBaseResponse(code, SimApiBaseResponse.getDefaultMessage(code))
|
||||
}
|
||||
|
||||
/**
|
||||
* GET/POST /versions:返回 SimApi/App 版本信息(HashMap 由 SimApiResponseFilter 自动封装)。
|
||||
*/
|
||||
@HttpGet["versions"]
|
||||
public func versions(): HashMap<String, Any> {
|
||||
versionsMap()
|
||||
}
|
||||
|
||||
@HttpPost["versions"]
|
||||
public func versionsPost(): HashMap<String, Any> {
|
||||
versionsMap()
|
||||
public func exceptionHandler(@FromRoute code: Int64): Unit {
|
||||
SimApiError.error(code: code)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -216,10 +216,15 @@ extend WebHost <: SimApiHostExtensions {
|
||||
}
|
||||
|
||||
// ===== 中间件与路由(对齐 C# UseSimApi(WebApplication) 的挂载顺序) =====
|
||||
// C# 挂载顺序(先挂载 = 外层):CORS(L425) → AuthGate(L454) → Auth(L462) → RequestLog(L519) → Exception(L525)
|
||||
// OPTIONS 预检请求在 CORS 处短路(204,不调用 next),因此 RequestLog/Exception 均不会执行
|
||||
// C# 挂载顺序(先挂载 = 外层):ForwardedHeaders(L419) → CORS(L425) → AuthGate(L454) → Auth(L462)
|
||||
// → 内置路由(L465-496) → Swagger(L502) → RequestLog(L519) → Exception(L525) → LowerUrl → Job
|
||||
|
||||
// CORS(对齐 C# builder.UseCors("any"),最先挂载)
|
||||
// ForwardedHeaders(占位:soulsoft 暂无内置,对齐 C# 最先挂载)
|
||||
if (options.enableForwardHeaders) {
|
||||
logger.info("开始配置ForwardedHeaders...")
|
||||
}
|
||||
|
||||
// CORS(对齐 C# builder.UseCors("any"))
|
||||
if (options.enableCors) {
|
||||
logger.info("开始配置 Cors全部允许...")
|
||||
this.useCors()
|
||||
@@ -237,18 +242,6 @@ extend WebHost <: SimApiHostExtensions {
|
||||
this.use<SimApiAuthMiddleware>()
|
||||
}
|
||||
|
||||
// 请求日志中间件(对齐 C# builder.UseMiddleware<SimApiRequestLogMiddleware>())
|
||||
if (options.enableRequestLog) {
|
||||
logger.info("开始配置 SimApiRequestLog...")
|
||||
this.use<SimApiRequestLogMiddleware>()
|
||||
}
|
||||
|
||||
// 异常中间件最后挂载(最内层,对齐 C# builder.UseMiddleware<SimApiExceptionMiddleware>())
|
||||
if (options.enableSimApiException) {
|
||||
logger.info("开始配置 SimApiException...")
|
||||
this.use<SimApiExceptionMiddleware>()
|
||||
}
|
||||
|
||||
// 内置路由:RouteOptions 自定义路径时真实注册(默认路径已由内置控制器特性路由覆盖,
|
||||
// 对齐 C# MapControllerRoute 语义;soulsoft 无约定路由 defaults,用 mapGet/mapPost 委托实现)
|
||||
let routeOptions = options.simApiRouteOptions
|
||||
@@ -308,11 +301,23 @@ extend WebHost <: SimApiHostExtensions {
|
||||
logger.info("注册内置Route: WebConfig => ${route}")
|
||||
}
|
||||
|
||||
// SimApiDoc(占位)
|
||||
// SimApiDoc(占位,对齐 C# UseSwagger/UseSwaggerUI)
|
||||
if (options.enableSimApiDoc) {
|
||||
logger.info("开始配置 SimApiDoc...")
|
||||
}
|
||||
|
||||
// 请求日志中间件(对齐 C# builder.UseMiddleware<SimApiRequestLogMiddleware>())
|
||||
if (options.enableRequestLog) {
|
||||
logger.info("开始配置 SimApiRequestLog...")
|
||||
this.use<SimApiRequestLogMiddleware>()
|
||||
}
|
||||
|
||||
// 异常中间件最后挂载(最内层,对齐 C# builder.UseMiddleware<SimApiExceptionMiddleware>())
|
||||
if (options.enableSimApiException) {
|
||||
logger.info("开始配置 SimApiException...")
|
||||
this.use<SimApiExceptionMiddleware>()
|
||||
}
|
||||
|
||||
// URL 小写
|
||||
if (options.enableLowerUrl) {
|
||||
logger.info("开始配置使用URL小写...")
|
||||
@@ -329,11 +334,6 @@ extend WebHost <: SimApiHostExtensions {
|
||||
logger.info("开始配置 SimApiResponseFilter...")
|
||||
}
|
||||
|
||||
// ForwardedHeaders(占位:soulsoft 暂无内置)
|
||||
if (options.enableForwardHeaders) {
|
||||
logger.info("开始配置ForwardedHeaders...")
|
||||
}
|
||||
|
||||
// 映射控制器端点(对齐 C# UseSimApi 中的 MapControllers)
|
||||
let callSiteFactory = this.services.getOrThrow<IServiceProviderIsService>()
|
||||
if (callSiteFactory.isService<ApplicationPartManager>()) {
|
||||
|
||||
@@ -96,9 +96,17 @@ struct SimApiActionInvoker {
|
||||
SimApiError.error(code: 401, message: "需要登录")
|
||||
}
|
||||
|
||||
// 2. 类型权限校验 → 403
|
||||
// 2. 类型权限校验 → 403(对齐 C# Types.Intersect(loginInfo.Type).Any(),支持逗号分隔多类型)
|
||||
if (!auth.`type`.isEmpty()) {
|
||||
if (!loginItem._types.contains(auth.`type`)) {
|
||||
let requiredTypes = auth.`type`.split(",")
|
||||
var matched = false
|
||||
for (t in requiredTypes) {
|
||||
if (loginItem._types.contains(t)) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
SimApiError.error(code: 403, message: "无权访问")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 序列化库,未移植。
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public class SimApiLogger <: ILogger {
|
||||
}
|
||||
|
||||
public func isEnabled(logLevel: LogLevel): Bool {
|
||||
logLevel != LogLevel.Off
|
||||
true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +96,7 @@ public class SimApiLogger <: ILogger {
|
||||
let hour = pad2(dt.hour)
|
||||
let minute = pad2(dt.minute)
|
||||
let second = pad2(dt.second)
|
||||
let millis = pad3(dt.nanosecond / 1000000)
|
||||
let millis = pad4(dt.nanosecond / 100000)
|
||||
"${year}-${month}-${day} ${hour}:${minute}:${second}:${millis}"
|
||||
}
|
||||
|
||||
@@ -107,11 +107,14 @@ public class SimApiLogger <: ILogger {
|
||||
"${v}"
|
||||
}
|
||||
|
||||
private static func pad3(v: Int64): String {
|
||||
private static func pad4(v: Int64): String {
|
||||
if (v < 10) {
|
||||
return "00${v}"
|
||||
return "000${v}"
|
||||
}
|
||||
if (v < 100) {
|
||||
return "00${v}"
|
||||
}
|
||||
if (v < 1000) {
|
||||
return "0${v}"
|
||||
}
|
||||
"${v}"
|
||||
|
||||
Reference in New Issue
Block a user