新增全局 API 请求/响应拦截器

1,209 阅读3分钟

简单来说就是,项目中肯定会通过调取 API 来做一些事情,比如说获取数据,执行一些操作,然后每次调取 API,都会做一些相同的事情。比如说

  • 每个 API 的调取都需要传递有 token。
  • 都需要判断登录是否过期,过期了都需要跳转到登录页。
  • 每个 API 在调取时报错,需要统一提示一个什么错误。
  • 每个 API 的请求时,都会除了返回 data,还有其它数据,在拦截器中处理,可以只返回想要的 data。而不需要每次 res.data.xxx 这么获取。
  • 等等等等...

这些就都可以在全局请求/响应拦截器中统一实现,就不需要在每次调取 API 的时候再传入相应数据或做一些重复的操作

本项目拦截器封装的文件是 src/utils/request.ts ,暂时是简单的写了点,需要根据实际 API 的请求需要和返回数据进行相应的调整。

/*
 * @Description: 公共 request 拦截器
 * @Author: kivet
 * @Date: 2022-01-10 13:19:33
 * @LastEditTime: 2022-01-26 10:36:27
 */

import { extend } from 'umi-request';
import { message } from 'antd';
import { DruidLocalStorage } from '@/utils/storage';
import { StorageEnum } from '@/utils/enum';
import { Helper } from '@/utils/helper';

const queryString = require('querystring');

interface ResponseError<D = any> extends Error {
  name: string;
  data: D;
  response: Response;
}

interface RequestParams {
  /** 请求方式: get、post、put、delete */
  method?: string;
  data?: {
    showOriginData?: boolean; // ? 是否返回原始响应数据
    skipError?: boolean; // ? 是否不显示任何错误信息
    [index: string]: any;
  };
  headers?: any;
}

/** 是否返回原始响应数据 */
let isShowOriginResponse = false;
/** 是否不显示任何错误信息 */
let isSkipError = false;

/**
 * 默认请求参数,适用于通用的请求参数
 */
// const defaultParams = {};

/**
 * 网络异常处理,未得到服务器响应或请求超时
 * @param error
 */
const errorHandler = (error: ResponseError) => {
  const { response = {} as Response } = error;
  const { status, url } = response || {};
  console.error(`请求错误 ${status}: ${url}`);
};

/**
 * request原始对象
 */
const request = extend({
  timeout: 60000,
  errorHandler,
});

/**
 * 全局请求拦截器
 */
request.interceptors.request.use(
  (tempUrl: string, tempOptions: RequestParams) => {
    const { method = 'post', data: params = {}, headers = {} } = tempOptions;

    const { showOriginData = false, skipError = false, ...realData } = params;
    isShowOriginResponse = showOriginData;
    isSkipError = skipError;

    // ? 处理请求地址,如果请求地址带协议则不拼接API_BASE参数
    let url = `${API_BASE}${tempUrl}`;

    if (tempUrl.includes('http')) {
      url = tempUrl;
    }

    // ? git 请求时,可能会有额外的参数拼接到 URL 地址后面
    if (method === 'get') {
      const stringified = params ? `?${queryString.stringify(params)}` : '';
      url += `${stringified}`;
    }

    // 移除没必要传递给服务器的参数
    // const { showOriginData = false, skipError = false, ...realData } =
    //   (method === 'get' ? tempOptions.params : tempOptions.data) || {};

    // 组装请求参数,移除空值
    const data = {
      // ...Helper.handleNullData(defaultParams),
      ...Helper.handleNullData(realData),
    };

    // 组装请求配置
    const options = {
      // ...tempOptions,
      data,
      headers: {
        'x-druid-authentication':
          DruidLocalStorage.get(StorageEnum.TOKEN) || '',
        ...headers,
      },
    };

    return { url, options };
  },
);

// 全局响应拦截器,克隆响应对象做解析处理
request.interceptors.response.use(async (originResponse, options) => {
  const response = await originResponse.clone().json();
  const {
    data = '',
    code = '',
    errCode = '',
    msg = '',
    message: msgTwo = '',
  } = response;

  if (errCode === '401') {
    Helper.handleRedirect();
    return;
  }
  // console.log('response', response);
  if (!isSkipError) {
    if (code === '0' || code === '-1') {
      console.warn(`===接口响应失败===\nerrCode: ${errCode}\nmsg: ${msg}`);
      // 请求返回数据有误
      message.error(msg || msgTwo, 1);
    }
  }
  // 返回原始数据
  if (isShowOriginResponse) {
    return originResponse;
  }

  // 如果服务器返回的data字段为null,则返回空字符串,避免解构赋值默认值无法生效
  if (data == null) {
    return '';
  }

  // 仅返回处理后的data数据(无code,msg,errCode字段)
  return data ? Helper.handleNullData(data) : '';
});

export default request;

返回目录文档