# reqeustSDK **Repository Path**: nigutou/reqeust-sdk ## Basic Information - **Project Name**: reqeustSDK - **Description**: 通用 HTTP 请求库,插件化、多适配器、类型安全。支持重试、缓存、并发控制、鉴权刷新等策略,解耦请求实现与业务逻辑。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-29 - **Last Updated**: 2026-06-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # @cccode/request 通用 HTTP 请求库 — **统一拦截层 · 多适配器 · 类型安全** ## 适用场景 - 需要在多个前端项目间复用请求层能力 - 需要统一处理重试、缓存、并发控制、幂等、鉴权刷新 - 希望解耦「请求实现层(axios/fetch)」和「业务层策略」 ## 是否值得引入 这个 SDK 的价值不在于替代 axios 或 fetch 本身,而在于统一复杂请求行为,并把这些行为沉淀成可复用的基础设施。 适合引入的情况: - 存在多个项目或多个运行环境,需要复用同一套请求规范 - 请求层需要统一处理 token 刷新、业务响应解包、错误分类、重试、缓存、并发控制或幂等 - 团队希望 axios/fetch 等底层实现可替换,业务插件不绑定具体 HTTP 库 - 业务代码里已经出现多套重复的 request 封装、错误处理和鉴权刷新逻辑 不一定需要引入的情况: - 只有一个很小的项目,接口数量少,请求行为也简单 - 只需要给 axios 加 token header 和一个错误 toast - 没有跨项目复用诉求,也没有缓存、重试、并发、幂等这类统一策略 落地建议: - SDK 保持通用,不引入 UI、路由、状态管理或具体业务存储 - 业务项目保留一层薄封装,注入项目自己的 token、toast、跳登录逻辑 - 不要默认开启所有插件,按接口风险和业务场景启用 - 优先使用 `responsePlugin`、`authPlugin`、`retryPlugin`,谨慎按场景使用 `cachePlugin`、`queuePlugin`、`idempotentPlugin` ## 特性 - **依赖倒置 (DIP)**:核心层不依赖具体实现,通过 `Requestor` 接口适配任意 HTTP 库 - **统一拦截层**:通过 `plugin` 在请求前、响应后、错误处理、完成回调四阶段拦截请求 - **内置适配器**:Axios、Fetch 开箱即用 - **内置插件**:重试、并发队列、缓存、幂等、鉴权、响应解包 - **TypeScript 优先**:完整类型推导,零 `any` ## 架构分层 ```text @cccode/request ├── core/ # 协议与管线:types / error / inject / createClient ├── plugins/ # 通用插件:retry / queue / cache / idempotent / auth / response ├── adapters/ # 请求实现:axios / fetch └── upload/ # 可选大文件上传模块:分片 / 并发 / 进度 / 暂停续传 ``` - `core` 只依赖接口,不依赖具体实现(符合 DIP) - `plugins` 是跨适配器复用的统一拦截层 - `adapters` 负责把第三方请求库适配到 `Requestor` 接口,并暴露必要的底层扩展点 - `upload` 是可选子模块,不进入普通请求 plugin 主链;任务接口复用 `Requestor`,分片上传默认使用 XHR 完整架构说明见 `docs/architecture.html`,可直接用浏览器打开。 ## 安装 ```bash npm install @cccode/request # Axios 适配器需要安装 axios(可选) npm install axios ``` ## 快速开始 ```typescript import { createClient, inject, createAxiosRequestor, retryPlugin, responsePlugin, authPlugin, } from '@cccode/request' // 1. 创建底层适配器 const axiosRequestor = createAxiosRequestor({ baseURL: 'https://api.example.com', timeout: 30000, }) // 2. 注入全局实现 inject(axiosRequestor) // 3. 创建带插件的客户端 const request = createClient({ plugins: [ retryPlugin({ maxRetries: 3 }), authPlugin({ getAccessToken: () => localStorage.getItem('token'), getRefreshToken: () => localStorage.getItem('refreshToken'), refreshTokenApi: async (refreshToken) => { // 你的刷新 token 逻辑 const res = await fetch('/auth/refresh', { method: 'POST', body: JSON.stringify({ refreshToken }), }) return res.json() }, onTokenRefreshed: (tokens) => { localStorage.setItem('token', tokens.accessToken) localStorage.setItem('refreshToken', tokens.refreshToken) }, onAuthFailed: () => { window.location.href = '/login' }, }), responsePlugin({ successCodes: [0, 200] }), ], }, axiosRequestor) // 4. 发起请求 const res = await request.get<{ name: string }>('/user/profile') console.log(res.data.name) ``` ## JavaScript 项目 可以在纯 JavaScript 项目中使用,不要求项目必须是 TypeScript。SDK 同时提供 ESM 和 CommonJS 入口。 ESM: ```javascript import { createClient, createFetchRequestor, responsePlugin, } from '@cccode/request' const requestor = createFetchRequestor({ baseURL: 'https://api.example.com', }) const request = createClient({ plugins: [responsePlugin()], }, requestor) const response = await request.get('/user/profile') console.log(response.data) ``` CommonJS: ```javascript const { createClient, createFetchRequestor, responsePlugin, } = require('@cccode/request') const requestor = createFetchRequestor({ baseURL: 'https://api.example.com', }) const request = createClient({ plugins: [responsePlugin()], }, requestor) ``` JS 项目注意点: - 不写 TypeScript 泛型,例如不要写 `request.get()` - Node.js 18+ 才内置 `fetch`;低版本 Node 可以使用 axios 适配器,或给 `createFetchRequestor({ fetch })` 注入 fetch polyfill - 完整示例见 `example/javascript-esm.js` 和 `example/javascript-commonjs.cjs` ## Plugin = 统一拦截层 `plugin` 就是这个 SDK 的统一拦截器抽象,`axios` 和 `fetch` 都走同一条生命周期: 1. `onBeforeRequest(config, context)` 2. `onAfterResponse(response, config, context)` 3. `onError(error, config, context)` 4. `onFinally(config, context)` - `onBeforeRequest` 可以修改请求配置或直接短路返回缓存响应 - 短路返回的 `RequestResponse` 仍会继续进入 `onAfterResponse`,因此缓存命中也会经过 `responsePlugin` 解包和业务错误处理 - `onError` 可以通过返回 `RequestResponse` 恢复错误 - `context.dispatch(config)` 可以通过完整拦截链重新发起请求,适合 `authPlugin` 这类刷新 token 后重放请求的场景 如果能力要同时支持 `axios/fetch`,优先写成 `plugin`,不要再维护第二套 interceptor 体系。 推荐插件顺序: ```typescript plugins: [ queuePlugin(), retryPlugin(), cachePlugin(), idempotentPlugin(), authPlugin(authOptions), responsePlugin(), ] ``` `responsePlugin` 通常放在靠后位置,让缓存、幂等、鉴权重放拿到统一响应后再做业务解包。 ## 使用 Fetch 适配器(零依赖) ```typescript import { createClient, inject, createFetchRequestor } from '@cccode/request' const fetchRequestor = createFetchRequestor({ baseURL: 'https://api.example.com', fetch: window.fetch.bind(window), }) inject(fetchRequestor) const request = createClient({}, fetchRequestor) ``` ## 适配器扩展点 - `createAxiosRequestor({ instance })` 支持复用业务侧自定义的 `axios` 实例 - `createFetchRequestor({ fetch })` 支持注入自定义 `fetch` 实现 - 通用策略放在 `plugin`;只有依赖底层 HTTP 库细节的能力才下沉到 adapter ## 错误模型 `RequestError.type` 用来区分错误来源: | 类型 | 来源 | 说明 | | ---------- | ---------------------------- | --------------------------------------- | | `network` | 适配器 | DNS、断网、CORS 等网络层错误 | | `timeout` | 适配器 | 请求超时 | | `abort` | 适配器 | 外部主动取消 | | `http` | 适配器 | HTTP 非 2xx 状态码 | | `business` | `responsePlugin` | HTTP 成功但业务 `code` 非成功码 | | `unknown` | 核心兜底 | 未能识别的异常 | 职责边界: - `axios/fetch` 适配器只处理传输层和 HTTP 状态码,不从响应体提取业务 `code/message` - `responsePlugin` 负责识别 `{ code, message, data }` 并抛出 `business` 错误 - HTTP 错误的原始响应体会保留在 `RequestError.cause`,方便日志或上层诊断 ```typescript try { await request.get('/user/profile') } catch (error) { if (error instanceof RequestError) { if (error.isHttpError()) { console.log(error.status) } if (error.isBusinessError()) { console.log(error.code) } } } ``` ## 重试策略 `retryPlugin` 默认只重试 `GET`、`HEAD`、`OPTIONS` 这类安全/幂等方法,避免网络抖动导致重复提交。 对 `POST`、`PUT`、`PATCH` 等请求,如果业务确认可重试,需要显式开启: ```typescript await request.post('/orders', payload, { meta: { idempotent: true, retry: true, retryCount: 1, }, }) ``` ## 鉴权刷新 `authPlugin` 默认在 HTTP 401 时刷新 token。对于 HTTP 200 但业务码表示登录过期的接口,可以配置 `refreshErrorCode`: ```typescript authPlugin({ getAccessToken, getRefreshToken, refreshTokenApi, onTokenRefreshed, onAuthFailed, refreshErrorCode: 401005, }) ``` 如果后端规则更复杂,可以使用 `shouldRefresh` 完全接管刷新判定: ```typescript authPlugin({ getAccessToken, getRefreshToken, refreshTokenApi, onTokenRefreshed, onAuthFailed, shouldRefresh: error => error.isHttpError() && error.status === 401, }) ``` 刷新后的请求会通过 `context.dispatch()` 重新进入完整插件链,因此仍会经过并发队列、重试、响应解包等插件。 ## 按需导入 ```typescript // 仅导入核心 import { createClient, inject } from '@cccode/request/core' // 仅导入插件 import { retryPlugin, cachePlugin } from '@cccode/request/plugins' // 仅导入 Axios 适配器 import { createAxiosRequestor } from '@cccode/request/adapters/axios' // 仅导入 Fetch 适配器 import { createFetchRequestor } from '@cccode/request/adapters/fetch' // 仅导入大文件上传模块 import { createChunkUploader } from '@cccode/request/upload' ``` ## 大文件上传 大文件上传能力通过可选子路径 `@cccode/request/upload` 使用,不影响主入口和现有插件链。上传模块负责分片、并发、分片级重试、进度、暂停、继续和取消;`init/status/complete` 复用传入的 `Requestor`,分片上传默认使用 XHR 以获取上传进度。 ```typescript import { createChunkUploader } from '@cccode/request/upload' const uploader = createChunkUploader({ client: request, chunkSize: 5 * 1024 * 1024, concurrency: 3, retryCount: 3, endpoints: { init: '/upload/init', chunk: '/upload/chunk', complete: '/upload/complete', status: '/upload/status', }, }) const task = uploader.createTask(file, { metadata: { bizType: 'video' }, onProgress(progress) { console.log(progress.percent) }, }) await task.start() task.pause() await task.resume() task.cancel() ``` 后端协议见 `docs/upload-protocol.html`,前端实现说明见 `docs/upload-implementation-plan.html`。 ## 与业务项目集成建议 建议在业务项目中保留一层薄封装(如 `src/lib/request/index.ts`): - 对外统一导出 `request`、`fetchRequest` - 在业务层注入项目专属插件(如 UI toast 错误提示) - 避免业务代码直接依赖 SDK 内部目录结构 - 只在接口确认具备幂等保障时,为 POST/PUT/PATCH 显式开启重试 - 对缓存、队列、幂等这类插件按接口逐步启用,不建议全局无差别开启 ## 示例 - `example/basic.ts`:Fetch 适配器 + `responsePlugin` 的最小使用方式 - `example/advanced.ts`:日志、队列、重试、缓存、幂等、鉴权刷新组合使用 - `example/upload.ts`:大文件分片上传、进度、暂停、继续和取消 - `example/javascript-esm.js`:JavaScript ESM 项目用法 - `example/javascript-commonjs.cjs`:JavaScript CommonJS 项目用法 - `example/README.md`:示例说明 ## 内置插件 | 插件 | 说明 | 配置项 | | ------------------ | -------------------------------------- | ------------------------------------------------------------- | | `retryPlugin` | 请求失败自动重试,支持指数退避 | `maxRetries`, `delay`, `exponentialBackoff`, `retryCondition` | | `queuePlugin` | 全局并发控制 + 分组串行 | `maxConcurrent` | | `cachePlugin` | 内存缓存,支持 TTL 和 LRU 淘汰 | `defaultDuration`, `maxSize`, `onlyGet` | | `idempotentPlugin` | 幂等请求去重 | `ttl`, `maxSize` | | `authPlugin` | Token 注入 + 自动刷新 + 队列等待 | `getAccessToken`, `refreshTokenApi`, ... | | `responsePlugin` | `ApiResponse` 解包 + 业务错误码抛出 | `successCodes` | ## 自定义插件 ```typescript import type { RequestPlugin } from '@cccode/request' const logPlugin: RequestPlugin = { name: 'log', onBeforeRequest(config) { console.log(`[${config.method}] ${config.url}`) return config }, onAfterResponse(response, config) { console.log(`[${config.method}] ${config.url} -> ${response.status}`) return response }, onError(error) { console.error(`[ERROR] ${error.type}: ${error.message}`) }, } ``` ## License MIT