@h-ai/cli 0.1.0-alpha.12 → 0.1.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/index.js +1 -1
- package/dist/{src-XJ6YXNAF.js → src-RZ4DPYNJ.js} +4 -4
- package/dist/{src-XJ6YXNAF.js.map → src-RZ4DPYNJ.js.map} +1 -1
- package/package.json +2 -2
- package/templates/apps/android-app/src/lib/api.ts.hbs +5 -4
- package/templates/base/package.json.hbs +21 -21
- package/templates/base/src/app.css.hbs +2 -1
- package/templates/skills/AGENTS.md +1 -0
- package/templates/skills/copilot-instructions.md +3 -0
- package/templates/skills/hai-ai/SKILL.md +44 -30
- package/templates/skills/hai-api-client/SKILL.md +94 -295
- package/templates/skills/hai-api-contract/SKILL.md +272 -0
- package/templates/skills/hai-build/SKILL.md +25 -13
- package/templates/skills/hai-capacitor/SKILL.md +7 -7
- package/templates/skills/hai-ci/SKILL.md +112 -0
- package/templates/skills/hai-core/SKILL.md +65 -2
- package/templates/skills/hai-crypto/SKILL.md +42 -1
- package/templates/skills/hai-framework-sync/SKILL.md +98 -0
- package/templates/skills/hai-iam/SKILL.md +13 -35
- package/templates/skills/hai-kit/SKILL.md +85 -412
- package/templates/skills/hai-payment/SKILL.md +8 -10
- package/templates/skills/hai-pr-review/SKILL.md +109 -0
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-serv/SKILL.md +658 -0
- package/templates/skills/hai-storage/SKILL.md +7 -47
- package/templates/skills/hai-ui/SKILL.md +2 -1
|
@@ -1,355 +1,154 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-api-client
|
|
3
|
-
description: 使用 @h-ai/api-client
|
|
3
|
+
description: 使用 @h-ai/api-client 构建多端共用的 oRPC/OpenAPI typed client,通过 apiClient.init() 初始化,支持 Bearer Token 自动管理、401 刷新重试与 contract 嵌套方法调用;当需求涉及客户端 API 请求、Token 管理或多端数据层时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-api-client
|
|
7
7
|
|
|
8
|
-
> `@h-ai/api-client`
|
|
8
|
+
> `@h-ai/api-client` 是跨端 typed API client。公共 HTTP API 由 `@h-ai/api-contract` 定义,服务端由 `@h-ai/serv` 挂载,客户端直接调用 `apiClient.<domain>.<group>.<operation>()`。
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## 运行环境
|
|
13
|
-
|
|
14
|
-
**浏览器端专用。** 服务端 `+page.server.ts` / `+server.ts` 应直接调用模块 API(如 `iam.auth.login()`),不经过 HTTP 自环。
|
|
15
|
-
|
|
16
|
-
### 与 kit.client 的分工
|
|
17
|
-
|
|
18
|
-
| 场景 | 推荐 | 原因 |
|
|
19
|
-
|------|------|------|
|
|
20
|
-
| SSR SvelteKit 应用(同源请求) | `kit.client.create().apiFetch`(见 hai-kit SKILL) | 自动 CSRF、传输加密、同源 Cookie 透传 |
|
|
21
|
-
| SPA / 原生 App(跨域请求) | **本模块** `api.init()` + `api.call()` | 完整 Token 管理、401 自动刷新、独立 baseUrl |
|
|
22
|
-
| 契约调用(类型安全,跨域) | **本模块** `api.call(endpoint, input)` | EndpointDef 双向 Zod 校验 |
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 适用场景
|
|
10
|
+
## 使用步骤
|
|
27
11
|
|
|
28
|
-
|
|
29
|
-
- Bearer Token 自动管理(存储、附加、刷新)
|
|
30
|
-
- 基于 EndpointDef 契约的类型安全调用
|
|
31
|
-
- 流式响应(SSE / ReadableStream)
|
|
32
|
-
- 拦截器(请求/响应前后处理)
|
|
12
|
+
### 1. 初始化
|
|
33
13
|
|
|
34
|
-
|
|
14
|
+
浏览器默认使用 httpOnly cookie 存储(推荐):
|
|
35
15
|
|
|
36
|
-
|
|
16
|
+
```ts
|
|
17
|
+
import { apiClient } from '@h-ai/api-client'
|
|
37
18
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
import { api } from '@h-ai/api-client'
|
|
42
|
-
|
|
43
|
-
// 应用启动时初始化
|
|
44
|
-
await api.init({
|
|
45
|
-
baseUrl: 'http://localhost:3000',
|
|
46
|
-
auth: {
|
|
47
|
-
refreshUrl: '/auth/refresh',
|
|
48
|
-
// Token 刷新失败回调(可选,常用于跳转登录页)
|
|
49
|
-
onRefreshFailed: () => {
|
|
50
|
-
window.location.href = '/login'
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
timeout: 15000,
|
|
19
|
+
await apiClient.init({
|
|
20
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
21
|
+
auth: {},
|
|
54
22
|
})
|
|
55
23
|
```
|
|
56
24
|
|
|
57
|
-
|
|
25
|
+
启用传输加密时,只传顶层 `crypto` 句柄;api-client 内部自动调用 `crypto.transport.createClient()`:
|
|
58
26
|
|
|
59
|
-
|
|
27
|
+
```ts
|
|
28
|
+
import { apiClient } from '@h-ai/api-client'
|
|
29
|
+
import { crypto } from '@h-ai/crypto'
|
|
60
30
|
|
|
61
|
-
|
|
62
|
-
// GET 请求
|
|
63
|
-
const users = await api.get<User[]>('/api/v1/users')
|
|
31
|
+
await crypto.init()
|
|
64
32
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
33
|
+
await apiClient.init({
|
|
34
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
35
|
+
auth: {},
|
|
36
|
+
transport: { crypto }, // 默认协商路径:/api/v1/_hai/key-exchange
|
|
69
37
|
})
|
|
70
|
-
|
|
71
|
-
// 带查询参数(直接传入 Record<string, unknown>)
|
|
72
|
-
const page = await api.get<PageResult>('/api/v1/users', { page: 1, pageSize: 20 })
|
|
73
38
|
```
|
|
74
39
|
|
|
75
|
-
|
|
40
|
+
App/Capacitor 场景传入自定义 TokenStorage:
|
|
76
41
|
|
|
77
|
-
```
|
|
78
|
-
import {
|
|
42
|
+
```ts
|
|
43
|
+
import { createCapacitorTokenStorage } from '@h-ai/capacitor'
|
|
79
44
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
password: 'StrongPassword123',
|
|
45
|
+
await apiClient.init({
|
|
46
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
47
|
+
auth: { storage: createCapacitorTokenStorage() },
|
|
84
48
|
})
|
|
85
|
-
|
|
86
|
-
if (loginResult.success) {
|
|
87
|
-
// loginResult.data 类型自动推导为 { user, tokens, agreements? }
|
|
88
|
-
await api.auth.setTokens(loginResult.data.tokens)
|
|
89
|
-
}
|
|
90
49
|
```
|
|
91
50
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
// 手动设置 Token(登录后)
|
|
96
|
-
await api.auth.setTokens({
|
|
97
|
-
accessToken: 'xxx',
|
|
98
|
-
refreshToken: 'yyy',
|
|
99
|
-
expiresIn: 3600,
|
|
100
|
-
tokenType: 'Bearer',
|
|
101
|
-
})
|
|
51
|
+
SSR / Node.js 测试场景请显式传入内存存储:
|
|
102
52
|
|
|
103
|
-
|
|
104
|
-
|
|
53
|
+
```ts
|
|
54
|
+
import { apiClient } from '@h-ai/api-client'
|
|
105
55
|
|
|
106
|
-
|
|
107
|
-
api.
|
|
108
|
-
|
|
56
|
+
await apiClient.init({
|
|
57
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
58
|
+
auth: { storage: apiClient.tokenStorage.memory() },
|
|
109
59
|
})
|
|
110
60
|
```
|
|
111
61
|
|
|
112
|
-
###
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
const controller = new AbortController()
|
|
62
|
+
### 2. 调用 typed API
|
|
116
63
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
64
|
+
```ts
|
|
65
|
+
const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
66
|
+
if (login.success) {
|
|
67
|
+
// 默认 httpOnly cookie 模式下,refresh token 由服务端 Set-Cookie 管理。
|
|
68
|
+
await apiClient.auth.setTokens(login.data.tokens)
|
|
120
69
|
}
|
|
121
70
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
### 6. 关闭
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
await api.close()
|
|
71
|
+
const me = await apiClient.iam.auth.currentUser()
|
|
72
|
+
const upload = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar.png' })
|
|
73
|
+
const answer = await apiClient.ai.chats.sendMessage({ message: 'hello' })
|
|
130
74
|
```
|
|
131
75
|
|
|
132
|
-
|
|
76
|
+
### 3. 自定义 contract
|
|
133
77
|
|
|
134
|
-
|
|
78
|
+
```ts
|
|
79
|
+
import { apiClient } from '@h-ai/api-client'
|
|
80
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
135
81
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
| `api.close()` | 关闭并释放资源 | 重复调用安全 |
|
|
140
|
-
| `api.isInitialized` | 初始化状态 | `boolean` |
|
|
141
|
-
| `api.config` | 当前配置 | 未初始化时为 `null` |
|
|
142
|
-
| `api.get<T>(path, params?)` | GET 请求 | params 附加到 URL query string |
|
|
143
|
-
| `api.post<T>(path, body?)` | POST 请求 | body 自动 JSON 序列化 |
|
|
144
|
-
| `api.put<T>(path, body?)` | PUT 请求 | — |
|
|
145
|
-
| `api.patch<T>(path, body?)` | PATCH 请求 | — |
|
|
146
|
-
| `api.delete<T>(path, params?)` | DELETE 请求 | params 附加到 URL query string |
|
|
147
|
-
| `api.call(endpoint, input)` | 契约调用 | 入参/出参 Zod 双向校验,路径/方法由契约决定 |
|
|
148
|
-
| `api.upload(path, file, options?)` | 文件上传 | FormData,支持附加字段 |
|
|
149
|
-
| `api.stream(path, body?)` | 流式请求 | 返回 AsyncIterable<string>(SSE) |
|
|
150
|
-
| `api.auth.setTokens(tokens)` | 设置 Token | 存入 TokenStorage |
|
|
151
|
-
| `api.auth.clear()` | 清空 Token | 清空存储 |
|
|
152
|
-
| `api.auth.onTokenRefreshed(cb)` | 刷新回调 | 返回取消订阅函数,Token 自动刷新成功时通知 |
|
|
153
|
-
|
|
154
|
-
### EndpointDef 契约结构
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
interface EndpointDef<TInput, TOutput> {
|
|
158
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
159
|
-
path: string
|
|
160
|
-
input: ZodSchema<TInput>
|
|
161
|
-
output: ZodSchema<TOutput>
|
|
162
|
-
requireAuth?: boolean
|
|
163
|
-
meta?: { summary?: string; tags?: string[] }
|
|
164
|
-
}
|
|
82
|
+
const contract = apiContract.create({ iam: apiContract.iam })
|
|
83
|
+
const client = apiClient.create(contract)
|
|
84
|
+
await client.init({ baseUrl: 'https://api.example.com/api/v1' })
|
|
165
85
|
```
|
|
166
86
|
|
|
167
|
-
###
|
|
87
|
+
### 4. 关闭
|
|
168
88
|
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
getAccessToken: () => Promise<string | null>
|
|
172
|
-
setAccessToken: (token: string) => Promise<void>
|
|
173
|
-
getRefreshToken: () => Promise<string | null>
|
|
174
|
-
setRefreshToken: (token: string) => Promise<void>
|
|
175
|
-
clear: () => Promise<void>
|
|
176
|
-
}
|
|
89
|
+
```ts
|
|
90
|
+
await apiClient.close()
|
|
177
91
|
```
|
|
178
92
|
|
|
179
|
-
|
|
180
|
-
App 端使用 `@h-ai/capacitor` 的 `createCapacitorTokenStorage()`。
|
|
93
|
+
## Token Storage 适配器
|
|
181
94
|
|
|
182
|
-
|
|
95
|
+
| 工厂调用 | 存储位置 | 适用场景 |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| `apiClient.tokenStorage.httpOnlyCookie()` | httpOnly cookie(服务端管理) | **默认**;浏览器端推荐,refresh token 不暴露给 JS |
|
|
98
|
+
| `apiClient.tokenStorage.memory()` | 内存 | SSR / Node.js 单元测试(需显式传入) |
|
|
99
|
+
| `apiClient.tokenStorage.localStorage()` | localStorage | 非敏感场景(有 XSS 风险,生产不推荐) |
|
|
183
100
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| 错误码 | code | 说明 |
|
|
187
|
-
|--------|------|------|
|
|
188
|
-
| `HaiApiClientError.NETWORK_ERROR` | `hai:api-client:001` | 网络不可达 |
|
|
189
|
-
| `HaiApiClientError.TIMEOUT` | `hai:api-client:002` | 请求超时 |
|
|
190
|
-
| `HaiApiClientError.SERVER_ERROR` | `hai:api-client:003` | 5xx 服务端错误 |
|
|
191
|
-
| `HaiApiClientError.UNAUTHORIZED` | `hai:api-client:004` | 401 未认证 |
|
|
192
|
-
| `HaiApiClientError.FORBIDDEN` | `hai:api-client:005` | 403 无权限 |
|
|
193
|
-
| `HaiApiClientError.NOT_FOUND` | `hai:api-client:006` | 404 资源不存在 |
|
|
194
|
-
| `HaiApiClientError.VALIDATION_FAILED` | `hai:api-client:007` | 请求参数校验失败 |
|
|
195
|
-
| `HaiApiClientError.TOKEN_REFRESH_FAILED` | `hai:api-client:008` | Token 刷新失败 |
|
|
196
|
-
| `HaiApiClientError.NOT_INITIALIZED` | `hai:api-client:010` | 未初始化 |
|
|
197
|
-
| `HaiApiClientError.CONFIG_ERROR` | `hai:api-client:011` | 配置错误 |
|
|
198
|
-
| `HaiApiClientError.UNKNOWN` | `hai:api-client:099` | 未知错误 |
|
|
101
|
+
### httpOnly Cookie 模式
|
|
199
102
|
|
|
200
|
-
|
|
103
|
+
Access token 存在内存中,refresh token 由服务端通过 `HttpOnly` cookie 管理,浏览器 JS 无法读取,可有效防止 XSS 窃取 token。
|
|
201
104
|
|
|
202
|
-
|
|
105
|
+
**前提**:服务端需配置 `serv.createApp({ iam, refreshCookie: {} })`,参见 `hai-serv` skill。
|
|
203
106
|
|
|
204
|
-
|
|
107
|
+
启用 `transport` 时,登录、业务请求以及 401 后的自动刷新都会复用同一个加密 fetch 链路;不要在业务代码中手写明文 `/auth/refresh` 请求。
|
|
205
108
|
|
|
206
|
-
|
|
109
|
+
```ts
|
|
110
|
+
import { apiClient } from '@h-ai/api-client'
|
|
207
111
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
│ xx-api-contract.ts ← xxEndpoints(method + path + schema)│
|
|
213
|
-
└─────────────┬──────────────────────────┬────────────────────┘
|
|
214
|
-
│ │
|
|
215
|
-
┌────────▼────────┐ ┌─────────▼─────────┐
|
|
216
|
-
│ 客户端(浏览器)│ │ 服务端(SvelteKit)│
|
|
217
|
-
│ api.call(ep, i) │ │ kit.fromContract │
|
|
218
|
-
│ @h-ai/api-client│ │ @h-ai/kit │
|
|
219
|
-
└────────┬────────┘ └─────────┬─────────┘
|
|
220
|
-
│ HTTP(类型安全) │
|
|
221
|
-
└──────────────────────────┘
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### 客户端流程(本模块责任)
|
|
225
|
-
|
|
226
|
-
1. 从 `@h-ai/xx/api` 导入 `xxEndpoints`
|
|
227
|
-
2. `api.call(xxEndpoints.xxx, input)` → Zod 校验入参 → 按 method/path 发起 HTTP → Zod 校验出参
|
|
228
|
-
3. 入参或出参校验失败均返回 `VALIDATION_FAILED`(1206)
|
|
229
|
-
4. 返回 `HaiResult<TOutput>`
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
import { storageEndpoints } from '@h-ai/storage/api'
|
|
233
|
-
import { iamEndpoints } from '@h-ai/iam/api'
|
|
234
|
-
import { aiEndpoints } from '@h-ai/ai/api'
|
|
235
|
-
|
|
236
|
-
// 存储:获取上传签名 URL
|
|
237
|
-
const upload = await api.call(storageEndpoints.presignUpload, {
|
|
238
|
-
key: 'avatars/user-1.png',
|
|
239
|
-
contentType: 'image/png',
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
// IAM:登录
|
|
243
|
-
const login = await api.call(iamEndpoints.login, {
|
|
244
|
-
username: 'admin',
|
|
245
|
-
password: 'xxx',
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// AI:发送消息
|
|
249
|
-
const chat = await api.call(aiEndpoints.sendMessage, {
|
|
250
|
-
conversationId: 'conv-1',
|
|
251
|
-
content: 'Hello',
|
|
112
|
+
// apiClient.tokenStorage.httpOnlyCookie() 是默认存储,无需显式指定
|
|
113
|
+
await apiClient.init({
|
|
114
|
+
baseUrl: 'https://api.example.com/api/v1',
|
|
115
|
+
auth: {},
|
|
252
116
|
})
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### 已有契约模块一览
|
|
256
|
-
|
|
257
|
-
| 模块 | 导入路径 | 端点对象 | 典型端点 |
|
|
258
|
-
| ---------- | -------------------- | -------------------- | ------------------------------- |
|
|
259
|
-
| `storage` | `@h-ai/storage/api` | `storageEndpoints` | presignUpload, listFiles, … |
|
|
260
|
-
| `iam` | `@h-ai/iam/api` | `iamEndpoints` | login, logout, currentUser, … |
|
|
261
|
-
| `ai` | `@h-ai/ai/api` | `aiEndpoints` | chat, chatStream, sendMessage |
|
|
262
|
-
| `payment` | `@h-ai/payment/api` | `paymentEndpoints` | createOrder, queryOrder, … |
|
|
263
|
-
|
|
264
117
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
### 与 @h-ai/iam/api 契约配合
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
import { iamEndpoints } from '@h-ai/iam/api'
|
|
271
|
-
|
|
272
|
-
// 登录(入参字段为 identifier + password)
|
|
273
|
-
const login = await api.call(iamEndpoints.login, { identifier, password })
|
|
118
|
+
// 登录后设置 access token(refresh token 由服务端写入 cookie,前端无需操作)
|
|
119
|
+
const login = await apiClient.iam.auth.login({ identifier: 'alice', password: 'secret' })
|
|
274
120
|
if (login.success) {
|
|
275
|
-
|
|
276
|
-
await api.auth.setTokens(login.data.tokens)
|
|
121
|
+
await apiClient.auth.setTokens(login.data.tokens)
|
|
277
122
|
}
|
|
123
|
+
```
|
|
278
124
|
|
|
279
|
-
|
|
280
|
-
const me = await api.call(iamEndpoints.currentUser, {})
|
|
281
|
-
// me.data 类型:{ user, roles, permissions }
|
|
125
|
+
## 核心 API
|
|
282
126
|
|
|
283
|
-
|
|
284
|
-
|
|
127
|
+
| API | 用途 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `apiClient.init(config)` | 初始化默认 API Service client |
|
|
130
|
+
| `apiClient.close()` | 清理 client 状态 |
|
|
131
|
+
| `apiClient.auth.setTokens(tokens)` | 写入 access token;非 httpOnly 存储会同时写入 refresh token |
|
|
132
|
+
| `apiClient.auth.clear()` | 清理 Token |
|
|
133
|
+
| `apiClient.auth.onTokenRefreshed(cb)` | 监听刷新结果 |
|
|
134
|
+
| `apiClient.create(contract)` | 创建自定义 typed client |
|
|
135
|
+
| `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()` | TokenStorage 工厂 |
|
|
285
136
|
|
|
286
|
-
|
|
287
|
-
const accessToken = '...' // 从 Token 存储获取
|
|
288
|
-
await api.call(iamEndpoints.logout, { accessToken })
|
|
289
|
-
await api.auth.clear()
|
|
290
|
-
```
|
|
137
|
+
### Transport 配置
|
|
291
138
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
1. 使用存储的 `refreshToken` 调用 `auth.refreshUrl`(POST)
|
|
297
|
-
2. 刷新成功 → 更新存储 → 用新 Token 重试原请求(仅重试一次)
|
|
298
|
-
3. 刷新失败 → 清空 Token → 触发 `onRefreshFailed` 回调
|
|
299
|
-
4. 多个并发 401 → **自动去重**,只发一次刷新请求
|
|
300
|
-
|
|
301
|
-
> 流式请求(`api.stream()`)同样支持 401 自动刷新 + 重试。
|
|
302
|
-
|
|
303
|
-
#### 可用的 IAM 端点
|
|
304
|
-
|
|
305
|
-
| 分类 | 端点 | 方法 | 路径 | 认证 |
|
|
306
|
-
|------|------|------|------|------|
|
|
307
|
-
| 认证 | `iamEndpoints.login` | POST | /auth/login | 否 |
|
|
308
|
-
| | `iamEndpoints.loginWithOtp` | POST | /auth/login/otp | 否 |
|
|
309
|
-
| | `iamEndpoints.logout` | POST | /auth/logout | 是 |
|
|
310
|
-
| | `iamEndpoints.currentUser` | GET | /auth/me | 是 |
|
|
311
|
-
| | `iamEndpoints.refreshToken` | POST | /auth/refresh | 否 |
|
|
312
|
-
| | `iamEndpoints.sendOtp` | POST | /auth/otp/send | 否 |
|
|
313
|
-
| | `iamEndpoints.register` | POST | /auth/register | 否 |
|
|
314
|
-
| | `iamEndpoints.changePassword` | POST | /auth/change-password | 是 |
|
|
315
|
-
| | `iamEndpoints.updateCurrentUser` | PUT | /auth/me | 是 |
|
|
316
|
-
| 用户管理 | `iamEndpoints.listUsers` | GET | /iam/users | 是 |
|
|
317
|
-
| | `iamEndpoints.getUser` | GET | /iam/users/:id | 是 |
|
|
318
|
-
| | `iamEndpoints.createUser` | POST | /iam/users | 是 |
|
|
319
|
-
| | `iamEndpoints.updateUser` | PUT | /iam/users/:id | 是 |
|
|
320
|
-
| | `iamEndpoints.deleteUser` | DELETE | /iam/users/:id | 是 |
|
|
321
|
-
| | `iamEndpoints.adminResetPassword` | POST | /iam/users/:id/reset-password | 是 |
|
|
322
|
-
| 角色管理 | `iamEndpoints.listRoles` | GET | /iam/roles | 是 |
|
|
323
|
-
| | `iamEndpoints.getRole` | GET | /iam/roles/:id | 是 |
|
|
324
|
-
| | `iamEndpoints.createRole` | POST | /iam/roles | 是 |
|
|
325
|
-
| | `iamEndpoints.updateRole` | PUT | /iam/roles/:id | 是 |
|
|
326
|
-
| | `iamEndpoints.deleteRole` | DELETE | /iam/roles/:id | 是 |
|
|
327
|
-
| 权限管理 | `iamEndpoints.listPermissions` | GET | /iam/permissions | 是 |
|
|
328
|
-
| | `iamEndpoints.getPermission` | GET | /iam/permissions/:id | 是 |
|
|
329
|
-
| | `iamEndpoints.createPermission` | POST | /iam/permissions | 是 |
|
|
330
|
-
| | `iamEndpoints.deletePermission` | DELETE | /iam/permissions/:id | 是 |
|
|
331
|
-
|
|
332
|
-
### Android App 中使用
|
|
333
|
-
|
|
334
|
-
```typescript
|
|
335
|
-
import { api } from '@h-ai/api-client'
|
|
336
|
-
import { createCapacitorTokenStorage } from '@h-ai/capacitor'
|
|
337
|
-
|
|
338
|
-
// 应用启动时初始化
|
|
339
|
-
await api.init({
|
|
340
|
-
baseUrl: import.meta.env.PUBLIC_API_BASE,
|
|
341
|
-
auth: {
|
|
342
|
-
storage: createCapacitorTokenStorage(),
|
|
343
|
-
refreshUrl: '/api/v1/auth/refresh',
|
|
344
|
-
},
|
|
345
|
-
})
|
|
346
|
-
```
|
|
139
|
+
| 字段 | 说明 |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `transport.crypto` | 已初始化的 `@h-ai/crypto` 实例 |
|
|
142
|
+
| `transport.keyExchangePath` | 相对于 `baseUrl` 的协商子路径,默认 `/_hai/key-exchange` |
|
|
347
143
|
|
|
348
|
-
|
|
144
|
+
服务端必须对应启用 `serv.createApp({ transport: { crypto } })`。
|
|
145
|
+
自动刷新会沿用 `transport.encryptedFetch`,并在 httpOnly cookie 模式下发送空请求体 + `credentials: 'include'`,由服务端从 cookie 读取 refresh token。
|
|
349
146
|
|
|
350
|
-
##
|
|
147
|
+
## 常见模式
|
|
351
148
|
|
|
352
|
-
- `
|
|
353
|
-
-
|
|
354
|
-
- `
|
|
355
|
-
- `
|
|
149
|
+
- `baseUrl` 通常包含服务端 `apiPrefix`,例如 `/api/v1`。
|
|
150
|
+
- 业务错误不 throw,统一判断 `HaiResult.success`。
|
|
151
|
+
- 客户端不导入服务端 `procedures` 或 `@h-ai/serv`。
|
|
152
|
+
- 客户端不导入 `@h-ai/crypto` 的内部 transport 工厂,只通过 `transport: { crypto }` 装配。
|
|
153
|
+
- 前端只依赖 `@h-ai/api-client` 和 contract 类型,不依赖业务模块实现。
|
|
154
|
+
- httpOnly cookie 模式需服务端与客户端同步配置,不支持跨域刷新(`SameSite=Strict`)。
|