@djvlc/openapi-client-core 1.0.0
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 +318 -0
- package/dist/index.d.mts +996 -0
- package/dist/index.d.ts +996 -0
- package/dist/index.js +1396 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1327 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# @djvlc/openapi-client-core
|
|
2
|
+
|
|
3
|
+
OpenAPI Client 公共运行时库,为 `@djvlc/openapi-*-client` 系列包提供核心功能。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **结构化错误类**:`DjvApiError`、`NetworkError`、`TimeoutError`、`AbortError`
|
|
8
|
+
- **增强版 fetch**:超时、重试(支持 Retry-After)、取消支持
|
|
9
|
+
- **Middleware 系统**:请求/响应拦截
|
|
10
|
+
- **拦截器系统**:更灵活的请求/响应/错误处理
|
|
11
|
+
- **请求去重**:避免相同 GET 请求重复发送
|
|
12
|
+
- **指标收集**:请求级别的性能指标和统计
|
|
13
|
+
- **日志系统**:可配置的请求日志
|
|
14
|
+
- **客户端工厂**:简化客户端创建
|
|
15
|
+
- **自动版本注入**:构建时自动从 package.json 注入版本号
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
通常你不需要直接安装这个包,它会作为 client 包的依赖自动安装。
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @djvlc/openapi-client-core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 错误处理
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { DjvApiError, NetworkError, TimeoutError, AbortError } from '@djvlc/openapi-client-core';
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await client.PageApi.resolvePage({ pageUid: 'xxx' });
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (DjvApiError.is(e)) {
|
|
34
|
+
// 业务错误(服务器返回的错误响应)
|
|
35
|
+
console.log('业务错误:', e.code, e.message);
|
|
36
|
+
console.log('HTTP 状态码:', e.status);
|
|
37
|
+
console.log('追踪 ID:', e.traceId);
|
|
38
|
+
|
|
39
|
+
// 增强方法
|
|
40
|
+
if (e.isAuthError()) {
|
|
41
|
+
// 认证错误 (401/403)
|
|
42
|
+
redirectToLogin();
|
|
43
|
+
} else if (e.isRateLimited()) {
|
|
44
|
+
// 限流错误 (429)
|
|
45
|
+
const retryAfter = e.getRetryAfter(); // 秒
|
|
46
|
+
console.log('请等待', retryAfter, '秒后重试');
|
|
47
|
+
} else if (e.isServerError()) {
|
|
48
|
+
// 服务端错误 (5xx)
|
|
49
|
+
console.log('服务器繁忙,请稍后重试');
|
|
50
|
+
}
|
|
51
|
+
} else if (TimeoutError.is(e)) {
|
|
52
|
+
console.log('请求超时', e.timeoutMs, 'ms');
|
|
53
|
+
} else if (AbortError.is(e)) {
|
|
54
|
+
console.log('请求已取消');
|
|
55
|
+
} else if (NetworkError.is(e)) {
|
|
56
|
+
console.log('网络错误:', e.message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### DjvApiError 增强方法
|
|
62
|
+
|
|
63
|
+
| 方法 | 说明 |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `isUnauthorized()` | 是否 401 未授权 |
|
|
66
|
+
| `isForbidden()` | 是否 403 禁止访问 |
|
|
67
|
+
| `isAuthError()` | 是否认证错误 (401/403) |
|
|
68
|
+
| `isClientError()` | 是否客户端错误 (4xx) |
|
|
69
|
+
| `isServerError()` | 是否服务端错误 (5xx) |
|
|
70
|
+
| `isRateLimited()` | 是否限流 (429) |
|
|
71
|
+
| `isNotFound()` | 是否未找到 (404) |
|
|
72
|
+
| `isConflict()` | 是否冲突 (409) |
|
|
73
|
+
| `isValidationError()` | 是否验证失败 (400/422) |
|
|
74
|
+
| `getRetryAfter()` | 获取 Retry-After 头的值(秒) |
|
|
75
|
+
| `getRetryDelayMs(default)` | 获取重试延迟(毫秒) |
|
|
76
|
+
|
|
77
|
+
## 重试配置(支持 Retry-After)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createUserClient } from '@djvlc/openapi-user-client';
|
|
81
|
+
|
|
82
|
+
const client = createUserClient({
|
|
83
|
+
baseUrl: 'https://api.example.com',
|
|
84
|
+
retry: {
|
|
85
|
+
maxRetries: 3, // 最大重试次数
|
|
86
|
+
retryDelayMs: 1000, // 基础延迟
|
|
87
|
+
exponentialBackoff: true, // 指数退避
|
|
88
|
+
maxDelayMs: 30000, // 最大延迟
|
|
89
|
+
respectRetryAfter: true, // 尊重 Retry-After 响应头(默认 true)
|
|
90
|
+
onRetry: (error, attempt, delayMs) => {
|
|
91
|
+
console.log('第', attempt, '次重试,延迟', delayMs, 'ms');
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
当服务器返回 `429 Too Many Requests` 或 `503 Service Unavailable` 并带有 `Retry-After` 头时,SDK 会优先使用该值作为重试延迟。
|
|
98
|
+
|
|
99
|
+
## 请求去重
|
|
100
|
+
|
|
101
|
+
避免相同的 GET 请求并发执行多次:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { createRequestDeduper } from '@djvlc/openapi-client-core';
|
|
105
|
+
|
|
106
|
+
const deduper = createRequestDeduper({
|
|
107
|
+
getOnly: true, // 只对 GET 请求去重(默认)
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 包装 fetch
|
|
111
|
+
const dedupedFetch = deduper.wrap(fetch);
|
|
112
|
+
|
|
113
|
+
// 相同的请求只会执行一次,结果共享
|
|
114
|
+
const [r1, r2] = await Promise.all([
|
|
115
|
+
dedupedFetch('/api/user'),
|
|
116
|
+
dedupedFetch('/api/user'),
|
|
117
|
+
]);
|
|
118
|
+
// 只发送了 1 个请求!
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 指标收集
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import {
|
|
125
|
+
createMetricsCollector,
|
|
126
|
+
createMetricsMiddleware
|
|
127
|
+
} from '@djvlc/openapi-client-core';
|
|
128
|
+
|
|
129
|
+
// 创建指标收集器
|
|
130
|
+
const metrics = createMetricsCollector({
|
|
131
|
+
maxMetrics: 1000, // 最大保留数量
|
|
132
|
+
ttlMs: 3600000, // 1 小时过期
|
|
133
|
+
onMetrics: (m) => {
|
|
134
|
+
// 每次请求完成时回调
|
|
135
|
+
console.log(m.method, m.path, '-', m.durationMs, 'ms');
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// 添加到客户端
|
|
140
|
+
const client = createUserClient({
|
|
141
|
+
baseUrl: 'https://api.example.com',
|
|
142
|
+
middleware: [createMetricsMiddleware(metrics)],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 获取指标摘要
|
|
146
|
+
const summary = metrics.summary();
|
|
147
|
+
console.log('总请求数:', summary.totalRequests);
|
|
148
|
+
console.log('成功率:', (summary.successRate * 100).toFixed(1), '%');
|
|
149
|
+
console.log('P50:', summary.p50Ms, 'ms');
|
|
150
|
+
console.log('P95:', summary.p95Ms, 'ms');
|
|
151
|
+
console.log('P99:', summary.p99Ms, 'ms');
|
|
152
|
+
|
|
153
|
+
// 按路径查看
|
|
154
|
+
const pageMetrics = metrics.getByPath('/api/resolve/page');
|
|
155
|
+
|
|
156
|
+
// 清空并获取所有指标
|
|
157
|
+
const allMetrics = metrics.flush();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 指标字段
|
|
161
|
+
|
|
162
|
+
| 字段 | 类型 | 说明 |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `requestId` | string | 请求唯一标识 |
|
|
165
|
+
| `url` | string | 完整 URL |
|
|
166
|
+
| `path` | string | 路径(不含 query) |
|
|
167
|
+
| `method` | string | HTTP 方法 |
|
|
168
|
+
| `startTime` | number | 开始时间戳 |
|
|
169
|
+
| `endTime` | number | 结束时间戳 |
|
|
170
|
+
| `durationMs` | number | 耗时(毫秒) |
|
|
171
|
+
| `status` | number | HTTP 状态码 |
|
|
172
|
+
| `success` | boolean | 是否成功 (2xx) |
|
|
173
|
+
| `retryCount` | number? | 重试次数 |
|
|
174
|
+
| `traceId` | string? | 追踪 ID |
|
|
175
|
+
|
|
176
|
+
## 请求取消
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const controller = new AbortController();
|
|
180
|
+
|
|
181
|
+
// 在某个时机取消请求
|
|
182
|
+
setTimeout(() => controller.abort(), 5000);
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await client.PageApi.resolvePage(
|
|
186
|
+
{ pageUid: 'xxx' },
|
|
187
|
+
{ signal: controller.signal }
|
|
188
|
+
);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
if (AbortError.is(e)) {
|
|
191
|
+
console.log('请求已取消');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 日志
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { createConsoleLogger } from '@djvlc/openapi-client-core';
|
|
200
|
+
|
|
201
|
+
const client = createUserClient({
|
|
202
|
+
baseUrl: 'https://api.example.com',
|
|
203
|
+
debug: true, // 开启调试日志
|
|
204
|
+
logger: createConsoleLogger({
|
|
205
|
+
prefix: '[MyApp-API]',
|
|
206
|
+
level: 'debug',
|
|
207
|
+
timestamp: true,
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## 拦截器
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const client = createUserClient({
|
|
216
|
+
baseUrl: 'https://api.example.com',
|
|
217
|
+
interceptors: {
|
|
218
|
+
request: [
|
|
219
|
+
(context) => {
|
|
220
|
+
// 修改请求
|
|
221
|
+
context.init.headers = new Headers(context.init.headers);
|
|
222
|
+
context.init.headers.set('X-Custom', 'value');
|
|
223
|
+
return context;
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
response: [
|
|
227
|
+
async (response, context) => {
|
|
228
|
+
// 处理响应
|
|
229
|
+
if (response.status === 401) {
|
|
230
|
+
// Token 刷新逻辑
|
|
231
|
+
const newToken = await refreshToken();
|
|
232
|
+
// 重试请求...
|
|
233
|
+
}
|
|
234
|
+
return response;
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
error: [
|
|
238
|
+
(error, context) => {
|
|
239
|
+
// 统一错误上报
|
|
240
|
+
reportError(error);
|
|
241
|
+
throw error;
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Token 自动刷新
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { createTokenRefreshInterceptor } from '@djvlc/openapi-client-core';
|
|
252
|
+
|
|
253
|
+
const client = createUserClient({
|
|
254
|
+
baseUrl: 'https://api.example.com',
|
|
255
|
+
interceptors: {
|
|
256
|
+
response: [
|
|
257
|
+
createTokenRefreshInterceptor({
|
|
258
|
+
refreshToken: async () => {
|
|
259
|
+
const newToken = await authService.refresh();
|
|
260
|
+
return newToken;
|
|
261
|
+
},
|
|
262
|
+
}),
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## API
|
|
269
|
+
|
|
270
|
+
### 错误类
|
|
271
|
+
|
|
272
|
+
| 类 | 说明 |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `DjvApiError` | 业务错误(HTTP 非 2xx 响应) |
|
|
275
|
+
| `NetworkError` | 网络层错误 |
|
|
276
|
+
| `TimeoutError` | 超时错误 |
|
|
277
|
+
| `AbortError` | 请求被取消 |
|
|
278
|
+
|
|
279
|
+
### 工具函数
|
|
280
|
+
|
|
281
|
+
| 函数 | 说明 |
|
|
282
|
+
|---|---|
|
|
283
|
+
| `isRetryableError(error)` | 判断错误是否可重试 |
|
|
284
|
+
| `getRetryDelay(error, defaultMs)` | 获取错误的推荐重试延迟 |
|
|
285
|
+
| `createConsoleLogger(opts)` | 创建控制台日志器 |
|
|
286
|
+
| `createSilentLogger()` | 创建静默日志器 |
|
|
287
|
+
| `createTokenRefreshInterceptor(opts)` | 创建 Token 刷新拦截器 |
|
|
288
|
+
| `createRequestDeduper(opts)` | 创建请求去重器 |
|
|
289
|
+
| `createMetricsCollector(opts)` | 创建指标收集器 |
|
|
290
|
+
| `createMetricsMiddleware(collector)` | 创建指标收集中间件 |
|
|
291
|
+
|
|
292
|
+
### 类型
|
|
293
|
+
|
|
294
|
+
| 类型 | 说明 |
|
|
295
|
+
|---|---|
|
|
296
|
+
| `BaseClientOptions` | 客户端配置选项 |
|
|
297
|
+
| `RetryOptions` | 重试配置 |
|
|
298
|
+
| `Logger` | 日志接口 |
|
|
299
|
+
| `Middleware` | 中间件接口 |
|
|
300
|
+
| `Interceptors` | 拦截器配置 |
|
|
301
|
+
| `RequestMetrics` | 请求指标 |
|
|
302
|
+
| `MetricsSummary` | 指标摘要 |
|
|
303
|
+
| `MetricsCollector` | 指标收集器接口 |
|
|
304
|
+
|
|
305
|
+
## 版本信息
|
|
306
|
+
|
|
307
|
+
SDK 版本在构建时自动注入:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { SDK_VERSION, SDK_NAME, getSdkInfo } from '@djvlc/openapi-client-core';
|
|
311
|
+
|
|
312
|
+
console.log(SDK_NAME + '@' + SDK_VERSION);
|
|
313
|
+
// 输出: @djvlc/openapi-client-core@1.0.0
|
|
314
|
+
|
|
315
|
+
const info = getSdkInfo();
|
|
316
|
+
console.log(info);
|
|
317
|
+
// 输出: { name: '@djvlc/openapi-client-core', version: '1.0.0' }
|
|
318
|
+
```
|