使用umi+react+dva+ant-design开发项目时,通过对umi-request进行二次封装方便进行后端接口的请求
一、话不多说先贴完整代码:
@/utils/request.ts
import { extend } from 'umi-request';
import { history } from 'umi';
import { message } from 'antd';
import { stringify } from 'querystring';
import { stringify as qsStringify } from 'qs';
const printErrorInfo = (error: {}) => { //在控制台打印出对应的错误信息,方便定位问题
// console.log('error', error);
const { code, message, status, timestamp, request = {} } = error || {};
const { url, options } = request;
const { method, headers, params, data } = options;
const paramsKeys = Object.keys(params || {});
const tempParams = paramsKeys && paramsKeys.length ? params : data; // 接口的参数有可能在 params 中也有可能在 data 里
const errorTagStyle = [
'color: #ff4d4f',
'background: #fff2f0',
'padding: 2px 6px',
'font-size: 12px',
'border-radius: 2px',
'border: 1px solid #ff4d4f',
].join(';');
console.log('%c接口报错信息', errorTagStyle);
console.log(`
------------- error-start -------------
时间:${timestamp || new Date().toString()}
HTTP Status:${status || 200}
接口路径:${url}
请求类型:${method}
headers:${JSON.stringify(headers)}
参数:${JSON.stringify(tempParams || {})}
code:${code}
message:${message}
------------- error-end ---------------
`);
};
// const codeMessage: Record = {
// 200: '服务器成功返回请求的数据。',
// 201: '新建或修改数据成功。',
// 202: '一个请求已经进入后台排队(异步任务)。',
// 204: '删除数据成功。',
// 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
// 401: '用户没有权限(令牌、用户名、密码错误)。',
// 403: '用户得到授权,但是访问是被禁止的。',
// 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
// 406: '请求的格式不可得。',
// 410: '请求的资源被永久删除,且不会再得到的。',
// 422: '当创建一个对象时,发生一个验证错误。',
// 500: '服务器发生错误,请检查服务器。',
// 502: '网关错误。',
// 503: '服务不可用,服务器暂时过载或维护。',
// 504: '网关超时。',
// }; -------因为我们是后端直接返回对应的message信息,所以不需要用到这个,具体情况看个人的项目
/** 异常处理程序 */
const errorHandler = (error: { response: Response }): Response => {
if (error) {
printErrorInfo(error);
if (error.code === '40100') { //在这里对token过期或者没有登录时的情况跳转到登录页面
history.replace({
pathname: '/login',
search: stringify({
redirect: window.location.hash.substring(1),//记录是从哪个页面跳转到登录页的,登录后直接跳转到对应的页面
}),
});
localStorage.clear(); //跳到登录页时需要将存储在本地的信息全部清除掉
}
message.config({ maxCount: 1 });//当多个接口报错时只会显示一个错误提示
message.error(error.message, 1);
} else {
message.error('您的网络发生异常,无法连接服务器', 1);
}
return Promise.reject(error); //reject出去之后使用时才可通过.catch对异常情况进行处理
};
/** 配置request请求时的默认参数 */
const request = extend({
prefix: API_URL,
timeout: 60000,
errorHandler, // 默认错误处理
credentials: 'include', // 默认请求是否带上cookie
});
// request拦截器, 改变url 或 options.
request.interceptors.request.use((url, options) => {
const token = localStorage.getItem('token');//获取存储在本地的token
const { headers = {} } = options || {};
const tokenHeaders = {
'mxc-token': token,
...headers,
};
if (options.method?.toUpperCase() === 'GET') {
options.params = options.data;
} else {
//我们的请求参数和后端约定的是除了一些特殊情况使用formData 其他都使用json格式,因此默认是使用json格式
options.requestType = options.requestType ? options.requestType : 'json';
}
if (token) {
return {
url,
options: { ...options, headers },
};
}
return {
url,
options: { ...options },
};
});
request.interceptors.response.use(async (response) => {
const data = await response.clone().json();
if (data.success) {
return response; //我们的项目是通过success来判断是否请求成功,具体情况看个人项目
}
return Promise.reject(data);//注意:需要reject出去才会在请求不成功或返回错误的code时调用errorHandler
});
export default request;
二、上面贴了完整的代码,这里对一些在封装时需要注意的地方进行解析
1)、统一的错误处理函数:我们可以把请求接口时返回的各个错误情况或者请求失败在这里进行统一的处理,例如我这里对没有登录或者登录过期的情况进行统一的处理
注意:我们在这个函数内需要return Promise.reject,这样我们在使用这个函数时就可以通过catch去捕获请求失败时的情况然后做一些对应的操作
2、上面我们说可以把请求失败(404)或者请求错误(传参错误或者请求方法错误)统一放到errorHandler进行处理,但是需要注意的是在response.use里面需要对请求错误的情况进行return Promise.reject()不然errorHandler只会对请求失败(404或者没有网络)的情况进行捕获
3、由于get请求的请求参数是 params:{id:1}而post请求时是data:{id:1},因此为了使用该方法时更方便,于是做了如下处理:这样使用时不管get还是post请求都可以统一使用data:{id:1}进行传参
三、使用方法:
1、请求参数为json格式(post请求时,当我们不给函数传requestType时,默认json格式的)
import request from '@/utils/request'
export async function fetchTableData(params: any) {
return request('/crm-account/account/getAccountList', {
method: 'GET',//请求方式 GET POST
data: params, //请求参数
});
}
2、请求参数为formData时:
export async function createEmiNum(params: any) {
return request('/crm-account/account/createEmiNum', {
method: 'POST',
data: params,
requestType: 'form', //请求参数的格式
});
}
3、在我们项目中有一些情况是调用接口的前缀完全不一样(如调用http://aa.bb和调用http://cc.dd),这个时候就可以重新设置prefix去覆盖函数封装时设置的prefix
export async function listTreeChannel(params: any) {
return request('/channel/channel_type_tree', {
method: 'GET',
data: params,
prefix: API_URL_V2, //重新设置接口前缀
});
}