xrain 40e0726087 docs: README 更新(新功能文档 + 修正过时状态)
- 新增:@SimApiAuth/@OriginResponse/验签/AES body/AES/BaseModel/AuthSDK 使用文档
- 修正:AuthGate/AES 已实现、自动控制器扫描、DI 构造示例、移除 ApiResult 引用
- cjpm.lock:新增 soulsoft_net_http 等依赖
2026-08-16 22:47:05 +08:00
2026-08-16 12:46:15 +08:00
2026-08-16 12:46:15 +08:00

SimApi for Cangjiesimapi

仓颉版 SimApiASP.NET Core 风格 API 基础框架,移植自 C# 项目 SimApiE:\simcu\simapi-net)。

提供统一响应格式、异常拦截、Token 认证、缓存、工具集、HTTP 客户端等 API 基础能力。


快速开始

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/
│   ├── attributes/            # 声明式注解:@SimApiAuth(鉴权)、@OriginResponse(原样响应)
│   ├── authsdk/               # 认证中心 SDKSimApiAuthClient/Center/Iam + 网关中间件 + DTO
│   ├── communications/        # SimApiBaseResponse, PageResponse, SimApiLoginItem, 请求 DTO
│   ├── configurations/        # SimApiOptions + 各模块 Option(含 ConfigureSimApiXxx 回调)
│   ├── controllers/           # SimApiBaseController, SimApiCommonController, SimApiAuthControllerMVC 写法)
│   ├── exceptions/            # SimApiException
│   ├── extensions/            # SimApiExtensionsaddSimApi / useSimApi + 内置路由 + 响应封装)
│   ├── helpers/               # SimApiError, SimApiUtil, SimApiAuth, SimApiCache, SimApiHttpClient,
│   │                          #   SimApiAesUtil(AES-256), SimApiSignChecker(验签), SimApiAesBodyChecker(AES body)
│   ├── interfaces/            # ISimApiAuthChecker
│   ├── logger/                # SimApiLogger, SimApiLoggerProvider(彩色日志)
│   ├── macros/                # ReadTomlVersion(编译期读版本号)
│   ├── middlewares/           # SimApiExceptionMiddleware, SimApiAuthMiddleware, SimApiRequestLogMiddleware
│   └── models/                # SimApiBaseModel(实体基类)

模块说明

1. 错误处理 — SimApiError

import simapi.helpers.*

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

2. 认证 — SimApiAuth

import simapi.helpers.*
import 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 模式:零配置,适合开发/测试;登录态带过期时间(对齐 C# 过期语义),重启后丢失
  • Token 传参Header Token: <value> 或 Query token=<value>

2.1 声明式鉴权 — @SimApiAuth(注解类,对齐 C# [SimApiAuth]

标注在控制器方法或类上,请求派发时自动执行鉴权(未登录 401 → 类型不匹配 403 → 遍历执行 ISimApiAuthChecker),替代手动 requireLogin()

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]):

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 比对):

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(控制器再反序列化为目标类型):

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"
}

3. 缓存 — SimApiCache

// 由 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

SimApiUtil.cstNow            // UTC+8 时间
SimApiUtil.timestampNow      // 秒级时间戳
SimApiUtil.newGuid()         // UUID v4(对齐 C# Guid.NewGuid()
SimApiUtil.md5("text")       // 32 位十六进制
SimApiUtil.sha1("text")      // 40 位
SimApiUtil.base64Encode("text") / base64Decode("...")
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>
SimApiUtil.checkCell("13800138000")   // 手机号
SimApiUtil.checkEmail("a@b.com")      // 邮箱

4.1 AES 加解密 — SimApiAesUtil(对齐 C# SimApiAesUtil

纯仓颉实现 AES-256-CBC + PKCS7S-box/密钥扩展/轮函数),与 .NET 双向互操作已验证:

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

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

5. HTTP 客户端 — SimApiHttpClient

用于调用其他带签名/AES 的 SimApi 服务(内置 TLS 支持https 自动配置信任所有证书 + SNI,仓颉生态下 stdx TLS 动态加载 openssl 可用):

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}")

签名参数名可配置(simApiHttpClientOptions.signName / timestampName / nonceName / appIdName / signFieldsC# 侧为硬编码)。

5.1 请求日志 — enableRequestLog

记录每次请求的方法、URL、请求头、请求体、响应状态码、耗时与异常(对齐 C#):

  • 请求体按 JSON 字段级截断(仅对超长字符串字段截断,保留结构;非 JSON 整串截断)
  • 下游异常捕获记录后重抛(对齐 C# ExceptionDispatchInfo
  • 响应体因 soulsoft HttpResponse.body 只读不可替换,记录 Content-Length 作为替代(C# 用 MemoryStream 捕获)
builder.addSimApi { 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(替换 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 始终 错误反馈

路由路径可自定义(configureSimApiRoute,自定义值通过 mapGet/mapPost 真实注册,默认值由内置控制器特性路由覆盖):

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

7. 认证后处理 Hook — ISimApiAuthChecker

实现后每次认证成功都会调用(配合 @SimApiAuth 注解或手动 requireLogin):

import simapi.interfaces.*

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

8. 认证中心 SDK — AuthSDK(对齐 C# AuthSDK

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

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

SimApiOptions 完整配置

builder.addSimApi { 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.enableRequestLog       = false   // 请求日志中间件
    options.enableCors             = true    // 全量 CORS
    options.enableLogger           = true    // 控制台日志

    // .NET 风格子模块配置回调(对齐 C# ConfigureSimApiXxx
    options.configureSimApiRoute { route =>
        route.userInfoRoute = Some("/user/info")   // 内置路由自定义路径
        route.logoutRoute = Some("/auth/logout")
        route.webConfigRoute = Some("/config")
    }
    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 自动注册内置控制器 + 自动扫描调用者包中的控制器(对齐 C# Assembly.GetTypes() 扫描,见 SimApiControllerScanner):

控制器 路由 说明
SimApiCommonController /exception/{code}/config/versions/user/info 通用内置路由
SimApiAuthController /auth/logout 退出登录
SimApiBaseController 基类:loginInfo / loginToken / requireLogin() / getLogin()
import simapi.controllers.*
import simapi.attributes.{SimApiAuth}

// 控制器写法:继承 SimApiBaseController,注解路由 + DI 注入
@SimApiAuth                    // 类级鉴权(可选,替代 requireLogin
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 {} 内部自动扫描并注册。


未实现模块(选项占位)

以下 C# 原包功能因仓颉生态暂无对应库(Hangfire/MQTT/MinIO/Swashbuckle),选项保留但未实现

选项 原功能 状态
enableSimApiDoc Swagger 文档(可换 soulsoft_web_openapi 未实现
enableSimApiStorage S3/MinIO 存储 未实现
enableSynapse MQTT 通信 未实现
enableJob Hangfire 任务调度 未实现

已实现(曾为占位):enableSimApiAuthGateAuthSDK 认证中心)、SimApiAesUtil(纯仓颉 AES-256-CBC,与 .NET 双向互操作)、ISimApiAuthChecker(注解鉴权时执行)、内置路由自定义路径。


依赖

依赖 用途
soulsoft_web_http / routing / hosting Web 框架(ASP.NET Core 仓颉移植版)
soulsoft_extensions_logging 系列 日志
soulsoft_extensions_injection 依赖注入
soulsoft_extensions_configuration 配置
soulsoft_serialization JSON 序列化
redispkg.cangjie-lang.cn Redis 客户端(认证/缓存 Redis 模式)
stdxCANGJIE_STDX_PATH 标准扩展库(md5/sha1/base64/http

构建前需设置 CANGJIE_STDX_PATH 指向本地 stdx 的 static/stdx 目录。


许可证

MIT

S
Description
仓颉版 SimApi:ASP.NET Core 风格 API 基础框架,移植自 C# 项目
Readme
551 KiB
Languages
Cangjie 100%