重构: 源码文件蛇形命名, 接口去I头(BindRequestContext/SimApiAuthChecker), 清理C#相关说明, serialization依赖路径改 serialization-cj

This commit is contained in:
2026-08-25 13:43:26 +08:00
parent 829f009229
commit 30e5f165f2
56 changed files with 319 additions and 332 deletions
+1
View File
@@ -1,2 +1,3 @@
target/ target/
*.cj.macrocall *.cj.macrocall
.cache/
+32 -32
View File
@@ -1,6 +1,6 @@
# SimApi for Cangjiesimapi # SimApi for Cangjiesimapi
> 仓颉版 SimApiASP.NET Core 风格 API 基础框架,移植自 C# 项目 [SimApi](https://github.com/SimcuTeam/simapi-net)`E:\simcu\simapi-net` > 仓颉版 SimApiASP.NET Core 风格 API 基础框架,移植自 [SimApi](https://github.com/SimcuTeam/simapi-net)。
提供**统一响应格式、异常拦截、Token 认证、缓存、工具集、HTTP 客户端、S3 存储、声明式注解**等 API 基础能力。 提供**统一响应格式、异常拦截、Token 认证、缓存、工具集、HTTP 客户端、S3 存储、声明式注解**等 API 基础能力。
@@ -103,7 +103,7 @@ main(args: Array<String>) {
simapi-cj/ simapi-cj/
├── cjpm.toml # 包配置 ├── cjpm.toml # 包配置
├── src/ ├── src/
│ ├── SimApiExtensions.cj # 根包入口:SimApiExtensions 静态类(addSimApi / useSimApi + 内置路由 + 响应封装) │ ├── simapi_extensions.cj # 根包入口:SimApiExtensions 静态类(addSimApi / useSimApi + 内置路由 + 响应封装)
│ ├── annotations/ # 声明式注解:@SimApiAuth(鉴权)、@OriginResponse(原样响应)、 │ ├── annotations/ # 声明式注解:@SimApiAuth(鉴权)、@OriginResponse(原样响应)、
│ │ # @SimApiSign(验签)、@AesBodyAES body 解密) │ │ # @SimApiSign(验签)、@AesBodyAES body 解密)
│ ├── authsdk/ # 认证中心 SDKSimApiAuthClient/Center/Iam + 网关中间件 + DTO │ ├── authsdk/ # 认证中心 SDKSimApiAuthClient/Center/Iam + 网关中间件 + DTO
@@ -114,7 +114,7 @@ simapi-cj/
│ ├── helpers/ # SimApiError, SimApiUtil, SimApiAuth, SimApiCache, SimApiHttpClient, │ ├── helpers/ # SimApiError, SimApiUtil, SimApiAuth, SimApiCache, SimApiHttpClient,
│ │ # SimApiAesUtil(AES-256), SimApiSignChecker(验签), SimApiAesBodyChecker(AES body), │ │ # SimApiAesUtil(AES-256), SimApiSignChecker(验签), SimApiAesBodyChecker(AES body),
│ │ # SimApiStorage(S3/MinIO, 自实现 SigV4), SimApiRequestDelegateFactory, SimApiResultWriter │ │ # SimApiStorage(S3/MinIO, 自实现 SigV4), SimApiRequestDelegateFactory, SimApiResultWriter
│ ├── interfaces/ # ISimApiAuthChecker, IBindRequestContext, SimApiSignProviderBase, AesBodyProviderBase │ ├── interfaces/ # SimApiAuthChecker, BindRequestContext, SimApiSignProviderBase, AesBodyProviderBase
│ ├── logger/ # SimApiLogger, SimApiLoggerProvider(彩色日志) │ ├── logger/ # SimApiLogger, SimApiLoggerProvider(彩色日志)
│ ├── macros/ # ReadTomlVersion(编译期读版本号) │ ├── macros/ # ReadTomlVersion(编译期读版本号)
│ ├── middlewares/ # SimApiExceptionMiddleware, SimApiAuthMiddleware, SimApiRequestLogMiddleware │ ├── middlewares/ # SimApiExceptionMiddleware, SimApiAuthMiddleware, SimApiRequestLogMiddleware
@@ -155,12 +155,12 @@ auth.logoutAll("user-001") // 退出全部
- `"localhost:6379"`(基础) - `"localhost:6379"`(基础)
- `"localhost:6379,password=xxx"`(带密码) - `"localhost:6379,password=xxx"`(带密码)
- `"localhost:6379,password=xxx,db=2"`(带密码 + DB 索引) - `"localhost:6379,password=xxx,db=2"`(带密码 + DB 索引)
- **InMemory 模式**:零配置,适合开发/测试;登录态带过期时间(对齐 C# 过期语义),重启后丢失 - **InMemory 模式**:零配置,适合开发/测试;登录态带过期时间,重启后丢失
- **Token 传参**Header `Token: <value>` 或 Query `token=<value>` - **Token 传参**Header `Token: <value>` 或 Query `token=<value>`
### 2.1 声明式鉴权 — @SimApiAuth(对齐 C# [SimApiAuth] ### 2.1 声明式鉴权 — @SimApiAuth
标注在控制器**方法或类**上,请求派发时自动执行鉴权(未登录 401 → 类型不匹配 403 → 遍历执行 `ISimApiAuthChecker`): 标注在控制器**方法或类**上,请求派发时自动执行鉴权(未登录 401 → 类型不匹配 403 → 遍历执行 `SimApiAuthChecker`):
```cangjie ```cangjie
import simcu::simapi.annotations.{SimApiAuth} import simcu::simapi.annotations.{SimApiAuth}
@@ -174,11 +174,11 @@ public class MyController <: SimApiBaseController {
} }
``` ```
> 说明:仓颉注解参数须为编译期常量,`@SimApiAuth` 支持单个类型参数(`@SimApiAuth["admin"]`)或逗号分隔多类型(`@SimApiAuth["admin,user"]`,对齐 C# `type.Split(",")`);空参数表示任意已登录用户。 > 说明:仓颉注解参数须为编译期常量,`@SimApiAuth` 支持单个类型参数(`@SimApiAuth["admin"]`)或逗号分隔多类型(`@SimApiAuth["admin,user"]`);空参数表示任意已登录用户。
### 2.2 原样响应 — @OriginResponse ### 2.2 原样响应 — @OriginResponse
标注后跳过统一响应封装,接口返回什么就输出什么(对齐 C# `[OriginResponse]` 标注后跳过统一响应封装,接口返回什么就输出什么:
```cangjie ```cangjie
import simcu::simapi.annotations.{OriginResponse} import simcu::simapi.annotations.{OriginResponse}
@@ -190,7 +190,7 @@ public func raw(): String {
} }
``` ```
### 2.3 声明式验签 — @SimApiSign(对齐 C# [SimApiSign] ### 2.3 声明式验签 — @SimApiSign
标注在控制器**方法或类**上,请求派发时自动验签(appId 提取 → 密钥获取 → timestamp 过期校验 → nonce 去重 → MD5 比对): 标注在控制器**方法或类**上,请求派发时自动验签(appId 提取 → 密钥获取 → timestamp 过期校验 → nonce 去重 → MD5 比对):
@@ -210,9 +210,9 @@ public class MySignProvider <: SimApiSignProviderBase {
public func signedAction(): String { "ok" } public func signedAction(): String { "ok" }
``` ```
`SimApiSignProviderBase` 可配置:`appIdName` / `timestampName` / `nonceName` / `signName` / `queryExpires` / `duplicateRequestProtection` / `signFields`(与 C# 一致)。也可手动调用 `SimApiSignChecker.verify(context, provider, cache)` `SimApiSignProviderBase` 可配置:`appIdName` / `timestampName` / `nonceName` / `signName` / `queryExpires` / `duplicateRequestProtection` / `signFields`。也可手动调用 `SimApiSignChecker.verify(context, provider, cache)`
### 2.4 声明式 AES body — @AesBody(对齐 C# [AesBody] ### 2.4 声明式 AES body — @AesBody
标注在**参数**上,请求派发时自动解密 `{"data":"密文"}` body 并反序列化为参数类型: 标注在**参数**上,请求派发时自动解密 `{"data":"密文"}` body 并反序列化为参数类型:
@@ -252,14 +252,14 @@ Key 自动加前缀 `SimApi:Cache:`。
```cangjie ```cangjie
SimApiUtil.cstNow // UTC+8 时间 SimApiUtil.cstNow // UTC+8 时间
SimApiUtil.timestampNow // 秒级时间戳 SimApiUtil.timestampNow // 秒级时间戳
SimApiUtil.newGuid() // UUID v4(对齐 C# Guid.NewGuid() SimApiUtil.newGuid() // UUID v4
SimApiUtil.md5("text") // 32 位十六进制 SimApiUtil.md5("text") // 32 位十六进制
SimApiUtil.sha1("text") // 40 位 SimApiUtil.sha1("text") // 40 位
SimApiUtil.base64Encode("text") / base64Decode("...") SimApiUtil.base64Encode("text") / base64Decode("...")
SimApiUtil.base64Encode(obj) // 对象 → JSON → Base64(对齐 C# Base64Encode(object) SimApiUtil.base64Encode(obj) // 对象 → JSON → Base64
SimApiUtil.json(obj) // 对象 → JSON 字符串(simcu::serialization 反射) SimApiUtil.json(obj) // 对象 → JSON 字符串(simcu::serialization 反射)
SimApiUtil.escapeJson(s) // JSON 字符串转义 SimApiUtil.escapeJson(s) // JSON 字符串转义
SimApiUtil.fromJson<T>(json) // JSON → T对齐 C# FromJson<T>任意类免约束) SimApiUtil.fromJson<T>(json) // JSON → T(任意类免约束)
SimApiUtil.base64DecodeTo<T>(str) // Base64 → JSON → T SimApiUtil.base64DecodeTo<T>(str) // Base64 → JSON → T
SimApiUtil.checkCell("13800138000") // 手机号 SimApiUtil.checkCell("13800138000") // 手机号
SimApiUtil.checkEmail("a@b.com") // 邮箱 SimApiUtil.checkEmail("a@b.com") // 邮箱
@@ -267,9 +267,9 @@ SimApiUtil.checkEmail("a@b.com") // 邮箱
> JSON 序列化/反序列化统一走 **simcu::serialization**`JsonSerializer.Serialize` / `Deserialize<T>`),任意类免标注、免接口约束。 > JSON 序列化/反序列化统一走 **simcu::serialization**`JsonSerializer.Serialize` / `Deserialize<T>`),任意类免标注、免接口约束。
### 4.1 AES 加解密 — SimApiAesUtil(对齐 C# SimApiAesUtil ### 4.1 AES 加解密 — SimApiAesUtil
纯仓颉实现 AES-256-CBC + PKCS7S-box/密钥扩展/轮函数),与 .NET 双向互操作已验证: 纯仓颉实现 AES-256-CBC + PKCS7S-box/密钥扩展/轮函数),加解密结果跨语言互通已验证:
```cangjie ```cangjie
let encrypted = SimApiAesUtil.encrypt("明文", "key字符串") // Base64(随机IV + 密文) let encrypted = SimApiAesUtil.encrypt("明文", "key字符串") // Base64(随机IV + 密文)
@@ -279,7 +279,7 @@ let plain = SimApiAesUtil.decrypt(encrypted, "key字符串")
- 密钥:`SHA256(key 字符串)` → 32 字节;IV 每次随机 16 字节前置;输出 `Base64(IV + 密文)` - 密钥:`SHA256(key 字符串)` → 32 字节;IV 每次随机 16 字节前置;输出 `Base64(IV + 密文)`
-`SimApiHttpClient.aesQuery<T>` / `aesSignQuery<T>` 使用 -`SimApiHttpClient.aesQuery<T>` / `aesSignQuery<T>` 使用
### 4.2 实体基类 — SimApiBaseModel(对齐 C# SimApiBaseModel ### 4.2 实体基类 — SimApiBaseModel
```cangjie ```cangjie
import simcu::simapi.models.* import simcu::simapi.models.*
@@ -301,19 +301,19 @@ user.updateTime() // 刷新 _updatedAt
```cangjie ```cangjie
let client = SimApiHttpClient(options: SimApiHttpClientOptions()) // 配置 server/appId/appKey let client = SimApiHttpClient(options: SimApiHttpClientOptions()) // 配置 server/appId/appKey
// 返回泛型 T对齐 .NET SignQuery<T>/AesQuery<T>/AesSignQuery<T>),T 任意类免约束 // 返回泛型 TSignQuery<T>/AesQuery<T>/AesSignQuery<T>),T 任意类免约束
let resp1 = client.signQuery<SimApiLoginItem>("/api/hello", body: "{\"a\":1}") let resp1 = client.signQuery<SimApiLoginItem>("/api/hello", body: "{\"a\":1}")
let resp2 = client.aesQuery<SimApiLoginItem>("/api/data", body: "{\"a\":1}") let resp2 = client.aesQuery<SimApiLoginItem>("/api/data", body: "{\"a\":1}")
let resp3 = client.aesSignQuery<SimApiLoginItem>("/api/data", body: "{\"a\":1}") let resp3 = client.aesSignQuery<SimApiLoginItem>("/api/data", body: "{\"a\":1}")
``` ```
签名参数名可配置(`signName / timestampName / nonceName / appIdName / signFields`,对齐 C# 的 virtual 属性)。AES 请求体用 `SimApiOneFieldRequest<String>` 序列化为 `{"data":"密文"}`(对齐 C# 签名参数名可配置(`signName / timestampName / nonceName / appIdName / signFields`)。AES 请求体用 `SimApiOneFieldRequest<String>` 序列化为 `{"data":"密文"}`
### 5.1 请求日志 — enableRequestLog ### 5.1 请求日志 — enableRequestLog
记录每次请求的方法、URL、请求头、请求体、响应状态码、耗时与异常(对齐 C# 记录每次请求的方法、URL、请求头、请求体、响应状态码、耗时与异常:
- 请求体按 **JSON 字段级截断**(仅对超长字符串字段截断,保留结构;非 JSON 整串截断) - 请求体按 **JSON 字段级截断**(仅对超长字符串字段截断,保留结构;非 JSON 整串截断)
- 下游异常**捕获记录后重抛**(对齐 C# ExceptionDispatchInfo - 下游异常**捕获记录后重抛**
```cangjie ```cangjie
SimApiExtensions.addSimApi(builder) { options => SimApiExtensions.addSimApi(builder) { options =>
@@ -336,7 +336,7 @@ SimApiExtensions.addSimApi(builder) { options =>
### 5.2 日志格式 — SimApiLogger ### 5.2 日志格式 — SimApiLogger
`enableLogger`(默认 `true`)时自动使用 `SimApiLoggerProvider`,输出格式对齐 C# 原版 `enableLogger`(默认 `true`)时自动使用 `SimApiLoggerProvider`,输出格式与原版一致
``` ```
[ 分类 ][ 时间:毫秒 ][ 级别 ] [ 分类 ][ 时间:毫秒 ][ 级别 ]
@@ -345,7 +345,7 @@ SimApiExtensions.addSimApi(builder) { options =>
按级别着色:Debug 深紫 / Info 深青 / Warn 黄 / Error 红 / Fatal 深红。 按级别着色:Debug 深紫 / Info 深青 / Warn 黄 / Error 红 / Fatal 深红。
### 5.3 存储 — SimApiStorageS3/MinIO,对齐 C# SimApiStorage ### 5.3 存储 — SimApiStorageS3/MinIO
`enableSimApiStorage = true` 时注册 `SimApiStorage`Scoped,内部自实现 AWS Signature V4,基于 stdx.net.http,无需 Minio SDK): `enableSimApiStorage = true` 时注册 `SimApiStorage`Scoped,内部自实现 AWS Signature V4,基于 stdx.net.http,无需 Minio SDK):
@@ -371,7 +371,7 @@ SimApiExtensions.addSimApi(builder) { options =>
| `fullUrl(path)` / `getUrl(path)` | 补全访问 URL`~/` 前缀依赖请求上下文) | | `fullUrl(path)` / `getUrl(path)` | 补全访问 URL`~/` 前缀依赖请求上下文) |
| `getPath(url)` | 从 URL 还原相对路径(去掉 Endpoint/Bucket 或 ServeUrl 前缀) | | `getPath(url)` | 从 URL 还原相对路径(去掉 Endpoint/Bucket 或 ServeUrl 前缀) |
> 说明:桶不存在时自动创建(对齐 C# BucketExists + MakeBucket静态守卫只执行一次); > 说明:桶不存在时自动创建(静态守卫只执行一次);
> 预签名与上传使用 AWS SigV4HMAC-SHA256 基于 stdx SHA256 自实现),已用 AWS 官方测试向量验证签名正确。 > 预签名与上传使用 AWS SigV4HMAC-SHA256 基于 stdx SHA256 自实现),已用 AWS 官方测试向量验证签名正确。
### 6. 内置路由(UseSimApi 自动注册) ### 6. 内置路由(UseSimApi 自动注册)
@@ -392,21 +392,21 @@ options.configureSimApiRoute { route =>
} }
``` ```
### 7. 认证后处理 Hook — ISimApiAuthChecker ### 7. 认证后处理 Hook — SimApiAuthChecker
实现后每次认证成功都会调用(配合 `@SimApiAuth` 注解或手动 `requireLogin`): 实现后每次认证成功都会调用(配合 `@SimApiAuth` 注解或手动 `requireLogin`):
```cangjie ```cangjie
import simcu::simapi.interfaces.* import simcu::simapi.interfaces.*
class MyAuthChecker <: ISimApiAuthChecker { class MyAuthChecker <: SimApiAuthChecker {
public func run(loginItem: SimApiLoginItem, token: String): Unit { public func run(loginItem: SimApiLoginItem, token: String): Unit {
// 认证成功后执行 // 认证成功后执行
} }
} }
``` ```
### 8. 认证中心 SDK — AuthSDK(对齐 C# AuthSDK ### 8. 认证中心 SDK — AuthSDK
`enableSimApiAuthGate = true` 时注册 `SimApiAuthClient` / `SimApiAuthCenter` / `SimApiAuthIam` 单例并挂载网关透传中间件: `enableSimApiAuthGate = true` 时注册 `SimApiAuthClient` / `SimApiAuthCenter` / `SimApiAuthIam` 单例并挂载网关透传中间件:
@@ -457,7 +457,7 @@ SimApiExtensions.addSimApi(builder) { options =>
options.enableCors = true // 全量 CORS options.enableCors = true // 全量 CORS
options.enableLogger = true // 控制台日志 options.enableLogger = true // 控制台日志
// .NET 风格子模块配置回调(对齐 C# ConfigureSimApiXxx // 子模块配置回调(ConfigureSimApiXxx
options.configureSimApiRoute { route => options.configureSimApiRoute { route =>
route.userInfoRoute = Some("/user/info") // 内置路由自定义路径 route.userInfoRoute = Some("/user/info") // 内置路由自定义路径
route.logoutRoute = Some("/auth/logout") route.logoutRoute = Some("/auth/logout")
@@ -483,7 +483,7 @@ SimApiExtensions.addSimApi(builder) { options =>
## 内置控制器(MVC 写法) ## 内置控制器(MVC 写法)
simapi 提供 Spire MVC 控制器(继承 `SimApiBaseController`),`addSimApi` 自动注册内置控制器 + 自动扫描调用者包中的控制器(对齐 C# `Assembly.GetTypes()` 扫描) simapi 提供 Spire MVC 控制器(继承 `SimApiBaseController`),`addSimApi` 自动注册内置控制器 + 自动扫描调用者包中的控制器:
| 控制器 | 路由 | 说明 | | 控制器 | 路由 | 说明 |
|--------|------|------| |--------|------|------|
@@ -514,7 +514,7 @@ public class MyController <: SimApiBaseController {
## 未实现模块(选项占位) ## 未实现模块(选项占位)
以下 C# 原包功能因仓颉生态暂无对应库(Hangfire/MQTT/Swashbuckle),**选项保留但未实现**: 以下原包功能因仓颉生态暂无对应库(Hangfire/MQTT/Swashbuckle),**选项保留但未实现**:
| 选项 | 原功能 | 状态 | | 选项 | 原功能 | 状态 |
|------|--------|------| |------|--------|------|
@@ -522,7 +522,7 @@ public class MyController <: SimApiBaseController {
| `enableSynapse` | MQTT 通信 | ❌ 未实现 | | `enableSynapse` | MQTT 通信 | ❌ 未实现 |
| `enableJob` | Hangfire 任务调度 | ❌ 未实现 | | `enableJob` | Hangfire 任务调度 | ❌ 未实现 |
> ✅ 已实现(曾为占位):`enableSimApiStorage`S3/MinIO,自实现 AWS SigV4)、`enableSimApiAuthGate`AuthSDK 认证中心)、`SimApiAesUtil`(纯仓颉 AES-256-CBC,与 .NET 双向互操作)、`ISimApiAuthChecker`、`@SimApiSign` / `@AesBody` 声明式注解、内置路由自定义路径。 > ✅ 已实现(曾为占位):`enableSimApiStorage`S3/MinIO,自实现 AWS SigV4)、`enableSimApiAuthGate`AuthSDK 认证中心)、`SimApiAesUtil`(纯仓颉 AES-256-CBC)、`SimApiAuthChecker`、`@SimApiSign` / `@AesBody` 声明式注解、内置路由自定义路径。
--- ---
@@ -530,7 +530,7 @@ public class MyController <: SimApiBaseController {
| 依赖 | 用途 | | 依赖 | 用途 |
|------|------| |------|------|
| `soulsoft_web_http / routing / hosting` | Web 框架ASP.NET Core 仓颉移植版) | | `soulsoft_web_http / routing / hosting` | Web 框架 |
| `soulsoft_extensions_logging` 系列 | 日志 | | `soulsoft_extensions_logging` 系列 | 日志 |
| `soulsoft_extensions_injection` | 依赖注入 | | `soulsoft_extensions_injection` | 依赖注入 |
| `soulsoft_extensions_configuration` | 配置 | | `soulsoft_extensions_configuration` | 配置 |
+10 -10
View File
@@ -3,17 +3,17 @@ version = 0
[requires] [requires]
soulsoft_extensions_options_configuration = {version = "1.0.20260528"} soulsoft_extensions_options_configuration = {version = "1.0.20260528"}
soulsoft_web_http = {version = "1.0.20260528"} soulsoft_web_http = {version = "1.0.20260528"}
soulsoft_extensions_logging = {version = "1.0.20260528"}
soulsoft_extensions_options = {version = "1.0.20260528"}
soulsoft_web_routing = {version = "1.0.20260528"}
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
soulsoft_identity_claims = {version = "1.0.20260528"}
soulsoft_web_hosting = {version = "1.0.20260528"} soulsoft_web_hosting = {version = "1.0.20260528"}
soulsoft_web_routing = {version = "1.0.20260528"}
soulsoft_extensions_logging = {version = "1.0.20260528"}
soulsoft_web_cors = {version = "1.0.20260528"}
soulsoft_extensions_logging_console = {version = "1.0.20260528"}
redis = {version = "1.0.20260627"}
soulsoft_serialization = {version = "1.0.20260528"}
soulsoft_web_mvc = {version = "1.0.20260528"}
soulsoft_extensions_logging_configuration = {version = "1.0.20260528"} soulsoft_extensions_logging_configuration = {version = "1.0.20260528"}
soulsoft_extensions_hosting = {version = "1.0.20260528"} soulsoft_extensions_hosting = {version = "1.0.20260528"}
soulsoft_web_mvc = {version = "1.0.20260528"}
soulsoft_extensions_configuration = {version = "1.0.20260528"}
redis = {version = "1.0.20260627"}
soulsoft_web_cors = {version = "1.0.20260528"}
soulsoft_serialization = {version = "1.0.20260528"}
soulsoft_extensions_injection = {version = "1.0.20260528"} soulsoft_extensions_injection = {version = "1.0.20260528"}
soulsoft_identity_claims = {version = "1.0.20260528"}
soulsoft_extensions_configuration = {version = "1.0.20260528"}
soulsoft_extensions_options = {version = "1.0.20260528"}
+1 -1
View File
@@ -23,7 +23,7 @@
soulsoft_extensions_injection = "1.0.20260528" soulsoft_extensions_injection = "1.0.20260528"
soulsoft_extensions_options = "1.0.20260528" soulsoft_extensions_options = "1.0.20260528"
redis = "1.0.20260627" redis = "1.0.20260627"
"simcu::serialization" = { path = "../simapi-serialization" } "simcu::serialization" = { path = "../serialization-cj" }
[target] [target]
[target.x86_64-w64-mingw32] [target.x86_64-w64-mingw32]
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 声明式 AES body 解密注解(对齐 C# SimApi.Attributes.AesBodyAttribute * 声明式 AES body 解密注解。
* *
* 标注在控制器方法参数上,请求派发时(SimApiRequestDelegateFactory)自动解密并反序列化: * 标注在控制器方法参数上,请求派发时(SimApiRequestDelegateFactory)自动解密并反序列化:
* - 读取请求体 {"data": "密文"} * - 读取请求体 {"data": "密文"}
@@ -1,12 +1,12 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.annotations package simcu::simapi.annotations
/** /**
* 原样响应注解(对齐 C# SimApi.Attributes.OriginResponseAttribute * 原样响应注解。
* *
* 标注在控制器方法或类上,请求派发时 SimApiRequestDelegateFactory 跳过 * 标注在控制器方法或类上,请求派发时 SimApiRequestDelegateFactory 跳过
* 统一响应封装(SimApiBaseResponse 包装),接口返回什么就输出什么。 * 统一响应封装(SimApiBaseResponse 包装),接口返回什么就输出什么。
@@ -1,25 +1,25 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.annotations package simcu::simapi.annotations
/** /**
* 声明式鉴权注解(对齐 C# SimApi.Attributes.SimApiAuthAttribute * 声明式鉴权注解。
* *
* 标注在控制器方法或类上,请求派发时(SimApiRequestDelegateFactory)自动执行鉴权: * 标注在控制器方法或类上,请求派发时(SimApiRequestDelegateFactory)自动执行鉴权:
* - 未登录(无 LoginInfo)→ 401 * - 未登录(无 LoginInfo)→ 401
* - type 非空且登录用户类型不匹配 → 403 * - type 非空且登录用户类型不匹配 → 403
* - 遍历执行所有已注册的 ISimApiAuthChecker * - 遍历执行所有已注册的 SimApiAuthChecker
* *
* 用法: * 用法:
* @SimApiAuth // 任意已登录用户 * @SimApiAuth // 任意已登录用户
* @SimApiAuth["admin"] // 仅 admin 类型 * @SimApiAuth["admin"] // 仅 admin 类型
* @SimApiAuth["admin,user"] // admin 或 user 类型(对齐 C# type.Split(",") * @SimApiAuth["admin,user"] // admin 或 user 类型
* *
* 说明:仓颉注解参数须为编译期常量,String 无法作为 const 值数组元素, * 说明:仓颉注解参数须为编译期常量,String 无法作为 const 值数组元素,
* 故与 C# 的 string[] 不同,这里用逗号分隔字符串对齐 C# 多类型。 * 故用逗号分隔字符串表示多种类型。
*/ */
@Annotation[target: [MemberFunction, Type]] @Annotation[target: [MemberFunction, Type]]
public class SimApiAuth { public class SimApiAuth {
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 声明式签名校验注解(对齐 C# SimApi.Attributes.SimApiSignAttribute * 声明式签名校验注解。
* *
* 标注在控制器方法或类上,请求派发时(SimApiRequestDelegateFactory)自动执行验签: * 标注在控制器方法或类上,请求派发时(SimApiRequestDelegateFactory)自动执行验签:
* - 提取 appId / timestamp / nonce / signQuery 优先,其次 Header * - 提取 appId / timestamp / nonce / signQuery 优先,其次 Header
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* AuthSDK/SimApiAuthCenter:认证中心远程 SDK。 * AuthSDK/SimApiAuthCenter:认证中心远程 SDK。
*/ */
@@ -16,7 +16,7 @@ import simcu::simapi.communications.*
import simcu::simapi.helpers.* import simcu::simapi.helpers.*
/** /**
* 认证中心远程 SDK(对齐 C# SimApiAuthCenter * 认证中心远程 SDK
* 群组 / Profile / 内部应用 / 系统登录 / 安全验证 等接口,走签名请求。 * 群组 / Profile / 内部应用 / 系统登录 / 安全验证 等接口,走签名请求。
*/ */
public class SimApiAuthCenter { public class SimApiAuthCenter {
@@ -35,7 +35,7 @@ public class SimApiAuthCenter {
// ===== 公共 ===== // ===== 公共 =====
/** /**
* 委托 AuthCenter 进行应用签名验证(对齐 C# VerifySign * 委托 AuthCenter 进行应用签名验证。
*/ */
public func verifySign(appId: String, timestamp: String, nonce: String, sign: String): Unit { public func verifySign(appId: String, timestamp: String, nonce: String, sign: String): Unit {
let url = "${_client.server}/api/auth/sign/verify?appId=${appId}&timestamp=${timestamp}&nonce=${nonce}&sign=${sign}" let url = "${_client.server}/api/auth/sign/verify?appId=${appId}&timestamp=${timestamp}&nonce=${nonce}&sign=${sign}"
@@ -55,7 +55,7 @@ public class SimApiAuthCenter {
SimApiError.errorWhenFalse(isSuccess(response.status), code: Int64(response.status), SimApiError.errorWhenFalse(isSuccess(response.status), code: Int64(response.status),
message: "HTTP ERROR: ${response.status}") message: "HTTP ERROR: ${response.status}")
let json = readBodyText(response.body) let json = readBodyText(response.body)
let resp = JsonSerializer.Deserialize<SimApiBaseResponse>(json) let resp = JsonSerializer.deserialize<SimApiBaseResponse>(json)
SimApiError.errorWhen(resp.code != 200, code: 400, message: "签名验证失败") SimApiError.errorWhen(resp.code != 200, code: 400, message: "签名验证失败")
} finally { } finally {
response.close() response.close()
@@ -104,7 +104,7 @@ public class SimApiAuthCenter {
// ===== 群组相关 ===== // ===== 群组相关 =====
/** /**
* 根据 profileId 获取群组列表(对齐 C# GroupRelated * 根据 profileId 获取群组列表。
*/ */
public func groupRelated(profileId: String): Array<GroupRelatedItem> { public func groupRelated(profileId: String): Array<GroupRelatedItem> {
_client.signQuery<Array<GroupRelatedItem>>("/api/auth/group/related", _client.signQuery<Array<GroupRelatedItem>>("/api/auth/group/related",
@@ -112,7 +112,7 @@ public class SimApiAuthCenter {
} }
/** /**
* 按关键字搜索群组,输入群组 ID 精准搜索(对齐 C# GroupSearch * 按关键字搜索群组,输入群组 ID 精准搜索。
*/ */
public func groupSearch(keyword: String, skip!: Int64 = 0, take!: Int64 = 20): Array<AppAndProfileItem> { public func groupSearch(keyword: String, skip!: Int64 = 0, take!: Int64 = 20): Array<AppAndProfileItem> {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -123,7 +123,7 @@ public class SimApiAuthCenter {
} }
/** /**
* 使用组 ID 以及组内成员/管理员 profile 获取组的详细树结构(对齐 C# GroupDetail * 使用组 ID 以及组内成员/管理员 profile 获取组的详细树结构。
*/ */
public func groupDetail(groupId: String, profileId: String): GroupDetailTreeNode { public func groupDetail(groupId: String, profileId: String): GroupDetailTreeNode {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -133,7 +133,7 @@ public class SimApiAuthCenter {
} }
/** /**
* 获取 profile 在本组的所有子组(对齐 C# GroupRelatedIndex * 获取 profile 在本组的所有子组。
*/ */
public func groupRelatedIndex(groupId: String, profileId: String): Array<String> { public func groupRelatedIndex(groupId: String, profileId: String): Array<String> {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -146,7 +146,7 @@ public class SimApiAuthCenter {
// ===== Profile 相关 ===== // ===== Profile 相关 =====
/** /**
* 按关键字搜索用户 Profile(对齐 C# ProfileSearch * 按关键字搜索用户 Profile。
*/ */
public func profileSearch(keyword: String, skip!: Int64 = 0, take!: Int64 = 20): Array<AppAndProfileItem> { public func profileSearch(keyword: String, skip!: Int64 = 0, take!: Int64 = 20): Array<AppAndProfileItem> {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -157,7 +157,7 @@ public class SimApiAuthCenter {
} }
/** /**
* 通过 id 批量获取用户基本信息(对齐 C# ProfileList * 通过 id 批量获取用户基本信息。
*/ */
public func profileList(ids: Array<String>): Array<AppAndProfileItem> { public func profileList(ids: Array<String>): Array<AppAndProfileItem> {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -172,7 +172,7 @@ public class SimApiAuthCenter {
// ===== AuthGate 内部应用专用 ===== // ===== AuthGate 内部应用专用 =====
/** /**
* 获取是否为 App 的拥有者(对齐 C# CheckIsAppOwner,字段为 PascalCase * 获取是否为 App 的拥有者。
*/ */
public func checkIsAppOwner(profileId: String, applicationId: String): Bool { public func checkIsAppOwner(profileId: String, applicationId: String): Bool {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -182,7 +182,7 @@ public class SimApiAuthCenter {
} }
/** /**
* 根据用户 profileId 和提供的 appIds 获取应用列表(对齐 C# GetAppList,字段为 PascalCase * 根据用户 profileId 和提供的 appIds 获取应用列表。
*/ */
public func getAppList(profileId: String, appIds: Array<String>): Array<AppAndProfileItem> { public func getAppList(profileId: String, appIds: Array<String>): Array<AppAndProfileItem> {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -199,7 +199,7 @@ public class SimApiAuthCenter {
// ===== 系统登录 ===== // ===== 系统登录 =====
/** /**
* 获取登录授权 CODE(对齐 C# GetLoginCode * 获取登录授权 CODE。
* @param scene 场景标识。 * @param scene 场景标识。
* @param data 附加数据。 * @param data 附加数据。
* @param backUrl 回调地址。 * @param backUrl 回调地址。
@@ -217,12 +217,12 @@ public class SimApiAuthCenter {
} }
/** /**
* 使用 code 获取登录信息(对齐 C# GetLoginInfo,场景不匹配抛 403003 * 使用 code 获取登录信息。
*/ */
public func getLoginInfo(code: String, scene!: ?String = None): LoginInfoResponse { public func getLoginInfo(code: String, scene!: ?String = None): LoginInfoResponse {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
body["code"] = code body["code"] = code
// 说明:C# 的 ErrorWhenNull(resp, 400232, "登录信息获取失败") 对应 signQuery 内部 data.getOrThrow() 的 // 说明:ErrorWhenNull(resp, 400232, "登录信息获取失败") 对应 signQuery 内部 data.getOrThrow() 的
// None 分支;仓颉版 signQuery 返回非空 T(data 缺失即抛异常),故此处无需重复判空。 // None 分支;仓颉版 signQuery 返回非空 T(data 缺失即抛异常),故此处无需重复判空。
let resp = _client.signQuery<LoginInfoResponse>("/api/auth/login/get", body: SimApiUtil.json(Some(body))) let resp = _client.signQuery<LoginInfoResponse>("/api/auth/login/get", body: SimApiUtil.json(Some(body)))
SimApiError.errorWhen(resp.scene != scene, code: 403003, message: "登录场景不匹配") SimApiError.errorWhen(resp.scene != scene, code: 403003, message: "登录场景不匹配")
@@ -232,7 +232,7 @@ public class SimApiAuthCenter {
// ===== 安全验证 ===== // ===== 安全验证 =====
/** /**
* 获取安全验证代码(对齐 C# GetConfirmCode * 获取安全验证代码。
*/ */
public func getConfirmCode(scene: String, userId: String, data!: ?HashMap<String, Any> = None, public func getConfirmCode(scene: String, userId: String, data!: ?HashMap<String, Any> = None,
backUrl!: ?String = None): GetCodeResponse { backUrl!: ?String = None): GetCodeResponse {
@@ -247,7 +247,7 @@ public class SimApiAuthCenter {
} }
/** /**
* 使用安全验证 code 获取验证结果(对齐 C# Confirm,身份/场景不匹配分别抛 403002/403003 * 使用安全验证 code 获取验证结果。
*/ */
public func confirm(code: String, scene: String, userId!: ?String = None): ConfirmResponse { public func confirm(code: String, scene: String, userId!: ?String = None): ConfirmResponse {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* AuthSDK/SimApiAuthCenterMiddleware:网关透传认证中间件。 * AuthSDK/SimApiAuthCenterMiddleware:网关透传认证中间件。
*/ */
@@ -12,7 +12,7 @@ import simcu::simapi.configurations.*
import simcu::simapi.helpers.* import simcu::simapi.helpers.*
/** /**
* 网关透传认证中间件(对齐 C# SimApiAuthCenterMiddleware * 网关透传认证中间件:
* 当请求带 X-SimApi-Gate-Auth / X-SimApi-Gate-Time / X-SimApi-Gate-Sign 三头时, * 当请求带 X-SimApi-Gate-Auth / X-SimApi-Gate-Time / X-SimApi-Gate-Sign 三头时,
* 校验 MD5 签名(appId=..&auth=..&time=..&appKey=..),通过则 Base64 解码登录信息写入 LoginInfo。 * 校验 MD5 签名(appId=..&auth=..&time=..&appKey=..),通过则 Base64 解码登录信息写入 LoginInfo。
*/ */
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* AuthSDK/SimApiAuthClient:认证中心专用签名客户端。 * AuthSDK/SimApiAuthClient:认证中心专用签名客户端。
*/ */
@@ -10,7 +10,7 @@ import simcu::simapi.configurations.*
import simcu::simapi.helpers.* import simcu::simapi.helpers.*
/** /**
* 认证中心签名客户端(对齐 C# SimApiAuthClient * 认证中心签名客户端:
* SimApiHttpClient 子类,凭证(Server/AppId/AppKey)取自 SimApiAuthCenterOptions。 * SimApiHttpClient 子类,凭证(Server/AppId/AppKey)取自 SimApiAuthCenterOptions。
*/ */
public class SimApiAuthClient <: SimApiHttpClient { public class SimApiAuthClient <: SimApiHttpClient {
@@ -1,9 +1,9 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* AuthSDK 用到的 DTO(对齐 C# SimApiAuthCenterDto / SimApiAuthIamDto * AuthSDK 用到的 DTO。
* *
* 说明:C# 中这些 DTO 是 SimApiAuthCenterDto / SimApiAuthIamDto 的嵌套类; * 说明:原版中这些 DTO 是 SimApiAuthCenterDto / SimApiAuthIamDto 的嵌套类;
* 仓颉不支持在类体内声明嵌套类(unexpected class declaration in class body), * 仓颉不支持在类体内声明嵌套类(unexpected class declaration in class body),
* 故拍平为顶层类,语义与字段保持一致。 * 故拍平为顶层类,语义与字段保持一致。
* *
@@ -15,7 +15,7 @@ package simcu::simapi.authsdk
import std.collection.* import std.collection.*
/** /**
* 应用/Profile 通用项(对齐 C# AppAndProfileItem * 应用/Profile 通用项。
*/ */
public class AppAndProfileItem { public class AppAndProfileItem {
public var id: String = "" public var id: String = ""
@@ -25,8 +25,8 @@ public class AppAndProfileItem {
} }
/** /**
* 安全确认响应(对齐 C# ConfirmResponse * 安全确认响应。
* data 用 ?HashMap<String, Any> 对齐 C# Dictionary<string,object>?任意 JSON 对象 * data 用 ?HashMap<String, Any> 表示任意 JSON 对象。
*/ */
public class ConfirmResponse { public class ConfirmResponse {
public var applicationId: String = "" public var applicationId: String = ""
@@ -36,7 +36,7 @@ public class ConfirmResponse {
} }
/** /**
* 登录信息响应(对齐 C# LoginInfoResponse * 登录信息响应。
*/ */
public class LoginInfoResponse { public class LoginInfoResponse {
public var scene: ?String = None public var scene: ?String = None
@@ -48,7 +48,7 @@ public class LoginInfoResponse {
} }
/** /**
* 获取授权码响应(对齐 C# GetCodeResponse * 获取授权码响应。
*/ */
public class GetCodeResponse { public class GetCodeResponse {
public var code: String = "" public var code: String = ""
@@ -65,7 +65,7 @@ public class GetCodeResponse {
} }
/** /**
* 群组关联项(对齐 C# GroupRelatedItem * 群组关联项。
*/ */
public class GroupRelatedItem { public class GroupRelatedItem {
public var id: String = "" public var id: String = ""
@@ -78,7 +78,7 @@ public class GroupRelatedItem {
} }
/** /**
* 群组详情树节点(对齐 C# GroupDetailTreeNodechildren 递归) * 群组详情树节点。
*/ */
public class GroupDetailTreeNode { public class GroupDetailTreeNode {
public var id: String = "" public var id: String = ""
@@ -90,7 +90,7 @@ public class GroupDetailTreeNode {
} }
/** /**
* 权限项(对齐 C# PermissionItem * 权限项。
*/ */
public class PermissionItem { public class PermissionItem {
public var identifier: String = "" public var identifier: String = ""
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* AuthSDK/SimApiAuthIam:权限中心远程 SDK。 * AuthSDK/SimApiAuthIam:权限中心远程 SDK。
*/ */
@@ -11,7 +11,7 @@ import simcu::simapi.communications.*
import simcu::simapi.helpers.* import simcu::simapi.helpers.*
/** /**
* 权限中心远程 SDK(对齐 C# SimApiAuthIam * 权限中心远程 SDK
* 注册权限点 / 获取权限标识 / 校验权限。 * 注册权限点 / 获取权限标识 / 校验权限。
*/ */
public class SimApiAuthIam { public class SimApiAuthIam {
@@ -22,7 +22,7 @@ public class SimApiAuthIam {
} }
/** /**
* 向 IAM 注册权限(对齐 C# RegisterPermissions * 向 IAM 注册权限。
*/ */
public func registerPermissions(permissions: Array<PermissionItem>): Unit { public func registerPermissions(permissions: Array<PermissionItem>): Unit {
// 请求体:{"permissions":[{"identifier":...,"name":...,"group":...,"description":...},...]} // 请求体:{"permissions":[{"identifier":...,"name":...,"group":...,"description":...},...]}
@@ -41,7 +41,7 @@ public class SimApiAuthIam {
} }
/** /**
* 获取拥有的权限标识数组(对齐 C# GetPermissionOwned * 获取拥有的权限标识数组。
*/ */
public func getPermissionOwned(profileId: String, groupId!: ?String = None): Array<String> { public func getPermissionOwned(profileId: String, groupId!: ?String = None): Array<String> {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -53,7 +53,7 @@ public class SimApiAuthIam {
} }
/** /**
* 检测 profileId 是否有该权限,无权限抛 403(对齐 C# CheckPermission * 检测 profileId 是否有该权限,无权限抛 403。
*/ */
public func checkPermission(profileId: String, permission: String, groupId!: ?String = None): Unit { public func checkPermission(profileId: String, permission: String, groupId!: ?String = None): Unit {
var body = HashMap<String, Any>() var body = HashMap<String, Any>()
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 通用请求 DTO(对齐 C# Communications/SimApiBaseRequest.cs * 通用请求 DTO。
* *
* 说明:这些类仅作反序列化目标(FromJson<T> / @FromBody),无参构造由编译器自动提供; * 说明:这些类仅作反序列化目标(FromJson<T> / @FromBody),无参构造由编译器自动提供;
* 不声明显式构造器。 * 不声明显式构造器。
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.communications package simcu::simapi.communications
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.communications package simcu::simapi.communications
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* Communications/SimApiLoginItem:登录信息项。 * Communications/SimApiLoginItem:登录信息项。
*/ */
@@ -10,7 +10,6 @@ import std.collection.*
/** /**
* 登录信息项:Token 认证通过后注入请求上下文。 * 登录信息项:Token 认证通过后注入请求上下文。
* 对齐 C# SimApiLoginItemId / Type / Meta / Extra)。
* *
* 序列化/反序列化由 simapi_serialization 反射处理(免标注、免约束), * 序列化/反序列化由 simapi_serialization 反射处理(免标注、免约束),
* 字段 id/types/meta/extra 全部为受支持类型(String/Array/HashMap)。 * 字段 id/types/meta/extra 全部为受支持类型(String/Array/HashMap)。
@@ -1,13 +1,13 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiAuthCenterOptions.cs * AuthCenter 认证中心选项配置
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
/** /**
* 认证中心配置(对齐 C# SimApiAuthCenterOptions * 认证中心配置。
*/ */
public class SimApiAuthCenterOptions { public class SimApiAuthCenterOptions {
/** /**
@@ -1,7 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiDocOptions.cs。
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
@@ -9,7 +8,7 @@ package simcu::simapi.configurations
import std.collection.* import std.collection.*
/** /**
* 文档组配置(对齐 C# SimApiDocGroupOption * 文档组配置。
*/ */
public class SimApiDocGroup { public class SimApiDocGroup {
public var id: String = "" public var id: String = ""
@@ -26,13 +25,13 @@ public class SimApiDocGroup {
} }
/** /**
* 文档授权配置(对齐 C# SimApiAuthOption * 文档授权配置。
* Type 支持 "SimApiAuth"、"ClientCredentials"、"Implicit"、"AuthorizationCode"、"Password"。 * Type 支持 "SimApiAuth"、"ClientCredentials"、"Implicit"、"AuthorizationCode"、"Password"。
*/ */
public class SimApiAuthOption { public class SimApiAuthOption {
/** /**
* 认证方式(默认 ["SimApiAuth"])。 * 认证方式(默认 ["SimApiAuth"])。
* `type` 是仓颉关键字,用反引号转义以对齐 C# 属性名 Type * `type` 是仓颉关键字,用反引号转义保留属性名
*/ */
public var `type`: Array<String> = ["SimApiAuth"] public var `type`: Array<String> = ["SimApiAuth"]
@@ -58,7 +57,7 @@ public class SimApiAuthOption {
} }
/** /**
* 文档相关配置(默认值与 C# 一致)。 * 文档相关配置(默认值)。
*/ */
public class SimApiDocOptions { public class SimApiDocOptions {
/** /**
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiExceptionOptions.cs * 异常处理选项配置
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
@@ -9,7 +9,7 @@ package simcu::simapi.configurations
import std.collection.* import std.collection.*
/** /**
* 异常处理相关配置(默认值与 C# 一致)。 * 异常处理相关配置(默认值)。
*/ */
public class SimApiExceptionOptions { public class SimApiExceptionOptions {
/** /**
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiHttpClientOptions.cs * HTTP 客户端选项配置
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiJobOptions.cs * 后台任务选项配置(占位,未实现)
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
@@ -9,7 +9,7 @@ package simcu::simapi.configurations
import std.collection.* import std.collection.*
/** /**
* 任务调度服务器配置(对齐 C# SimApiJobServerConfig * 任务调度服务器配置。
*/ */
public class SimApiJobServer { public class SimApiJobServer {
/** /**
@@ -24,7 +24,7 @@ public class SimApiJobServer {
} }
/** /**
* 任务调度相关配置(默认值与 C# 一致)。 * 任务调度相关配置(默认值)。
*/ */
public class SimApiJobOptions { public class SimApiJobOptions {
/** /**
@@ -1,7 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiOptions.cs。
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
@@ -9,7 +8,7 @@ package simcu::simapi.configurations
import std.collection.* import std.collection.*
/** /**
* SimApi 全局配置:对应 C# 的 SimApi.Configurations.SimApiOptions * SimApi 全局配置。
*/ */
public class SimApiOptions { public class SimApiOptions {
/** /**
@@ -113,7 +112,7 @@ public class SimApiOptions {
public var simApiRequestLogOptions = SimApiRequestLogOptions() public var simApiRequestLogOptions = SimApiRequestLogOptions()
// ===== .NET 风格配置回调(对齐 C# ConfigureSimApiXxx(opt => ...) ===== // ===== 配置回调 =====
/** /**
* 配置路由选项。 * 配置路由选项。
@@ -172,7 +171,7 @@ public class SimApiOptions {
} }
/** /**
* 配置请求日志选项(对齐 C# ConfigureSimApiRequestLog * 配置请求日志选项。
* @param configure 请求日志配置回调。 * @param configure 请求日志配置回调。
*/ */
public func configureSimApiRequestLog(configure: (SimApiRequestLogOptions) -> Unit): Unit { public func configureSimApiRequestLog(configure: (SimApiRequestLogOptions) -> Unit): Unit {
@@ -180,7 +179,7 @@ public class SimApiOptions {
} }
/** /**
* 配置认证中心选项(对齐 C# ConfigureSimApiAuthCenter * 配置认证中心选项。
* @param configure 认证中心配置回调。 * @param configure 认证中心配置回调。
*/ */
public func configureSimApiAuthCenter(configure: (SimApiAuthCenterOptions) -> Unit): Unit { public func configureSimApiAuthCenter(configure: (SimApiAuthCenterOptions) -> Unit): Unit {
@@ -1,7 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiRequestLogOptions.cs。
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
@@ -16,7 +15,7 @@ public class SimApiRequestLogOptions {
public var showFullHeader: Bool = false public var showFullHeader: Bool = false
/** /**
* 是否打印完整的响应体(false 时截断到 200 字符,对齐 C#)。 * 是否打印完整的响应体(false 时截断到 200 字符)。
*/ */
public var showFullResponse: Bool = false public var showFullResponse: Bool = false
@@ -1,13 +1,13 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiRouteOptions.cs * 内置路由选项配置
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
/** /**
* 路由相关配置(默认值与 C# 一致)。 * 路由相关配置(默认值)。
*/ */
public class SimApiRouteOptions { public class SimApiRouteOptions {
/** /**
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiStorageOptions.cs * S3/MinIO 存储选项配置
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
@@ -1,13 +1,13 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 对齐 C# 的 Configurations/SimApiSynapseOptions.cs * Synapse 通信选项配置(占位,未实现)
*/ */
package simcu::simapi.configurations package simcu::simapi.configurations
/** /**
* MQTT 通信配置(默认值与 C# 一致)。 * MQTT 通信配置(默认值)。
*/ */
public class SimApiSynapseOptions { public class SimApiSynapseOptions {
/** /**
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* Controllers/SimApiAuthController:认证相关内置路由。 * Controllers/SimApiAuthController:认证相关内置路由。
*/ */
@@ -12,7 +12,6 @@ import simcu::simapi.helpers.*
/** /**
* 认证控制器:退出登录。 * 认证控制器:退出登录。
* 对齐 C# 的 SimApiAuthController。
*/ */
public class SimApiAuthController <: SimApiBaseController { public class SimApiAuthController <: SimApiBaseController {
private let _auth: SimApiAuth private let _auth: SimApiAuth
@@ -23,8 +22,8 @@ public class SimApiAuthController <: SimApiBaseController {
/** /**
* POST /auth/logout:退出登录(void 自动封装为 SimApiBaseResponse())。 * POST /auth/logout:退出登录(void 自动封装为 SimApiBaseResponse())。
* 对齐 C# LogoutRoute 默认值 /auth/logoutSimApiAuthController.Logout[HttpPost] * 对应路由 [HttpPost]
* 由 MapControllerRoute(pattern=LogoutRoute) 注册 * 由 MapControllerRoute(pattern=LogoutRoute) 注册。
*/ */
@HttpPost["/auth/logout"] @HttpPost["/auth/logout"]
public func logout(): Unit { public func logout(): Unit {
@@ -1,8 +1,8 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* Controllers/SimApiBaseController:基础控制器,所有控制器继承。 * Controllers/SimApiBaseController:基础控制器,所有控制器继承。
* 提供当前登录信息访问(对齐 C# 的 LoginInfo / LoginToken * 提供当前登录信息访问。
*/ */
package simcu::simapi.controllers package simcu::simapi.controllers
@@ -15,9 +15,8 @@ import simcu::simapi.interfaces.*
/** /**
* 基础控制器:所有控制器均继承本控制器。 * 基础控制器:所有控制器均继承本控制器。
* 对齐 C# 的 SimApiBaseController[Consumes]/[Produces] JSON + 登录信息)。
*/ */
public open class SimApiBaseController <: Controller & IBindRequestContext { public open class SimApiBaseController <: Controller & BindRequestContext {
/** /**
* 当前登录信息(需 EnableSimApiAuth;未登录抛 401)。 * 当前登录信息(需 EnableSimApiAuth;未登录抛 401)。
*/ */
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* Controllers/SimApiCommonController:通用内置路由。 * Controllers/SimApiCommonController:通用内置路由。
*/ */
@@ -14,8 +14,7 @@ import simcu::simapi.helpers.*
/** /**
* 通用控制器:错误反馈、WebConfig、用户信息。 * 通用控制器:错误反馈、WebConfig、用户信息。
* 对齐 C# 的 SimApiCommonController * 控制器直接返回 SimApiBaseResponse / SimApiResponse<T>
* 控制器直接返回 SimApiBaseResponse / SimApiResponse<T>(对齐 C# SimApiBaseResponse<T>)。
*/ */
public class SimApiCommonController <: SimApiBaseController { public class SimApiCommonController <: SimApiBaseController {
private let _options: SimApiOptions private let _options: SimApiOptions
@@ -26,7 +25,7 @@ public class SimApiCommonController <: SimApiBaseController {
/** /**
* GET /exception/{code}:错误反馈页面(始终注册)。 * GET /exception/{code}:错误反馈页面(始终注册)。
* 对齐 C# ExceptionHandler抛 SimApiException,由异常中间件统一输出。 * 抛 SimApiException,由异常中间件统一输出。
*/ */
@HttpGet["exception/{code}"] @HttpGet["exception/{code}"]
public func exceptionHandler(@FromRoute code: Int64): Unit { public func exceptionHandler(@FromRoute code: Int64): Unit {
@@ -35,8 +34,8 @@ public class SimApiCommonController <: SimApiBaseController {
/** /**
* POST/GET /config:给前端的自定义信息(含版本)。 * POST/GET /config:给前端的自定义信息(含版本)。
* 对齐 C# WebConfigRoute 默认值 /configSimApiCommonController.WebConfig[HttpPost, HttpGet] 无路径, * 对应路由 [HttpPost, HttpGet] 无路径,
* 由 MapControllerRoute(pattern=WebConfigRoute) 注册;soulsoft 约定路由不支持 defaults,故用特性路由直接对齐路径 * 由 MapControllerRoute(pattern=WebConfigRoute) 注册;soulsoft 约定路由不支持 defaults,故用特性路由直接对齐路径。
*/ */
@HttpGet["/config"] @HttpGet["/config"]
public func webConfig(): HashMap<String, Any> { public func webConfig(): HashMap<String, Any> {
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.exceptions package simcu::simapi.exceptions
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -14,21 +14,21 @@ import simcu::simapi.exceptions.*
import simcu::simapi.interfaces.* import simcu::simapi.interfaces.*
/** /**
* AES body 请求({"data": "密文"},对齐 C# SimApiOneFieldRequest<string>)。 * AES body 请求({"data": "密文"})。
*/ */
public class AesBodyRequest { public class AesBodyRequest {
public var data: String = "" public var data: String = ""
} }
/** /**
* 服务端 AES body 解密校验器(对齐 C# ModelBinders/AesBodyModelBinder * 服务端 AES body 解密校验器。
* *
* 仓颉无 ModelBinder 机制,按项目惯例由控制器在方法开头调用: * 仓颉无 ModelBinder 机制,按项目惯例由控制器在方法开头调用:
* let jsonStr = SimApiAesBodyChecker.decryptBody(context, provider) * let jsonStr = SimApiAesBodyChecker.decryptBody(context, provider)
* let request = JsonSerializer.Deserialize<XxxRequest>(jsonStr) * let request = JsonSerializer.deserialize<XxxRequest>(jsonStr)
* 或标注 @AesBody 注解自动执行(SimApiRequestDelegateFactory)。 * 或标注 @AesBody 注解自动执行(SimApiRequestDelegateFactory)。
* *
* 流程(与 C# 一致) * 流程:
* 1. 读取 body 并反序列化为 {"data": "密文"} * 1. 读取 body 并反序列化为 {"data": "密文"}
* 2. 校验 Data 非空 * 2. 校验 Data 非空
* 3. 提取 appIdQuery/Header * 3. 提取 appIdQuery/Header
@@ -53,7 +53,7 @@ public class SimApiAesBodyChecker {
} }
// 2. 反序列化 {"data": "密文"} // 2. 反序列化 {"data": "密文"}
let req = JsonSerializer.Deserialize<AesBodyRequest>(body) let req = JsonSerializer.deserialize<AesBodyRequest>(body)
if (req.data.isEmpty()) { if (req.data.isEmpty()) {
SimApiError.error(code: 400, message: "请求体缺少密文Data字段") SimApiError.error(code: 400, message: "请求体缺少密文Data字段")
} }
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -11,9 +11,9 @@ import stdx.crypto.digest.*
import stdx.encoding.base64.* import stdx.encoding.base64.*
/** /**
* AES-256-CBC + PKCS7 加解密工具(对齐 C# SimApiAesUtil * AES-256-CBC + PKCS7 加解密工具。
* *
* 约定(与 C# 完全一致) * 约定:
* - 密钥:SHA256(key 字符串) → 32 字节 * - 密钥:SHA256(key 字符串) → 32 字节
* - 模式:AES-256-CBCPKCS7 填充 * - 模式:AES-256-CBCPKCS7 填充
* - IV:每次加密随机生成 16 字节,前置在密文前 * - IV:每次加密随机生成 16 字节,前置在密文前
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -113,14 +113,14 @@ public class SimApiAuth {
let tokenKey = "${tokenCachePrefix}${token}" let tokenKey = "${tokenCachePrefix}${token}"
let json = loginItemJson(loginItem) let json = loginItemJson(loginItem)
if (let Some(redis) <- _redis) { if (let Some(redis) <- _redis) {
// 保留原过期时间(对齐 C# update 不刷新 TTL:先读旧 TTLSET 后重新续期 // 保留原过期时间:先读旧 TTL,SET 后重新续期
let ttl = redis.ttl(tokenKey) let ttl = redis.ttl(tokenKey)
redis.set(tokenKey, Blob.fromUtf8(json)) redis.set(tokenKey, Blob.fromUtf8(json))
if (ttl > 0) { if (ttl > 0) {
redis.expire(tokenKey, ttl) redis.expire(tokenKey, ttl)
} }
} else { } else {
// 保留原过期时间(对齐 C# update 不刷新 TTL // 保留原过期时间
let expireAt = match (_tokenStore.get(token)) { let expireAt = match (_tokenStore.get(token)) {
case Some(entry) => entry.expireAt case Some(entry) => entry.expireAt
case None => 0 case None => 0
@@ -162,10 +162,10 @@ public class SimApiAuth {
} }
} }
/// 解析登录信息;JSON 无效/非对象时返回 None(视为 token 无效,对齐 C# 返回 null /// 解析登录信息;JSON 无效/非对象时返回 None(视为 token 无效)
private static func parseLoginItemSafe(json: String): ?SimApiLoginItem { private static func parseLoginItemSafe(json: String): ?SimApiLoginItem {
try { try {
Some(JsonSerializer.Deserialize<SimApiLoginItem>(json)) Some(JsonSerializer.deserialize<SimApiLoginItem>(json))
} catch (_: Exception) { } catch (_: Exception) {
None None
} }
@@ -253,7 +253,7 @@ public class SimApiAuth {
} }
private static func generateToken(): String { private static func generateToken(): String {
// 对齐 C#token = Guid.NewGuid().ToString()(小写、8-4-4-4-12 // token = Guid.NewGuid().ToString()(小写、8-4-4-4-12
SimApiUtil.newGuid() SimApiUtil.newGuid()
} }
@@ -294,7 +294,7 @@ public class SimApiAuth {
} }
private static func loginItemJson(item: SimApiLoginItem): String { private static func loginItemJson(item: SimApiLoginItem): String {
// 统一 JSON 序列化:对齐 .NET JsonSerializer.Serialize(item) // 统一 JSON 序列化:JsonSerializer.Serialize(item)
JsonSerializer.Serialize(item) JsonSerializer.serialize(item)
} }
} }
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -28,7 +28,7 @@ private struct CacheEntry {
/** /**
* 缓存助手:Key 自动加前缀 "SimApi:Cache:"。 * 缓存助手:Key 自动加前缀 "SimApi:Cache:"。
* 存储后端与 SimApiAuth 一致:配置了 Redis 用 Redis(可带密码/DB 索引),否则 InMemory。 * 存储后端与 SimApiAuth 一致:配置了 Redis 用 Redis(可带密码/DB 索引),否则 InMemory。
* InMemory 模式同样支持过期(对齐 C# DistributedCache 的过期语义) * InMemory 模式同样支持过期。
*/ */
public class SimApiCache { public class SimApiCache {
private static let prefix = "SimApi:Cache:" private static let prefix = "SimApi:Cache:"
@@ -100,17 +100,17 @@ public class SimApiCache {
} }
/** /**
* 获取特定类型缓存(对齐 C# Get<T>从 JSON 反序列化,使用 simapi_serialization)。 * 获取特定类型缓存(从 JSON 反序列化,使用 simapi_serialization)。
*/ */
public func get<T>(key: String): ?T { public func get<T>(key: String): ?T {
match (getString(key)) { match (getString(key)) {
case Some(json) => Some(JsonSerializer.Deserialize<T>(json)) case Some(json) => Some(JsonSerializer.deserialize<T>(json))
case None => None case None => None
} }
} }
/** /**
* 获取 string 类型缓存(对应 C# Get(string)Cangjie 不支持按泛型重载,故拆分为 getString/get<T>)。 * 获取 string 类型缓存(仓颉不支持按泛型重载,故拆分为 getString/get<T>)。
*/ */
public func getString(key: String): ?String { public func getString(key: String): ?String {
if (let Some(redis) <- _redis) { if (let Some(redis) <- _redis) {
@@ -1,10 +1,10 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* Helpers/SimApiControllerScanner:自动发现调用者包中的 MVC 控制器。 * Helpers/SimApiControllerScanner:自动发现调用者包中的 MVC 控制器。
* *
* 对齐 C# 的控制器发现机制: * 控制器发现机制:
* - C# 通过 StackTrace 获取调用程序集,再 Assembly.GetTypes() 扫描所有类型 * - 通过 StackTrace 获取调用程序集,再 Assembly.GetTypes() 扫描所有类型
* - 仓颉版通过 Error.getStackTrace() 获取调用者包名,再 PackageInfo 枚举类型, * - 仓颉版通过 Error.getStackTrace() 获取调用者包名,再 PackageInfo 枚举类型,
* 过滤出继承 Controller 的类型(含子包) * 过滤出继承 Controller 的类型(含子包)
*/ */
@@ -35,20 +35,19 @@ public class SimApiControllerScanner {
} }
/** /**
* 扫描调用者包及其所有子包中 ISimApiAuthChecker 的实现类。 * 扫描调用者包及其所有子包中 SimApiAuthChecker 的实现类。
* 对齐 C# AddSimApi 中遍历调用者程序集 AddScoped 注册 checker 的机制。
* @return 找到的 checker 实现类型列表(不含抽象类型与接口本身)。 * @return 找到的 checker 实现类型列表(不含抽象类型与接口本身)。
*/ */
public static func scanAuthCheckers(): Array<TypeInfo> { public static func scanAuthCheckers(): Array<TypeInfo> {
let callerPackage = getCallerPackage() let callerPackage = getCallerPackage()
var result = ArrayList<TypeInfo>() var result = ArrayList<TypeInfo>()
collectImplementations(callerPackage, TypeInfo.of<ISimApiAuthChecker>(), result) collectImplementations(callerPackage, TypeInfo.of<SimApiAuthChecker>(), result)
result.toArray() result.toArray()
} }
/** /**
* 获取调用者(应用)包名:遍历栈帧,跳过 simapi/soulsoft/std 等框架包, * 获取调用者(应用)包名:遍历栈帧,跳过 simapi/soulsoft/std 等框架包,
* 返回第一个应用包的 declaringClass(对齐 C# 通过 StackTrace 找调用程序集) * 返回第一个应用包的 declaringClass。
*/ */
public static func getCallerPackage(): String { public static func getCallerPackage(): String {
try { try {
@@ -1,8 +1,8 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* *
* 错误抛出:提供「顶层函数」+「SimApiError 类」两种写法(对齐 C# using static SimApi.Helpers.SimApiError * 错误抛出:提供「顶层函数」+「SimApiError 类」两种写法。
* - 顶层函数:import simcu::simapi.helpers.* 后可直接 error(400) / errorWhen(...),无需前缀 * - 顶层函数:import simcu::simapi.helpers.* 后可直接 error(400) / errorWhen(...),无需前缀
* - SimApiError.error(...):旧写法,保留兼容 * - SimApiError.error(...):旧写法,保留兼容
*/ */
@@ -11,7 +11,7 @@ package simcu::simapi.helpers
import simcu::simapi.exceptions.* import simcu::simapi.exceptions.*
// ===== 顶层函数(推荐用法:直接 error(400),对齐 C# using static ===== // ===== 顶层函数(推荐用法:直接 error(400) =====
/** /**
* 直接抛错。 * 直接抛错。
@@ -47,17 +47,16 @@ public func errorWhenFalse(condition: Bool, code!: Int64 = 400, message!: String
} }
/** /**
* 给定的可选值None 时抛错。 * 给定的可选值None 时抛错,否则返回解包后的值
*/ */
public func errorWhenNull(condition: ?Any, code!: Int64 = 404, message!: String = ""): Unit { public func errorWhenNone<T>(value: ?T, code!: Int64 = 404, message!: String = ""): T {
SimApiError.errorWhenNull(condition, code: code, message: message) SimApiError.errorWhenNone(value, code: code, message: message)
} }
// ===== 兼容门面(旧写法 SimApiError.error(...) 仍可用,内部为真实实现) ===== // ===== 兼容门面(旧写法 SimApiError.error(...) 仍可用,内部为真实实现) =====
/** /**
* 错误抛出辅助类:所有业务错误统一通过这里抛出 SimApiException。 * 错误抛出辅助类:所有业务错误统一通过这里抛出 SimApiException。
* 对应 C# 的 SimApi.Helpers.SimApiError。
*/ */
public class SimApiError { public class SimApiError {
private init() {} private init() {}
@@ -98,12 +97,12 @@ public class SimApiError {
} }
/** /**
* 给定的可选值None 时抛错。 * 给定的可选值None 时抛错,否则返回解包后的值
*/ */
public static func errorWhenNull(condition: ?Any, code!: Int64 = 404, message!: String = ""): Unit { public static func errorWhenNone<T>(value: ?T, code!: Int64 = 404, message!: String = ""): T {
match (condition) { match (value) {
case None => error(code: code, message: message) case Some(v) => v
case _ => () case None => throw SimApiException(code, message: message)
} }
} }
} }
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -17,8 +17,8 @@ import simcu::simapi.exceptions.*
/** /**
* HTTP 客户端:用于调用其他带签名/AES 的 SimApi 服务。 * HTTP 客户端:用于调用其他带签名/AES 的 SimApi 服务。
* 对齐 C# 的 SimApi.Helpers.SimApiHttpClient * 说明
* - 内部使用 stdx.net.http 的 HttpClient(等价 .NET 的 System.Net.Http.HttpClient * - 内部使用 stdx.net.http 的 HttpClient(等价 System.Net.Http.HttpClient
* - 返回泛型 T(反序列化响应 body 的 data 字段),不再返回 String * - 返回泛型 T(反序列化响应 body 的 data 字段),不再返回 String
* @param T 响应 data 的数据类型(任意类,simapi_serialization 反射反序列化)。 * @param T 响应 data 的数据类型(任意类,simapi_serialization 反射反序列化)。
*/ */
@@ -41,7 +41,7 @@ public open class SimApiHttpClient {
/** /**
* 发起签名请求(GET query 签名 + POST body)。 * 发起签名请求(GET query 签名 + POST body)。
* 对齐 C# SignQuery<T>query 串 = SignFields + AppId + timestamp + nonce,整体拼 AppKey 取 MD5 作为 sign。 * SignQuery<T>query 串 = SignFields + AppId + timestamp + nonce,整体拼 AppKey 取 MD5 作为 sign。
* @param url 请求路径(相对路径,自动拼接 server)。 * @param url 请求路径(相对路径,自动拼接 server)。
* @param body 请求体 JSON 字符串(可选)。 * @param body 请求体 JSON 字符串(可选)。
* @param queries 额外查询参数(可选)。 * @param queries 额外查询参数(可选)。
@@ -74,7 +74,7 @@ public open class SimApiHttpClient {
/** /**
* 发起 AES 加密请求:body 加密后放入 {"data": "..."} 提交。 * 发起 AES 加密请求:body 加密后放入 {"data": "..."} 提交。
* 对齐 C# AesQuery<T>SimApiOneFieldRequest<string> { Data = Encrypt(body, AppKey) } * AesQuery<T>:对应 SimApiOneFieldRequest<string> { Data = Encrypt(body, AppKey) }。
* @param url 请求路径(相对路径,自动拼接 server)。 * @param url 请求路径(相对路径,自动拼接 server)。
* @param body 请求体 JSON 字符串。 * @param body 请求体 JSON 字符串。
* @return 响应 data 字段反序列化后的 T。 * @return 响应 data 字段反序列化后的 T。
@@ -91,7 +91,6 @@ public open class SimApiHttpClient {
/** /**
* 发起 AES 加密 + 签名请求。 * 发起 AES 加密 + 签名请求。
* 对齐 C# AesSignQuery<T>。
* @param url 请求路径(相对路径,自动拼接 server)。 * @param url 请求路径(相对路径,自动拼接 server)。
* @param body 请求体 JSON 字符串。 * @param body 请求体 JSON 字符串。
* @param queries 额外查询参数(可选)。 * @param queries 额外查询参数(可选)。
@@ -105,7 +104,7 @@ public open class SimApiHttpClient {
/** /**
* 发起 POST 请求并反序列化 SimApiResponse<T>,返回 data 字段。 * 发起 POST 请求并反序列化 SimApiResponse<T>,返回 data 字段。
* 对齐 C# Query<T> * Query<T>
* ErrorWhenFalse(IsSuccessStatusCode) → ReadFromJsonAsync<SimApiResponse<T>> → ErrorWhen(Code != 200) → return Data。 * ErrorWhenFalse(IsSuccessStatusCode) → ReadFromJsonAsync<SimApiResponse<T>> → ErrorWhen(Code != 200) → return Data。
* 注意:必须 noProxy(),否则会走系统代理(192.168.0.250:8118)导致连接被拒。 * 注意:必须 noProxy(),否则会走系统代理(192.168.0.250:8118)导致连接被拒。
* 反序列化使用 simapi_serializationDeserialize<T> 免约束)。 * 反序列化使用 simapi_serializationDeserialize<T> 免约束)。
@@ -128,7 +127,7 @@ public open class SimApiHttpClient {
SimApiError.errorWhenFalse(isSuccess(response.status), code: Int64(response.status), SimApiError.errorWhenFalse(isSuccess(response.status), code: Int64(response.status),
message: "HTTP ERROR: ${response.status}") message: "HTTP ERROR: ${response.status}")
let json = readBodyText(response.body) let json = readBodyText(response.body)
let result = JsonSerializer.Deserialize<SimApiResponse<T>>(json) let result = JsonSerializer.deserialize<SimApiResponse<T>>(json)
SimApiError.errorWhen(result.code != 200, code: result.code, message: result.message) SimApiError.errorWhen(result.code != 200, code: result.code, message: result.message)
return result.data.getOrThrow() return result.data.getOrThrow()
} finally { } finally {
@@ -168,12 +167,12 @@ public open class SimApiHttpClient {
} }
private func aesEncrypt(plain: String): String { private func aesEncrypt(plain: String): String {
// 对齐 C#SimApiAesUtil.Encrypt(plain, AppKey)AES-256-CBC + PKCS7Base64(IV + 密文) // SimApiAesUtil.Encrypt(plain, AppKey)AES-256-CBC + PKCS7Base64(IV + 密文)
SimApiAesUtil.encrypt(plain, appKey) SimApiAesUtil.encrypt(plain, appKey)
} }
private static func generateNonce(): String { private static func generateNonce(): String {
// 对齐 C#nonce 直接用 Guid.NewGuid() // nonce 直接用 Guid.NewGuid()
SimApiUtil.newGuid() SimApiUtil.newGuid()
} }
@@ -1,8 +1,8 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 自定义 IRequestDelegateFactory:在结果派发时自动封装响应, * 自定义 IRequestDelegateFactory:在结果派发时自动封装响应,
* 对齐 C# SimApiResponseFilterIResultFilter)的行为: * SimApiResponseFilterIResultFilter)的行为:
* - 返回 SimApiBaseResponse 或其子类 → 原样输出 * - 返回 SimApiBaseResponse 或其子类 → 原样输出
* - 返回 null/voidUnit)→ SimApiBaseResponse(){code:200, message:成功} * - 返回 null/voidUnit)→ SimApiBaseResponse(){code:200, message:成功}
* - 返回 String → SimApiResponse<String>data 为字符串) * - 返回 String → SimApiResponse<String>data 为字符串)
@@ -35,7 +35,7 @@ import simcu::simapi.interfaces.*
/** /**
* 自定义请求委托工厂:接管 soulsoft 的 ControllerRequestDelegateFactory * 自定义请求委托工厂:接管 soulsoft 的 ControllerRequestDelegateFactory
* 在结果派发时自动封装响应(对齐 C# SimApiResponseFilter * 在结果派发时自动封装响应。
*/ */
public class SimApiRequestDelegateFactory <: IRequestDelegateFactory { public class SimApiRequestDelegateFactory <: IRequestDelegateFactory {
private let _mvcOptions: MvcOptions private let _mvcOptions: MvcOptions
@@ -106,7 +106,7 @@ struct SimApiActionInvoker {
// Query/Form/Route/Header/Services → soulsoft // Query/Form/Route/Header/Services → soulsoft
bound[index] = soulsoftBound[index] bound[index] = soulsoftBound[index]
} else if (let Some(aes) <- parameter.findAnnotation<AesBody>()) { } else if (let Some(aes) <- parameter.findAnnotation<AesBody>()) {
// @AesBody → 解密 body 后按参数类型反序列化(对齐 C# AesBodyModelBinder // @AesBody → 解密 body 后按参数类型反序列化
bound[index] = bindAesBody(context, parameter, aes) bound[index] = bindAesBody(context, parameter, aes)
} else { } else {
// FromBody → simapi_serialization 按运行时类型反序列化(免 @Serialization 宏) // FromBody → simapi_serialization 按运行时类型反序列化(免 @Serialization 宏)
@@ -135,14 +135,14 @@ struct SimApiActionInvoker {
SimApiError.error(code: 400, message: "请求体不能为空") SimApiError.error(code: 400, message: "请求体不能为空")
} }
try { try {
return JsonSerializer.Deserialize(parameter.typeInfo, body) return JsonSerializer.deserialize(parameter.typeInfo, body)
} catch (ex: Exception) { } catch (ex: Exception) {
SimApiError.error(code: 400, message: "请求体反序列化失败: ${ex.message}") SimApiError.error(code: 400, message: "请求体反序列化失败: ${ex.message}")
} }
() ()
} }
/// @AesBody 参数绑定:解密 body 后按参数类型反序列化(对齐 C# AesBodyModelBinder /// @AesBody 参数绑定:解密 body 后按参数类型反序列化
private func bindAesBody(context: ActionBindingContext, parameter: ParameterInfo, aes: AesBody): Any { private func bindAesBody(context: ActionBindingContext, parameter: ParameterInfo, aes: AesBody): Any {
// 1. 从 DI 解析 keyProviderAesBodyProviderBase 实现) // 1. 从 DI 解析 keyProviderAesBodyProviderBase 实现)
let provider = resolveAesProvider(aes.keyProvider) let provider = resolveAesProvider(aes.keyProvider)
@@ -150,7 +150,7 @@ struct SimApiActionInvoker {
let plain = SimApiAesBodyChecker.decryptBody(context.httpContext, provider) let plain = SimApiAesBodyChecker.decryptBody(context.httpContext, provider)
// 3. 按参数类型反序列化明文 JSON // 3. 按参数类型反序列化明文 JSON
try { try {
return JsonSerializer.Deserialize(parameter.typeInfo, plain) return JsonSerializer.deserialize(parameter.typeInfo, plain)
} catch (ex: Exception) { } catch (ex: Exception) {
SimApiError.error(code: 400, message: "AES body 反序列化失败: ${ex.message}") SimApiError.error(code: 400, message: "AES body 反序列化失败: ${ex.message}")
} }
@@ -176,7 +176,7 @@ struct SimApiActionInvoker {
AesBodyProviderBase() AesBodyProviderBase()
} }
/// 检查 @SimApiSign 注解并执行验签(对齐 C# SimApiSignAttribute.OnActionExecuting /// 检查 @SimApiSign 注解并执行验签
private func checkSimApiSign() { private func checkSimApiSign() {
var sign: ?SimApiSign = None var sign: ?SimApiSign = None
for (item in actionDescriptor.endpointMetadata) { for (item in actionDescriptor.endpointMetadata) {
@@ -241,8 +241,8 @@ struct SimApiActionInvoker {
"" ""
} }
/// 检查 @SimApiAuth 注解并执行鉴权(对齐 C# SimApiAuthAttribute.OnActionExecuting /// 检查 @SimApiAuth 注解并执行鉴权:
/// 未登录 401 → 遍历执行 ISimApiAuthChecker → 类型权限 403 /// 未登录 401 → 遍历执行 SimApiAuthChecker → 类型权限 403
private func checkSimApiAuth() { private func checkSimApiAuth() {
var auth: ?SimApiAuthAttribute = None var auth: ?SimApiAuthAttribute = None
for (item in actionDescriptor.endpointMetadata) { for (item in actionDescriptor.endpointMetadata) {
@@ -265,17 +265,17 @@ struct SimApiActionInvoker {
SimApiError.error(code: 401, message: "需要登录") SimApiError.error(code: 401, message: "需要登录")
} }
// 2. 遍历执行 ISimApiAuthChecker(对齐 C# GetServices<ISimApiAuthChecker>():一次解析全部实现) // 2. 遍历执行 SimApiAuthChecker
let token = match (context.items.get("LoginToken")) { let token = match (context.items.get("LoginToken")) {
case Some(v) => if (let s: String <- v) { s } else { "" } case Some(v) => if (let s: String <- v) { s } else { "" }
case None => "" case None => ""
} }
let checkers = context.services.getAll<ISimApiAuthChecker>() let checkers = context.services.getAll<SimApiAuthChecker>()
for (checker in checkers) { for (checker in checkers) {
checker.run(loginItem, token) checker.run(loginItem, token)
} }
// 3. 类型权限校验 → 403(对齐 C# Types.Intersect(loginInfo.Type).Any(),支持逗号分隔多类型) // 3. 类型权限校验 → 403
if (!auth.`type`.isEmpty()) { if (!auth.`type`.isEmpty()) {
let requiredTypes = auth.`type`.split(",") let requiredTypes = auth.`type`.split(",")
var matched = false var matched = false
@@ -312,9 +312,9 @@ struct SimApiActionInvoker {
} }
} }
/// 结果派发 + 自动封装(对齐 C# SimApiResponseFilter /// 结果派发 + 自动封装
private func dispatchResult(actionResult: Any) { private func dispatchResult(actionResult: Any) {
// @OriginResponse:跳过统一封装,原样输出(对齐 C# OnResultExecuting 遇注解直接 return // @OriginResponse:跳过统一封装,原样输出
var originResponse = false var originResponse = false
for (item in actionDescriptor.endpointMetadata) { for (item in actionDescriptor.endpointMetadata) {
if (item is OriginResponse) { if (item is OriginResponse) {
@@ -324,7 +324,7 @@ struct SimApiActionInvoker {
} }
if (originResponse) { if (originResponse) {
if (let s: String <- actionResult) { if (let s: String <- actionResult) {
// String 原样输出文本(对齐 C# string 返回直接写入) // String 原样输出文本
context.response.contentType = "application/json; charset=utf-8" context.response.contentType = "application/json; charset=utf-8"
SimApiResponseWriter.write(context, s) SimApiResponseWriter.write(context, s)
} else { } else {
@@ -367,10 +367,10 @@ struct SimApiActionInvoker {
} }
/// 通过 DI 容器实例化控制器,并注入当前 HttpContext /// 通过 DI 容器实例化控制器,并注入当前 HttpContext
/// (通过 IBindRequestContext 接口而非 SimApiBaseController,避免 helpers↔controllers 循环依赖) /// (通过 BindRequestContext 接口而非 SimApiBaseController,避免 helpers↔controllers 循环依赖)
private func createControllerInstance(): Object { private func createControllerInstance(): Object {
let instance = ActivatorUtilities.createInstance(context.services, actionDescriptor.controllerType) let instance = ActivatorUtilities.createInstance(context.services, actionDescriptor.controllerType)
if (let controller: IBindRequestContext <- instance) { if (let controller: BindRequestContext <- instance) {
controller.bindRequestContext(context) controller.bindRequestContext(context)
} }
return instance return instance
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -11,7 +11,7 @@ import simcu::simapi.communications.*
/** /**
* 响应封装:对写操作(Unit)返回统一成功响应,对已有 SimApiBaseResponse 透传。 * 响应封装:对写操作(Unit)返回统一成功响应,对已有 SimApiBaseResponse 透传。
* 在仓颉版中以中间件形式实现,对应 C# 的 SimApiResponseFilter * 在仓颉版中以中间件形式实现。
*/ */
public class SimApiResponseFilter { public class SimApiResponseFilter {
@@ -26,7 +26,7 @@ public class SimApiResponseFilter {
next(context) next(context)
if (!context.response.hasStarted) { if (!context.response.hasStarted) {
context.response.contentType = "application/json; charset=utf-8" context.response.contentType = "application/json; charset=utf-8"
SimApiResponseWriter.write(context, JsonSerializer.Serialize(SimApiBaseResponse())) SimApiResponseWriter.write(context, JsonSerializer.serialize(SimApiBaseResponse()))
} }
} }
} }
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -11,7 +11,7 @@ import soulsoft_web_http.*
* 响应写出工具:写出响应体并缓存文本,供请求日志中间件读取。 * 响应写出工具:写出响应体并缓存文本,供请求日志中间件读取。
* *
* 背景:soulsoft 的 HttpResponse.body 为只写流(read 抛 UnsupportedException), * 背景:soulsoft 的 HttpResponse.body 为只写流(read 抛 UnsupportedException),
* 无法像 C# 那样用 MemoryStream 替换 Body 捕获响应内容; * 无法像原版那样用 MemoryStream 替换 Body 捕获响应内容;
* 故在统一写出入口缓存文本,请求日志中间件直接从 context.items 读取。 * 故在统一写出入口缓存文本,请求日志中间件直接从 context.items 读取。
*/ */
public class SimApiResponseWriter { public class SimApiResponseWriter {
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -11,7 +11,7 @@ import simcu::serialization.*
import simcu::simapi.communications.* import simcu::simapi.communications.*
/** /**
* 统一响应封装工具(对齐 C# SimApiResponseFilter 的包装分支) * 统一响应封装工具。
* 供 SimApiRequestDelegateFactory 与内置路由委托复用: * 供 SimApiRequestDelegateFactory 与内置路由委托复用:
* - SimApiBaseResponse(含子类)→ 原样输出 * - SimApiBaseResponse(含子类)→ 原样输出
* - String → SimApiResponse<String>data 为字符串) * - String → SimApiResponse<String>data 为字符串)
@@ -45,6 +45,6 @@ public class SimApiResultWriter {
/// simapi_serialization 序列化后直接写响应体(经 SimApiResponseWriter 缓存,供请求日志读取) /// simapi_serialization 序列化后直接写响应体(经 SimApiResponseWriter 缓存,供请求日志读取)
private static func writeJson(context: HttpContext, obj: Any): Unit { private static func writeJson(context: HttpContext, obj: Any): Unit {
context.response.contentType = "application/json; charset=utf-8" context.response.contentType = "application/json; charset=utf-8"
SimApiResponseWriter.write(context, JsonSerializer.Serialize(obj)) SimApiResponseWriter.write(context, JsonSerializer.serialize(obj))
} }
} }
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -11,13 +11,13 @@ import simcu::simapi.exceptions.*
import simcu::simapi.interfaces.* import simcu::simapi.interfaces.*
/** /**
* 服务端验签校验器(对齐 C# Attributes/SimApiSignAttribute.OnActionExecuting * 服务端验签校验器。
* *
* 仓颉无声明式 ActionFilter 机制,按项目惯例(同 requireLogin)由控制器在需要验签的方法开头调用: * 仓颉无声明式 ActionFilter 机制,按项目惯例(同 requireLogin)由控制器在需要验签的方法开头调用:
* SimApiSignChecker.verify(context, provider, cache) * SimApiSignChecker.verify(context, provider, cache)
* 或标注 @SimApiSign 注解自动执行(SimApiRequestDelegateFactory)。 * 或标注 @SimApiSign 注解自动执行(SimApiRequestDelegateFactory)。
* *
* 校验流程(与 C# 完全一致) * 校验流程:
* 1. 提取 appIdQuery/Header * 1. 提取 appIdQuery/Header
* 2. provider.getKey(appId) 获取密钥 * 2. provider.getKey(appId) 获取密钥
* 3. 提取并解析 timestamp / nonce * 3. 提取并解析 timestamp / nonce
@@ -111,7 +111,7 @@ public class SimApiSignChecker {
} }
} }
/// 从 Query 或 Header 取参数(Query 优先,对齐 C# FirstOrDefault 语义 /// 从 Query 或 Header 取参数(Query 优先)
private static func getParam(context: HttpContext, name: String): ?String { private static func getParam(context: HttpContext, name: String): ?String {
let q = context.request.query.get(name) let q = context.request.query.get(name)
if (q != None && q != Some("")) { if (q != None && q != Some("")) {
@@ -1,13 +1,13 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* Helpers/SimApiStorageS3/MinIO 兼容存储助手。 * Helpers/SimApiStorageS3/MinIO 兼容存储助手。
* *
* 仓颉生态暂无 Minio SDK,此处自实现 AWS Signature V4HMAC-SHA256 基于 stdx SHA256): * 仓颉生态暂无 Minio SDK,此处自实现 AWS Signature V4HMAC-SHA256 基于 stdx SHA256):
* - 预签名 URLPUT 上传 / GET 下载) * - 预签名 URLPUT 上传 / GET 下载)
* - 直接 PUT 上传(对象 + 建桶) * - 直接 PUT 上传(对象 + 建桶)
* - HEAD 检测桶是否存在 * - HEAD 检测桶是否存在
* 与 C# SimApiStorageMinio SDK)的公开方法语义对齐: * 公开方法语义对齐:
* GetUploadUrl / GetDownloadUrl / UploadFile / FullUrl / GetUrl / GetPath * GetUploadUrl / GetDownloadUrl / UploadFile / FullUrl / GetUrl / GetPath
*/ */
@@ -26,7 +26,7 @@ import soulsoft_web_http.*
import simcu::simapi.configurations.* import simcu::simapi.configurations.*
/** /**
* 上传预签名 URL 响应(对齐 C# record GetUploadUrlResponse(UploadUrl, DownloadUrl, Path) * 上传预签名 URL 响应。
*/ */
public class GetUploadUrlResponse { public class GetUploadUrlResponse {
public var uploadUrl: String = "" public var uploadUrl: String = ""
@@ -43,12 +43,12 @@ public class GetUploadUrlResponse {
} }
/** /**
* S3/MinIO 存储助手(对齐 C# Helpers/SimApiStorage * S3/MinIO 存储助手。
* *
* 说明: * 说明:
* - 注册为 Scoped 以注入 IHttpContextAccessorsoulsoft DI 禁止 singleton 消费 scoped 服务); * - 注册为 Scoped 以注入 IHttpContextAccessorsoulsoft DI 禁止 singleton 消费 scoped 服务);
* 桶的检测/创建由静态守卫保证整个进程只执行一次(对齐 C# 构造函数中 BucketExists+MakeBucket * 桶的检测/创建由静态守卫保证整个进程只执行一次。
* - fullUrl/getUrl 的 "~/" 分支依赖当前请求上下文(对齐 C# IHttpContextAccessor * - fullUrl/getUrl 的 "~/" 分支依赖当前请求上下文。
*/ */
public class SimApiStorage { public class SimApiStorage {
private static var _bucketEnsured: Bool = false private static var _bucketEnsured: Bool = false
@@ -89,12 +89,12 @@ public class SimApiStorage {
_useSsl = useSsl _useSsl = useSsl
_host = host _host = host
_httpContextAccessor = httpContextAccessor _httpContextAccessor = httpContextAccessor
// 桶不存在则创建(对齐 C# BucketExists + MakeBucket;静态守卫保证只执行一次) // 桶不存在则创建
ensureBucketOnce() ensureBucketOnce()
} }
/** /**
* 获取上传预签名 URL(对齐 C# GetUploadUrl,默认 7200 秒) * 获取上传预签名 URL。
*/ */
public func getUploadUrl(path: String, expire!: Int64 = 7200): GetUploadUrlResponse { public func getUploadUrl(path: String, expire!: Int64 = 7200): GetUploadUrlResponse {
checkPath(path) checkPath(path)
@@ -104,7 +104,7 @@ public class SimApiStorage {
} }
/** /**
* 获取下载预签名 URL(对齐 C# GetDownloadUrl,默认 600 秒) * 获取下载预签名 URL。
*/ */
public func getDownloadUrl(path: String, expire!: Int64 = 600): String { public func getDownloadUrl(path: String, expire!: Int64 = 600): String {
checkPath(path) checkPath(path)
@@ -113,7 +113,7 @@ public class SimApiStorage {
} }
/** /**
* 直接上传文件(对齐 C# UploadFiledata 为文件字节) * 直接上传文件。
*/ */
public func uploadFile(path: String, data: Array<Byte>, contentType!: String = "image/png"): Unit { public func uploadFile(path: String, data: Array<Byte>, contentType!: String = "image/png"): Unit {
checkPath(path) checkPath(path)
@@ -122,7 +122,7 @@ public class SimApiStorage {
} }
/** /**
* 批量删除对象(对齐 C# Minio RemoveObjectsAsyncS3 原生 DeleteObjects 接口: * 批量删除对象(S3 原生 DeleteObjects 接口:
* POST /{bucket}?delete,一次请求删除多个对象,无需逐个删除)。 * POST /{bucket}?delete,一次请求删除多个对象,无需逐个删除)。
* @param paths 对象路径数组(每个须以 / 开头)。 * @param paths 对象路径数组(每个须以 / 开头)。
*/ */
@@ -143,7 +143,7 @@ public class SimApiStorage {
} }
/** /**
* 使用 path 获取完整的访问 URL(对齐 C# FullUrl * 使用 path 获取完整的访问 URL。
*/ */
public func fullUrl(path: ?String): ?String { public func fullUrl(path: ?String): ?String {
if (let Some(p) <- path) { if (let Some(p) <- path) {
@@ -162,7 +162,7 @@ public class SimApiStorage {
} }
/** /**
* 获取一个 Path 的访问 URL(对齐 C# GetUrl * 获取一个 Path 的访问 URL。
*/ */
public func getUrl(path: ?String): ?String { public func getUrl(path: ?String): ?String {
if (let Some(p) <- path) { if (let Some(p) <- path) {
@@ -181,7 +181,7 @@ public class SimApiStorage {
} }
/** /**
* 从 URL 中获取相对路径(对齐 C# GetPath;去掉 Endpoint/Bucket 或 ServeUrl 前缀) * 从 URL 中获取相对路径。
*/ */
public func getPath(url: ?String): ?String { public func getPath(url: ?String): ?String {
if (let Some(u) <- url) { if (let Some(u) <- url) {
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.helpers package simcu::simapi.helpers
@@ -17,7 +17,7 @@ import simcu::simapi.communications.*
import simcu::simapi.macros.* import simcu::simapi.macros.*
/** /**
* 工具类:对应 C# 的 SimApi.Helpers.SimApiUtil * 工具类。
* 提供时间、哈希、Base64、JSON、校验等常用能力。 * 提供时间、哈希、Base64、JSON、校验等常用能力。
*/ */
public class SimApiUtil { public class SimApiUtil {
@@ -88,7 +88,7 @@ public class SimApiUtil {
toHexString(sha.finish()) toHexString(sha.finish())
} }
/// 按 .NET Md5/Sha1 的 mode 格式化:x2/x3/x4 → 每个字节 2/3/4 位十六进制 /// 按 Md5/Sha1 的 mode 格式化:x2/x3/x4 → 每个字节 2/3/4 位十六进制
private static func formatHex(bytes: Array<Byte>, mode: String): String { private static func formatHex(bytes: Array<Byte>, mode: String): String {
if (mode == "x2") { if (mode == "x2") {
return toHexString(bytes) return toHexString(bytes)
@@ -134,14 +134,14 @@ public class SimApiUtil {
/** /**
* 判断是否是 Email 地址。 * 判断是否是 Email 地址。
* 说明:.NET 使用 System.Net.Mail.MailAddress 校验,仓颉无等价 API,此处用正则近似。 * 说明:原版使用 System.Net.Mail.MailAddress 校验,仓颉无等价 API,此处用正则近似。
*/ */
public static func checkEmail(email: String): Bool { public static func checkEmail(email: String): Bool {
Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$").matches(email) Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$").matches(email)
} }
/** /**
* 生成 UUID v4 字符串(对齐 C# Guid.NewGuid().ToString():小写、8-4-4-4-12 连字符格式) * 生成 UUID v4 字符串。
* 仓颉标准库没有 GUID 生成器(stdx 的 GUID 是 stdx.net.http 内部类型), * 仓颉标准库没有 GUID 生成器(stdx 的 GUID 是 stdx.net.http 内部类型),
* 此处用随机数自行构造:16 字节随机数 + 版本位(4)+ 变体位(10)。 * 此处用随机数自行构造:16 字节随机数 + 版本位(4)+ 变体位(10)。
*/ */
@@ -172,18 +172,18 @@ public class SimApiUtil {
} }
/** /**
* 将对象序列化为 JSON 字符串(对齐 C# SimApiUtil.Json * 将对象序列化为 JSON 字符串。
* @param obj 任意对象(None 输出 null)。 * @param obj 任意对象(None 输出 null)。
*/ */
public static func json(obj: ?Any): String { public static func json(obj: ?Any): String {
if (let Some(obj) <- obj) { if (let Some(obj) <- obj) {
return JsonSerializer.Serialize(obj) return JsonSerializer.serialize(obj)
} }
"null" "null"
} }
/** /**
* JSON 字符串转义(对齐 C# 内部转义逻辑) * JSON 字符串转义。
* @param s 原始字符串。 * @param s 原始字符串。
* @return 转义后可直接放入 JSON 字符串字面量的内容。 * @return 转义后可直接放入 JSON 字符串字面量的内容。
*/ */
@@ -203,17 +203,17 @@ public class SimApiUtil {
} }
/** /**
* 从 JSON 字符串反序列化为 T(对齐 C# SimApiUtil.FromJson<T> * 从 JSON 字符串反序列化为 T。
* @param T 目标类型(任意类,无需接口/宏约束)。 * @param T 目标类型(任意类,无需接口/宏约束)。
* @param jsonString JSON 字符串。 * @param jsonString JSON 字符串。
* @return 反序列化结果。 * @return 反序列化结果。
*/ */
public static func fromJson<T>(jsonString: String): T { public static func fromJson<T>(jsonString: String): T {
JsonSerializer.Deserialize<T>(jsonString) JsonSerializer.deserialize<T>(jsonString)
} }
/** /**
* 对象 Base64 编码(对象 → JSON → Base64,对齐 C# Base64Encode(object))。 * 对象 Base64 编码(对象 → JSON → Base64)。
* @param obj 任意对象(DTO/基础类型/HashMap 等)。 * @param obj 任意对象(DTO/基础类型/HashMap 等)。
* @return Base64 字符串。 * @return Base64 字符串。
*/ */
@@ -223,7 +223,7 @@ public class SimApiUtil {
} }
/** /**
* Base64 → JSON → T 反序列化(对齐 C# Base64Decode<T> * Base64 → JSON → T 反序列化。
* @param T 目标类型(任意类,无需接口/宏约束)。 * @param T 目标类型(任意类,无需接口/宏约束)。
* @param base64Str Base64 字符串。 * @param base64Str Base64 字符串。
* @return 反序列化结果。 * @return 反序列化结果。
@@ -233,7 +233,7 @@ public class SimApiUtil {
} }
/** /**
* 分页(对齐 C# Paginate 扩展;仓颉无 IQueryable,改为对 Array<T> 切片) * 分页。
*/ */
public static func paginate<T>(list: Array<T>, page: Int64, count: Int64): 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 p = if (page < 1) { 1 } else { page }
@@ -247,5 +247,5 @@ public class SimApiUtil {
list[skip..end] list[skip..end]
} }
// 说明:C# 的 XmlDeserialize<T> 依赖 System.Xml.Serialization,仓颉生态无 XML 序列化库,未移植。 // 说明:XmlDeserialize<T> 依赖 System.Xml.Serialization,仓颉生态无 XML 序列化库,未移植。
} }
@@ -1,10 +1,10 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* AES body 密钥提供器基类(对齐 C# ModelBinders/AesBodyProviderBase * AES body 密钥提供器基类。
* *
* 说明:本类含配置字段(appIdName),故用 open class 而非 interface * 说明:本类含配置字段(appIdName),故用 open class 而非 interface
* (Cangjie 接口不能声明字段),与 .NET 抽象类对应。应用继承本类并实现 getKey。 * (Cangjie 接口不能声明字段),与抽象类对应。应用继承本类并实现 getKey。
*/ */
package simcu::simapi.interfaces package simcu::simapi.interfaces
@@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* Interfaces/IBindRequestContext:请求上下文绑定接口。 * Interfaces/BindRequestContext:请求上下文绑定接口。
* *
* 说明:SimApiBaseController 实现本接口,由 SimApiRequestDelegateFactorysimapi.helpers * 说明:SimApiBaseController 实现本接口,由 SimApiRequestDelegateFactorysimapi.helpers
* 在创建控制器后注入当前 HttpContext。放在 interfaces 包是为了避免 * 在创建控制器后注入当前 HttpContext。放在 interfaces 包是为了避免
@@ -15,7 +15,7 @@ import soulsoft_web_http.*
/** /**
* 请求上下文绑定接口:控制器实现后,框架在派发请求时把当前 HttpContext 注入。 * 请求上下文绑定接口:控制器实现后,框架在派发请求时把当前 HttpContext 注入。
*/ */
public interface IBindRequestContext { public interface BindRequestContext {
/** /**
* 绑定当前请求上下文。 * 绑定当前请求上下文。
* @param context 当前请求的 HttpContext。 * @param context 当前请求的 HttpContext。
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.interfaces package simcu::simapi.interfaces
@@ -9,9 +9,8 @@ import simcu::simapi.communications.*
/** /**
* 认证后处理 Hook:实现后每次认证成功都会调用。 * 认证后处理 Hook:实现后每次认证成功都会调用。
* 对应 C# 的 SimApi.Interfaces.ISimApiAuthChecker。
*/ */
public interface ISimApiAuthChecker { public interface SimApiAuthChecker {
/** /**
* 认证成功后执行。 * 认证成功后执行。
* @param loginItem 登录信息。 * @param loginItem 登录信息。
@@ -1,10 +1,10 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* 签名提供器基类(对齐 C# ModelBinders/SimApiSignProviderBase * 签名提供器基类。
* *
* 说明:本类含配置字段(appIdName/queryExpires 等),故用 open class 而非 interface * 说明:本类含配置字段(appIdName/queryExpires 等),故用 open class 而非 interface
* (Cangjie 接口不能声明字段),与 .NET 抽象类对应。应用继承本类并实现 getKey。 * (Cangjie 接口不能声明字段),与抽象类对应。应用继承本类并实现 getKey。
*/ */
package simcu::simapi.interfaces package simcu::simapi.interfaces
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.logger package simcu::simapi.logger
@@ -11,7 +11,7 @@ import std.time.*
import soulsoft_extensions_logging.* import soulsoft_extensions_logging.*
/** /**
* SimApi 日志记录器:输出格式对齐 C# 原版 SimApiLogger * SimApi 日志记录器。
* 格式:`[ 分类 ][ 时间 ][ 级别 ]\n消息\n[异常]` * 格式:`[ 分类 ][ 时间 ][ 级别 ]\n消息\n[异常]`
* 按日志级别着色输出。 * 按日志级别着色输出。
*/ */
@@ -39,7 +39,7 @@ public class SimApiLogger <: ILogger {
sb.append("${ex}\n") sb.append("${ex}\n")
} }
sb.append(resetColor()) sb.append(resetColor())
// 对齐 C# Console.WriteLinemessage 末尾 \n 之后再补一个 \n,形成空行分隔 // message 末尾 \n 之后再补一个 \n,形成空行分隔
sb.append("\n") sb.append("\n")
let writer = getStdOut() let writer = getStdOut()
@@ -52,7 +52,7 @@ public class SimApiLogger <: ILogger {
} }
/** /**
* 级别名称,对齐 C# 的 LogLevel.ToString() * 级别名称。
*/ */
private static func levelName(logLevel: LogLevel): String { private static func levelName(logLevel: LogLevel): String {
match (logLevel) { match (logLevel) {
@@ -67,7 +67,7 @@ public class SimApiLogger <: ILogger {
} }
/** /**
* 级别对应 ANSI 前景色(对齐 C# ConsoleColor * 级别对应 ANSI 前景色:
* Debug→DarkMagenta, Info→DarkCyan, Warn→Yellow, Error→Red, Fatal→DarkRed, 其他→White * Debug→DarkMagenta, Info→DarkCyan, Warn→Yellow, Error→Red, Fatal→DarkRed, 其他→White
*/ */
private static func levelColor(logLevel: LogLevel): String { private static func levelColor(logLevel: LogLevel): String {
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.middlewares package simcu::simapi.middlewares
@@ -11,7 +11,6 @@ import simcu::simapi.helpers.*
/** /**
* 认证信息获取中间件:从 Header Token 或 Query token 解析登录信息并注入上下文。 * 认证信息获取中间件:从 Header Token 或 Query token 解析登录信息并注入上下文。
* 对应 C# 的 SimApi.Middlewares.SimApiAuthMiddleware。
*/ */
public class SimApiAuthMiddleware <: IMiddleware { public class SimApiAuthMiddleware <: IMiddleware {
private let _auth: SimApiAuth private let _auth: SimApiAuth
@@ -21,7 +20,7 @@ public class SimApiAuthMiddleware <: IMiddleware {
} }
/** /**
* 中间件入口(IMiddleware 风格,对齐 C# UseMiddleware<T> 的 InvokeAsync)。 * 中间件入口(IMiddleware 风格)。
*/ */
public func invoke(context: HttpContext, next: RequestDelegate): Unit { public func invoke(context: HttpContext, next: RequestDelegate): Unit {
var token = context.request.headers.get("Token") var token = context.request.headers.get("Token")
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.middlewares package simcu::simapi.middlewares
@@ -15,7 +15,6 @@ import simcu::simapi.helpers.*
/** /**
* 异常处理中间件:全异常捕获,统一输出 HTTP 200 + JSON 响应。 * 异常处理中间件:全异常捕获,统一输出 HTTP 200 + JSON 响应。
* 对应 C# 的 SimApi.Middlewares.SimApiExceptionMiddleware。
*/ */
public class SimApiExceptionMiddleware <: IMiddleware { public class SimApiExceptionMiddleware <: IMiddleware {
private let _options: SimApiOptions private let _options: SimApiOptions
@@ -27,7 +26,7 @@ public class SimApiExceptionMiddleware <: IMiddleware {
} }
/** /**
* 中间件入口(IMiddleware 风格,对齐 C# UseMiddleware<T> 的 InvokeAsync)。 * 中间件入口(IMiddleware 风格)。
*/ */
public func invoke(context: HttpContext, next: RequestDelegate): Unit { public func invoke(context: HttpContext, next: RequestDelegate): Unit {
try { try {
@@ -71,6 +70,6 @@ public class SimApiExceptionMiddleware <: IMiddleware {
} }
private func responseJson(response: SimApiBaseResponse): String { private func responseJson(response: SimApiBaseResponse): String {
JsonSerializer.Serialize(response) JsonSerializer.serialize(response)
} }
} }
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.middlewares package simcu::simapi.middlewares
@@ -17,16 +17,15 @@ import simcu::simapi.helpers.*
/** /**
* 请求日志中间件:记录请求方法、URL、请求头、请求体、响应状态码、响应体、耗时与异常。 * 请求日志中间件:记录请求方法、URL、请求头、请求体、响应状态码、响应体、耗时与异常。
* 对应 C# 的 SimApi.Middlewares.SimApiRequestLogMiddleware。
* *
* 对齐说明: * 对齐说明:
* - 请求体按 JSON 字段级截断(对齐 C#:仅对超长字符串字段截断,保留结构) * - 请求体按 JSON 字段级截断
* - 捕获下游异常并记录,随后重抛(对齐 C# ExceptionDispatchInfo + edi.Throw * - 捕获下游异常并记录,随后重抛
* - 响应体:soulsoft HttpResponse.body 只读且不可读回(read 抛 UnsupportedException), * - 响应体:soulsoft HttpResponse.body 只读且不可读回(read 抛 UnsupportedException),
* 无法像 C# 那样用 MemoryStream 替换捕获;改为在各统一写出入口 * 无法像原版那样用 MemoryStream 替换捕获;改为在各统一写出入口
* SimApiResponseWriter)缓存响应文本,此处直接读取。 * SimApiResponseWriter)缓存响应文本,此处直接读取。
* 响应行格式:*( Response [status] ) => [ N bytes ],随后换行输出响应体结构; * 响应行格式:*( Response [status] ) => [ N bytes ],随后换行输出响应体结构;
* ShowFullResponse=false 时截断到 200 字符(对齐 C#,长度仍显示完整字节数) * ShowFullResponse=false 时截断到 200 字符
* - ShowFullUrl=false 时仅显示路径+查询串;ShowRunTime=true 时请求行显示 [POST] (xxxms) * - ShowFullUrl=false 时仅显示路径+查询串;ShowRunTime=true 时请求行显示 [POST] (xxxms)
*/ */
public class SimApiRequestLogMiddleware <: IMiddleware { public class SimApiRequestLogMiddleware <: IMiddleware {
@@ -39,7 +38,7 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
} }
/** /**
* 中间件入口(IMiddleware 风格,对齐 C# UseMiddleware<T> 的 InvokeAsync)。 * 中间件入口(IMiddleware 风格)。
*/ */
public func invoke(context: HttpContext, next: RequestDelegate): Unit { public func invoke(context: HttpContext, next: RequestDelegate): Unit {
let start = MonoTime.now() let start = MonoTime.now()
@@ -61,7 +60,7 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
sb.append("*( RequestBody ) =>\n") sb.append("*( RequestBody ) =>\n")
sb.append(readRequestBody(context)) sb.append(readRequestBody(context))
// 调用下一级,捕获异常以便记录并重抛(对齐 C# ExceptionDispatchInfo // 调用下一级,捕获异常以便记录并重抛
var exception: ?Exception = None var exception: ?Exception = None
try { try {
next(context) next(context)
@@ -96,7 +95,7 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
let display = if (_options.simApiRequestLogOptions.showFullResponse) { let display = if (_options.simApiRequestLogOptions.showFullResponse) {
responseBody responseBody
} else if (responseBody.size > 200) { } else if (responseBody.size > 200) {
// 对齐 C#ShowFullResponse=false 时截断到 200 字符(长度仍显示完整字节数) // ShowFullResponse=false 时截断到 200 字符(长度仍显示完整字节数)
responseBody[0..200] + "...(${responseBody.size})" responseBody[0..200] + "...(${responseBody.size})"
} else { } else {
responseBody responseBody
@@ -114,14 +113,14 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
_logger.info(sbHead.toString() + sb.toString()) _logger.info(sbHead.toString() + sb.toString())
// 重抛原异常(对齐 C# edi?.Throw(),由外层 ExceptionMiddleware 处理 // 重抛原异常,由外层 ExceptionMiddleware 处理
if (let Some(ex) <- exception) { if (let Some(ex) <- exception) {
throw ex throw ex
} }
} }
/// 构造完整请求 URL:stdx 服务端请求 URL 只有路径(无 scheme/host),需手动拼接。 /// 构造完整请求 URL:stdx 服务端请求 URL 只有路径(无 scheme/host),需手动拼接。
/// 对齐 C# 的 {Scheme}://{Host}{Path}{QueryString}Host 优先取 Host 请求头。 /// {Scheme}://{Host}{Path}{QueryString}Host 优先取 Host 请求头。
private func buildDisplayUrl(context: HttpContext): String { private func buildDisplayUrl(context: HttpContext): String {
var sb = StringBuilder() var sb = StringBuilder()
let scheme = context.request.scheme let scheme = context.request.scheme
@@ -196,7 +195,7 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
} }
} }
/// 请求体截断:JSON 字段级截断(对齐 C#:仅对超长字符串字段截断),非 JSON 则整串截断 /// 请求体截断:JSON 字段级截断,非 JSON 则整串截断
private func truncateBody(body: String): String { private func truncateBody(body: String): String {
let maxLen = _options.simApiRequestLogOptions.requestStringLogLength let maxLen = _options.simApiRequestLogOptions.requestStringLogLength
if (maxLen <= 0) { if (maxLen <= 0) {
@@ -204,7 +203,7 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
} }
try { try {
// 用 simapi_serialization 解析为动态结构,仅截断超长字符串字段(保持结构) // 用 simapi_serialization 解析为动态结构,仅截断超长字符串字段(保持结构)
let map = JsonSerializer.Deserialize<HashMap<String, Any>>(body) let map = JsonSerializer.deserialize<HashMap<String, Any>>(body)
var sb = StringBuilder() var sb = StringBuilder()
sb.append("{") sb.append("{")
var first = true var first = true
@@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
*/ */
package simcu::simapi.models package simcu::simapi.models
@@ -11,7 +11,7 @@ import std.time.*
import simcu::simapi.helpers.* import simcu::simapi.helpers.*
/** /**
* 实体基类(对齐 C# Models/SimApiBaseModel * 实体基类。
* 提供: * 提供:
* - Id(默认 Guid)、CreatedAt / UpdatedAt(默认当前时间) * - Id(默认 Guid)、CreatedAt / UpdatedAt(默认当前时间)
* - MapData:反射式字段映射(源 → 目标,同名 + 同类型;可忽略字段/白名单) * - MapData:反射式字段映射(源 → 目标,同名 + 同类型;可忽略字段/白名单)
@@ -30,7 +30,7 @@ public open class SimApiBaseModel {
/** /**
* 反射映射:把 source 的同名同类型非忽略字段赋值到 this(对齐 C# MapData(source, mapAll) * 反射映射:把 source 的同名同类型非忽略字段赋值到 this。
* @param source 源对象。 * @param source 源对象。
* @param mapAll 为 true 时连忽略字段(Id/CreatedAt/UpdatedAt)也映射。 * @param mapAll 为 true 时连忽略字段(Id/CreatedAt/UpdatedAt)也映射。
*/ */
@@ -46,7 +46,7 @@ public open class SimApiBaseModel {
} }
/** /**
* 反射映射:仅映射白名单字段(对齐 C# MapData(source, mapFields) * 反射映射:仅映射白名单字段。
* @param source 源对象。 * @param source 源对象。
* @param mapFields 白名单字段名。 * @param mapFields 白名单字段名。
*/ */
@@ -62,7 +62,7 @@ public open class SimApiBaseModel {
} }
/** /**
* 更新 UpdatedAt 为当前时间(对齐 C# UpdateTime * 更新 UpdatedAt 为当前时间。
*/ */
public func updateTime(): Unit { public func updateTime(): Unit {
let targetProps = collectProps(TypeInfo.of(this)) let targetProps = collectProps(TypeInfo.of(this))
@@ -1,8 +1,8 @@
/* /*
* Copyright (c) 2025 SimcuTeam. All rights reserved. * Copyright (c) 2025 SimcuTeam. All rights reserved.
* 移植自 C# 项目 SimApiE:\simcu\simapi-net),遵循 MIT 许可证。 * 遵循 MIT 许可证。
* *
* 对齐 .NET根命名空间 SimApi 下的静态类 SimApiExtensionsAddSimApi + UseSimApi)。 * 根命名空间 SimApi 下的静态类 SimApiExtensionsAddSimApi + UseSimApi)。
* 本文件同时是 simapi 根包的入口锚点:cjpm 要求 src 根目录至少有一个 .cj 文件, * 本文件同时是 simapi 根包的入口锚点:cjpm 要求 src 根目录至少有一个 .cj 文件,
* 否则不会扫描 src 子目录(helpers/controllers/...),整个包将编译为空。 * 否则不会扫描 src 子目录(helpers/controllers/...),整个包将编译为空。
* *
@@ -44,7 +44,7 @@ import simcu::simapi.logger.*
import simcu::simapi.middlewares.* import simcu::simapi.middlewares.*
/** /**
* SimApi 扩展入口(对齐 C# 根命名空间 SimApi 的静态类 SimApiExtensions * SimApi 扩展入口。
*/ */
public class SimApiExtensions { public class SimApiExtensions {
private init() {} private init() {}
@@ -58,10 +58,10 @@ public class SimApiExtensions {
public static func addSimApi(builder: WebHostBuilder, configure: (SimApiOptions) -> Unit): WebHostBuilder { public static func addSimApi(builder: WebHostBuilder, configure: (SimApiOptions) -> Unit): WebHostBuilder {
// 自动注册路由(对齐 builder.Services.AddRouting() // 自动注册路由(对齐 builder.Services.AddRouting()
builder.services.addRouting() builder.services.addRouting()
// 先构造配置,供后续按开关注册服务(对齐 C# AddSimApi 中先读 options 再注册) // 先构造配置,供后续按开关注册服务
let options = SimApiOptions() let options = SimApiOptions()
configure(options) configure(options)
// 响应封装(对齐 C# SimApiResponseFilter,受 EnableSimApiResponseFilter 开关控制) // 响应封装:
// 启用时注册自定义 IRequestDelegateFactory 自动封装响应。 // 启用时注册自定义 IRequestDelegateFactory 自动封装响应。
// 必须在 addControllers 之前:soulsoft 用 tryAddSingleton 注册,先到先得,不会被覆盖。 // 必须在 addControllers 之前:soulsoft 用 tryAddSingleton 注册,先到先得,不会被覆盖。
// 未启用时使用 soulsoft 默认派发(String→ContentResult / ISerializable→ObjectResult / 其余→204)。 // 未启用时使用 soulsoft 默认派发(String→ContentResult / ISerializable→ObjectResult / 其余→204)。
@@ -69,7 +69,7 @@ public class SimApiExtensions {
builder.services.addSingleton<IRequestDelegateFactory, SimApiRequestDelegateFactory>() builder.services.addSingleton<IRequestDelegateFactory, SimApiRequestDelegateFactory>()
} }
// 自动注册 MVC + 控制器(对齐 builder.Services.AddControllers() // 自动注册 MVC + 控制器(对齐 builder.Services.AddControllers()
// 自动扫描调用者包中的 Controller 子类(对齐 C# 的 Assembly.GetTypes() 扫描) // 自动扫描调用者包中的 Controller 子类
let controllers = SimApiControllerScanner.scan() let controllers = SimApiControllerScanner.scan()
addControllers(builder.services, controllers) addControllers(builder.services, controllers)
// 注册 SimApi 服务 // 注册 SimApi 服务
@@ -87,16 +87,16 @@ public class SimApiExtensions {
} }
/** /**
* 应用 SimApi 中间件与内置路由(日志输出对齐 C# UseSimApi)。 * 应用 SimApi 中间件与内置路由(日志输出)。
* @param host 构建完成的主机。 * @param host 构建完成的主机。
*/ */
public static func useSimApi(host: WebHost): Unit { public static func useSimApi(host: WebHost): Unit {
let options = host.services.getOrThrow<SimApiOptions>() let options = host.services.getOrThrow<SimApiOptions>()
let loggerFactory = host.services.getOrThrow<ILoggerFactory>() let loggerFactory = host.services.getOrThrow<ILoggerFactory>()
// 对齐 C# ILogger<SimApiOptions>分类名为 SimApiOptions 的全限定名 // 分类名为 SimApiOptions 的全限定名
let logger = loggerFactory.createLogger<SimApiOptions>() let logger = loggerFactory.createLogger<SimApiOptions>()
// ===== 基础信息(对齐 C# UseSimApi(IHost) 开头) ===== // ===== 基础信息 =====
let now = DateTime.now() let now = DateTime.now()
logger.info("当前时区: ${now.zoneId}") logger.info("当前时区: ${now.zoneId}")
logger.info("主应用版本: ${SimApiUtil.appVersion}\nSimApi版本: ${SimApiUtil.simApiVersion}") logger.info("主应用版本: ${SimApiUtil.appVersion}\nSimApi版本: ${SimApiUtil.simApiVersion}")
@@ -112,7 +112,7 @@ public class SimApiExtensions {
} }
// SimApiStorage(已实现:addSimApi 中注册 Scoped,桶初始化惰性执行; // SimApiStorage(已实现:addSimApi 中注册 Scoped,桶初始化惰性执行;
// 对齐 C# 的 GetService 预热,但 scoped 服务不能从根解析,故仅输出配置日志) // 但 scoped 服务不能从根解析,故仅输出配置日志)
if (options.enableSimApiStorage) { if (options.enableSimApiStorage) {
logger.info("开始配置 SimApiStorage...") logger.info("开始配置 SimApiStorage...")
} }
@@ -133,35 +133,35 @@ public class SimApiExtensions {
logger.info("开始配置 SimApiJob ...") logger.info("开始配置 SimApiJob ...")
} }
// ===== 中间件与路由(对齐 C# UseSimApi(WebApplication) 的挂载顺序) ===== // ===== 中间件与路由 =====
// C# 挂载顺序(先挂载 = 外层):ForwardedHeaders(L419) → CORS(L425) → AuthGate(L454) → Auth(L462) // 挂载顺序(先挂载 = 外层):ForwardedHeaders(L419) → CORS(L425) → AuthGate(L454) → Auth(L462)
// → 内置路由(L465-496) → Swagger(L502) → RequestLog(L519) → Exception(L525) → LowerUrl → Job // → 内置路由(L465-496) → Swagger(L502) → RequestLog(L519) → Exception(L525) → LowerUrl → Job
// ForwardedHeaders(占位:soulsoft 暂无内置,对齐 C# 最先挂载 // ForwardedHeaders(占位:soulsoft 暂无内置)
if (options.enableForwardHeaders) { if (options.enableForwardHeaders) {
logger.info("开始配置ForwardedHeaders...") logger.info("开始配置ForwardedHeaders...")
} }
// CORS(对齐 C# builder.UseCors("any") // CORS
if (options.enableCors) { if (options.enableCors) {
logger.info("开始配置 Cors全部允许...") logger.info("开始配置 Cors全部允许...")
host.useCors() host.useCors()
} }
// AuthGate(对齐 C# UseMiddleware<SimApiAuthCenterMiddleware> // AuthGate
if (options.enableSimApiAuthGate) { if (options.enableSimApiAuthGate) {
logger.info("开始配置 SimApiAuthGate...") logger.info("开始配置 SimApiAuthGate...")
host.use<SimApiAuthCenterMiddleware>() host.use<SimApiAuthCenterMiddleware>()
} }
// 认证中间件(对齐 C# builder.UseMiddleware<SimApiAuthMiddleware>() // 认证中间件
if (options.enableSimApiAuth) { if (options.enableSimApiAuth) {
logger.info("开始配置 SimApiAuth...") logger.info("开始配置 SimApiAuth...")
host.use<SimApiAuthMiddleware>() host.use<SimApiAuthMiddleware>()
} }
// 内置路由:RouteOptions 自定义路径时真实注册(默认路径已由内置控制器特性路由覆盖, // 内置路由:RouteOptions 自定义路径时真实注册(默认路径已由内置控制器特性路由覆盖,
// 对齐 C# MapControllerRoute 语义;soulsoft 无约定路由 defaults,用 mapGet/mapPost 委托实现) // soulsoft 无约定路由 defaults,用 mapGet/mapPost 委托实现)
let routeOptions = options.simApiRouteOptions let routeOptions = options.simApiRouteOptions
if (let Some(route) <- routeOptions.userInfoRoute) { if (let Some(route) <- routeOptions.userInfoRoute) {
if (route != "/user/info") { if (route != "/user/info") {
@@ -219,18 +219,18 @@ public class SimApiExtensions {
logger.info("注册内置Route: WebConfig => ${route}") logger.info("注册内置Route: WebConfig => ${route}")
} }
// SimApiDoc(占位,对齐 C# UseSwagger/UseSwaggerUI // SimApiDoc(占位soulsoft 暂无内置 Swagger 文档页
if (options.enableSimApiDoc) { if (options.enableSimApiDoc) {
logger.info("开始配置 SimApiDoc...") logger.info("开始配置 SimApiDoc...")
} }
// 请求日志中间件(对齐 C# builder.UseMiddleware<SimApiRequestLogMiddleware>() // 请求日志中间件
if (options.enableRequestLog) { if (options.enableRequestLog) {
logger.info("开始配置 SimApiRequestLog...") logger.info("开始配置 SimApiRequestLog...")
host.use<SimApiRequestLogMiddleware>() host.use<SimApiRequestLogMiddleware>()
} }
// 异常中间件最后挂载(最内层,对齐 C# builder.UseMiddleware<SimApiExceptionMiddleware>() // 异常中间件最后挂载(最内层)
if (options.enableSimApiException) { if (options.enableSimApiException) {
logger.info("开始配置 SimApiException...") logger.info("开始配置 SimApiException...")
host.use<SimApiExceptionMiddleware>() host.use<SimApiExceptionMiddleware>()
@@ -247,12 +247,12 @@ public class SimApiExtensions {
} }
// 响应封装(已实现:addSimApi 中按开关注册 SimApiRequestDelegateFactory 自动封装, // 响应封装(已实现:addSimApi 中按开关注册 SimApiRequestDelegateFactory 自动封装,
// 对齐 C# SimApiResponseFilter此处仅输出配置日志) // 此处仅输出配置日志)
if (options.enableSimApiResponseFilter) { if (options.enableSimApiResponseFilter) {
logger.info("开始配置 SimApiResponseFilter...") logger.info("开始配置 SimApiResponseFilter...")
} }
// 映射控制器端点(对齐 C# UseSimApi 中的 MapControllers // 映射控制器端点
let callSiteFactory = host.services.getOrThrow<IServiceProviderIsService>() let callSiteFactory = host.services.getOrThrow<IServiceProviderIsService>()
if (callSiteFactory.isService<ApplicationPartManager>()) { if (callSiteFactory.isService<ApplicationPartManager>()) {
host.mapControllers() host.mapControllers()
@@ -262,10 +262,10 @@ public class SimApiExtensions {
// ===== 私有辅助 ===== // ===== 私有辅助 =====
private static func addSimApiCore(builder: WebHostBuilder, options: SimApiOptions): WebHostBuilder { private static func addSimApiCore(builder: WebHostBuilder, options: SimApiOptions): WebHostBuilder {
// 注册单例配置(对齐 C# builder.AddSingleton(simApiOptions) // 注册单例配置
builder.services.addSingleton<SimApiOptions>(options) builder.services.addSingleton<SimApiOptions>(options)
// 子配置不单独注册:中间件统一注入 SimApiOptions 后访问其属性 // 子配置不单独注册:中间件统一注入 SimApiOptions 后访问其属性
// (对齐 C# SimApiExceptionMiddleware(..., SimApiOptions simApiOptions) 风格)
// 自定义日志格式(替换默认 console provider // 自定义日志格式(替换默认 console provider
if (options.enableLogger) { if (options.enableLogger) {
@@ -277,20 +277,20 @@ public class SimApiExtensions {
} }
// 中间件无需注册:挂载时由 ActivatorUtilities 从 DI 解析构造参数创建 // 中间件无需注册:挂载时由 ActivatorUtilities 从 DI 解析构造参数创建
// (对齐 C# builder.UseMiddleware<T>(),其中间件由 UseMiddleware 创建)
// 认证(DI 自动注入 SimApiOptions // 认证(DI 自动注入 SimApiOptions
if (options.enableSimApiAuth) { if (options.enableSimApiAuth) {
builder.services.addSingleton<SimApiAuth, SimApiAuth>() builder.services.addSingleton<SimApiAuth, SimApiAuth>()
} }
// ISimApiAuthChecker 自动扫描注册(对齐 C# AddSimApi 中遍历调用者程序集 AddScoped(ISimApiAuthChecker, type) // SimApiAuthChecker 自动扫描注册
// 扫描调用者包中的实现类,按【接口】注册;执行时用 getAll<ISimApiAuthChecker>() 一次解析全部实现, // 扫描调用者包中的实现类,按【接口】注册;执行时用 getAll<SimApiAuthChecker>() 一次解析全部实现,
// 无需在 SimApiOptions 里维护类型列表。 // 无需在 SimApiOptions 里维护类型列表。
if (options.enableSimApiAuth) { if (options.enableSimApiAuth) {
let checkers = SimApiControllerScanner.scanAuthCheckers() let checkers = SimApiControllerScanner.scanAuthCheckers()
for (checkerType in checkers) { for (checkerType in checkers) {
builder.services.addScoped(TypeInfo.of<ISimApiAuthChecker>(), checkerType) builder.services.addScoped(TypeInfo.of<SimApiAuthChecker>(), checkerType)
} }
} }
@@ -304,22 +304,22 @@ public class SimApiExtensions {
builder.services.addSingleton<SimApiHttpClient, SimApiHttpClient>() builder.services.addSingleton<SimApiHttpClient, SimApiHttpClient>()
} }
// 存储(S3/MinIO,对齐 C# AddHttpContextAccessor + AddSingleton<SimApiStorage> // 存储(S3/MinIO):
// 仓颉版注册为 Scoped 以便注入 IHttpContextAccessorDI 禁止 singleton 消费 scoped), // 仓颉版注册为 Scoped 以便注入 IHttpContextAccessorDI 禁止 singleton 消费 scoped),
// 桶初始化由静态守卫保证只执行一次 // 桶初始化由静态守卫保证只执行一次
if (options.enableSimApiStorage) { if (options.enableSimApiStorage) {
builder.services.addHttpContextAccessor() builder.services.addHttpContextAccessor()
builder.services.addScoped<SimApiStorage, SimApiStorage>() builder.services.addScoped<SimApiStorage, SimApiStorage>()
} }
// AuthGate 认证中心 SDK(对齐 C# 注册 SimApiAuthClient/Center/Iam 单例) // AuthGate 认证中心 SDK
if (options.enableSimApiAuthGate) { if (options.enableSimApiAuthGate) {
builder.services.addSingleton<SimApiAuthClient, SimApiAuthClient>() builder.services.addSingleton<SimApiAuthClient, SimApiAuthClient>()
builder.services.addSingleton<SimApiAuthCenter, SimApiAuthCenter>() builder.services.addSingleton<SimApiAuthCenter, SimApiAuthCenter>()
builder.services.addSingleton<SimApiAuthIam, SimApiAuthIam>() builder.services.addSingleton<SimApiAuthIam, SimApiAuthIam>()
} }
// CORS(对齐 C# builder.Services.AddCors(policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()) // CORS
if (options.enableCors) { if (options.enableCors) {
builder.services.addCors { builder.services.addCors {
cors => cors =>
@@ -335,7 +335,7 @@ public class SimApiExtensions {
return builder return builder
} }
/// 注册 MVC 服务,并注册 SimApi 内置控制器 + 用户控制器到 ApplicationPartManager(对齐 .NET AddControllers() /// 注册 MVC 服务,并注册 SimApi 内置控制器 + 用户控制器到 ApplicationPartManager。
private static func addControllers(services: ServiceCollection, controllerTypes: Array<TypeInfo>): MvcBuilder { private static func addControllers(services: ServiceCollection, controllerTypes: Array<TypeInfo>): MvcBuilder {
// 调用 soulsoft_web_mvc 的无参 addControllers() 注册 MVC 核心服务 // 调用 soulsoft_web_mvc 的无参 addControllers() 注册 MVC 核心服务
let mvc = services.addControllers() let mvc = services.addControllers()