5.6.9 • Published 6 months ago
@cimom/vben-effects-request v5.6.9
@cimom/vben-effects-request
HTTP 请求客户端工具包,基于 Axios 封装,提供了请求拦截、响应处理、错误处理、取消请求等功能,简化了 API 调用的实现。
安装
npm install @cimom/vben-effects-request基本使用
创建请求客户端
import { createRequestClient } from '@cimom/vben-effects-request';
// 创建请求客户端实例
const requestClient = createRequestClient({
// 基础URL
baseURL: 'https://api.example.com',
// 超时时间(毫秒)
timeout: 10000,
// 请求头
headers: {
'Content-Type': 'application/json',
},
// 是否携带凭证(cookies)
withCredentials: true,
});
// 导出实例供应用使用
export default requestClient;发送请求
import requestClient from './request';
// GET 请求
async function fetchUsers() {
const { data } = await requestClient.get('/users', {
params: {
page: 1,
limit: 10,
},
});
return data;
}
// POST 请求
async function createUser(userData) {
const { data } = await requestClient.post('/users', userData);
return data;
}
// PUT 请求
async function updateUser(id, userData) {
const { data } = await requestClient.put(`/users/${id}`, userData);
return data;
}
// DELETE 请求
async function deleteUser(id) {
const { data } = await requestClient.delete(`/users/${id}`);
return data;
}使用拦截器
import requestClient from './request';
// 请求拦截器
requestClient.interceptors.request.use(
(config) => {
// 在发送请求前做些什么
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
},
);
// 响应拦截器
requestClient.interceptors.response.use(
(response) => {
// 对响应数据做些什么
const { code, data, message } = response.data;
if (code === 0) {
return data;
} else {
// 处理业务错误
console.error(message);
return Promise.reject(new Error(message));
}
},
(error) => {
// 对响应错误做些什么
if (error.response) {
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
switch (error.response.status) {
case 401:
// 未授权,跳转到登录页
break;
case 403:
// 禁止访问
break;
case 404:
// 资源不存在
break;
case 500:
// 服务器错误
break;
default:
// 其他错误
break;
}
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
console.error('网络错误,请检查您的网络连接');
} else {
// 发送请求时出了点问题
console.error('请求配置错误', error.message);
}
return Promise.reject(error);
},
);API 参考
createRequestClient
创建请求客户端实例。
function createRequestClient(config?: RequestClientConfig): RequestClient;RequestClientConfig 配置项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
baseURL | string | '' | 基础URL,将自动加在URL前面 |
timeout | number | 10000 | 请求超时时间(毫秒) |
headers | Record<string, string> | {} | 请求头 |
withCredentials | boolean | false | 是否携带凭证(cookies) |
responseType | 'arraybuffer' \| 'blob' \| 'document' \| 'json' \| 'text' \| 'stream' | 'json' | 响应数据类型 |
maxRedirects | number | 5 | 最大重定向次数 |
validateStatus | (status: number) => boolean | status >= 200 && status < 300 | 定义哪些HTTP状态码是有效的 |
paramsSerializer | (params: any) => string | - | 参数序列化函数 |
cancelToken | CancelToken | - | 取消请求的令牌 |
signal | AbortSignal | - | 取消请求的信号 |
onUploadProgress | (progressEvent: any) => void | - | 上传进度回调 |
onDownloadProgress | (progressEvent: any) => void | - | 下载进度回调 |
RequestClient 实例方法
| 方法名 | 参数 | 返回值 | 说明 |
|---|---|---|---|
get | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 GET 请求 |
post | (url: string, data?: any, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 POST 请求 |
put | (url: string, data?: any, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 PUT 请求 |
delete | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 DELETE 请求 |
head | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 HEAD 请求 |
options | (url: string, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 OPTIONS 请求 |
patch | (url: string, data?: any, config?: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送 PATCH 请求 |
request | (config: RequestConfig) => Promise<AxiosResponse> | Promise<AxiosResponse> | 发送自定义请求 |
拦截器
// 请求拦截器
requestClient.interceptors.request.use(
(config) => config,
(error) => Promise.reject(error),
);
// 响应拦截器
requestClient.interceptors.response.use(
(response) => response,
(error) => Promise.reject(error),
);取消请求
// 使用 CancelToken
const source = requestClient.CancelToken.source();
requestClient.get('/users', {
cancelToken: source.token,
});
// 取消请求
source.cancel('请求被用户取消');
// 使用 AbortController (更现代的方式)
const controller = new AbortController();
requestClient.get('/users', {
signal: controller.signal,
});
// 取消请求
controller.abort();预设拦截器
该包提供了一些常用的预设拦截器,可以直接使用:
import {
createRequestClient,
presetInterceptors,
} from '@cimom/vben-effects-request';
const requestClient = createRequestClient({
baseURL: 'https://api.example.com',
});
// 使用预设拦截器
requestClient.interceptors.request.use(
presetInterceptors.addTokenInterceptor('Bearer', () =>
localStorage.getItem('token'),
),
);
requestClient.interceptors.response.use(
presetInterceptors.transformResponseInterceptor(),
presetInterceptors.handleErrorInterceptor(),
);可用的预设拦截器
| 拦截器名称 | 说明 |
|---|---|
addTokenInterceptor | 添加认证令牌到请求头 |
transformResponseInterceptor | 转换响应数据,提取有效数据 |
handleErrorInterceptor | 处理错误响应,包括网络错误、超时等 |
addTimestampInterceptor | 为 GET 请求添加时间戳,避免缓存 |
loggerInterceptor | 记录请求和响应日志 |
高级用法
创建多个请求实例
import { createRequestClient } from '@cimom/vben-effects-request';
// 创建用于普通 API 的请求客户端
export const apiClient = createRequestClient({
baseURL: 'https://api.example.com',
});
// 创建用于文件上传的请求客户端
export const uploadClient = createRequestClient({
baseURL: 'https://upload.example.com',
timeout: 60000, // 更长的超时时间
headers: {
'Content-Type': 'multipart/form-data',
},
});
// 创建用于第三方 API 的请求客户端
export const thirdPartyClient = createRequestClient({
baseURL: 'https://third-party-api.com',
withCredentials: false,
});请求重试
import { createRequestClient } from '@cimom/vben-effects-request';
import axios from 'axios';
const requestClient = createRequestClient({
baseURL: 'https://api.example.com',
});
// 添加响应拦截器实现请求重试
requestClient.interceptors.response.use(
(response) => response,
async (error) => {
const config = error.config;
// 设置重试次数和计数器
if (!config || !config.retry) {
config.retry = 3;
config.retryCount = 0;
config.retryDelay = 1000;
}
// 检查是否还有重试次数
if (config.retryCount < config.retry) {
config.retryCount += 1;
// 延迟重试
await new Promise((resolve) => {
setTimeout(resolve, config.retryDelay * config.retryCount);
});
// 重新发送请求
return requestClient(config);
}
return Promise.reject(error);
},
);请求队列和并发控制
import { createRequestClient } from '@cimom/vben-effects-request';
const requestClient = createRequestClient({
baseURL: 'https://api.example.com',
});
// 创建请求队列
class RequestQueue {
private queue = [];
private maxConcurrent = 3;
private runningCount = 0;
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({
requestFn,
resolve,
reject,
});
this.processQueue();
});
}
private async processQueue() {
if (this.runningCount >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.runningCount += 1;
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await requestFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.runningCount -= 1;
this.processQueue();
}
}
}
// 使用请求队列
const queue = new RequestQueue(3);
// 添加请求到队列
async function fetchData(id) {
return queue.add(() => requestClient.get(`/data/${id}`));
}
// 批量请求
async function fetchBatch(ids) {
return Promise.all(ids.map((id) => fetchData(id)));
}请求缓存
import { createRequestClient } from '@cimom/vben-effects-request';
const requestClient = createRequestClient({
baseURL: 'https://api.example.com',
});
// 简单的内存缓存
const cache = new Map();
// 添加请求拦截器检查缓存
requestClient.interceptors.request.use((config) => {
// 只缓存 GET 请求
if (config.method.toLowerCase() === 'get') {
const cacheKey = `${config.url}?${new URLSearchParams(config.params).toString()}`;
// 检查缓存是否存在且未过期
const cachedData = cache.get(cacheKey);
if (cachedData && Date.now() < cachedData.expiry) {
// 返回缓存数据
return Promise.reject({
__CACHE__: true,
data: cachedData.data,
});
}
}
return config;
});
// 添加响应拦截器存储缓存
requestClient.interceptors.response.use((response) => {
// 只缓存 GET 请求
if (response.config.method.toLowerCase() === 'get') {
const cacheKey = `${response.config.url}?${new URLSearchParams(response.config.params).toString()}`;
// 缓存响应数据,设置过期时间为 5 分钟
cache.set(cacheKey, {
data: response.data,
expiry: Date.now() + 5 * 60 * 1000,
});
}
return response;
});
// 处理缓存错误
requestClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.__CACHE__) {
return Promise.resolve({
data: error.data,
__fromCache__: true,
});
}
return Promise.reject(error);
},
);示例
用户认证 API
import requestClient from './request';
// 用户认证相关 API
export const authApi = {
// 登录
login: (username: string, password: string) => {
return requestClient.post('/auth/login', { username, password });
},
// 注册
register: (userData: any) => {
return requestClient.post('/auth/register', userData);
},
// 获取当前用户信息
getCurrentUser: () => {
return requestClient.get('/auth/me');
},
// 刷新令牌
refreshToken: (refreshToken: string) => {
return requestClient.post('/auth/refresh', { refreshToken });
},
// 登出
logout: () => {
return requestClient.post('/auth/logout');
},
};文件上传
import requestClient from './request';
// 文件上传 API
export const fileApi = {
// 上传单个文件
uploadFile: (file: File, onProgress?: (percent: number) => void) => {
const formData = new FormData();
formData.append('file', file);
return requestClient.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (onProgress) {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
onProgress(percent);
}
},
});
},
// 上传多个文件
uploadFiles: (files: File[], onProgress?: (percent: number) => void) => {
const formData = new FormData();
files.forEach((file, index) => {
formData.append(`files[${index}]`, file);
});
return requestClient.post('/upload/multiple', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (onProgress) {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total,
);
onProgress(percent);
}
},
});
},
// 下载文件
downloadFile: (fileId: string) => {
return requestClient.get(`/files/${fileId}/download`, {
responseType: 'blob',
});
},
};带有分页的数据列表
import requestClient from './request';
// 分页参数接口
interface PaginationParams {
page: number;
limit: number;
sort?: string;
order?: 'asc' | 'desc';
search?: string;
}
// 分页响应接口
interface PaginationResponse<T> {
items: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}
// 用户 API
export const userApi = {
// 获取用户列表
getUsers: (params: PaginationParams) => {
return requestClient.get<PaginationResponse<User>>('/users', { params });
},
// 获取用户详情
getUser: (id: string) => {
return requestClient.get<User>(`/users/${id}`);
},
// 创建用户
createUser: (userData: Partial<User>) => {
return requestClient.post<User>('/users', userData);
},
// 更新用户
updateUser: (id: string, userData: Partial<User>) => {
return requestClient.put<User>(`/users/${id}`, userData);
},
// 删除用户
deleteUser: (id: string) => {
return requestClient.delete(`/users/${id}`);
},
};
// 用户接口
interface User {
id: string;
username: string;
email: string;
fullName: string;
role: string;
status: 'active' | 'inactive';
createdAt: string;
updatedAt: string;
}请求错误处理
import requestClient from './request';
import { message } from 'ant-design-vue';
// 添加响应拦截器处理错误
requestClient.interceptors.response.use(
(response) => {
// 处理成功响应
return response;
},
(error) => {
// 处理错误响应
let errorMessage = '未知错误';
if (error.response) {
// 服务器返回了错误状态码
const { status, data } = error.response;
switch (status) {
case 400:
errorMessage = data.message || '请求参数错误';
break;
case 401:
errorMessage = '未授权,请重新登录';
// 跳转到登录页
window.location.href = '/login';
break;
case 403:
errorMessage = '拒绝访问';
break;
case 404:
errorMessage = '请求的资源不存在';
break;
case 500:
errorMessage = '服务器错误';
break;
default:
errorMessage = `请求错误 (${status})`;
break;
}
} else if (error.request) {
// 请求已发送但没有收到响应
errorMessage = '网络错误,请检查您的网络连接';
} else {
// 请求配置出错
errorMessage = error.message;
}
// 显示错误消息
message.error(errorMessage);
// 将错误传递给调用者
return Promise.reject(error);
},
);
// 使用 try/catch 处理错误
async function fetchData() {
try {
const response = await requestClient.get('/data');
return response.data;
} catch (error) {
// 错误已经被拦截器处理,这里可以做额外处理
console.error('获取数据失败:', error);
return null;
}
}