外观
TypeScript SDK
@atkonbase/sdk—— ATKONBASE 官方 TypeScript SDK。Node ≥ 18 / 浏览器 / Worker 通用,使用全局fetch,无运行时依赖。本页讲用法;端点契约见接口参考,认证形态的概念背景见集成拓扑。
安装
bash
pnpm add @atkonbase/sdk包必须用 pnpm 安装(preinstall 钩子强制)。
最小示例(APP_ONLY)
最常见的服务端到服务端调用——只用 Client 身份。withClientCredentials 一行构造,baseUrl 只传一次:
ts
import { AtkonbaseClient, V1DocumentApi } from "@atkonbase/sdk";
const client = AtkonbaseClient.withClientCredentials(
"https://api.atkonbase.example.com",
"${clientId}",
"${clientSecret}",
);
// typed API(路径 / 参数序列化 / 鉴权头 / 401 重试全自动)
const v1 = client.api(V1DocumentApi);
const dto = await v1.someEndpoint({ /* ... */ });
console.log(dto.data);高级构造
需要自定义 fetch / timeoutMs / defaultHeaders / 关闭 401 自动刷新时,改用全量构造(与 withClientCredentials 等价,只是把装配显式展开):
ts
import { AtkonbaseClient, createClientCredentialsTokenProvider } from "@atkonbase/sdk";
const clientToken = createClientCredentialsTokenProvider({
baseUrl: "https://api.atkonbase.example.com",
clientId: "${clientId}",
clientSecret: "${clientSecret}",
});
const client = new AtkonbaseClient({
baseUrl: "https://api.atkonbase.example.com",
auth: { clientToken }, // V1Auth:只承载 clientToken
timeoutMs: 60_000, // 默认 30s
autoRefreshOn401: true, // 默认开
});转义出口——spec 未覆盖或返回结构不规则时直接走原始路径:
ts
const data = await client.request<MyResponse>("/v1/some/resource", {
method: "GET",
query: { page: 1, size: 20 }, // 数组会展开为多值
});转义出口默认仅注入
clientToken(APP_ONLY)。需代授权时优先用actAsToken/actAsSource视图(见下);low-level 场景也可在headers里手动传X-Atk-User-Token/X-Atk-Act-As-*。
三种认证形态
身份分两层:clientToken 是 client 级长期固定身份,由 client 承载;代授权(代表谁)是 per-call 的,由调用层 actAsToken / actAsSource 表达,各返回一个只暴露 api() 的 ScopedView。
| 形态 | 调用 | 注入的请求头 |
|---|---|---|
| APP_ONLY | client.api(Xxx) | Authorization: Bearer <client> |
| 强通道(代表已登录用户) | client.actAsToken(userToken).api(Xxx) | 上 + X-Atk-User-Token: <user> |
| 弱通道(代表外部身份) | client.actAsSource(source, sourceId).api(Xxx) | 上 + X-Atk-Act-As-Source + X-Atk-Act-As-Source-Id |
ts
// APP_ONLY:纯 client 身份
const dto = await client.api(V1DocumentApi).someEndpoint({ /* ... */ });
// 强通道:代表一个已签发用户会话 token 的用户
const userDto = await client
.actAsToken(userSessionToken)
.api(V1DocumentApi)
.someEndpoint({ /* ... */ });
// 弱通道:Client 主张代表外部身份(需 server 侧开启 actAsAllowed)
const extDto = await client
.actAsSource("wechat", "openid-xxx")
.api(V1DocumentApi)
.someEndpoint({ /* ... */ });要点:
- 代授权是 per-call 的:
actAsToken/actAsSource每次按需调用产出ScopedView,同一个client可对不同终端用户安全并发复用——所有视图共享同一clientToken刷新单点与 401 自动刷新中间件。 - 通道互斥在编译期成立:
ScopedView只暴露api(),类型层面无法再叠加另一通道(强、弱不可同时);actAsSource双参签名保证source与sourceId成对。约束前移到编译期,不再依赖运行期校验。
TokenProvider 与凭据刷新
token 字段接受三种形态:
ts
type TokenProvider =
| string // 固定 token
| (() => string | Promise<string>) // 动态 resolver(你自己控制缓存)
| RefreshableTokenProvider; // 可被 SDK 主动 invalidatecreateClientCredentialsTokenProvider 用 clientId + clientSecret 调 POST /v1/auth/token,返回带缓存 + 提前续期 + 并发去重的 RefreshableTokenProvider。withClientCredentials 工厂内部即用它装配;需要调参时单独构造:
ts
const provider = createClientCredentialsTokenProvider({
baseUrl: "https://api.atkonbase.example.com",
clientId: "${clientId}",
clientSecret: "${clientSecret}",
refreshSkewMs: 60_000, // 提前续期窗口,默认 60s
timeoutMs: 10_000, // token 端点超时,默认 10s
});resolve():缓存命中(未到 skew 窗口)直接返回;否则发刷新请求。- 并发去重:多个
resolve共享同一 in-flight 请求。 - 跨多个
AtkonbaseClient共享缓存时,复用同一个 provider 实例——不要重复调createClientCredentialsTokenProvider,每次都产出独立闭包状态。
401 自动刷新
autoRefreshOn401: true(默认)且 clientToken 是 RefreshableTokenProvider 时,收到 HTTP 401 会先 invalidate() 再重发一次(仅一次,避免循环)。固定字符串 token / 纯 resolver / 关闭开关时,退回「401 直接抛错」。
错误处理
typed API(client.api(...))的中间件已把 HTTP 非 2xx 与 code !== 0 都转成 AtkonbaseApiError,业务代码可直接读 dto.data,无需再判 code。解包规则:
| 情况 | 行为 |
|---|---|
JSON code === 0 | 返回 payload.data |
JSON code !== 0 或 !ok | 抛 AtkonbaseApiError(payload, httpStatus) |
204 / Content-Length: 0 | ok 时返回 undefined;否则抛错 |
| 非 JSON | ok 时返回字符串;否则抛错 |
| 网络 / 超时 | 抛 AtkonbaseNetworkError |
ts
import { AtkonbaseApiError, AtkonbaseNetworkError } from "@atkonbase/sdk";
try {
const dto = await client.api(V1DocumentApi).someEndpoint({ /* ... */ });
// 直接用 dto.data
} catch (e) {
if (e instanceof AtkonbaseApiError) {
// e.code(server 业务 code)/ e.httpStatus / e.message / e.data
} else if (e instanceof AtkonbaseNetworkError) {
// 网络 / 超时 / abort,原始异常在 e.cause
} else {
throw e;
}
}错误码常量 errorCodes
TypeScript SDK 提供错误码常量集合 errorCodes,与 Java SDK ErrorCodes 同名同值、同 V1 暴露子集。可直接引用具名常量判定 e.code,无需直接写死数字:
ts
import { AtkonbaseApiError, errorCodes } from "@atkonbase/sdk";
try {
const dto = await client.api(V1DocumentApi).someEndpoint({ /* ... */ });
} catch (e) {
if (e instanceof AtkonbaseApiError) {
if (e.code === errorCodes.USER_TOKEN_INVALID) {
// 用户 token 失效——引导重新登录 / 刷新
} else if (e.code === errorCodes.PERMISSION_DENIED) {
// 权限不足
}
// 请始终保留一个通用错误分支处理未显式判定的 code,不要假设错误码集合是封闭的(后续版本可能新增)
}
}完整含义与触发条件见错误码参考。
public 分享通路
AtkonbasePublicClient 覆盖 /public/s/{tenantCode}/{token}/** 匿名分享端点,无 auth,与 V1 client 类型层面互不替换:
ts
import { AtkonbasePublicClient } from "@atkonbase/sdk";
const publicClient = new AtkonbasePublicClient({ baseUrl });
// 1. 拉元信息(是否需要密码、文件大小、过期时间等)
const meta = await publicClient.getMeta(tenantCode, token);
// 2. 需要密码时,验证后拿一次性 ticket
let ticket: string | undefined;
if (meta.requiresPassword) {
const verify = await publicClient.verifyPassword(tenantCode, token, userInputPassword);
ticket = verify.ticket;
}
// 3. 拼下载 URL —— 推荐浏览器原生导航(绕开 CORS / 内存峰值)
const url = publicClient.buildDownloadUrl(tenantCode, token, { ticket });
window.location.href = url;⚠️ ticket 是 IP-bound:ticket 与首次验密码请求的公网 IP 绑定。
verifyPassword调用方与最终下载方必须是同一公网 IP,否则下载被拒。服务端代理场景下不要把 ticket 透传给前端浏览器——服务端拿到的 ticket 绑的是服务端出口 IP;正确做法是集成方服务端自己用 ticket 走buildDownloadUrl+fetch流式转发。前端直连无此问题。
错误处理与 v1 一致:getMeta / verifyPassword 失败抛 AtkonbaseApiError,网络 / 超时抛 AtkonbaseNetworkError;buildDownloadUrl 仅拼字符串,不抛业务错。
buildDownloadUrl第三参数的disposition?: "inline" | "attachment"参数当前保留但不生效:下载一律以attachment(触发下载)响应。如需浏览器内预览,请改用接口参考中的预览模式端点。
与 Java SDK 的差异
| 主题 | TypeScript | Java |
|---|---|---|
| 主入口 | client.api(V1DocumentApi) + client.request<T>(path) 转义出口 | client.api(V1DocumentApi.class) |
| 代授权 | client.actAsToken(token) / actAsSource(source, id) → ScopedView | 同左(形态镜像一致) |
TokenProvider 异步性 | 原生支持异步(() => Promise<string>) | Java 侧为同步签名 String resolve() |
| 错误统一 | 中间件把非 2xx 与 code !== 0 转成 AtkonbaseApiError,成功读 dto.data | api() 内化解包:非 2xx 与 code != 0 抛 AtkonbaseApiException,成功读 dto.getData() |
| 错误码常量 | errorCodes 覆盖 V1 子集 | ErrorCodes 覆盖 V1 子集 |
Java 用法见 Java SDK。