2026-08-16 12:46:15 +08:00
|
|
|
|
# SimApi for Cangjie(simapi)
|
|
|
|
|
|
|
|
|
|
|
|
> 仓颉版 SimApi:ASP.NET Core 风格 API 基础框架,移植自 C# 项目 [SimApi](https://github.com/SimcuTeam/simapi-net)(`E:\simcu\simapi-net`)。
|
|
|
|
|
|
|
|
|
|
|
|
提供**统一响应格式、异常拦截、Token 认证、缓存、工具集、HTTP 客户端**等 API 基础能力。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
package your_app
|
|
|
|
|
|
|
|
|
|
|
|
import soulsoft_web_http.*
|
|
|
|
|
|
import soulsoft_web_routing.*
|
|
|
|
|
|
import soulsoft_web_hosting.*
|
|
|
|
|
|
import soulsoft_extensions_logging.*
|
|
|
|
|
|
import soulsoft_extensions_injection.*
|
|
|
|
|
|
import simapi.extensions.*
|
|
|
|
|
|
import simapi.communications.*
|
|
|
|
|
|
|
|
|
|
|
|
main(args: Array<String>) {
|
|
|
|
|
|
let builder = WebHost.createBuilder(args)
|
|
|
|
|
|
builder.services.addRouting()
|
|
|
|
|
|
builder.services.addLogging()
|
|
|
|
|
|
|
|
|
|
|
|
// 注册 SimApi 服务(与 addLogging 同样式)
|
|
|
|
|
|
builder.addSimApi { options =>
|
|
|
|
|
|
options.enableSimApiAuth = true // Token 认证(未配 Redis 自动用 InMemory)
|
|
|
|
|
|
options.enableSimApiCache = true // 缓存
|
|
|
|
|
|
options.enableSimApiException = true // 全局异常拦截
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let host = builder.build()
|
|
|
|
|
|
host.useSimApi()
|
|
|
|
|
|
|
|
|
|
|
|
// 业务接口:返回统一响应格式
|
|
|
|
|
|
host.mapGet("hello") {
|
|
|
|
|
|
context =>
|
|
|
|
|
|
context.response.write(SimApiBaseResponse().toJsonString(dataJson: "\"hello cangjie.\""))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
host.run()
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 统一响应格式
|
|
|
|
|
|
|
|
|
|
|
|
所有接口输出 JSON,HTTP 状态码始终 `200`,错误信息在 `code` 字段:
|
|
|
|
|
|
|
|
|
|
|
|
| code | 含义 |
|
|
|
|
|
|
| ---- | ---------- |
|
|
|
|
|
|
| 200 | 成功 |
|
|
|
|
|
|
| 204 | 无数据 |
|
|
|
|
|
|
| 400 | 参数错误 |
|
|
|
|
|
|
| 401 | 需要登录 |
|
|
|
|
|
|
| 403 | 无权访问 |
|
|
|
|
|
|
| 404 | 资源不存在 |
|
|
|
|
|
|
| 500 | 服务器错误 |
|
|
|
|
|
|
|
|
|
|
|
|
### 异常处理流程
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
请求 → SimApiExceptionMiddleware(全异常捕获→HTTP 200+JSON)
|
|
|
|
|
|
→ SimApiAuthMiddleware(Token→LoginInfo)
|
|
|
|
|
|
→ 路由 → 业务处理
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 项目结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
simapi-cj/
|
|
|
|
|
|
├── cjpm.toml # 包配置
|
|
|
|
|
|
├── src/
|
2026-08-16 22:47:05 +08:00
|
|
|
|
│ ├── attributes/ # 声明式注解:@SimApiAuth(鉴权)、@OriginResponse(原样响应)
|
|
|
|
|
|
│ ├── authsdk/ # 认证中心 SDK:SimApiAuthClient/Center/Iam + 网关中间件 + DTO
|
|
|
|
|
|
│ ├── communications/ # SimApiBaseResponse, PageResponse, SimApiLoginItem, 请求 DTO
|
2026-08-16 12:46:15 +08:00
|
|
|
|
│ ├── configurations/ # SimApiOptions + 各模块 Option(含 ConfigureSimApiXxx 回调)
|
|
|
|
|
|
│ ├── controllers/ # SimApiBaseController, SimApiCommonController, SimApiAuthController(MVC 写法)
|
|
|
|
|
|
│ ├── exceptions/ # SimApiException
|
2026-08-16 22:47:05 +08:00
|
|
|
|
│ ├── extensions/ # SimApiExtensions(addSimApi / useSimApi + 内置路由 + 响应封装)
|
|
|
|
|
|
│ ├── helpers/ # SimApiError, SimApiUtil, SimApiAuth, SimApiCache, SimApiHttpClient,
|
|
|
|
|
|
│ │ # SimApiAesUtil(AES-256), SimApiSignChecker(验签), SimApiAesBodyChecker(AES body)
|
2026-08-16 12:46:15 +08:00
|
|
|
|
│ ├── interfaces/ # ISimApiAuthChecker
|
|
|
|
|
|
│ ├── logger/ # SimApiLogger, SimApiLoggerProvider(彩色日志)
|
2026-08-16 22:47:05 +08:00
|
|
|
|
│ ├── macros/ # ReadTomlVersion(编译期读版本号)
|
|
|
|
|
|
│ ├── middlewares/ # SimApiExceptionMiddleware, SimApiAuthMiddleware, SimApiRequestLogMiddleware
|
|
|
|
|
|
│ └── models/ # SimApiBaseModel(实体基类)
|
2026-08-16 12:46:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 模块说明
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 错误处理 — SimApiError
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.helpers.*
|
|
|
|
|
|
|
|
|
|
|
|
SimApiError.error(500, "服务器内部错误") // 直接抛错
|
|
|
|
|
|
SimApiError.errorWhen(amount <= 0, 400, "金额无效") // 条件为 true 时抛错
|
|
|
|
|
|
SimApiError.errorWhenFalse(hasPermission, 403, "无权操作")
|
|
|
|
|
|
SimApiError.errorWhenNone(someOptional, 404, "用户不存在")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 认证 — SimApiAuth
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.helpers.*
|
|
|
|
|
|
import simapi.communications.*
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
// 由 DI 注入(构造参数 options: SimApiOptions,从配置读 RedisConfiguration;未配则 InMemory)
|
|
|
|
|
|
let auth: SimApiAuth = ... // 例:控制器构造注入
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
let token = auth.login(SimApiLoginItem(id: "user-001")) // 默认 7 天
|
|
|
|
|
|
let login = auth.getLogin(token) // 获取登录信息
|
|
|
|
|
|
auth.logout(token) // 退出登录
|
|
|
|
|
|
auth.logoutAll("user-001") // 退出全部
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
- **Redis 模式**:配置 `RedisConfiguration` 时使用,支持多实例共享。连接串格式:
|
|
|
|
|
|
- `"localhost:6379"`(基础)
|
|
|
|
|
|
- `"localhost:6379,password=xxx"`(带密码)
|
|
|
|
|
|
- `"localhost:6379,password=xxx,db=2"`(带密码 + DB 索引)
|
|
|
|
|
|
- **InMemory 模式**:零配置,适合开发/测试;登录态带过期时间(对齐 C# 过期语义),重启后丢失
|
2026-08-16 12:46:15 +08:00
|
|
|
|
- **Token 传参**:Header `Token: <value>` 或 Query `token=<value>`
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
### 2.1 声明式鉴权 — @SimApiAuth(注解类,对齐 C# [SimApiAuth])
|
|
|
|
|
|
|
|
|
|
|
|
标注在控制器**方法或类**上,请求派发时自动执行鉴权(未登录 401 → 类型不匹配 403 → 遍历执行 `ISimApiAuthChecker`),替代手动 `requireLogin()`:
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.attributes.{SimApiAuth}
|
|
|
|
|
|
|
|
|
|
|
|
@SimApiAuth // 类级:整个控制器需登录
|
|
|
|
|
|
public class MyController <: SimApiBaseController {
|
|
|
|
|
|
|
|
|
|
|
|
@SimApiAuth["admin"] // 方法级:仅 admin 类型可访问
|
|
|
|
|
|
@HttpPost["my/admin-only"]
|
|
|
|
|
|
public func adminOnly(): String { "ok" }
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> 说明:仓颉注解参数须为编译期常量,`@SimApiAuth` 支持单类型参数(`@SimApiAuth["admin"]`);空参数表示任意已登录用户。多个 `ISimApiAuthChecker` 通过 `SimApiOptions.authCheckers` 注册(由 addSimApi 扫描调用者包填充)。
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 原样响应 — @OriginResponse
|
|
|
|
|
|
|
|
|
|
|
|
标注后跳过统一响应封装,接口返回什么就输出什么(对齐 C# `[OriginResponse]`):
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.attributes.{OriginResponse}
|
|
|
|
|
|
|
|
|
|
|
|
@OriginResponse
|
|
|
|
|
|
@HttpGet["raw"]
|
|
|
|
|
|
public func raw(): String {
|
|
|
|
|
|
"{\"raw\":true}" // 直接输出,不包 {code,message,data}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 服务端验签 — SimApiSignChecker(对齐 C# [SimApiSign])
|
|
|
|
|
|
|
|
|
|
|
|
校验带签名请求(appId 提取 → 密钥获取 → timestamp 过期校验 → nonce 去重 → MD5 比对):
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.helpers.{SimApiSignProviderBase, SimApiSignChecker}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 继承 Provider 实现密钥获取
|
|
|
|
|
|
public class MySignProvider <: SimApiSignProviderBase {
|
|
|
|
|
|
public override func getKey(appId: ?String): ?String {
|
|
|
|
|
|
// 根据 appId 返回密钥(如查库)
|
|
|
|
|
|
Some("my-secret-key")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 控制器方法开头调用校验
|
|
|
|
|
|
public func signedAction(): String {
|
|
|
|
|
|
SimApiSignChecker.verify(context, provider, cache)
|
|
|
|
|
|
"ok"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Provider 可配置:`appIdName` / `timestampName` / `nonceName` / `signName` / `queryExpires` / `duplicateRequestProtection` / `signFields`(与 C# `SimApiSignProviderBase` 一致)。
|
|
|
|
|
|
|
|
|
|
|
|
### 2.4 AES body 解密 — SimApiAesBodyChecker(对齐 C# [AesBody])
|
|
|
|
|
|
|
|
|
|
|
|
服务端接收 `{"data":"密文"}` 加密 body,解密后返回明文 JSON(控制器再反序列化为目标类型):
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.helpers.{AesBodyProviderBase, SimApiAesBodyChecker}
|
|
|
|
|
|
|
|
|
|
|
|
public class MyAesProvider <: AesBodyProviderBase {
|
|
|
|
|
|
public override func getKey(appId: ?String): ?String {
|
|
|
|
|
|
Some("aes-secret-key")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public func create(@FromBody req: AesBodyRequest): String {
|
|
|
|
|
|
let json = SimApiAesBodyChecker.decryptBody(context, provider) // 解密后的 JSON 字符串
|
|
|
|
|
|
let dto = JsonSerializer.deserializeObject<MyDto>(json)
|
|
|
|
|
|
"ok"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
|
### 3. 缓存 — SimApiCache
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
2026-08-16 22:47:05 +08:00
|
|
|
|
// 由 DI 注入(构造参数 options: SimApiOptions)
|
|
|
|
|
|
let cache: SimApiCache = ...
|
2026-08-16 12:46:15 +08:00
|
|
|
|
cache.set("key", "value")
|
|
|
|
|
|
let v = cache.get("key") // ?String
|
|
|
|
|
|
cache.hasKey("key") // Bool
|
|
|
|
|
|
cache.remove("key")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Key 自动加前缀 `SimApi:Cache:`。
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 工具集 — SimApiUtil
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
SimApiUtil.cstNow // UTC+8 时间
|
|
|
|
|
|
SimApiUtil.timestampNow // 秒级时间戳
|
2026-08-16 22:47:05 +08:00
|
|
|
|
SimApiUtil.newGuid() // UUID v4(对齐 C# Guid.NewGuid())
|
2026-08-16 12:46:15 +08:00
|
|
|
|
SimApiUtil.md5("text") // 32 位十六进制
|
|
|
|
|
|
SimApiUtil.sha1("text") // 40 位
|
|
|
|
|
|
SimApiUtil.base64Encode("text") / base64Decode("...")
|
2026-08-16 22:47:05 +08:00
|
|
|
|
SimApiUtil.base64Encode(obj) // 对象 → JSON → Base64(对齐 C# Base64Encode(object))
|
|
|
|
|
|
SimApiUtil.fromJson<T>(json) // JSON → T(对齐 C# FromJson<T>,T 需 ISerialization<T>)
|
|
|
|
|
|
SimApiUtil.base64DecodeTo<T>(str) // Base64 → JSON → T(对齐 C# Base64Decode<T>)
|
2026-08-16 12:46:15 +08:00
|
|
|
|
SimApiUtil.checkCell("13800138000") // 手机号
|
|
|
|
|
|
SimApiUtil.checkEmail("a@b.com") // 邮箱
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
### 4.1 AES 加解密 — SimApiAesUtil(对齐 C# SimApiAesUtil)
|
|
|
|
|
|
|
|
|
|
|
|
纯仓颉实现 AES-256-CBC + PKCS7(S-box/密钥扩展/轮函数),与 .NET 双向互操作已验证:
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
let encrypted = SimApiAesUtil.encrypt("明文", "key字符串") // Base64(随机IV + 密文)
|
|
|
|
|
|
let plain = SimApiAesUtil.decrypt(encrypted, "key字符串")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- 密钥:`SHA256(key 字符串)` → 32 字节;IV 每次随机 16 字节前置;输出 `Base64(IV + 密文)`
|
|
|
|
|
|
- 供 `SimApiHttpClient.aesQuery<T>` / `aesSignQuery<T>` 使用
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 实体基类 — SimApiBaseModel(对齐 C# SimApiBaseModel)
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.models.*
|
|
|
|
|
|
|
|
|
|
|
|
public class User <: SimApiBaseModel {
|
|
|
|
|
|
public var _name: String = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let user = User() // _id 自动 GUID、_createdAt/_updatedAt 自动当前时间
|
|
|
|
|
|
user.mapData(source) // 反射映射:源对象同名同类型字段 → this(忽略 Id/CreatedAt/UpdatedAt)
|
|
|
|
|
|
user.mapData(source, ["_name"]) // 白名单映射
|
|
|
|
|
|
user.updateTime() // 刷新 _updatedAt
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
|
### 5. HTTP 客户端 — SimApiHttpClient
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
用于调用其他带签名/AES 的 SimApi 服务(**内置 TLS 支持**:`https` 自动配置信任所有证书 + SNI,仓颉生态下 stdx TLS 动态加载 openssl 可用):
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
let client = SimApiHttpClient(options: SimApiHttpClientOptions()) // 配置 server/appId/appKey
|
|
|
|
|
|
|
|
|
|
|
|
// 返回泛型 T(对齐 .NET SignQuery<T>/AesQuery<T>/AesSignQuery<T>),T 需实现 ISerialization<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}")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
签名参数名可配置(`simApiHttpClientOptions.signName / timestampName / nonceName / appIdName / signFields`,C# 侧为硬编码)。
|
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
|
### 5.1 请求日志 — enableRequestLog
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
记录每次请求的方法、URL、请求头、请求体、响应状态码、耗时与异常(对齐 C#):
|
|
|
|
|
|
- 请求体按 **JSON 字段级截断**(仅对超长字符串字段截断,保留结构;非 JSON 整串截断)
|
|
|
|
|
|
- 下游异常**捕获记录后重抛**(对齐 C# ExceptionDispatchInfo)
|
|
|
|
|
|
- 响应体因 soulsoft `HttpResponse.body` 只读不可替换,记录 `Content-Length` 作为替代(C# 用 MemoryStream 捕获)
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
builder.addSimApi { options =>
|
|
|
|
|
|
options.enableRequestLog = true
|
|
|
|
|
|
options.simApiRequestLogOptions.showFullHeader = true // 打印完整 Header(默认只打 Token/Query-Id)
|
2026-08-16 22:47:05 +08:00
|
|
|
|
options.simApiRequestLogOptions.requestStringLogLength = 200 // 请求体字段截断长度(0 不截断)
|
2026-08-16 12:46:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
输出示例:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
[GET] /hello
|
|
|
|
|
|
*( RequestHeaders [Full] ) =>
|
|
|
|
|
|
{"host":"127.0.0.1:5000",...}
|
|
|
|
|
|
*( RequestBody ) =>
|
2026-08-16 22:47:05 +08:00
|
|
|
|
{"name":"AAAA...(200)","image":"x"}
|
2026-08-16 12:46:15 +08:00
|
|
|
|
*( Response [200] ) => 1.756400ms
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 日志格式 — SimApiLogger
|
|
|
|
|
|
|
|
|
|
|
|
`enableLogger`(默认 `true`)时自动使用 `SimApiLoggerProvider`(替换 soulsoft 默认控制台格式),输出格式对齐 C# 原版:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
[ 分类 ][ 时间:毫秒 ][ 级别 ]
|
|
|
|
|
|
消息内容
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
按级别着色:
|
|
|
|
|
|
|
|
|
|
|
|
| 级别 | 颜色 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| Debug | 深紫(DarkMagenta) |
|
|
|
|
|
|
| Info | 深青(DarkCyan) |
|
|
|
|
|
|
| Warn | 黄(Yellow) |
|
|
|
|
|
|
| Error | 红(Red) |
|
|
|
|
|
|
| Fatal | 深红(DarkRed 粗体近似) |
|
|
|
|
|
|
| 其他 | 白(White) |
|
|
|
|
|
|
|
|
|
|
|
|
### 6. 内置路由(UseSimApi 自动注册)
|
|
|
|
|
|
|
|
|
|
|
|
| 路由 | 方法 | 条件 | 说明 |
|
|
|
|
|
|
| ----------------- | -------- | ---------------------------- | -------------------------- |
|
|
|
|
|
|
| `/versions` | GET/POST | 始终 | 返回 SimApi/App 版本 |
|
|
|
|
|
|
| `/user/info` | POST | `enableSimApiAuth` | 需登录,返回 LoginInfo |
|
|
|
|
|
|
| `/auth/logout` | POST | `enableSimApiAuth` | 退出登录 |
|
|
|
|
|
|
| `/exception/{code}` | GET | 始终 | 错误反馈 |
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
路由路径可自定义(`configureSimApiRoute`,自定义值通过 `mapGet/mapPost` 真实注册,默认值由内置控制器特性路由覆盖):
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
options.configureSimApiRoute { route =>
|
|
|
|
|
|
route.userInfoRoute = Some("/my/user/info") // 自定义路径生效
|
|
|
|
|
|
route.logoutRoute = Some("/my/auth/logout")
|
|
|
|
|
|
route.webConfigRoute = Some("/my/config")
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
|
### 7. 认证后处理 Hook — ISimApiAuthChecker
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
实现后每次认证成功都会调用(配合 `@SimApiAuth` 注解或手动 `requireLogin`):
|
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.interfaces.*
|
|
|
|
|
|
|
|
|
|
|
|
class MyAuthChecker <: ISimApiAuthChecker {
|
|
|
|
|
|
public func run(loginItem: SimApiLoginItem, token: String): Unit {
|
|
|
|
|
|
// 认证成功后执行
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
### 8. 认证中心 SDK — AuthSDK(对齐 C# AuthSDK)
|
|
|
|
|
|
|
|
|
|
|
|
`enableSimApiAuthGate = true` 时注册 `SimApiAuthClient` / `SimApiAuthCenter` / `SimApiAuthIam` 单例并挂载网关透传中间件:
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
builder.addSimApi { options =>
|
|
|
|
|
|
options.enableSimApiAuthGate = true
|
|
|
|
|
|
options.configureSimApiAuthCenter { auth =>
|
|
|
|
|
|
auth.server = "https://auth.example.com"
|
|
|
|
|
|
auth.appId = "app-id"
|
|
|
|
|
|
auth.appKey = "app-key"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 类 | 说明 |
|
|
|
|
|
|
|----|------|
|
|
|
|
|
|
| `SimApiAuthClient` | `SimApiHttpClient` 子类,凭证取 AuthCenterOptions |
|
|
|
|
|
|
| `SimApiAuthCenter` | 群组/Profile/内部应用/系统登录/安全验证等 12 个接口 + `VerifySign` |
|
|
|
|
|
|
| `SimApiAuthIam` | 注册权限 / 获取权限标识 / 校验权限(无权限抛 403) |
|
|
|
|
|
|
| `SimApiAuthCenterMiddleware` | 网关透传:`X-SimApi-Gate-Auth/Time/Sign` 三头 MD5 校验 → Base64 解码 LoginInfo |
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.authsdk.*
|
|
|
|
|
|
|
|
|
|
|
|
let center = SimApiAuthCenter(client) // client 从 DI 注入
|
|
|
|
|
|
let groups = center.groupRelated(profileId) // 群组列表
|
|
|
|
|
|
let loginInfo = center.getLoginInfo(code) // 登录信息(场景校验)
|
|
|
|
|
|
let iam = SimApiAuthIam(client)
|
|
|
|
|
|
iam.checkPermission(profileId, "app:create") // 无权限抛 403
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## SimApiOptions 完整配置
|
|
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
builder.addSimApi { options =>
|
2026-08-16 22:47:05 +08:00
|
|
|
|
options.redisConfiguration = "localhost:6379" // Redis(可选,支持 ,password=xxx,db=2)
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 功能开关
|
|
|
|
|
|
options.enableSimApiAuth = false // Token 认证
|
|
|
|
|
|
options.enableSimApiCache = true // 缓存
|
|
|
|
|
|
options.enableSimApiException = true // 全局异常拦截
|
|
|
|
|
|
options.enableSimApiResponseFilter = true // 响应统一封装
|
|
|
|
|
|
options.enableSimApiHttpClient = false // HTTP 客户端
|
2026-08-16 22:47:05 +08:00
|
|
|
|
options.enableSimApiAuthGate = false // 认证中心 SDK + 网关中间件
|
2026-08-16 12:46:15 +08:00
|
|
|
|
options.enableRequestLog = false // 请求日志中间件
|
|
|
|
|
|
options.enableCors = true // 全量 CORS
|
|
|
|
|
|
options.enableLogger = true // 控制台日志
|
|
|
|
|
|
|
|
|
|
|
|
// .NET 风格子模块配置回调(对齐 C# ConfigureSimApiXxx)
|
|
|
|
|
|
options.configureSimApiRoute { route =>
|
2026-08-16 22:47:05 +08:00
|
|
|
|
route.userInfoRoute = Some("/user/info") // 内置路由自定义路径
|
|
|
|
|
|
route.logoutRoute = Some("/auth/logout")
|
|
|
|
|
|
route.webConfigRoute = Some("/config")
|
2026-08-16 12:46:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
options.configureSimApiRequestLog { opt =>
|
|
|
|
|
|
opt.showFullResponse = true
|
|
|
|
|
|
opt.showFullHeader = false
|
|
|
|
|
|
opt.requestStringLogLength = 50
|
|
|
|
|
|
}
|
|
|
|
|
|
options.configureSimApiHttpClient { http =>
|
|
|
|
|
|
http.appId = "your-app-id"
|
|
|
|
|
|
http.appKey = "your-app-key"
|
|
|
|
|
|
http.server = "https://api.example.com"
|
|
|
|
|
|
}
|
2026-08-16 22:47:05 +08:00
|
|
|
|
options.configureSimApiAuthCenter { auth =>
|
|
|
|
|
|
auth.server = "https://auth.example.com"
|
|
|
|
|
|
auth.appId = "auth-app-id"
|
|
|
|
|
|
auth.appKey = "auth-app-key"
|
|
|
|
|
|
}
|
2026-08-16 12:46:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 内置控制器(MVC 写法)
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
simapi 提供 Spire MVC 控制器(继承 `SimApiBaseController`),`addSimApi` 自动注册内置控制器 + 自动扫描调用者包中的控制器(对齐 C# `Assembly.GetTypes()` 扫描,见 `SimApiControllerScanner`):
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
| 控制器 | 路由 | 说明 |
|
|
|
|
|
|
|--------|------|------|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
| `SimApiCommonController` | `/exception/{code}`、`/config`、`/versions`、`/user/info` | 通用内置路由 |
|
2026-08-16 12:46:15 +08:00
|
|
|
|
| `SimApiAuthController` | `/auth/logout` | 退出登录 |
|
2026-08-16 22:47:05 +08:00
|
|
|
|
| `SimApiBaseController` | — | 基类:`loginInfo` / `loginToken` / `requireLogin()` / `getLogin()` |
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
```cangjie
|
|
|
|
|
|
import simapi.controllers.*
|
2026-08-16 22:47:05 +08:00
|
|
|
|
import simapi.attributes.{SimApiAuth}
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
// 控制器写法:继承 SimApiBaseController,注解路由 + DI 注入
|
2026-08-16 22:47:05 +08:00
|
|
|
|
@SimApiAuth // 类级鉴权(可选,替代 requireLogin)
|
2026-08-16 12:46:15 +08:00
|
|
|
|
public class MyController <: SimApiBaseController {
|
|
|
|
|
|
private let _auth: SimApiAuth
|
|
|
|
|
|
public init(auth: SimApiAuth) { this._auth = auth }
|
|
|
|
|
|
|
|
|
|
|
|
@HttpPost["my/route"]
|
2026-08-16 22:47:05 +08:00
|
|
|
|
public func myAction(@FromBody request: MyRequest): String {
|
|
|
|
|
|
"ok"
|
2026-08-16 12:46:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
宿主无需手动注册控制器:`builder.addSimApi {}` 内部自动扫描并注册。
|
|
|
|
|
|
|
2026-08-16 12:46:15 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 未实现模块(选项占位)
|
|
|
|
|
|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
以下 C# 原包功能因仓颉生态暂无对应库(Hangfire/MQTT/MinIO/Swashbuckle),**选项保留但未实现**:
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
| 选项 | 原功能 | 状态 |
|
|
|
|
|
|
|------|--------|------|
|
2026-08-16 22:47:05 +08:00
|
|
|
|
| `enableSimApiDoc` | Swagger 文档(可换 soulsoft_web_openapi) | ❌ 未实现 |
|
2026-08-16 12:46:15 +08:00
|
|
|
|
| `enableSimApiStorage` | S3/MinIO 存储 | ❌ 未实现 |
|
|
|
|
|
|
| `enableSynapse` | MQTT 通信 | ❌ 未实现 |
|
|
|
|
|
|
| `enableJob` | Hangfire 任务调度 | ❌ 未实现 |
|
2026-08-16 22:47:05 +08:00
|
|
|
|
|
|
|
|
|
|
> ✅ 已实现(曾为占位):`enableSimApiAuthGate`(AuthSDK 认证中心)、`SimApiAesUtil`(纯仓颉 AES-256-CBC,与 .NET 双向互操作)、`ISimApiAuthChecker`(注解鉴权时执行)、内置路由自定义路径。
|
2026-08-16 12:46:15 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 依赖
|
|
|
|
|
|
|
|
|
|
|
|
| 依赖 | 用途 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `soulsoft_web_http / routing / hosting` | Web 框架(ASP.NET Core 仓颉移植版) |
|
|
|
|
|
|
| `soulsoft_extensions_logging` 系列 | 日志 |
|
|
|
|
|
|
| `soulsoft_extensions_injection` | 依赖注入 |
|
|
|
|
|
|
| `soulsoft_extensions_configuration` | 配置 |
|
|
|
|
|
|
| `soulsoft_serialization` | JSON 序列化 |
|
|
|
|
|
|
| `redis`(pkg.cangjie-lang.cn) | Redis 客户端(认证/缓存 Redis 模式) |
|
|
|
|
|
|
| `stdx`(CANGJIE_STDX_PATH) | 标准扩展库(md5/sha1/base64/http) |
|
|
|
|
|
|
|
|
|
|
|
|
> 构建前需设置 `CANGJIE_STDX_PATH` 指向本地 stdx 的 `static/stdx` 目录。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 许可证
|
|
|
|
|
|
|
|
|
|
|
|
MIT
|