# SimApi for Cangjie（simapi）

> 仓颉版 SimApi：ASP.NET Core 风格 API 基础框架，移植自 [SimApi](https://github.com/SimcuTeam/simapi-net)。

提供**统一响应格式、异常拦截、Token 认证、缓存、工具集、HTTP 客户端、S3 存储、声明式注解、OpenAPI 文档**等 API 基础能力。

---

## 引入

两种方式任选其一：

**方式一：中央仓**（需先 `cjpm publish` 发布 `simcu::simapi`）

```toml
[dependencies]
"simcu::simapi" = "1.0.3"
```

**方式二：Git 仓库**

```toml
[dependencies]
"simcu::simapi" = { git = "https://gitcode.com/simcu/simapi-cj.git", version = "1.0.3" }
```

> 本地开发也可用 path 依赖：`"simcu::simapi" = { path = "../simapi-cj" }`

---

## 快速开始

```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 simcu::simapi.*
import simcu::simapi.communications.*
import simcu::simapi.helpers.*

main(args: Array<String>) {
    let builder = WebHost.createBuilder(args)
    builder.services.addRouting()
    builder.services.addLogging()

    // 注册 SimApi 服务（与 addLogging 同样式）
    SimApiExtensions.addSimApi(builder) { options =>
        options.enableSimApiAuth = true     // Token 认证（未配 Redis 自动用 InMemory）
        options.enableSimApiCache = true    // 缓存
        options.enableSimApiException = true // 全局异常拦截
    }

    let host = builder.build()
    SimApiExtensions.useSimApi(host)

    // 业务接口：返回对象自动封装为统一响应格式（{code, message, data}）
    host.mapGet("hello") {
        context =>
            context.response.write(SimApiUtil.json(Some(SimApiResponse<String>("hello cangjie."))))
    }

    host.run()
}
```

### 统一响应格式

所有接口输出 JSON，HTTP 状态码始终 `200`，错误信息在 `code` 字段：

| code | 含义       |
| ---- | ---------- |
| 200  | 成功       |
| 204  | 无数据     |
| 400  | 参数错误   |
| 401  | 需要登录   |
| 403  | 无权访问   |
| 404  | 资源不存在 |
| 500  | 服务器错误 |

响应 JSON（经 simcu::serialization 反射序列化，字段**无下划线**）：

```json
{ "code": 200, "message": "成功", "data": { ... } }
```

### 异常处理流程

```
请求 → SimApiExceptionMiddleware(全异常捕获→HTTP 200+JSON)
     → SimApiAuthMiddleware(Token→LoginInfo)
     → 路由 → 业务处理
```

---

## 项目结构

```
simapi-cj/
├── cjpm.toml                  # 包配置
├── src/
│   ├── simapi_extensions.cj   # 根包入口：SimApiExtensions 静态类（addSimApi / useSimApi + 内置路由 + 响应封装）
│   ├── annotations/           # 声明式注解：@SimApiAuth（鉴权）、@OriginResponse（原样响应）、
│   │                          #   @SimApiSign（验签）、@AesBody（AES body 解密）
│   ├── authsdk/               # 认证中心 SDK：SimApiAuthClient/Center/Iam + 网关中间件 + DTO
│   ├── communications/        # SimApiBaseResponse, PageResponse, SimApiLoginItem, 请求 DTO
│   ├── configurations/        # SimApiOptions + 各模块 Option（含 ConfigureSimApiXxx 回调）
│   ├── controllers/           # SimApiBaseController, SimApiCommonController, SimApiAuthController（MVC 写法）
│   ├── exceptions/            # SimApiException
│   ├── helpers/               # SimApiError, SimApiUtil, SimApiAuth, SimApiCache, SimApiHttpClient,
│   │                          #   SimApiAesUtil(AES-256), SimApiSignChecker(验签), SimApiAesBodyChecker(AES body),
│   │                          #   SimApiStorage(S3/MinIO, 自实现 SigV4), SimApiRequestDelegateFactory, SimApiResultWriter
│   ├── interfaces/            # SimApiAuthChecker, BindRequestContext, SimApiSignProviderBase, AesBodyProviderBase
│   ├── logger/                # SimApiLogger, SimApiLoggerProvider（彩色日志）
│   ├── macros/                # ReadTomlVersion（编译期读版本号）、EnumString（枚举字符串双向转换）
│   ├── middlewares/           # SimApiExceptionMiddleware, SimApiAuthMiddleware, SimApiRequestLogMiddleware
│   ├── models/                # SimApiBaseModel（实体基类）
│   └── openapi/               # OpenAPI 文档生成 + Swagger UI 内置资源
│       ├── annotations/       # @SimApiDoc（文档元数据注解）
│       ├── metadata/          # IApiGroupNamesProvider, IApiResponseTypeMetadata 等元数据接口
│       ├── models/            # OpenApiDocument, OpenApiSchema, OpenApiInfo 等 OpenAPI 模型
│       ├── services/          # OpenApiDocumentService（文档生成）, OpenApiSchemaService, OpenApiOptions
│       └── infrastructure/    # OpenApiConstants
```

---

## 模块说明

### 1. 错误处理 — SimApiError

```cangjie
import simcu::simapi.helpers.*

SimApiError.error(500, "服务器内部错误")          // 直接抛错
SimApiError.errorWhen(amount <= 0, 400, "金额无效") // 条件为 true 时抛错
SimApiError.errorWhenFalse(hasPermission, 403, "无权操作")
SimApiError.errorWhenNull(someOptional, 404, "用户不存在")
```

### 2. 认证 — SimApiAuth

```cangjie
import simcu::simapi.helpers.*
import simcu::simapi.communications.*

// 由 DI 注入（构造参数 options: SimApiOptions，从配置读 RedisConfiguration；未配则 InMemory）
let auth: SimApiAuth = ...   // 例：控制器构造注入

let token = auth.login(SimApiLoginItem(id: "user-001"))  // 默认 7 天
let login = auth.getLogin(token)                // 获取登录信息
auth.logout(token)                              // 退出登录
auth.logoutAll("user-001")                      // 退出全部
```

- **Redis 模式**：配置 `RedisConfiguration` 时使用，支持多实例共享。连接串格式：
  - `"localhost:6379"`（基础）
  - `"localhost:6379,password=xxx"`（带密码）
  - `"localhost:6379,password=xxx,db=2"`（带密码 + DB 索引）
- **InMemory 模式**：零配置，适合开发/测试；登录态带过期时间，重启后丢失
- **Token 传参**：Header `Token: <value>` 或 Query `token=<value>`

### 2.1 声明式鉴权 — @SimApiAuth

标注在控制器**方法或类**上，请求派发时自动执行鉴权（未登录 401 → 类型不匹配 403 → 遍历执行 `SimApiAuthChecker`）：

```cangjie
import simcu::simapi.annotations.{SimApiAuth}

@SimApiAuth                    // 类级：整个控制器需登录
public class MyController <: SimApiBaseController {

    @SimApiAuth["admin"]       // 方法级：仅 admin 类型可访问
    @HttpPost["my/admin-only"]
    public func adminOnly(): String { "ok" }
}
```

> 说明：仓颉注解参数须为编译期常量，`@SimApiAuth` 支持单个类型参数（`@SimApiAuth["admin"]`）或逗号分隔多类型（`@SimApiAuth["admin,user"]`）；空参数表示任意已登录用户。

### 2.2 原样响应 — @OriginResponse

标注后跳过统一响应封装，接口返回什么就输出什么：

```cangjie
import simcu::simapi.annotations.{OriginResponse}

@OriginResponse
@HttpGet["raw"]
public func raw(): String {
    "{\"raw\":true}"   // 直接输出，不包 {code,message,data}
}
```

### 2.3 声明式验签 — @SimApiSign

标注在控制器**方法或类**上，请求派发时自动验签（appId 提取 → 密钥获取 → timestamp 过期校验 → nonce 去重 → MD5 比对）：

```cangjie
import simcu::simapi.annotations.{SimApiSign}
import simcu::simapi.interfaces.{SimApiSignProviderBase}

// 1. 继承 Provider 实现密钥获取（并注册到 DI）
public class MySignProvider <: SimApiSignProviderBase {
    public override func getKey(appId: ?String): ?String {
        Some("my-secret-key")
    }
}

// 2. 方法标注 @SimApiSign，自动验签（provider 类型名从 DI 解析）
@SimApiSign["MySignProvider"]
public func signedAction(): String { "ok" }
```

`SimApiSignProviderBase` 可配置：`appIdName` / `timestampName` / `nonceName` / `signName` / `queryExpires` / `duplicateRequestProtection` / `signFields`。也可手动调用 `SimApiSignChecker.verify(context, provider, cache)`。

### 2.4 声明式 AES body — @AesBody

标注在**参数**上，请求派发时自动解密 `{"data":"密文"}` body 并反序列化为参数类型：

```cangjie
import simcu::simapi.annotations.{AesBody}
import simcu::simapi.interfaces.{AesBodyProviderBase}

public class MyAesProvider <: AesBodyProviderBase {
    public override func getKey(appId: ?String): ?String {
        Some("aes-secret-key")
    }
}

public func create(@AesBody["MyAesProvider"] request: CreateRequest): String {
    // request 已自动解密并反序列化
    "ok"
}
```

也可手动调用 `SimApiAesBodyChecker.decryptBody(context, provider)` 获取明文 JSON 字符串。

### 3. 缓存 — SimApiCache

```cangjie
// 由 DI 注入（构造参数 options: SimApiOptions）
let cache: SimApiCache = ...
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      // 秒级时间戳
SimApiUtil.newGuid()         // UUID v4
SimApiUtil.md5("text")       // 32 位十六进制
SimApiUtil.sha1("text")      // 40 位
SimApiUtil.base64Encode("text") / base64Decode("...")
SimApiUtil.base64Encode(obj) // 对象 → JSON → Base64
SimApiUtil.json(obj)         // 对象 → JSON 字符串（simcu::serialization 反射）
SimApiUtil.escapeJson(s)     // JSON 字符串转义
SimApiUtil.fromJson<T>(json) // JSON → T（任意类免约束）
SimApiUtil.base64DecodeTo<T>(str)  // Base64 → JSON → T
SimApiUtil.checkCell("13800138000")   // 手机号
SimApiUtil.checkEmail("a@b.com")      // 邮箱
```

> JSON 序列化/反序列化统一走 **simcu::serialization**（`JsonSerializer.Serialize` / `Deserialize<T>`），任意类免标注、免接口约束。

### 4.1 AES 加解密 — SimApiAesUtil

纯仓颉实现 AES-256-CBC + PKCS7（S-box/密钥扩展/轮函数），加解密结果跨语言互通已验证：

```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

```cangjie
import simcu::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
```

### 5. HTTP 客户端 — SimApiHttpClient

用于调用其他带签名/AES 的 SimApi 服务（**基于 stdx.net.http，不依赖 soulsoft_net_http**；内置 TLS：`https` 自动配置信任所有证书 + SNI）：

```cangjie
let client = SimApiHttpClient(options: SimApiHttpClientOptions())  // 配置 server/appId/appKey

// 返回泛型 T（SignQuery<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`）。AES 请求体用 `SimApiOneFieldRequest<String>` 序列化为 `{"data":"密文"}`。

### 5.1 请求日志 — enableRequestLog

记录每次请求的方法、URL、请求头、请求体、响应状态码、耗时与异常：
- 请求体按 **JSON 字段级截断**（仅对超长字符串字段截断，保留结构；非 JSON 整串截断）
- 下游异常**捕获记录后重抛**

```cangjie
SimApiExtensions.addSimApi(builder) { options =>
    options.enableRequestLog = true
    options.simApiRequestLogOptions.showFullHeader = true   // 打印完整 Header（默认只打 Token/Query-Id）
    options.simApiRequestLogOptions.requestStringLogLength = 200  // 请求体字段截断长度（0 不截断）
}
```

输出示例：

```
[GET] /hello
*( RequestHeaders [Full] ) =>
{"host":"127.0.0.1:5000",...}
*( RequestBody ) =>
{"name":"AAAA...(200)","image":"x"}
*( Response [200] ) => 1.756400ms
```

### 5.2 日志格式 — SimApiLogger

`enableLogger`（默认 `true`）时自动使用 `SimApiLoggerProvider`，输出格式与原版一致：

```
[ 分类 ][ 时间:毫秒 ][ 级别 ]
消息内容
```

按级别着色：Debug 深紫 / Info 深青 / Warn 黄 / Error 红 / Fatal 深红。

### 5.3 存储 — SimApiStorage（S3/MinIO）

`enableSimApiStorage = true` 时注册 `SimApiStorage`（Scoped，内部自实现 AWS Signature V4，基于 stdx.net.http，无需 Minio SDK）：

```cangjie
SimApiExtensions.addSimApi(builder) { options =>
    options.enableSimApiStorage = true
    options.configureSimApiStorage { storage =>
        storage.endpoint = "http://192.168.0.2:9000"   // 必须 http:// 或 https:// 开头
        storage.serveUrl = "https://files.example.com" // 文件访问地址，不能以 / 结尾
        storage.bucket = "app-files"
        storage.accessKey = "minioadmin"
        storage.secretKey = "minioadmin"
    }
}
```

| 方法                                              | 说明                                                                      |
| ------------------------------------------------- | ------------------------------------------------------------------------- |
| `getUploadUrl(path, expire=7200)`                 | 上传预签名 URL，返回 `GetUploadUrlResponse(UploadUrl, DownloadUrl, Path)` |
| `getDownloadUrl(path, expire=600)`                | 下载预签名 URL                                                            |
| `uploadFile(path, data, contentType="image/png")` | 直接 PUT 上传（字节数组）                                                 |
| `deleteFiles(paths)`                              | 批量删除对象（S3 原生 DeleteObjects，一次请求删多个）                     |
| `fullUrl(path)` / `getUrl(path)`                  | 补全访问 URL（`~/` 前缀依赖请求上下文）                                   |
| `getPath(url)`                                    | 从 URL 还原相对路径（去掉 Endpoint/Bucket 或 ServeUrl 前缀）              |

> 说明：桶不存在时自动创建（静态守卫只执行一次）；
> 预签名与上传使用 AWS SigV4（HMAC-SHA256 基于 stdx SHA256 自实现），已用 AWS 官方测试向量验证签名正确。

### 6. 内置路由（UseSimApi 自动注册）

| 路由                | 方法 | 条件               | 说明                                           |
| ------------------- | ---- | ------------------ | ---------------------------------------------- |
| `/user/info`        | POST | `enableSimApiAuth` | 需登录，返回 LoginInfo                         |
| `/auth/logout`      | POST | `enableSimApiAuth` | 退出登录                                       |
| `/exception/{code}` | GET  | 始终               | 错误反馈（抛 SimApiException，不出现在文档中） |

路由路径可自定义（`configureSimApiRoute`）：

```cangjie
options.configureSimApiRoute { route =>
    route.userInfoRoute = Some("/my/user/info")   // 自定义路径生效
    route.logoutRoute = Some("/my/auth/logout")
    route.webConfigRoute = Some("/my/config")
}
```

### 7. 认证后处理 Hook — SimApiAuthChecker

实现后每次认证成功都会调用（配合 `@SimApiAuth` 注解）：

```cangjie
import simcu::simapi.interfaces.*

class MyAuthChecker <: SimApiAuthChecker {
    public func run(loginItem: SimApiLoginItem, token: String): Unit {
        // 认证成功后执行
    }
}
```

### 8. 认证中心 SDK — AuthSDK

`enableSimApiAuthGate = true` 时注册 `SimApiAuthClient` / `SimApiAuthCenter` / `SimApiAuthIam` 单例并挂载网关透传中间件：

```cangjie
SimApiExtensions.addSimApi(builder) { 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 simcu::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
```

---

## OpenAPI 文档 — enableSimApiDoc

`enableSimApiDoc = true` 时自动生成 OpenAPI 3.0 JSON 文档并内置 Swagger UI 静态资源（无需外部文件，打包后不失效）。

### 文档分组

支持多个文档组，未标注 `@SimApiDoc` 的接口默认进入默认组文档：

```cangjie
SimApiExtensions.addSimApi(builder) { options =>
    options.enableSimApiDoc = true
    options.configureSimApiDoc { doc =>
        doc.apiGroups.add(SimApiDocGroup("api", name: "App接口", description: "对接App相关接口"))
        doc.apiGroups.add(SimApiDocGroup("admin", name: "后台管理接口", description: "后台管理接口"))
    }
}
```

- `SimApiDocGroup(id, name!, description!, isDefault!)`：`name` 默认取 `id` 值
- `distinctGroups()`：按 id 去重，**用户配置覆盖默认值**（保留最后出现）
- 若所有组均未标记 `isDefault`，第一个组视为默认组

### @SimApiDoc 注解

```cangjie
import simcu::simapi.openapi.annotations.*

@SimApiDoc[tags: "登录", summary: "用户登录"]
@SimApiDoc[tags: "认证", summary: "后台登录", groupNames: "admin"]
@SimApiDoc[tags: "公共", groupNames: "api,admin"]   // 同时出现在 api 和 admin 文档
@SimApiDoc[tags: "公共", groupNames: "*"]            // 出现在所有文档
@SimApiDoc[ignore: true]                             // 不出现在文档中
```

- `groupNames`：逗号分隔多个组名，`*` 表示所有文档，空串表示未分组（仅进默认文档）
- `ignore: true`：从文档中隐藏

### 路由前缀

```cangjie
options.configureSimApiDoc { doc =>
    doc.urlPrefix = "docs"   // 默认值，可自定义
}
```

| 路由                  | 说明                                       |
| --------------------- | ------------------------------------------ |
| `/{prefix}/all.html`  | 多文档切换页（顶部栏下拉选择所有文档）     |
| `/{prefix}/{id}.html` | 单文档页（无顶部栏，自动加载 `{id}.json`） |
| `/{prefix}/urls`      | 文档列表 JSON（供 Swagger UI 下拉）        |
| `/{prefix}/{id}.json` | OpenAPI 文档 JSON                          |

> Swagger UI 静态资源（CSS/JS/HTML）以 Base64 内联编译，运行时由 `OpenApiUIMiddleware` 解码输出。资源更新后运行 `pwsh tools/gen-swagger-ui-resources.ps1` 重新生成。

### 响应封装与文档

接口返回值自动封装为统一响应格式，**文档中 response schema 也体现封装**：

| 返回类型                                                      | `@OriginResponse` | 文档 response schema                    |
| ------------------------------------------------------------- | ----------------- | --------------------------------------- |
| `SimApiBaseResponse`/`SimApiResponse<T>`/`SimApiDataResponse` | -                 | 原样不封装                              |
| 任意类型                                                      | ✓                 | 原样不封装                              |
| `Unit` (void)                                                 | ✗                 | `{code: int64, message: string}`        |
| `String`                                                      | ✗                 | `{code, message, data: {type: string}}` |
| DTO                                                           | ✗                 | `{code, message, data: {$ref: DTO}}`    |

### 认证锁图标

仅标注了 `@SimApiAuth` 的接口在文档中显示锁图标（operation 级 `security`），未标注的接口不显示。

动态注册的路由（lambda）需用 `withSimApiAuth` 扩展方法手动添加认证元数据：

```cangjie
host.mapPost(route, { context => ... })
    .withOpenApi(SimApiDoc(tags: "认证", summary: "获取用户信息"))
    .withSimApiAuth(SimApiAuth())
    .withResponseType(TypeInfo.of<SimApiLoginItem>())
```

- `withSimApiAuth`：添加 `@SimApiAuth` 元数据（显示锁图标）
- `withResponseType`：添加响应类型元数据（动态路由无 `ControllerActionDescriptor`，需手动指定返回类型才能生成 response schema）

---

### EnumString — 枚举字符串双向转换

仓颉 1.1.3 枚举没有默认 `ToString`，且字符串插值要求实现 `ToString` 接口（见 RULE.MD §6.5）。在枚举声明上标注 `@EnumString`，编译期自动在枚举体外生成 extend，提供纯成员名的双向转换：

```cangjie
import simcu::simapi.macros.*

@EnumString
public enum AssetKind {
    | Passport
    | Package
}

// 生成（示意）：
// extend AssetKind <: ToString {
//     public func toString(): String { ... }                 // "Passport" / "Package"
//     public static func fromString(s: String): AssetKind    // 反查，未匹配抛 Exception
// }

let kind = AssetKind.fromString("Passport")  // AssetKind.Passport
let s = "${kind}"                            // "Passport"
// AssetKind.fromString("Unknown")            // 抛 Exception: AssetKind.fromString: 未知枚举值 'Unknown'
```

- 与 `@Derive[ToString]` 的区别：Derive 输出带类型名前缀（`Kind.A`），本宏输出**纯成员名**，适合写库 / 读库还原 / 字符串插值。
- 仅支持**纯无参构造器**枚举；带参数构造器、`...`（non-exhaustive）在编译期直接报错。
- 生成的是普通 extend，**零运行时开销**（match 穷尽构造器，无反射）。

### ReadTomlVersion — 编译期读版本号

读取 cjpm.toml 的 `version` 字段并替换变量声明，用于把版本号写进响应头等场景：

```cangjie
import simcu::simapi.macros.*

@ReadTomlVersion[path: "cjpm.toml"]
let appVersion: String = ""

@ReadTomlVersion[path: "../simapi-cj/cjpm.toml"]   // 读取其他包版本
let simApiVersion: String = ""
```

---

## SimApiOptions 完整配置

```cangjie
SimApiExtensions.addSimApi(builder) { options =>
    options.redisConfiguration = "localhost:6379"  // Redis（可选，支持 ,password=xxx,db=2）

    // 功能开关
    options.enableSimApiAuth       = false   // Token 认证
    options.enableSimApiCache      = true    // 缓存
    options.enableSimApiException  = true    // 全局异常拦截
    options.enableSimApiResponseFilter = true // 响应统一封装
    options.enableSimApiHttpClient = false   // HTTP 客户端
    options.enableSimApiAuthGate   = false   // 认证中心 SDK + 网关中间件
    options.enableSimApiDoc        = false   // OpenAPI 文档 + Swagger UI
    options.enableRequestLog       = false   // 请求日志中间件
    options.enableCors             = true    // 全量 CORS
    options.enableLogger           = true    // 控制台日志

    // 子模块配置回调（ConfigureSimApiXxx）
    options.configureSimApiRoute { route =>
        route.userInfoRoute = Some("/user/info")   // 内置路由自定义路径
        route.logoutRoute = Some("/auth/logout")
        route.webConfigRoute = Some("/config")
    }
    options.configureSimApiDoc { doc =>
        doc.urlPrefix = "docs"                      // 文档路由前缀（默认 "docs"）
        doc.apiGroups.add(SimApiDocGroup("api", name: "App接口", description: "App接口文档"))
        doc.apiGroups.add(SimApiDocGroup("admin", name: "后台管理", description: "后台管理接口"))
    }
    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"
    }
    options.configureSimApiAuthCenter { auth =>
        auth.server = "https://auth.example.com"
        auth.appId = "auth-app-id"
        auth.appKey = "auth-app-key"
    }
}
```

## 内置控制器（MVC 写法）

simapi 提供 Spire MVC 控制器（继承 `SimApiBaseController`），`addSimApi` 自动注册内置控制器 + 自动扫描调用者包中的控制器：

| 控制器                   | 路由                                         | 说明                                                                 |
| ------------------------ | -------------------------------------------- | -------------------------------------------------------------------- |
| `SimApiCommonController` | `/exception/{code}`、`/config`、`/user/info` | 通用内置路由                                                         |
| `SimApiAuthController`   | `/auth/logout`                               | 退出登录                                                             |
| `SimApiBaseController`   | —                                            | 基类：`loginInfo` / `loginToken`（访问时自动校验登录，未登录抛 401） |

```cangjie
import simcu::simapi.controllers.*
import simcu::simapi.annotations.{SimApiAuth}

// 控制器写法：继承 SimApiBaseController，注解路由 + DI 注入
@SimApiAuth                    // 类级鉴权
public class MyController <: SimApiBaseController {
    private let _auth: SimApiAuth
    public init(auth: SimApiAuth) { this._auth = auth }

    @HttpPost["my/route"]
    public func myAction(@FromBody request: MyRequest): String {
        "ok"
    }
}
```

宿主无需手动注册控制器：`builder.addSimApi {}` 内部自动扫描并注册。

---

## 未实现模块（选项占位）

以下原包功能因仓颉生态暂无对应库（Hangfire/MQTT），**选项保留但未实现**：

| 选项            | 原功能            | 状态     |
| --------------- | ----------------- | -------- |
| `enableSynapse` | MQTT 通信         | ❌ 未实现 |
| `enableJob`     | Hangfire 任务调度 | ❌ 未实现 |

> ✅ 已实现（曾为占位）：`enableSimApiDoc`（OpenAPI 文档 + Swagger UI）、`enableSimApiStorage`（S3/MinIO，自实现 AWS SigV4）、`enableSimApiAuthGate`（AuthSDK 认证中心）、`SimApiAesUtil`（纯仓颉 AES-256-CBC）、`SimApiAuthChecker`、`@SimApiSign` / `@AesBody` 声明式注解、内置路由自定义路径。

---

## 依赖

| 依赖                                    | 用途                                   |
| --------------------------------------- | -------------------------------------- |
| `soulsoft_web_http / routing / hosting` | Web 框架                               |
| `soulsoft_extensions_logging` 系列      | 日志                                   |
| `soulsoft_extensions_injection`         | 依赖注入                               |
| `soulsoft_extensions_configuration`     | 配置                                   |
| `simcu::serialization`（path 依赖）     | JSON 序列化（simapi 自研，反射免标注） |
| `redis`（pkg.cangjie-lang.cn）          | Redis 客户端（认证/缓存 Redis 模式）   |
| `stdx`（CANGJIE_STDX_PATH）             | 标准扩展库（md5/sha1/base64/http/tls） |
| `soulsoft_web_mvc`                      | MVC 框架（控制器路由、模型绑定）       |

> 构建前需设置 `CANGJIE_STDX_PATH` 指向本地 stdx 的 `static/stdx` 目录。
> OpenAPI Swagger UI 静态资源内置（Base64 内联），更新资源后运行 `pwsh tools/gen-swagger-ui-resources.ps1`。

---

## 许可证

MIT
