前端接口请求支持内容缓存和过期时间
支持用户自定义缓存时间,在规则时间内读取缓存内容,超出时间后重新请求接口
首先封装一下 axios,这一步可做可不做。但是在实际开发场景中都会对 axios 做二次封装,我们在二次封装的 axios 基础上继续封装,增加支持缓存功能
request.js
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import cache from '@/plugins/cache'
import qs from 'qs'
// 本地开发环境需要加请求头
if (process.env.NODE_ENV === 'development') {
axios.defaults.headers['tenantId'] = 'yikon'
}
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
axios.defaults.headers['lang'] = 'CN'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 100000,
})
// request拦截器
service.interceptors.request.use(
(config) => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + qs.stringify(config.params)
url = url.slice(0, -1)
config.params = {}
config.url = url
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime(),
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
// 忽略重复请求的地址
const exUrls = [
'/bioTask/uploadFileByCondition',
'/LabTq/updateTqTaskRecordById',
'/cskSamle/uploadVcfFile',
'/cskReport/updateReport',
]
const s_url = sessionObj.url // 请求地址
const s_data = sessionObj.data // 请求数据
const s_time = sessionObj.time // 请求时间
const interval = 3000 // 间隔时间(ms),小于此时间视为重复提交
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url &&
!exUrls.includes(config.url)
) {
const message = '数据正在处理,请勿重复提交'
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
return config
},
(error) => {
Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(res) => {
// 未设置状态码则默认成功状态
const code = res.data.code || '0'
// 获取错误信息
const msg = res.data.message
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401 || code === '10006') {
MessageBox.confirm('登录状态已过期,请重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
store.dispatch('LogOut').then(() => {
location.href = '/login'
})
})
} else if (code !== '0') {
Message({
message: msg || '接口请求异常',
type: 'error',
})
return Promise.reject(new Error(msg))
} else {
return res.data
}
},
(error) => {
let { message } = error
if (message === 'Network Error') {
message = '后端接口连接异常'
} else if (message.includes('timeout')) {
message = '系统接口请求超时'
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常'
}
Message({
message: message,
type: 'error',
duration: 5 * 1000,
})
return Promise.reject(error)
}
)
export default service
新建 catchAjax.js
,当我们想用接口缓存时,就用 catchAjax 方法,不想用时还用上面的 request 文件,互不影响
const cacheMap = new Map()
// 定义状态池
const statusMap = new Map()
// 回调callbackMap
const callbackMap = new Map()
// 引入axios
import myAxios from '@/utils/request'
// qs用于序列化对象,将对象序列化为用&拼接的参数
import qs from 'qs'
// 一般只缓存GET接口
function generateCacheKey(request) {
return request.url + '?' + qs.stringify(request.params)
}
// 返回指定分钟后的时间戳 过期时间
function generateExpTime(minutes) {
// 获取当前时间戳
let now = new Date()
// 添加分钟数
now.setMinutes(now.getMinutes() + minutes)
// 返回未来的时间戳
return now.getTime()
}
// 导出请求方法
export function cacheRequest(request) {
if (request.method && request.method.toUpperCase() !== 'GET') {
throw new Error('cacheRequest 仅支持GET请求')
}
if (request.expTime && !/^\d+$/.test(request.expTime)) {
throw new Error('expTime 必须是正整数')
}
// 用当前请求的 url + 参数 来当做缓存的key
const cacheKey = generateCacheKey(request)
// 判断状态池中是否有数据
if (statusMap.has(cacheKey)) {
// 获取当前的状态
const currentStatus = statusMap.get(cacheKey)
// 如果接口已经在缓存中,则进入这里
if (currentStatus === 'complete') {
// 判断是否过期
let nowTime = new Date().getTime()
// 已经过期的数据不能从缓存中取,设置这个状态是pending,重新走接口
if (nowTime >= cacheMap.get(cacheKey).expTime) {
statusMap.set(cacheKey, 'pending')
} else {
// 没有过期则从缓存中返回数据
return Promise.resolve(cacheMap.get(cacheKey)?.data)
}
}
if (currentStatus === 'pending') {
// 判断回调池中是否有数据
return new Promise((resolve, reject) => {
if (callbackMap.has(cacheKey)) {
callbackMap.get(cacheKey).push({
onSuccess: resolve,
onError: reject,
})
} else {
callbackMap.set(cacheKey, [
{
onSuccess: resolve,
onError: reject,
},
])
}
})
}
}
// 设置接口状态
statusMap.set(cacheKey, 'pending')
// 判断是否需要缓存,并且缓存池中有数据时,返回缓存池中的数据
return myAxios(request)
.then((res) => {
// 接口响应成功后吧当前的请求状态设置为complete,下次请求时就会走缓存,不会走网络
statusMap.set(cacheKey, 'complete')
// 往缓存中赛数据,同时设置过期时间
cacheMap.set(cacheKey, {
data: res,
// 默认缓存5分钟
expTime: generateExpTime(request.expTime || 5),
})
// 判断在接口响应期间是否有请求,如果有请求,则遍历所有的回调并执行
if (callbackMap.has(cacheKey)) {
callbackMap.get(cacheKey).forEach((callback) => {
callback.onSuccess(res)
})
// 响应完数据后吧回调删除
callbackMap.delete(cacheKey)
}
// 返回真实的接口数据
return res
})
.catch((error) => {
statusMap.delete(cacheKey)
if (callbackMap.has(cacheKey)) {
callbackMap.get(cacheKey).forEach((callback) => {
callback.onError(error)
})
callbackMap.delete(cacheKey)
}
return Promise.resolve(error)
})
}
使用方法
<template>
<div>
<el-button type="primary" @click="cacheAxios">测试</el-button>
</div>
</template>
<script>
import { cacheRequest } from '@/utils/catchAjax'
const getArticleList = (params) => {
return cacheRequest({
url: 'http://localhost:10086/order/list',
method: 'get',
params,
expTime: 1, // 缓存一分钟
})
}
export default {
name: 'index',
methods: {
cacheAxios() {
getArticleList({
pageNum: 1,
pageSize: 10,
}).then((res) => {
console.log(res)
})
},
},
}
</script>
我们在 1 分钟内连续点击按钮,发现只会走一次接口,但是控制台可以打印多次数据