封装axios
-
可以实现多域名接口
-
取消重复请求
-
取消的请求共享正确请求的返回结果
*A请求和B请求重复, 先发送A请求,再发送B请求, 因为B请求和A请求一样,则取消B请求,B请求返回的是A请求的返回结果*
3.1 当取消请求响应时,正确请求的结果还未返回时, 通过发布订阅模式订阅回调函数, 当正确请求的结果返回时发布,触发回调函数的执行,返回结果通过回调函数参数传递,再通过Promise.resolve将结果返回;
5.2 当取消请求响应时,正确请求的结果已经返回,则在正确请求结果返回时,将结果缓存,在取消请求响应时,判断缓存中是否有结果,有则直接返回。
-
添加公用header
-
统一处理未登录,无权限接口
js
import axios from 'axios';
import { netConfig } from '@/config/net.config';
import EventEmitter from './EventEmitter';
import md5 from 'md5';
const { contentType, invalidCode, noPermissionCode, requestTimeout } = netConfig;
import router from '@/router/index.js';
import { ElMessageBox } from 'element-plus';
import qs from 'qs';
import { getCurrAppId } from '@/common/project';
import { getDataSource, setDataSource } from './cookies';
const BaseUrlOP = import.meta.env.VITE_APP_WEB_URL;
const BaseUrl = import.meta.env.VITE_APP_BASE_URL;
const BaseUrlSG = import.meta.env.VITE_APP_BASE_URL_SG;
const RunnerUrl = import.meta.env.VITE_APP_RUNNER_URL;
const QaPlatUrl = import.meta.env.VITE_APP_PLATQA_URL;
const eventEmitter = new EventEmitter();
// eslint-disable-next-line no-unused-vars
let tokenLose = true;
/**
*
* @description 处理code异常
* @param {*} code
* @param msg
*/
const handleCode = (code, msg) => {
switch (code) {
case invalidCode:
tokenLose = false;
ElMessageBox.confirm(msg, '重新登录', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
// 处理重新登录逻辑
})
.catch(() => {
tokenLose = true;
});
break;
case noPermissionCode:
router.push({ path: '/401' }).catch(() => {});
break;
default:
break;
}
};
// reqList数组存储时间段内的请求
const reqList = [];
// 阻止重复请求; 如果某一段时间内请求重复,则只保留第一个请求,取消其他请求
const stopRepeatRequest = (config, cancel, errorMessage) => {
const errorMsg = errorMessage || '';
for (let i = 0; i < reqList.length; i++) {
if (
reqList[i].url === config.url &&
reqList[i].method === config.method &&
reqList[i].data === JSON.stringify(config.data)
) {
// url、method、请求参数,三者md5加密,生成唯一hash
let hash = md5(config.url + config.method + JSON.stringify(config.data));
cancel(errorMsg + ':' + hash);
return;
}
}
reqList.push({
url: config.url,
method: config.method,
data: JSON.stringify(config.data),
});
};
// 允许某个请求可以继续进行
const allowRequest = (config) => {
for (let i = 0; i < reqList.length; i++) {
if (
reqList[i].url === config.url &&
reqList[i].method === config.method &&
reqList[i].data === config.data
) {
let hash = md5(config.url + config.method + JSON.stringify(config.data));
cacheResponse[hash]; // 清除缓存
reqList.splice(i, 1);
break;
}
}
};
// cacheResponse对象缓存请求结果,
const cacheResponse = {};
// 创建axios实例
const instance = axios.create({
timeout: requestTimeout,
headers: {
'Content-Type': contentType,
},
});
// 请求拦截器
instance.interceptors.request.use(
(config) => {
if (config.url.includes('/aaa/***')) {
config.baseURL = BaseUrlOP;
} else if (config.url.includes('/bbb/***')) {
config.baseURL = RunnerUrl;
} else if (
config.url.startsWith('/ccc/***') ||
config.url.startsWith('/ddd/***')
) {
config.baseURL = QaPlatUrl;
} else {
config.headers['App-Id'] = getCurrAppId();
let url = '?' + window.location.href.split('?')[1];
const urlSearchParems = new URLSearchParams(url);
const params = Object.fromEntries(urlSearchParems.entries());
if (params && params.env === 'os') {
config.baseURL = BaseUrlSG;
setDataSource('out');
} else if (params && params.env === 'main') {
config.baseURL = BaseUrl;
setDataSource('in');
} else {
if (getDataSource() === 'out') {
config.baseURL = BaseUrlSG;
} else {
config.baseURL = BaseUrl;
}
}
}
config.url = config.url.replaceAll('//', '/');
if (
config.data &&
config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=UTF-8'
)
config.data = qs.stringify(config.data);
config.withCredentials = true;
instance.defaults.retryDelay = 1000;
// config.crossDomain = true;
let cancel;
config.cancelToken = new axios.CancelToken(function (c) {
cancel = c;
});
stopRepeatRequest(config, cancel, `${config.url}请求被中断`);
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
/// 增加延迟,相同的请求不得在短时间内重复发送
setTimeout(() => {
allowRequest(response.config);
}, 1000);
let config = response.config;
let hash = md5(config.url + config.method + JSON.stringify(config.data));
cacheResponse[hash] = response; // 发布时还没有订阅; 将结果缓存
eventEmitter.emit(hash, response); // 订阅发生在发布之前
console.log('emit', config.url, hash, response, eventEmitter.subscribers);
return response;
},
async (error) => {
if (axios.isCancel(error)) {
let hash = error.message.split(':')[1];
if (cacheResponse[hash]) return cacheResponse[hash]; // 未取消请求已经返回了结果,直接使用该缓存结果
let p = await new Promise((resolve) => {
// 订阅必须在发布之前
eventEmitter.subscribe(hash, (res) => {
resolve(res);
});
});
return p;
} else {
/// 增加延迟,相同的请求不得在短时间内重复发送
setTimeout(() => {
allowRequest(error.config);
}, 1000);
if (error.response) {
const res = error.response.data;
if (res.data) {
const { data } = res;
const { retcode, message } = data;
handleCode(retcode, message);
}
}
return Promise.reject(error);
}
}
);
export default instance;
发布订阅EventEmitter的实现
js
class EventEmitter {
constructor() {
this.subscribers = {};
}
// 订阅
subscribe(eventName, callback) {
if (!this.subscribers[eventName]) {
this.subscribers[eventName] = [];
}
this.subscribers[eventName].push(callback);
}
// 取消订阅
unsubscribe(eventName, callback) {
if (this.subscribers[eventName]) {
this.subscribers[eventName] = this.subscribers[eventName].filter((cb) => cb != callback);
}
}
// 发布
emit(eventName, ...args) {
if (this.subscribers[eventName])
this.subscribers[eventName].forEach((callback) => {
callback.apply(null, args);
});
}
}
export default EventEmitter;