DJV Low-code Platform - OpenAPI Client 公共运行时
npm install @djvlc/openapi-client-coreOpenAPI Client 公共运行时库,为 @djvlc/openapi-*-client 系列包提供核心功能。
- 🔌 模块化架构:按功能拆分,每个模块职责单一
- 🔗 拦截器系统:请求/响应/错误拦截器,支持顺序控制
- 🔐 多种认证:Bearer/API Key/Basic/自定义认证
- 🔄 智能重试:支持 Retry-After、指数退避、自定义策略
- 🎯 请求去重:避免相同请求并发执行
- 📊 指标收集:请求级别的性能指标和统计
- 📝 日志系统:可配置的请求日志
- 🛡️ 类型安全:完整的 TypeScript 类型定义
``bash`
pnpm add @djvlc/openapi-client-core
``
src/
├── types/ # 类型定义
├── errors/ # 错误处理
├── auth/ # 认证模块
├── interceptors/ # 拦截器
│ ├── request/ # 请求拦截器
│ ├── response/ # 响应拦截器
│ └── error/ # 错误拦截器
├── plugins/ # 插件
├── clients/ # HTTP 客户端
├── utils/ # 工具函数
└── index.ts # 主入口
`typescript
import { createFetchClient } from '@djvlc/openapi-client-core';
const client = createFetchClient({
baseUrl: 'https://api.example.com',
timeout: 30000,
auth: {
type: 'bearer',
getToken: () => localStorage.getItem('token'),
},
});
// 发起请求
const data = await client.get
`
`typescript
import { createAxiosInstance } from '@djvlc/openapi-client-core';
import { PagesApi, Configuration } from './generated';
const axiosInstance = createAxiosInstance({
baseUrl: '/api/admin',
auth: {
type: 'bearer',
getToken: () => localStorage.getItem('token'),
},
retry: {
maxRetries: 2,
initialDelayMs: 1000,
maxDelayMs: 10000,
backoff: 'exponential',
},
});
const config = new Configuration();
const pagesApi = new PagesApi(config, undefined, axiosInstance);
`
本库提供两种 HTTP 客户端实现,共享相同的配置接口,功能完全对等:
| 特性 | FetchClient | Axios Client |
|------|-------------|--------------|
| 依赖 | 无(原生 fetch) | 需要 axios |
| 使用场景 | 独立使用、轻量场景 | OpenAPI 生成代码集成 |
| Bundle 大小 | 更小 | 需包含 axios |
| 请求拦截器 | ✅ 支持 | ✅ 支持 |
| 响应拦截器 | ✅ 支持 | ✅ 支持 |
| 错误拦截器 | ✅ 支持 | ✅ 支持 |
| 认证 | ✅ 全类型 | ✅ 全类型 |
| 自动重试 | ✅ 支持 | ✅ 支持 |
| 超时控制 | ✅ AbortController | ✅ 原生 timeout |
| 请求取消 | ✅ AbortSignal | ✅ CancelToken |
| 调试日志 | ✅ 支持 | ✅ 支持 |
| Retry-After | ✅ 尊重 | ✅ 尊重 |
``
┌─────────────────────────────────────────────────────────────┐
│ 你的使用场景是什么? │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
使用 OpenAPI 生成的代码? 独立发起 HTTP 请求?
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Axios Client │ │ FetchClient │
│ (推荐) │ │ (推荐) │
└─────────────────┘ └─────────────────┘
createEnhancedFetch 创建一个增强的 fetch 函数,专为与 typescript-fetch 生成的 API 类集成设计:
`typescript
import { createEnhancedFetch, createConsoleLogger } from '@djvlc/openapi-client-core';
import { PagesApi, Configuration } from '@djvlc/openapi-admin-client';
// 创建增强的 fetch 函数
const enhancedFetch = createEnhancedFetch({
baseUrl: '/api/admin',
timeout: 30000,
// 认证
auth: {
type: 'bearer',
getToken: () => localStorage.getItem('token') ?? '',
},
// 重试
retry: {
maxRetries: 3,
backoffStrategy: 'exponential',
},
// 日志
logger: createConsoleLogger({ prefix: '[API]' }),
debug: true,
});
// 与生成的 API 类集成
const config = new Configuration({ basePath: '/api/admin' });
const pagesApi = new PagesApi(config, '/api/admin', enhancedFetch);
// 使用
const pages = await pagesApi.listPages();
`
增强功能包括:
- ✅ 自动认证(Bearer/API Key/Basic/自定义)
- ✅ 智能重试(指数退避 + Retry-After)
- ✅ 超时控制(AbortController)
- ✅ 请求追踪(X-Request-ID, X-Trace-ID)
- ✅ 错误转换(ApiError, NetworkError, TimeoutError)
- ✅ 调试日志
FetchClient 基于原生 fetch API,适合独立使用(不依赖 OpenAPI 生成代码):
`typescript
import {
createFetchClient,
createConsoleLogger,
type FetchClientConfig,
type RequestInterceptor,
type ResponseInterceptor,
type ErrorInterceptor,
} from '@djvlc/openapi-client-core';
const config: FetchClientConfig = {
// ============ 基础配置 ============
/* 必填:API 基础 URL /
baseUrl: 'https://api.example.com',
/* 请求超时时间(毫秒),默认 30000 /
timeout: 30000,
/* 默认请求头 /
headers: {
'X-Custom-Header': 'value',
},
// ============ 认证配置 ============
/* 认证配置 /
auth: {
type: 'bearer',
getToken: async () => await tokenService.getAccessToken(),
// 其他选项见 "认证" 章节
},
// ============ 重试配置 ============
/* 是否启用自动重试,默认 true /
enableRetry: true,
/* 重试策略 /
retry: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 30000,
backoffStrategy: 'exponential',
retryableStatusCodes: [429, 500, 502, 503, 504],
retryOnNetworkError: true,
retryOnTimeout: true,
respectRetryAfter: true,
jitterFactor: 0.1,
onRetry: (info) => {
console.log(重试 ${info.attempt}/${info.maxRetries});${context.options.method} ${context.options.path} -> ${response.status}
},
},
// ============ 拦截器 ============
/* 请求拦截器列表 /
requestInterceptors: [
{
name: 'custom-header',
order: 0,
intercept(context) {
context.options.headers['X-Request-Time'] = Date.now().toString();
return context.options;
},
},
],
/* 响应拦截器列表 /
responseInterceptors: [
{
name: 'response-logger',
order: 0,
intercept(response, context) {
console.log();请求失败: ${error.message}
return response;
},
},
],
/* 错误拦截器列表 /
errorInterceptors: [
{
name: 'error-logger',
order: 0,
intercept(error, context) {
console.error();
return undefined; // 不处理,继续传播
},
},
],
// ============ 调试配置 ============
/* 日志器 /
logger: createConsoleLogger({ prefix: '[API]', level: 'debug' }),
/* 是否启用调试日志,默认 false /
debug: true,
};
const client = createFetchClient(config);
// 使用客户端
const user = await client.get
const created = await client.post
const updated = await client.put
const patched = await client.patch
await client.delete('/users/1');
`
createAxiosInstance 返回一个配置好的 Axios 实例,适合与 OpenAPI 生成代码集成:
`typescript
import {
createAxiosInstance,
createConsoleLogger,
type AxiosClientConfig,
} from '@djvlc/openapi-client-core';
import { PagesApi, ComponentsApi, Configuration } from '@djvlc/openapi-admin-client';
const config: AxiosClientConfig = {
// ============ 基础配置 ============
/* 必填:API 基础 URL /
baseUrl: '/api/admin',
/* 请求超时时间(毫秒),默认 30000 /
timeout: 30000,
/* 默认请求头 /
headers: {
'X-Client-Version': '1.0.0',
},
// ============ 认证配置 ============
auth: {
type: 'bearer',
getToken: () => localStorage.getItem('accessToken') ?? '',
},
// ============ 重试配置 ============
enableRetry: true,
retry: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 30000,
backoffStrategy: 'exponential',
retryableStatusCodes: [429, 500, 502, 503, 504],
onRetry: (info) => {
console.log([Retry] ${info.method} ${info.url} - attempt ${info.attempt});
},
},
// ============ 拦截器(与 FetchClient 相同) ============
requestInterceptors: [...],
responseInterceptors: [...],
errorInterceptors: [...],
// ============ 调试配置 ============
logger: createConsoleLogger({ prefix: '[Admin API]' }),
debug: process.env.NODE_ENV === 'development',
};
// 创建 Axios 实例
const axiosInstance = createAxiosInstance(config);
// 与 OpenAPI 生成代码集成
const apiConfig = new Configuration();
const pagesApi = new PagesApi(apiConfig, undefined, axiosInstance);
const componentsApi = new ComponentsApi(apiConfig, undefined, axiosInstance);
// 使用生成的 API 客户端
const pages = await pagesApi.listPages({ limit: 10 });
const page = await pagesApi.getPage({ id: 'page-123' });
`
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| baseUrl | string | - | 必填,API 基础 URL |timeout
| | number | 30000 | 请求超时(毫秒) |headers
| | Record | {} | 默认请求头 |auth
| | AuthConfig | - | 认证配置 |retry
| | RetryConfig | - | 重试配置 |enableRetry
| | boolean | true | 是否启用重试 |requestInterceptors
| | RequestInterceptor[] | [] | 请求拦截器 |responseInterceptors
| | ResponseInterceptor[] | [] | 响应拦截器 |errorInterceptors
| | ErrorInterceptor[] | [] | 错误拦截器 |logger
| | Logger | - | 日志器实例 |debug
| | boolean | false | 是否启用调试日志 |
`typescript
import {
createAxiosInstance,
createConsoleLogger,
createMetricsCollector,
createMetricsInterceptors,
createLoggingInterceptor,
createReportingInterceptor,
ApiError,
} from '@djvlc/openapi-client-core';
// ============ 创建日志器 ============
const logger = createConsoleLogger({
prefix: '[DJV-API]',
level: process.env.NODE_ENV === 'production' ? 'warn' : 'debug',
timestamp: true,
});
// ============ 创建指标收集器 ============
const metricsCollector = createMetricsCollector({
maxMetrics: 1000,
ttlMs: 300000, // 5 分钟
onMetrics: (m) => {
// 发送到监控系统
if (m.durationMs > 3000) {
logger.warn(慢请求: ${m.method} ${m.path} - ${m.durationMs}ms);
}
},
});
const metricsInterceptors = createMetricsInterceptors(metricsCollector);
// ============ 创建错误上报拦截器 ============
const errorReporter = createReportingInterceptor({
reporter: {
report: (error, context) => {
// 只上报服务端错误
if (ApiError.is(error) && error.isServerError()) {
Sentry.captureException(error, {
extra: {
requestId: context.requestId,
traceId: context.traceId,
url: context.url,
},
});
}
},
},
sampleRate: 1.0, // 生产环境 100% 采样
});
// ============ 创建客户端 ============
const axiosInstance = createAxiosInstance({
baseUrl: import.meta.env.VITE_API_BASE_URL,
timeout: 30000,
auth: {
type: 'bearer',
getToken: () => authStore.getAccessToken(),
},
retry: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 30000,
backoffStrategy: 'exponential',
jitterFactor: 0.2,
retryableStatusCodes: [429, 500, 502, 503, 504],
respectRetryAfter: true,
onRetry: (info) => {
logger.info(重试请求: ${info.method} ${info.url} (${info.attempt}/${info.maxRetries}));
},
},
requestInterceptors: [
metricsInterceptors.request,
{
name: 'app-version',
order: 100,
intercept(ctx) {
ctx.options.headers['X-App-Version'] = APP_VERSION;
ctx.options.headers['X-Platform'] = 'web';
return ctx.options;
},
},
],
responseInterceptors: [
metricsInterceptors.response,
],
errorInterceptors: [
metricsInterceptors.error,
errorReporter,
{
name: 'auth-redirect',
order: 1000,
intercept(error, context) {
if (ApiError.is(error) && error.isUnauthorized()) {
// Token 过期,跳转登录
authStore.logout();
router.push('/login');
}
return undefined;
},
},
],
logger,
debug: import.meta.env.DEV,
});
// ============ 定期上报指标 ============
setInterval(() => {
const summary = metricsCollector.summary();
analytics.track('api_metrics', {
totalRequests: summary.totalRequests,
successRate: summary.successRate,
p50: summary.p50Ms,
p95: summary.p95Ms,
p99: summary.p99Ms,
});
}, 60000);
export { axiosInstance, metricsCollector };
`
`typescript
import {
ApiError,
NetworkError,
TimeoutError,
AbortError,
isRetryableError,
} from '@djvlc/openapi-client-core';
try {
await client.get('/users/me');
} catch (e) {
if (ApiError.is(e)) {
// 业务错误(服务器返回的错误响应)
console.log('业务错误:', e.code, e.message);
console.log('HTTP 状态码:', e.statusCode);
console.log('追踪 ID:', e.traceId);
// 状态码判断
if (e.isAuthError()) {
// 认证错误 (401/403)
redirectToLogin();
} else if (e.isRateLimited()) {
// 限流错误 (429)
const retryAfter = e.getRetryAfter(); // 秒
console.log('请等待', retryAfter, '秒后重试');
} else if (e.isServerError()) {
// 服务端错误 (5xx)
console.log('服务器繁忙,请稍后重试');
}
} else if (TimeoutError.is(e)) {
console.log('请求超时', e.timeoutMs, 'ms');
} else if (AbortError.is(e)) {
console.log('请求已取消');
} else if (NetworkError.is(e)) {
console.log('网络错误:', e.message);
}
// 判断是否可重试
if (isRetryableError(e)) {
console.log('此错误可以重试');
}
}
`
| 方法 | 说明 |
|---|---|
| isUnauthorized() | 是否 401 未授权 |isForbidden()
| | 是否 403 禁止访问 |isAuthError()
| | 是否认证错误 (401/403) |isClientError()
| | 是否客户端错误 (4xx) |isServerError()
| | 是否服务端错误 (5xx) |isRateLimited()
| | 是否限流 (429) |isNotFound()
| | 是否未找到 (404) |isConflict()
| | 是否冲突 (409) |isValidationError()
| | 是否验证失败 (400/422) |getRetryAfter()
| | 获取 Retry-After 头的值(秒) |getRetryDelayMs(default)
| | 获取重试延迟(毫秒) |
`typescript
import { createFetchClient } from '@djvlc/openapi-client-core';
const client = createFetchClient({
baseUrl: 'https://api.example.com',
auth: {
type: 'bearer',
getToken: async () => {
// 支持异步获取 Token
return await tokenService.getAccessToken();
},
headerName: 'Authorization', // 可选,默认 'Authorization'
prefix: 'Bearer', // 可选,默认 'Bearer'
},
});
`
`typescript`
const client = createFetchClient({
baseUrl: 'https://api.example.com',
auth: {
type: 'api-key',
apiKey: 'your-api-key',
headerName: 'X-API-Key', // 可选
},
});
`typescript`
const client = createFetchClient({
baseUrl: 'https://api.example.com',
auth: {
type: 'basic',
username: 'admin',
password: 'secret',
},
});
`typescript`
const client = createFetchClient({
baseUrl: 'https://api.example.com',
auth: {
type: 'custom',
authenticate: async (headers) => {
headers['X-Custom-Auth'] = await getCustomToken();
headers['X-Timestamp'] = Date.now().toString();
},
},
});
`typescript
import {
createFetchClient,
createAuthInterceptor,
createRequestIdInterceptor,
createTraceInterceptor,
createBearerAuthenticator,
} from '@djvlc/openapi-client-core';
const client = createFetchClient({
baseUrl: 'https://api.example.com',
requestInterceptors: [
// 请求 ID 拦截器
createRequestIdInterceptor({
headerName: 'X-Request-ID',
}),
// 追踪拦截器
createTraceInterceptor({
traceIdHeader: 'X-Trace-ID',
addTraceparent: true,
getTraceparent: () => otel.getCurrentSpan()?.spanContext(),
}),
],
});
`
`typescript
import {
createFetchClient,
createLoggingInterceptor,
createReportingInterceptor,
createConsoleLogger,
} from '@djvlc/openapi-client-core';
const client = createFetchClient({
baseUrl: 'https://api.example.com',
errorInterceptors: [
// 日志拦截器
createLoggingInterceptor({
logger: createConsoleLogger({ prefix: '[API]' }),
verbose: true,
}),
// 错误上报拦截器
createReportingInterceptor({
reporter: {
report: (error, context) => {
Sentry.captureException(error, { extra: context });
},
},
sampleRate: 0.1, // 10% 采样
}),
],
});
`
`typescript
import type { RequestInterceptor, ErrorInterceptor } from '@djvlc/openapi-client-core';
// 自定义请求拦截器
const customRequestInterceptor: RequestInterceptor = {
name: 'custom-request',
order: 0,
intercept(context) {
// 修改请求
context.options.headers['X-Custom'] = 'value';
return context.options;
},
};
// 自定义错误拦截器
const customErrorInterceptor: ErrorInterceptor = {
name: 'custom-error',
order: 0,
intercept(error, context) {
console.log('请求失败:', context.url, error.message);
return undefined; // 不处理,让错误继续传播
},
};
const client = createFetchClient({
baseUrl: 'https://api.example.com',
requestInterceptors: [customRequestInterceptor],
errorInterceptors: [customErrorInterceptor],
});
`
`typescript`
const client = createFetchClient({
baseUrl: 'https://api.example.com',
enableRetry: true, // 启用重试,默认 true
retry: {
maxRetries: 3,
initialDelayMs: 1000,
maxDelayMs: 30000,
backoffStrategy: 'exponential',
},
});
`typescript
import type { RetryConfig, RetryInfo, RetryContext } from '@djvlc/openapi-client-core';
const retryConfig: RetryConfig = {
// ============ 基础参数 ============
/* 最大重试次数,默认 3 /
maxRetries: 3,
/* 初始延迟(毫秒),默认 1000 /
initialDelayMs: 1000,
/* 最大延迟(毫秒),默认 30000 /
maxDelayMs: 30000,
// ============ 退避策略 ============
/**
* 退避策略
* - 'fixed': 固定延迟,每次重试使用 initialDelayMs
- 'linear': 线性增长,delay = initialDelayMs attempt
- 'exponential': 指数增长,delay = initialDelayMs (2 ^ attempt)
* 默认 'exponential'
*/
backoffStrategy: 'exponential',
/**
* 抖动系数(0-1)
* 在计算的延迟基础上添加随机抖动,防止惊群效应
实际延迟 = delay (1 + random(-jitterFactor, +jitterFactor))
* 默认 0.1
*/
jitterFactor: 0.1,
// ============ 重试条件 ============
/**
* 可重试的 HTTP 状态码
* 默认 [429, 500, 502, 503, 504]
*/
retryableStatusCodes: [429, 500, 502, 503, 504],
/* 网络错误是否重试,默认 true /
retryOnNetworkError: true,
/* 超时是否重试,默认 true /
retryOnTimeout: true,
/* 是否尊重 Retry-After 响应头,默认 true /
respectRetryAfter: true,
/**
* 自定义重试判断函数
* 返回 true 表示应该重试
*/
shouldRetry: (error: Error, attempt: number, context: RetryContext): boolean => {
// 例:只重试 GET 请求
if (context.method !== 'GET') return false;
// 例:429 错误最多重试 5 次
if (context.statusCode === 429 && attempt >= 5) return false;
return true;
},
// ============ 回调 ============
/**
* 重试时的回调函数
* 可用于日志、监控等
*/
onRetry: (info: RetryInfo) => {
console.log(重试请求: ${info.method} ${info.url}); - 第 ${info.attempt}/${info.maxRetries} 次
console.log(); - 延迟 ${info.delayMs}ms
console.log(); - 原因: ${info.error.message}
console.log();`
},
};
假设 initialDelayMs = 1000,maxDelayMs = 30000:
| 重试次数 | fixed | linear | exponential |
|----------|-------|--------|-------------|
| 第 1 次 | 1000ms | 1000ms | 1000ms |
| 第 2 次 | 1000ms | 2000ms | 2000ms |
| 第 3 次 | 1000ms | 3000ms | 4000ms |
| 第 4 次 | 1000ms | 4000ms | 8000ms |
| 第 5 次 | 1000ms | 5000ms | 16000ms |
| 第 6 次 | 1000ms | 6000ms | 30000ms (capped) |
当 respectRetryAfter: true 时,客户端会检查响应头:
`http`
HTTP/1.1 429 Too Many Requests
Retry-After: 60
客户端会等待 60 秒后重试(但不超过 maxDelayMs)。
支持的格式:
- 秒数:Retry-After: 120Retry-After: Wed, 21 Oct 2025 07:28:00 GMT
- HTTP 日期:
`typescript
// 方式 1:全局禁用
const client = createFetchClient({
baseUrl: 'https://api.example.com',
enableRetry: false,
});
// 方式 2:不配置 retry
const client = createFetchClient({
baseUrl: 'https://api.example.com',
// 不传 retry 配置
});
// 方式 3:单次请求禁用(通过自定义 shouldRetry)
const retryConfig: RetryConfig = {
maxRetries: 3,
shouldRetry: (error, attempt, context) => {
// POST 请求不重试
if (context.method === 'POST') return false;
return true;
},
};
`
`typescript
import { createRequestDeduper } from '@djvlc/openapi-client-core';
const deduper = createRequestDeduper({
getOnly: true, // 只对 GET 请求去重
maxSize: 100, // 最大缓存数量
});
// 包装 fetch
const dedupedFetch = deduper.wrap(fetch);
// 相同的请求只会执行一次,结果共享
const [r1, r2] = await Promise.all([
dedupedFetch('/api/user'),
dedupedFetch('/api/user'),
]);
// 只发送了 1 个请求!
console.log('当前飞行中请求数:', deduper.inflightCount);
`
`typescript
import {
createMetricsCollector,
createMetricsInterceptors,
createFetchClient,
} from '@djvlc/openapi-client-core';
// 创建指标收集器
const metrics = createMetricsCollector({
maxMetrics: 1000,
ttlMs: 3600000, // 1 小时过期
onMetrics: (m) => {
console.log(${m.method} ${m.path} - ${m.durationMs}ms);
},
});
// 创建拦截器
const metricsInterceptors = createMetricsInterceptors(metrics);
const client = createFetchClient({
baseUrl: 'https://api.example.com',
requestInterceptors: [metricsInterceptors.request],
responseInterceptors: [metricsInterceptors.response],
errorInterceptors: [metricsInterceptors.error],
});
// 获取指标摘要
const summary = metrics.summary();
console.log('总请求数:', summary.totalRequests);
console.log('成功率:', (summary.successRate * 100).toFixed(1), '%');
console.log('P50:', summary.p50Ms, 'ms');
console.log('P95:', summary.p95Ms, 'ms');
console.log('P99:', summary.p99Ms, 'ms');
// 按路径查看
const pageMetrics = metrics.getByPath('/api/pages');
// 清空并获取所有指标
const allMetrics = metrics.flush();
`
| 字段 | 类型 | 说明 |
|---|---|---|
| requestId | string | 请求唯一标识 |url
| | string | 完整 URL |path
| | string | 路径(不含 query) |method
| | string | HTTP 方法 |startTime
| | number | 开始时间戳 |endTime
| | number | 结束时间戳 |durationMs
| | number | 耗时(毫秒) |status
| | number | HTTP 状态码 |success
| | boolean | 是否成功 (2xx) |retryCount
| | number | 重试次数 |traceId
| | string? | 追踪 ID |error
| | string? | 错误信息 |
`typescript
import { createConsoleLogger, createBufferLogger } from '@djvlc/openapi-client-core';
// 控制台日志器
const consoleLogger = createConsoleLogger({
prefix: '[DJV-API]',
level: 'debug', // 最低日志级别
timestamp: true, // 显示时间戳
});
// 缓冲日志器(用于测试)
const bufferLogger = createBufferLogger(1000);
// 使用
const client = createFetchClient({
baseUrl: 'https://api.example.com',
logger: consoleLogger,
debug: true,
});
// 获取缓冲的日志
const logs = bufferLogger.getLogs();
const errorLogs = bufferLogger.getLogsByLevel('error');
`
`typescript
import {
createFetchClient,
createTokenRefreshInterceptor,
} from '@djvlc/openapi-client-core';
// 创建客户端时配置 Token 刷新
const client = createFetchClient({
baseUrl: 'https://api.example.com',
auth: {
type: 'bearer',
getToken: () => tokenService.getAccessToken(),
},
});
// 添加 Token 刷新拦截器
client.interceptors.addErrorInterceptor(
createTokenRefreshInterceptor({
refreshToken: async () => {
const newToken = await authService.refresh();
tokenService.setAccessToken(newToken);
return newToken;
},
triggerStatusCodes: [401],
maxRetries: 1,
onTokenRefreshed: (newToken) => {
console.log('Token 已刷新');
},
onRefreshFailed: (error) => {
console.log('Token 刷新失败,跳转登录');
redirectToLogin();
},
executeRequest: (context) => client.request(context.originalOptions),
})
);
`
`typescript
import {
// 请求 ID
generateRequestId,
generateTraceId,
isValidRequestId,
// 重试延迟
calculateRetryDelay,
parseRetryAfter,
// 延迟执行
sleep,
sleepWithAbort,
// URL 处理
buildUrl,
extractPath,
parseQueryString,
joinPaths,
// 请求头处理
mergeHeaders,
getHeader,
setHeader,
removeHeader,
} from '@djvlc/openapi-client-core';
// 生成 ID
const requestId = generateRequestId(); // req_m5abc123_k7def456
const traceId = generateTraceId(); // trace_m5abc123_k7def456_x8ghi789
// URL 处理
const url = buildUrl('https://api.example.com', '/users', { page: 1, size: 10 });
// https://api.example.com/users?page=1&size=10
// 请求头处理
const headers = mergeHeaders(
{ 'Content-Type': 'application/json' },
{ 'Authorization': 'Bearer xxx' },
);
`
| 类 | 说明 |
|---|---|
| BaseClientError | 所有错误的基类 |ApiError
| | 业务错误(HTTP 非 2xx 响应) |NetworkError
| | 网络层错误 |TimeoutError
| | 超时错误 |AbortError
| | 请求被取消 |
| 类 | 说明 |
|---|---|
| BearerAuthenticator | Bearer Token 认证 |ApiKeyAuthenticator
| | API Key 认证 |BasicAuthenticator
| | Basic Auth 认证 |CustomAuthenticator
| | 自定义认证 |NoAuthenticator
| | 无认证 |
| 类 | 类型 | 说明 |
|---|---|---|
| AuthInterceptor | 请求 | 自动添加认证信息 |RequestIdInterceptor
| | 请求 | 生成请求 ID |TraceInterceptor
| | 请求 | 添加追踪上下文 |TimeoutInterceptor
| | 请求 | 智能设置超时 |ErrorTransformInterceptor
| | 响应 | 错误格式转换 |RetryInterceptor
| | 错误 | 自动重试 |TokenRefreshInterceptor
| | 错误 | Token 自动刷新 |LoggingInterceptor
| | 错误 | 错误日志记录 |ReportingInterceptor
| | 错误 | 错误上报监控 |
| 类/函数 | 说明 |
|---|---|
| FetchClient | 基于原生 fetch 的独立客户端 |createFetchClient()
| | 创建 FetchClient 实例 |createEnhancedFetch()
| | 创建增强的 fetch 函数(用于 typescript-fetch 生成代码) |createAxiosInstance()
| | 创建配置好的 Axios 实例(向后兼容) |
| 类/函数 | 说明 |
|---|---|
| RequestDeduper | 请求去重器 |DefaultMetricsCollector
| | 默认指标收集器 |ConsoleLogger
| | 控制台日志器 |BufferLogger
| | 缓冲日志器(测试用) |
`typescript
import { VERSION, SDK_NAME, getSdkInfo } from '@djvlc/openapi-client-core';
console.log(${SDK_NAME}@${VERSION});
// @djvlc/openapi-client-core@1.0.0
const info = getSdkInfo();
// { name: '@djvlc/openapi-client-core', version: '1.0.0' }
`
1. 模块路径变更:原来的单文件被拆分为多个模块目录
2. 类型定义变更:部分类型名称和结构有调整
3. HttpClient 重命名:HttpClient 改名为 FetchClientcreateClient
4. createClient 重命名: 改名为 createFetchClienttypescript-fetch
5. 默认使用 Fetch:OpenAPI 生成的客户端默认使用 ,不再依赖 axios
如果你之前使用 createAxiosInstance,现在推荐使用 createEnhancedFetch:
`typescript
// 旧代码(axios)
import { createAxiosInstance } from '@djvlc/openapi-client-core';
const axiosInstance = createAxiosInstance({ baseUrl: '/api' });
const api = new PagesApi(config, '/api', axiosInstance);
// 新代码(fetch)
import { createEnhancedFetch } from '@djvlc/openapi-client-core';
const enhancedFetch = createEnhancedFetch({ baseUrl: '/api' });
const api = new PagesApi(config, '/api', enhancedFetch);
`
- createAxiosInstance 仍然可用,用于需要 axios 的场景createEnhancedFetch` 中保持一致
- 所有配置选项在
MIT