外观
Java SDK
atkonbase-sdk-java—— ATKONBASE 官方 Java SDK(JDK 17,Java 业务后端;独立制品,无需引入服务端模块依赖)。本页讲用法;端点契约见接口参考,认证形态的概念背景见集成拓扑。
安装
包通过 Maven 坐标引入(如所在环境的制品仓库未托管该坐标,请向 ATKONBASE 获取制品仓库地址):
xml
<dependency>
<groupId>com.yhcm.atkonbase</groupId>
<artifactId>atkonbase-sdk-java</artifactId>
<version>0.5.0-SNAPSHOT</version>
</dependency>最小示例(APP_ONLY)
最常见的服务端到服务端调用——只用 Client 身份。withClientCredentials 一行构造,baseUrl 只传一次:
java
import com.yhcm.atkonbase.sdk.client.AtkonbaseClient;
import com.yhcm.atkonbase.sdk.generated.api.V1DocumentApi;
AtkonbaseClient client = AtkonbaseClient.withClientCredentials(
"https://api.atkonbase.example.com", "${clientId}", "${clientSecret}");
V1DocumentApi v1 = client.api(V1DocumentApi.class);
MyType data = v1.someV1Endpoint(/* ... */).getData();client.api(...) 已内化响应解包:业务 code != 0 或 HTTP 非 2xx 直接抛 AtkonbaseApiException(携带真实 HTTP 状态码),成功时返回 generated 统一返回体 ResponseDTO,集成方通过它的 getData() 取业务数据——无需任何手动解包。
高级构造
需要自定义超时 / OkHttpClient / 默认头 / 关闭 401 自动刷新时,改用全量 ClientOptions 构造(与 withClientCredentials 等价,只是把三步装配显式展开):
java
import com.yhcm.atkonbase.sdk.client.AtkonbaseClient;
import com.yhcm.atkonbase.sdk.client.ClientCredentialsTokenProvider;
import com.yhcm.atkonbase.sdk.client.RefreshableTokenProvider;
import com.yhcm.atkonbase.sdk.client.V1Auth;
RefreshableTokenProvider clientToken = ClientCredentialsTokenProvider.builder()
.baseUrl("https://api.atkonbase.example.com")
.clientId("${clientId}")
.clientSecret("${clientSecret}")
.build();
AtkonbaseClient client = new AtkonbaseClient(
AtkonbaseClient.ClientOptions.builder()
.baseUrl("https://api.atkonbase.example.com")
.auth(V1Auth.builder().clientToken(clientToken).build())
.timeoutMs(60_000L) // 默认 30s
.autoRefreshOn401(true) // 默认开
.build());
V1Auth是 client 级身份配置,只承载clientToken(app 自身长期固定身份)。代表某个终端用户 / 外部身份的「代授权」是 per-call 的,下沉到调用层表达(见下)——不挂在V1Auth上。
三种认证形态
身份分两层:clientToken 是 client 级长期固定身份,由 client 承载;代授权(代表谁)是 per-call 的,由调用层 actAsToken / actAsSource 表达,各返回一个只暴露 api() 的 ScopedView。
| 形态 | 调用 | 注入的请求头 |
|---|---|---|
| APP_ONLY | client.api(Xxx.class) | Authorization: Bearer <client> |
| 强通道(代表已登录用户) | client.actAsToken(userToken).api(Xxx.class) | 上 + X-Atk-User-Token: <user> |
| 弱通道(代表外部身份) | client.actAsSource(source, sourceId).api(Xxx.class) | 上 + X-Atk-Act-As-Source + X-Atk-Act-As-Source-Id |
java
// APP_ONLY:纯 client 身份
MyType data = client.api(V1DocumentApi.class)
.someEndpoint(/* ... */)
.getData();
// 强通道:代表一个已签发用户会话 token 的用户
MyType userData = client.actAsToken(userSessionToken)
.api(V1DocumentApi.class)
.someEndpoint(/* ... */)
.getData();
// 弱通道:Client 主张代表外部身份(需 server 侧开启 actAsAllowed)
MyType extData = client.actAsSource("wechat", "openid-xxx")
.api(V1DocumentApi.class)
.someEndpoint(/* ... */)
.getData();要点:
- 代授权是 per-call 的:
actAsToken/actAsSource每次按需调用产出ScopedView,同一个client可对不同终端用户安全并发复用——所有视图共享同一连接池与clientToken刷新单点。 - 通道互斥在编译期成立:
ScopedView只暴露api(),类型层面无法再叠加另一通道(强、弱不可同时);actAsSource双参签名保证source与sourceId成对。约束前移到编译期,不再有运行期校验。
TokenProvider 与凭据刷新
java
@FunctionalInterface
public interface TokenProvider {
String resolve(); // 同步签名,每次请求前由拦截器调一次
}工厂:
| 工厂 | 语义 |
|---|---|
TokenProvider.of(String) | 字面量 token |
TokenProvider.of(Supplier<String>) | 动态 token(你控制缓存) |
ClientCredentialsTokenProvider.builder()...build() | 可刷新 token(RefreshableTokenProvider,SDK 在 401 时主动 invalidate) |
⚠️
resolve()为同步签名,请在其中完成 token 获取(含必要的阻塞等待)。需要「动态 token + 异步刷新」时,在Supplier#get()内future.get(timeout)阻塞拉取。
ClientCredentialsTokenProvider 用 clientId + clientSecret 调 POST /v1/auth/token,带缓存 + 提前续期 + 并发去重(线程安全)。withClientCredentials 工厂内部即用它装配;需要调参时单独构造:
java
ClientCredentialsTokenProvider provider = ClientCredentialsTokenProvider.builder()
.baseUrl("https://api.atkonbase.example.com")
.clientId("${clientId}")
.clientSecret("${clientSecret}")
.refreshSkewMs(60_000L) // 提前续期窗口,默认 60s
.timeoutMs(10_000L) // token 端点超时,默认 10s
.build();跨多个
AtkonbaseClient共享缓存时,复用同一个 provider 实例——不要重复build(),每次产出独立闭包状态。
401 自动刷新
autoRefreshOn401(true)(默认)且 clientToken 是 RefreshableTokenProvider 时,收到 401 时先 invalidate() 当前 token,再用新 token 自动重试一次(仅一次,避免无限重试)。否则退回「401 直接抛 AtkonbaseApiException」。
错误处理
java
class AtkonbaseApiException extends RuntimeException {
int getCode(); // server 业务 code(0 成功,其它失败;常量见 ErrorCodes)
int getHttpStatus(); // 真实 HTTP 状态码
Object getData();
}
class AtkonbaseNetworkException extends RuntimeException {
// 由 IOException 包装;getCause() 返回原异常
}java
import com.yhcm.atkonbase.sdk.client.AtkonbaseApiException;
import com.yhcm.atkonbase.sdk.client.AtkonbaseNetworkException;
import com.yhcm.atkonbase.sdk.client.ErrorCodes;
try {
MyType data = client.api(V1DocumentApi.class).someEndpoint(/* ... */).getData();
} catch (AtkonbaseApiException e) {
if (e.getCode() == ErrorCodes.USER_TOKEN_INVALID) {
// 强通道 user_token 失效,触发用户重新登录
} else if (e.getCode() == ErrorCodes.PERMISSION_DENIED) {
// 权限不足
}
} catch (AtkonbaseNetworkException e) {
// 网络 / 超时
}错误码常量 ErrorCodes
com.yhcm.atkonbase.sdk.client.ErrorCodes 提供 V1 集成接口可能返回的业务错误码常量,覆盖全部 V1 接口。分段概览:
| 段位 | 含义 |
|---|---|
| 101* | 认证(含 USER_TOKEN_INVALID / INVALID_DELEGATION_HEADERS / V1_ACL_USER_IDENTITY_REQUIRED 等) |
| 102* | 权限 / ACL |
| 105* | IdP / SSO |
| 106* | API Client / Scope |
| 112* | 业务身份(用户 / 角色 / 部门) |
| 173* / 179* | 存储与文件上传 / 下载(预签名 / 分片会话 / 签名直读 / 配额 / MIME 校验) |
| 174* | 站内分享 / 分享链接 / 公开分享消费 |
| 175* | 容器生命周期(归档 / 回收站 / 恢复 / 移动 等) |
| 176* | 检索 |
| 177* | Webhook |
| 181* | 服务端摄入(/v1/ingest/*) |
| 182* / 183* | 元数据值 / 字段定义 / 内容类型 / 模型 Schema 导出 |
| 185* | 文档生命周期(发布 / 版本回退 / 回收站 等) |
| 186* / 187* | 内容 ACL / 资源访问前置校验 |
完整含义与触发条件见错误码参考。
public 分享通路
AtkonbasePublicClient 覆盖 /public/s/{tenantCode}/{token}/** 匿名分享端点,无 auth,与 V1 client 类型层面互不替换:
java
import com.yhcm.atkonbase.sdk.client.AtkonbasePublicClient;
import com.yhcm.atkonbase.sdk.client.AtkonbasePublicClient.PublicClientOptions;
import com.yhcm.atkonbase.sdk.client.AtkonbasePublicClient.BuildDownloadUrlOptions;
import com.yhcm.atkonbase.sdk.generatedpublic.model.PublicShareMetaDTO;
import com.yhcm.atkonbase.sdk.generatedpublic.model.ShareVerifyResultDTO;
AtkonbasePublicClient publicClient = new AtkonbasePublicClient(
PublicClientOptions.builder().baseUrl("https://api.atkonbase.example.com").build()
);
// 1. 拉元信息
PublicShareMetaDTO meta = publicClient.getMeta(tenantCode, token);
// 2. 需要密码时,验证后拿一次性 ticket
String ticket = null;
if (Boolean.TRUE.equals(meta.getRequiresPassword())) {
ShareVerifyResultDTO verify = publicClient.verifyPassword(tenantCode, token, userInputPassword);
ticket = verify.getTicket();
}
// 3. 拼下载 URL —— 浏览器原生导航 / 服务端流式转发
String url = publicClient.buildDownloadUrl(
tenantCode, token,
BuildDownloadUrlOptions.builder().ticket(ticket).build()
);⚠️ ticket 是 IP-bound:ticket 与验密码请求的公网 IP 绑定。
verifyPassword调用方与最终下载方必须是同一公网 IP,否则下载被拒。服务端代理场景下不要把 ticket 透传给前端浏览器——正确做法是集成方服务端自己用 ticket 走buildDownloadUrl+ OkHttp/HttpClient 流式转发。前端直连无此问题。
错误处理与 v1 一致:getMeta / verifyPassword 失败抛 AtkonbaseApiException;buildDownloadUrl 仅拼字符串,不抛业务错。
BuildDownloadUrlOptions.disposition("inline"/"attachment")参数当前保留但不生效:下载一律以attachment(触发下载)响应。如需浏览器内预览,请改用接口参考中的预览模式端点。
与 TypeScript SDK 的差异
| 主题 | Java | TypeScript |
|---|---|---|
| 主入口 | client.api(V1DocumentApi.class) | client.api(V1DocumentApi) + client.request<T>(path) 转义出口 |
| 代授权 | client.actAsToken(token) / actAsSource(source, id) → ScopedView | 同左(形态镜像一致) |
TokenProvider 异步性 | 同步 String resolve() | 原生支持异步(() => Promise<string>) |
| 错误统一 | api() 内化解包:非 2xx 与 code != 0 抛 AtkonbaseApiException,成功读 dto.getData() | 中间件把非 2xx 与 code !== 0 转成 AtkonbaseApiError,成功读 dto.data |
| 错误码常量 | ErrorCodes 覆盖 V1 子集 | errorCodes 覆盖 V1 子集 |
TypeScript 用法见 TypeScript SDK。