|
1 | | -**请求模块 使用说明** |
| 1 | +# Request 请求模块使用指南 |
2 | 2 |
|
3 | 3 | 位置: `src/service/request.js` |
4 | 4 |
|
5 | | -简介 |
6 | | -- 本文档说明如何使用仓库中增强过的 `request` 模块。该模块基于 `axios`,提供:可取消请求、并发/串行执行、重试机制、上传/下载进度回调、每次请求可开关错误弹窗及全局配置接口。 |
| 5 | +基于 Axios 封装的企业级 HTTP 请求库,提供了统一的错误处理、请求拦截、响应拦截、并发控制、文件处理等高级功能。 |
7 | 6 |
|
8 | | -导入 |
| 7 | +## 引入方式 |
9 | 8 |
|
10 | | -```js |
| 9 | +```javascript |
11 | 10 | import request from '@/service/request' |
12 | 11 | ``` |
13 | 12 |
|
14 | | -1) 全局配置 |
| 13 | +--- |
15 | 14 |
|
16 | | -- 设置 baseURL: |
| 15 | +## 一、基础使用 |
17 | 16 |
|
18 | | -```js |
19 | | -request.setBaseURL('https://api.example.com') |
| 17 | +### 1. GET 请求 |
| 18 | +用于获取数据,参数会自动序列化到 URL 中。 |
| 19 | + |
| 20 | +```javascript |
| 21 | +// 简单请求 |
| 22 | +const res = await request.get('/api/users') |
| 23 | + |
| 24 | +// 带参数请求: /api/users?page=1&pageSize=10 |
| 25 | +const data = await request.get('/api/users', { |
| 26 | + page: 1, |
| 27 | + pageSize: 10 |
| 28 | +}) |
20 | 29 | ``` |
21 | 30 |
|
22 | | -- 设置全局默认 headers: |
| 31 | +### 2. POST 请求 |
| 32 | +用于提交数据,默认使用 `application/json`。 |
23 | 33 |
|
24 | | -```js |
25 | | -request.setDefaultHeaders({ 'X-Trace-Id': 'abc123' }) |
| 34 | +```javascript |
| 35 | +const res = await request.post('/api/users', { |
| 36 | + username: 'john_doe', |
| 37 | + |
| 38 | +}) |
26 | 39 | ``` |
27 | 40 |
|
28 | | -2) 基本请求(返回 Promise,同时附带 `.cancel()`) |
| 41 | +### 3. PUT / DELETE 请求 |
29 | 42 |
|
30 | | -```js |
31 | | -// GET |
32 | | -const p = request.get('/api/users', { page: 1 }) |
33 | | -p.then(res => console.log(res)).catch(err => console.error(err)) |
| 43 | +```javascript |
| 44 | +// PUT: 更新资源 |
| 45 | +await request.put('/api/users/123', { |
| 46 | + role: 'admin' |
| 47 | +}) |
34 | 48 |
|
35 | | -// 取消请求 |
36 | | -p.cancel() |
| 49 | +// DELETE: 删除资源 |
| 50 | +await request.delete('/api/users/123') |
| 51 | +``` |
37 | 52 |
|
38 | | -// POST |
39 | | -request.post('/api/user', { name: 'alice' }) |
40 | | - .then(res => console.log(res)) |
41 | | - .catch(err => console.error(err)) |
| 53 | +### 4. 表单提交 |
| 54 | +自动将 Content-Type 设置为 `application/x-www-form-urlencoded` 并序列化数据。 |
42 | 55 |
|
43 | | -// 禁用该次请求的错误弹窗 |
44 | | -request.get('/api/maybe-missing', {}, { showError: false }) |
| 56 | +```javascript |
| 57 | +// 发送 application/x-www-form-urlencoded 数据 |
| 58 | +await request.form('/api/login', { |
| 59 | + username: 'admin', |
| 60 | + password: 'password123' |
| 61 | +}) |
45 | 62 | ``` |
46 | 63 |
|
47 | | -注意:当后端采用 `{ code, data, message }` 约定时,模块会在 `code === 0` 视作成功并返回后端对象;否则会按 `message` 展示错误(可用 `showError:false` 关闭)。如果后端返回不是该结构,则直接返回原始数据。 |
| 64 | +### 5. 文件上传 (带进度) |
| 65 | +支持 `FormData` 或普通对象(自动转换),并提供上传进度回调。 |
| 66 | + |
| 67 | +```javascript |
| 68 | +const formData = new FormData() |
| 69 | +formData.append('file', fileObject) |
48 | 70 |
|
49 | | -3) 上传与下载(支持进度回调) |
| 71 | +await request.upload('/api/upload', formData, { |
| 72 | + // 监听上传进度 |
| 73 | + onProgress: ({ percent, loaded, total }) => { |
| 74 | + console.log(`上传进度: ${percent}%`) |
| 75 | + } |
| 76 | +}) |
| 77 | +``` |
50 | 78 |
|
51 | | -```js |
52 | | -// 上传 |
53 | | -const fd = new FormData() |
54 | | -fd.append('file', fileInput.files[0]) |
55 | | -request.upload('/api/upload', fd, { |
56 | | - onUploadProgress: (e) => { |
57 | | - const pct = Math.round((e.loaded / e.total) * 100) |
58 | | - console.log('上传进度', pct) |
59 | | - }, |
60 | | -}).then(res => console.log('上传完成', res)) |
| 79 | +### 6. 文件下载 |
| 80 | +自动处理 Blob 响应,并触发浏览器下载行为。 |
61 | 81 |
|
62 | | -// 下载(会触发浏览器下载) |
63 | | -request.download('/api/export', { q: 'all' }, 'report.xlsx', { |
64 | | - onDownloadProgress: (e) => { |
65 | | - const pct = Math.round((e.loaded / e.total) * 100) |
66 | | - console.log('下载进度', pct) |
| 82 | +```javascript |
| 83 | +// 简单下载,文件名优先从响应头 Content-Disposition 获取 |
| 84 | +await request.download('/api/export/users') |
| 85 | +``` |
| 86 | + |
| 87 | +### 7. 自定义文件名下载 |
| 88 | +如果后端未返回文件名,或需要覆盖文件名。 |
| 89 | + |
| 90 | +```javascript |
| 91 | +await request.download( |
| 92 | + '/api/export/report', |
| 93 | + { year: 2024 }, // 查询参数 |
| 94 | + '2024年度报表.xlsx' // 指定文件名 |
| 95 | +) |
| 96 | +``` |
| 97 | + |
| 98 | +### 8. 自定义 Authorization |
| 99 | +默认会自动携带 Token,如需覆盖或使用特殊 Token。 |
| 100 | + |
| 101 | +```javascript |
| 102 | +await request.get('/api/external/data', {}, { |
| 103 | + headers: { |
| 104 | + 'Authorization': 'Bearer SPECIAL_TOKEN_123' |
67 | 105 | } |
68 | | -}).then(() => console.log('下载结束')) |
| 106 | +}) |
| 107 | +``` |
| 108 | + |
| 109 | +### 9. 禁用重复请求取消 |
| 110 | +默认情况下,相同的未完成请求会被自动取消(防抖)。如果业务需要允许重复请求(如聊天发送),可禁用此功能。 |
| 111 | + |
| 112 | +```javascript |
| 113 | +await request.post('/api/chat/send', { msg: 'hello' }, { |
| 114 | + cancelDuplicate: false // 允许重复发送 |
| 115 | +}) |
| 116 | +``` |
| 117 | + |
| 118 | +### 10. 自定义配置 |
| 119 | +控制 Token 携带和错误提示行为。 |
| 120 | + |
| 121 | +```javascript |
| 122 | +await request.get('/api/public/config', {}, { |
| 123 | + needToken: false, // 不携带 Token (适用于公开接口) |
| 124 | + showError: false // 关闭默认的错误提示 (适用于需要手动处理错误的场景) |
| 125 | +}) |
69 | 126 | ``` |
70 | 127 |
|
71 | | -4) 并行 / 并发控制 |
| 128 | +--- |
| 129 | + |
| 130 | +## 二、高级使用 |
| 131 | + |
| 132 | +### 1. 并发请求 (Parallel) |
| 133 | +同时发起多个请求,并限制最大并发数(默认 5)。 |
| 134 | + |
| 135 | +```javascript |
| 136 | +const userIds = [1, 2, 3, 4, 5, 6] |
72 | 137 |
|
73 | | -```js |
74 | | -// 简单并行:传入请求描述数组,返回 Promise 数组结果 |
75 | | -// 描述可以是:函数、axios config 对象,或 [method, url, body, config] |
76 | | -const results = await request.parallel([ |
77 | | - ['get', '/api/a'], |
78 | | - ['post', '/api/b', { x: 1 }], |
79 | | - () => request.get('/api/c') |
80 | | -], /* concurrency */ 3) |
| 138 | +// 构建请求任务数组 |
| 139 | +const tasks = userIds.map(id => ({ |
| 140 | + method: 'GET', |
| 141 | + url: `/api/users/${id}` |
| 142 | +})) |
81 | 143 |
|
| 144 | +// 执行并发请求,限制同时最多执行 3 个 |
| 145 | +const results = await request.parallel(tasks, 3) |
82 | 146 | console.log(results) |
83 | 147 | ``` |
84 | 148 |
|
85 | | -5) 串行 |
| 149 | +### 2. 串行请求 (Series) |
| 150 | +按顺序执行请求,前一个完成后才执行下一个(适用于有依赖关系的请求)。 |
86 | 151 |
|
87 | | -```js |
88 | | -// 依次执行 |
89 | | -const seriesResults = await request.series([ |
90 | | - () => request.get('/api/step1'), |
91 | | - ['post', '/api/step2', { id: 123 }], |
| 152 | +```javascript |
| 153 | +const results = await request.series([ |
| 154 | + // 任务 1 |
| 155 | + { method: 'POST', url: '/api/step1', data: { init: true } }, |
| 156 | + // 任务 2 |
| 157 | + { method: 'POST', url: '/api/step2', data: { confirm: true } } |
92 | 158 | ]) |
93 | 159 | ``` |
94 | 160 |
|
95 | | -6) 可取消的自定义请求 |
| 161 | +或者使用函数形式处理依赖: |
96 | 162 |
|
97 | | -```js |
98 | | -const promise = request.request({ method: 'get', url: '/api/long', params: { t: 1 } }) |
99 | | -// 取消 |
100 | | -promise.cancel() |
| 163 | +```javascript |
| 164 | +await request.series([ |
| 165 | + async () => { |
| 166 | + const user = await request.get('/api/user/current') |
| 167 | + // 依赖上一步的结果 |
| 168 | + return request.get(`/api/orders/${user.id}`) |
| 169 | + } |
| 170 | +]) |
101 | 171 | ``` |
102 | 172 |
|
103 | | -7) 重试(对任意返回 Promise 的函数) |
104 | | - |
105 | | -```js |
106 | | -await request.retry(() => request.get('/api/maybe-flaky'), 3, 1000) |
| 173 | +### 3. 轮询请求 |
| 174 | +结合 `RequestUtils.delay` 实现轮询。 |
| 175 | + |
| 176 | +```javascript |
| 177 | +import request, { RequestUtils } from '@/service/request' |
| 178 | + |
| 179 | +async function pollStatus(taskId) { |
| 180 | + while (true) { |
| 181 | + const res = await request.get(`/api/tasks/${taskId}`) |
| 182 | + |
| 183 | + if (res.status === 'COMPLETED') { |
| 184 | + return res.result |
| 185 | + } |
| 186 | + |
| 187 | + if (res.status === 'FAILED') { |
| 188 | + throw new Error('Task failed') |
| 189 | + } |
| 190 | + |
| 191 | + // 等待 2 秒后再次请求 |
| 192 | + await RequestUtils.delay(2000) |
| 193 | + } |
| 194 | +} |
107 | 195 | ``` |
108 | 196 |
|
109 | | -8) 访问底层 axios(高级用法) |
| 197 | +### 4. 重试机制 |
| 198 | +对于不稳定的接口,可以使用自动重试功能。 |
110 | 199 |
|
111 | | -```js |
112 | | -const ax = request.axios() |
113 | | -ax.get('/raw/endpoint').then(r => console.log(r)) |
| 200 | +```javascript |
| 201 | +// 如果请求失败,会自动重试 3 次,每次间隔 1000ms |
| 202 | +const data = await request.retry( |
| 203 | + () => request.get('/api/unstable-service'), |
| 204 | + 3, // 重试次数 |
| 205 | + 1000 // 延迟时间 (ms) |
| 206 | +) |
114 | 207 | ``` |
115 | 208 |
|
116 | | -9) 常见使用场景示例 |
117 | | -- 场景 A:页面 mount 时保护性加载权限数据,避免重复请求 |
| 209 | +### 5. 请求队列 |
| 210 | +利用 `parallel` 的并发限制特性实现请求队列。 |
| 211 | + |
| 212 | +```javascript |
| 213 | +// 假设有 100 个文件需要处理 |
| 214 | +const files = [...] |
118 | 215 |
|
119 | | -```js |
120 | | -// 使用 permissionService(单例)来避免多个组件重复向后端请求权限 |
121 | | -import permissionService from '@/service/permissionService' |
122 | | -// 在 App 初始化处调用一次 |
123 | | -permissionService.getPermissions() |
| 216 | +const uploadTasks = files.map(file => () => { |
| 217 | + return request.upload('/api/upload', { file }) |
| 218 | +}) |
| 219 | + |
| 220 | +// 创建一个最大并发数为 2 的上传队列 |
| 221 | +// 只有当前面的请求完成,后续请求才会开始 |
| 222 | +await request.parallel(uploadTasks, 2) |
124 | 223 | ``` |
125 | 224 |
|
126 | | -- 场景 B:文件导出,提供进度与取消 |
| 225 | +### 6. 批量上传文件(限制并发数) |
| 226 | +同上,这是处理大批量文件上传的最佳实践。 |
| 227 | + |
| 228 | +```javascript |
| 229 | +const fileList = [file1, file2, file3, ...] |
127 | 230 |
|
128 | | -```js |
129 | | -const downloadPromise = request.download('/api/export', {}, 'report.xlsx', { |
130 | | - onDownloadProgress: (e) => console.log('progress', e.loaded) |
| 231 | +// 封装上传任务 |
| 232 | +const tasks = fileList.map(file => { |
| 233 | + return () => request.upload('/api/files', { file }, { |
| 234 | + onProgress: (p) => console.log(`${file.name}: ${p.percent}%`) |
| 235 | + }) |
131 | 236 | }) |
132 | | -// 在需要时取消 |
133 | | -// downloadPromise.cancel() |
| 237 | + |
| 238 | +// 限制同时上传 3 个文件 |
| 239 | +await request.parallel(tasks, 3) |
134 | 240 | ``` |
135 | 241 |
|
136 | | -- 场景 C:批量请求但限制并发(防止后端压力) |
| 242 | +### 7. 全局请求前置处理 |
| 243 | +`request.js` 内部已内置了请求追踪和性能统计功能。 |
137 | 244 |
|
138 | | -```js |
139 | | -const tasks = urls.map((u) => ['get', u]) |
140 | | -const results = await request.parallel(tasks, 4) |
| 245 | +**请求追踪 (Request ID):** |
| 246 | +可以通过传入 `requestId` 来标记特定请求,方便在日志中追踪。 |
| 247 | + |
| 248 | +```javascript |
| 249 | +import { v4 as uuidv4 } from 'uuid' |
| 250 | + |
| 251 | +request.get('/api/trace-test', {}, { |
| 252 | + requestId: uuidv4() // 会添加到 Headers: X-Request-ID |
| 253 | +}) |
141 | 254 | ``` |
142 | 255 |
|
143 | | -备注 |
144 | | -- 默认超时时间与 baseURL 可通过 `request.setBaseURL` 与 `request.setDefaultHeaders` 调整。 |
145 | | -- 单次请求关闭弹窗:传 `config.showError = false`。 |
146 | | -- 所有请求返回的 Promise 都包含 `.cancel()`(内部使用 AbortController)。 |
| 256 | +**响应时间统计:** |
| 257 | +开发模式下,控制台会自动输出每个请求的耗时。 |
| 258 | +``` |
| 259 | +[Logger] 请求完成: GET /api/users [150ms] |
| 260 | +``` |
| 261 | + |
| 262 | +如果需要在业务代码中获取耗时,可以在拦截器中扩展(需修改 `request.js`),或者简单地在调用处计算: |
| 263 | + |
| 264 | +```javascript |
| 265 | +const start = Date.now() |
| 266 | +await request.get('/api/data') |
| 267 | +const duration = Date.now() - start |
| 268 | +console.log(`请求耗时: ${duration}ms`) |
| 269 | +``` |
147 | 270 |
|
148 | | -如需我把示例中的某些场景自动替换到代码中(例如把多个直接调用 `permissionAPI.getUserPermissions()` 的位置替换为 `permissionService.getPermissions()`),我可以继续扫描并提交补丁。 |
| 271 | +--- |
| 272 | + |
| 273 | +## 三、API 参考 |
| 274 | + |
| 275 | +### 配置对象 (Config) |
| 276 | +| 属性 | 类型 | 默认值 | 说明 | |
| 277 | +|---|---|---|---| |
| 278 | +| `cancelDuplicate` | boolean | `true` | 是否自动取消重复的进行中请求 | |
| 279 | +| `needToken` | boolean | `true` | 是否自动在 Header 中携带 Token | |
| 280 | +| `showError` | boolean | `true` | 请求失败时是否自动弹出错误提示 | |
| 281 | +| `requestId` | string | - | 自定义请求 ID,将放入 `X-Request-ID` 头 | |
| 282 | +| `returnFullResponse` | boolean | `false` | 是否返回完整的 Axios Response 对象,默认只返回 `data` | |
| 283 | +| `onProgress` | function | - | 上传/下载进度回调 | |
| 284 | + |
| 285 | +### 核心方法 |
| 286 | +- `request.get(url, params, config)` |
| 287 | +- `request.post(url, data, config)` |
| 288 | +- `request.put(url, data, config)` |
| 289 | +- `request.delete(url, params, config)` |
| 290 | +- `request.form(url, data, config)` |
| 291 | +- `request.upload(url, data, config)` |
| 292 | +- `request.download(url, params, fileName, config)` |
| 293 | +- `request.parallel(tasks, concurrency)` |
| 294 | +- `request.series(tasks)` |
| 295 | +- `request.retry(fn, attempts, delay)` |
0 commit comments