准备内容
- 引入 axios 插件
- 引入项目工具类 getToken, checkToken
- 引入 qs 库
- 引入组件库中部分组件 Message 和 Loading
- 引入 不需要loading的url 对象 notShowLoading
- 引入仓库 store
- 引入路由对象 router
import axios from 'axios';
import { getToken, checkToken } from '@/util/auth';
import QS from 'qs';
import { Message, Loading } from 'element-ui';
import notShowLoading from './notShowLoaing';
import store from '@/store/index';
import router from '@/router/index';
loading
定义全局变量
// 定义loading变量,
// 以服务的方式(Loading.service(options))调用的 Loading 需要异步关闭
let loading=null, // loading实例
needLoadingRequestCount = 0; // 接口请求数量
notShowLoading.js文件
导出不需要加载loading效果的对象,访客标签接口,访客信息接口,访客离开接口
key:不需要loading的请求url地址,value: {isNotShow: true}
// notShowLoading.js 文件
export default {
'/client/visitor/leaveMsg': {
isNoShow: true
},
'/client/visitor/info': {
isNoShow: true
},
'/client/visitor/tag': {
isNoShow: true
},
// ...
}
开始加载loading函数
function startLoading() {
//使用Element loading-start 方法
loading = Loading.service({ // loading实例
lock: true,
text: '加载中……',
background: 'rgba(0, 0, 0, 0.5)',
});
}
全屏加载Loading
// 开启loading 层
function showFullScreenLoading() {
// needLoadingRequestCount++;
// 放在'后面'这里判断条件为 0 ,放在'前面'这里判断逻辑为 1
if (needLoadingRequestCount === 0) {
startLoading();
}
needLoadingRequestCount++;
}
关闭全屏Loading
异步关闭Loading
function endLoading() {
//使用Element loading-close 方法
loading.close();
loading = null; // 清空实例
}
隐藏Loading
function tryHideFullScreenLoading() {
// if (needLoadingRequestCount <= 0) return;
if (loading && needLoadingRequestCount > 0) {
needLoadingRequestCount--;
}
if (needLoadingRequestCount === 0) {
endLoading();
}
}
取消重复请求文件
// 取消重复请求
// 1. 取消重复请求: 完全相同的接口在上一个pending状态时,自动取消下一个请求
// 判断重复请求的方法:只要是请求地址,请求方式,请求参数一样,
// 那么我们就认为请求是一样的
// Map 对象保存键值对,任何值(对象或者原始值)都可以作为一个键或一个值
const pendingRequest = new Map();
// addPendingRequest: 用于把当前请求信息添加到 pendingRequest 对象中;
function addPendingRequest(config) {
//cancelRequest: true
// 接口中定义该项则开启取消重复请求功能
if (config.cancelRequest) {
// 该请求唯一标识符
const requestKey = generateReqKey(config);
if (pendingRequest.has(requestKey)) {
// cancelToken取消请求配置项
// config.cancelToken 配置项默认配置
config.cancelToken = new Axios.CancelToken(cancel => {
// cancel 函数的参数会作为 promise 的 error 被捕获
cancel(`${config.url}请求已取消`)
})
} else {
config.cancelToken = config.cancelToken || new Axios.CancelToken((cancel) => {
pendingRequest.set(requestKey,cancel)
})
}
}
}
// removePendingRequest 检查是否存在重复请求,若存在则取消已发送的请求
function removePendingRequest(response) {
if (response && response.config && response.config.cancelRequest) {
const requestKey = generateReqKey(response.config)
if (pendingRequest.has(requestKey)) {
const cancelToken = pendingRequest.get(requestKey);
cancelToken(requestKey)
pendingRequest.delete(requestKey)
}
}
}
请求重发
// 请求重发
// 实现请求发生错误时,重新发送请求
function againRequest(error,axios) {
const config = error.config
// config.retry 具体接口配置的重发次数
if (!config && !config.retry) return Promise.reject(err)
// 设置用于记录重试计数的变量 默认为 0
config._retryCount = config._retryCount || 0;
// 判断是否超过了重试次数
if (config._retryCount >= config.retry) return Promise.reject(error)
// 重试次数自增
config._retryCount += 1;
//延时处理
const backoff = new Promise((resolve, reject) => {
setTimeout(()=>resolve(),config.retryDelay || 1000)
})
// 重新发起请求
return backoff.then(() => {
// 判断是否是字符串,
// 未确认config.data 再重发时变为字符串的原因
if (config.data && isJsonStr(config.data)) {
config.data = JSON.parse(config.data)
}
return axios(config)
})
}
请求缓存
// 请求缓存
const options = {
storage: true, // 是否开启localstorage 缓存
storageKey: 'apiCache',
storage_expire: 6000, // localStorage 数据存储时间 10 min (刷新页面判断是否清除)
expire: 20000 //每个接口数据缓存 (ms) 毫秒数
}
// 初始化 自执行函数
(function () {
let cache = window.localStorage.getItem(options.storageKey);
if (cache) {
let { storageExpire } = JSON.parse(cache)
// 未超时不做处理
if (storageExpire && getNowTime() - storageExpire < options.storage_expire) return;
}
window.localStorage.setItem(options.storageKey, JSON.stringify({ data: {}, storageExpire: getNowTime() }))
})();
function getCacheItem(key) {
let cache = window.localStorage.getItem(options.storageKey);
let { data, storageExpire } = JSON.parse(cache)
// (data && data[key]) 这句话就是 data[key] 对应的值
return (data && data[key]) || null;
}
function setCacheItem (key,value) {
let cache = window.localStorage.getItem(options.storageKey);
let { data, storageExpire } = JSON.parse(cache)
// 传递过来的值替换原来的值
data[key] = value;
// 存储起来
window.localStorage.setItem(options.storageKey, JSON.stringify({data,storageExpire}))
}
let _CACHES = {};
// 使用proxy 代理
let cacheHandler = {
get: function (target,key) {
let value = target[key];
console.log(`${key}被读取`, value);
if (options.storage && !value) {
value = getCacheItem(key)
}
return value;
},
set: function (target,key,value) {
console.log(`${key},被设置为 ${value}`);
target[key] = value;
if (options.storage) {
setCacheItem(key,value)
}
return true
}
}
let CACHES = new Proxy(_CACHES,cacheHandler)
function requestInterceptor(config,axios) {
// 开启缓存则保存请求结果和cancel 函数
if (config.cache) {
// 获取数据
let data = CACHES[`${generateReqKey(config)}`];
// 这里用于存储是默认时间还是用户传递过来的时间
let setExpireTime;
config.setExpireTime ? (setExpireTime = config.setExpireTime) : (setExpireTime = options.expire);
// 判断缓存数据是否存在 存在的话 是否过期 没过期就返回
if (data && ((getNowTime() - data.expire) < setExpireTime)) {
config.cancelToken = new Axios.CancelToken((cancel) => {
// cancel 函数的参数会作为 promise 的error 被捕获
cancel(data)
}) // 传递结果到catch 中
}
// else {
// // 不存在的话或者过期,设置
// }
}
}
function responseInterceptor(response) {
// 返回的code === 200 时候才会缓存下来
if (response && response.config.cache && response.data.code === 200) {
let data = {
expire: getNowTime(),
data: response
}
CACHES[`${generateReqKey(response.config)}`] = data
}
}
// 获取当前时间戳
function getNowTime() {
return new Date().getTime(); // 毫秒数
}
公共方法
// https://juejin.cn/post/6968487137670856711#heading-0
// 取消重复请求: 完全相同的接口在上一个pending状态时,自动取消下一个请求
// 请求失败自动重试: 接口请求后台异常时候,自动重新发起多次请求,直到达到所设次数
// 请求接口数据缓存:接口在设定时间内不会向后台获取数据,而是直接拿本地缓存
const testAPI= {
middleViewData: data => request.get('/jscApi/middleViewData', { data }), // 正常请求
cancelReq: data => request.get('http://localhost:3003/jscApi/middleViewData', { data, cancelRequest: true }), // 测试取消请求
reqAgainSend: data => request.get('/equ/equTypeList11', { data, retry: 3, retryDelay: 1000 }), // 测试请求重发,除了原请求外还会重发3次
cacheEquList: data => request.get('/equ/equList', { data, cache: true, setExpireTime: 30000 }), // 测试缓存请求带参数:setExpireTime 为缓存有效时间ms
cacheEquListParams: data => request.get('/equ/equList', { data, cache: true }) // 测试缓存请求参数值不一样
};
// cancelRequest: true, // 接口中定义该项则开启取消重复请求功能
// retry: 3, retryDelay: 1000, // retry 请求重试次数,retryDelay 两次重试之间的时间间隔
// cache: true, setExpireTime: 30000, // cache: true 开启当前接口缓存,setExpireTime 当前接口缓存时限
function generateReqKey(config) {
// 响应的时候, response.config 中的data 是一个 JSON字符串,所以需要转换一下
if (config && config.data && isJsonStr(config.data)) {
config.data = JSON.parse(config.data)
}
const { method, url, params, data } = config // 请求方法,地址,参数,
return [method, url, qs.stringify(params), qs.stringify(data)].join('&'); // 拼接
}
// 判断一个字符串是否为JSON 字符串
const isJsonStr = str => {
if (typeof str === 'string') {
try {
var obj = JSON.parse(str)
if (typeof obj === 'object' && obj) {
return true
} else {
return false
}
} catch (error) {
console.log('error:' + str + '!!!' + error);
return false
}
}
}
创建axios实例
const axios = Axios.create({
baseURL: process.env.VUE_APP_BASEURL || '',
timeout: 30000
})
const responseHandle = {
200: response => {
return response.data
},
401: response => {
Notification({
title: '认证异常',
message: '登录状态已过期,请重新登录!',
type: 'error'
});
clearToken();
window.location.href = window.location.origin;
},
default: response => {
Notification({
title: '操作失败',
message: response.data.msg,
type: 'error'
});
return Promise.reject(response)
}
}
// 请求拦截
axios.interceptors.request.use(config => {
// 请求头用于接口token 认证
getToken() && (config.headers['Authorization'] == getToken())
if (config.method.toLocaleLowerCase() === 'post' || config.method.toLocaleLowerCase() === 'put') {
// 参数统一处理, 请求都使用data 传参
config.data = config.data.data
} else if (config.method.toLocaleLowerCase() === 'get' || config.method.toLocaleLowerCase() === 'delete') {
// 参数统一处理
config.params = config.data
} else {
alert( `不允许的请求方法: ${config.method}`)
}
// pending中的请求,后续请求不发送(由于存放的penddingMap 的 key 和参数有关,所以放在参数处理之后)
addPendingRequest(config) // 把当前请求信息添加到penddingRequest对象中
// 请求缓存
requestInterceptor(config, axios)
return config;
}, error => { // 有缓存时被取消请求回到error中
return Promise.reject(error)
})
// 添加响应拦截器
axios.interceptors.response.use(response => {
// 响应正常的时候就从penddingQuest对象中移除请求
removePendingRequest(response);
responseInterceptor(response);
return responseHandle[response.data.code || 'default'](response)
},
error => {
// 从 pendingRequest列表中移除请求
removePendingRequest(error.config || {});
// 需要特殊处理请求被取消的情况
if (!axios.isCancel(error)) {// axios.isCancel(thrown) 取消的失败
// 不是取消引起的失败失败
// 请求重发
return againRequest(error,axios)
}
// 请求缓存处理方式
if (axios.isCancel(error) && error.message.data && error.message.data.config.cache) {
return Promise.resolve(error.message.data.data.data); // 返回结果数据
}
return Promise.reject(error)
}
)
创建axios对象 service
创建service对象,并设置跨域是否需要使用凭证
let ishttps = 'https:' == document.location.protocol ? true : false,
service = null;
if (ishttps) {
console.log('https');
service = axios.create({
timeout: 1200000, // 请求超时时间
baseURL: process.env.VUE_APP_BASE_API_HTTPS,
});
} else {
console.log('http');
service = axios.create({
timeout: 1200000, // 请求超时时间
baseURL: process.env.VUE_APP_BASE_API_HTTP,
});
}
// 跨域请求是否需要使用凭证
// axios 默认发送请求是不携带cookie的
service.defaults.withCredentials = true;
请求拦截
//请求拦截器
service.interceptors.request.use(
(config) => {
let token;
if (config.url.indexOf('/auth/oauth/token') >= 0) {
token = 'basic b25lY2M6cTlzZUhFTHA=';
} else {
checkToken();
token = getToken();
if (!token) {
//
store.dispatch('user/clearInfo');
// 回到登录页
router.push('/login');
return Promise.reject({
code: -1,
});
}
}
// console.log(token)
config.headers['Authorization'] = token;
const isShow = notShowLoading[config.url];
if (!isShow) {
showFullScreenLoading();
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
响应拦截
//响应拦截器
service.interceptors.response.use(
(res) => {
tryHideFullScreenLoading();
// 请求结果包了一层data,可以同意处理
const data = res.data || res;
return Promise.resolve(data);
},
(error) => {
tryHideFullScreenLoading();
if (error && error.response) {
const { response } = error;
switch (response.status) {
// token 过期?
case 401:
store.dispatch('user/logout').then(() => {
router.push('/login');
});
break;
default:
Message({
type: 'error',
message: response.data.msg,
showClose: true,
});
return Promise.reject(response);
}
}
}
);
get/ post / put / delete 请求封装
/**
@param url : 地址
params:参数 不传为默认值
contentType:请 contentType类型 不传为默认值
cb: validateStatus() onDownloadProgress()...请求函数等其他额外配置 以对象形式传入
*/
export const $get = (url, params = {}, contentType = 'application/x-www-form-urlencoded', headercb = {}, cb = {}) => {
return service.get(
url,
{
params: params,
},
{
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
}
);
};
//post
export const $post = (url, params = {}, contentType = 'application/x-www-form-urlencoded', headercb = {}, cb = {}) => {
let str;
if (contentType === 'application/x-www-form-urlencoded') {
str = QS.stringify(params);
} else {
str = params;
}
return service.post(url, str, {
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
});
};
//put
export const $put = (url, params = {}, contentType = 'application/x-www-form-urlencoded', headercb = {}, cb = {}) => {
let str;
if (contentType === 'application/x-www-form-urlencoded') {
str = QS.stringify(params);
} else {
str = params;
}
return service.put(url, str, {
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
});
};
//delete
export const $delete = (
url,
params = {},
contentType = 'application/x-www-form-urlencoded',
headercb = {},
cb = {}
) => {
return service.delete(
url,
{
params: params,
},
{
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
}
);
};
export const $download = (url, params = {}, contentType = 'application/x-www-form-urlencoded') => {
let str;
if (contentType === 'application/x-www-form-urlencoded') {
str = QS.stringify(params);
} else {
str = params;
}
return service.post(url, str, {
headers: {
'Content-Type': contentType,
},
responseType: 'blob',
});
};
export const $downloadGet = (url, params = {}, contentType = 'application/x-www-form-urlencoded') => {
return service.get(url, {
params: params,
headers: {
'Content-Type': contentType,
},
responseType: 'blob',
});
};
http.js文件
整个封装请求完整代码
// http.js文件
import axios from 'axios';
import { getToken, checkToken } from '@/util/auth';
import QS from 'qs';
import { Message, Loading } from 'element-ui';
import notShowLoading from './notShowLoaing';
import store from '@/store/index';
import router from '@/router/index';
let loading; //定义loading变量
let needLoadingRequestCount = 0;
function startLoading() {
//使用Element loading-start 方法
loading = Loading.service({
lock: true,
text: '加载中……',
background: 'rgba(0, 0, 0, 0.5)',
});
}
function endLoading() {
//使用Element loading-close 方法
loading.close();
}
function showFullScreenLoading() {
if (needLoadingRequestCount === 0) {
startLoading();
}
needLoadingRequestCount++;
}
function tryHideFullScreenLoading() {
if (needLoadingRequestCount <= 0) return;
needLoadingRequestCount--;
if (needLoadingRequestCount === 0) {
endLoading();
}
}
let ishttps = 'https:' == document.location.protocol ? true : false,
service = null;
if (ishttps) {
console.log('https');
service = axios.create({
timeout: 1200000, // 请求超时时间
baseURL: process.env.VUE_APP_BASE_API_HTTPS,
});
} else {
console.log('http');
service = axios.create({
timeout: 1200000, // 请求超时时间
baseURL: process.env.VUE_APP_BASE_API_HTTP,
});
}
// const service = axios.create({
// timeout: 5000, // 请求超时时间
// baseURL: process.env.VUE_APP_BASE_API,
// })
// 跨域请求是否需要使用凭证
// axios 默认发送请求是不携带cookie的
service.defaults.withCredentials = true;
//请求拦截器
service.interceptors.request.use(
(config) => {
let token;
if (config.url.indexOf('/auth/oauth/token') >= 0) {
token = 'basic b25lY2M6cTlzZUhFTHA=';
} else {
checkToken();
token = getToken();
if (!token) {
store.dispatch('user/clearInfo');
router.push('/login');
return Promise.reject({
code: -1,
});
}
}
// console.log(token)
config.headers['Authorization'] = token;
const isShow = notShowLoading[config.url];
if (!isShow) {
showFullScreenLoading();
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
//响应拦截器
service.interceptors.response.use(
(res) => {
tryHideFullScreenLoading();
const data = res.data || res;
return Promise.resolve(data);
},
(error) => {
tryHideFullScreenLoading();
if (error && error.response) {
const { response } = error;
switch (response.status) {
case 401: // token 过期?
store.dispatch('user/logout').then(() => {
router.push('/login');
});
break;
default:
Message({
type: 'error',
message: response.data.msg,
showClose: true,
});
return Promise.reject(response);
}
}
}
);
/*
* @param url : 地址
params:参数 不传为默认值
contentType:请 contentType类型 不传为默认值
cb: validateStatus() onDownloadProgress()...请求函数等其他额外配置 以对象形式传入
*/
//get
export const $get = (url, params = {}, contentType = 'application/x-www-form-urlencoded', headercb = {}, cb = {}) => {
return service.get(
url,
{
params: params,
},
{
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
}
);
};
//post
export const $post = (url, params = {}, contentType = 'application/x-www-form-urlencoded', headercb = {}, cb = {}) => {
let str;
if (contentType === 'application/x-www-form-urlencoded') {
str = QS.stringify(params);
} else {
str = params;
}
return service.post(url, str, {
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
});
};
//put
export const $put = (url, params = {}, contentType = 'application/x-www-form-urlencoded', headercb = {}, cb = {}) => {
let str;
if (contentType === 'application/x-www-form-urlencoded') {
str = QS.stringify(params);
} else {
str = params;
}
return service.put(url, str, {
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
});
};
//delete
export const $delete = (
url,
params = {},
contentType = 'application/x-www-form-urlencoded',
headercb = {},
cb = {}
) => {
return service.delete(
url,
{
params: params,
},
{
headers: {
'Content-Type': contentType,
...headercb,
},
...cb,
}
);
};
export const $download = (url, params = {}, contentType = 'application/x-www-form-urlencoded') => {
let str;
if (contentType === 'application/x-www-form-urlencoded') {
str = QS.stringify(params);
} else {
str = params;
}
return service.post(url, str, {
headers: {
'Content-Type': contentType,
},
responseType: 'blob',
});
};
export const $downloadGet = (url, params = {}, contentType = 'application/x-www-form-urlencoded') => {
return service.get(url, {
params: params,
headers: {
'Content-Type': contentType,
},
responseType: 'blob',
});
};