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