5.6.9 • Published 6 months ago

@cimom/vben-effects-request v5.6.9

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

@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 配置项

配置项类型默认值说明
baseURLstring''基础URL,将自动加在URL前面
timeoutnumber10000请求超时时间(毫秒)
headersRecord<string, string>{}请求头
withCredentialsbooleanfalse是否携带凭证(cookies)
responseType'arraybuffer' \| 'blob' \| 'document' \| 'json' \| 'text' \| 'stream''json'响应数据类型
maxRedirectsnumber5最大重定向次数
validateStatus(status: number) => booleanstatus >= 200 && status < 300定义哪些HTTP状态码是有效的
paramsSerializer(params: any) => string-参数序列化函数
cancelTokenCancelToken-取消请求的令牌
signalAbortSignal-取消请求的信号
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;
  }
}
5.6.9

6 months ago

5.6.3

6 months ago

5.6.1

6 months ago

5.6.0

6 months ago

5.5.9

6 months ago