前端接口请求支持内容缓存和过期时间-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

前端接口请求支持内容缓存和过期时间

支持用户自定义缓存时间在规则时间内读取缓存内容超出时间后重新请求接口

首先封装一下 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'

// 本地开发环境需要加请求头
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 = []
        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>

image-20231030181210907

我们在 1 分钟内连续点击按钮发现只会走一次接口但是控制台可以打印多次数据

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6