我写技术文章没那么多废话,直接上代码:
1.在ApiCache核心代码:(apiCache.ts)
// 制定修饰器
export const ApiCache = (...args: any) => {
return decorate(handleApiCache, args);
}
const decorate = (handleDescription: any, entryArgs: any) => {
// 判断当前最后数据是否是descriptor,如果是descriptor,直接使用
// 例如 log 这样的修饰器
if (isDescriptor(entryArgs[entryArgs.length - 1])) {
return handleDescription(...entryArgs);
} else {
// 如果不是
// 例如 add(1) plus(20) 这样的修饰器
return function () {
return handleDescription(...Array.prototype.slice.call(arguments), ...entryArgs);
};
}
}
const isDescriptor = (descriptor: any) => {
if (descriptor) {
return (
descriptor.hasOwnProperty('configurable') &&
descriptor.hasOwnProperty('enumerable') &&
descriptor.hasOwnProperty('writable') &&
descriptor.hasOwnProperty('value')
);
}
return false;
}
const handleApiCache = (target: any, name: any, descriptor: any, ...config: any) => {
// 拿到函数体并保存
const fn = descriptor.value;
// 修改函数体
descriptor.value = function () {
const key = generateKey(name, arguments);
let promise = ExpiresCache.get(key);
let firstFlag = false;
if (!promise) {
// 设定promise
promise = fn.apply(null, arguments).catch((error: any) => {
// 在请求回来后,如果出现问题,把promise从cache中删除
console.error(`接口请求有误,清理【${name}】缓存`);
ExpiresCache.delete(key);
// 返回错误
return Promise.reject(error);
});
// 使用缓存,缓存过期之后再次get就会获取null,而从服务端继续请求
firstFlag = true
ExpiresCache.set(key, promise, config[0]);
}
promise.then((res: any) => {
if (firstFlag) {
console['clog'](0, name, arguments, res);
} else {
console['clog'](1, name, arguments, res);
}
})
return promise;
};
return descriptor;
}
// 生成key值
const generateKey = (name: string, args: any) => {
return encodeURIComponent(`${name}-${JSON.stringify(args)}`);
}
class ItemCache {
private data: any;
private timeout: number;
private cacheTime: number;
constructor(data: any, timeout: number) {
this.data = data;
// 设定超时时间,设定为多少秒
this.timeout = timeout;
// 创建对象时候的时间,大约设定为数据获得的时间
this.cacheTime = new Date().getTime();
}
}
class ExpiresCache {
// 定义静态数据map来作为缓存池
static cacheMap = new Map();
// 数据是否超时
static isOverTime(name: any) {
const data = ExpiresCache.cacheMap.get(name);
// 没有数据 一定超时
if (!data) {
return true;
}
// 获取系统当前时间戳
const currentTime = new Date().getTime();
// 获取当前时间与存储时间的过去的秒数
const overTime = (currentTime - data.cacheTime) / 1000;
// 如果过去的秒数大于当前的超时时间,也返回null让其去服务端取数据
if (Math.abs(overTime) > data.timeout) {
// 此代码可以没有,不会出现问题,但是如果有此代码,再次进入该方法就可以减少判断。
ExpiresCache.cacheMap.delete(name);
return true;
}
// 不超时
return false;
}
// 删除 cache 中的 data
static delete(name: string | Error) {
return ExpiresCache.cacheMap.delete(name);
}
// 获取
static get(name: string | Error) {
const isDataOverTime = ExpiresCache.isOverTime(name);
// 如果 数据超时,返回null,但是没有超时,返回数据,而不是 ItemCache 对象
return isDataOverTime ? null : ExpiresCache.cacheMap.get(name).data;
}
// 默认存储60分钟
static set(name: string | Error, data: any, timeout = 3600) {
// 设置 itemCache
const itemCache = new ItemCache(data, timeout);
// 缓存
ExpiresCache.cacheMap.set(name, itemCache);
}
}
//定义一个log
const clog = (type: number, name: any, params: any, data: any) => {
if (type == 0) {
console.log('%c 👉【非缓存】:第一次保存【%s】接口的数据,参数为:%O,数据为:%O ', 'color:green', name, Object.keys(params).length > 0 ? { ...params } : '', data)
} else {
console.log('%c ✨【缓存】:调用【%s】接口的缓存数据,参数为:%O,数据为:%O ', 'color:green', name, Object.keys(params).length > 0 ? { ...params } : '', data)
}
}
console['clog'] = clog
2.在通用接口(common.ts)里使用:
import { ApiCache } from '@/utils/apiCache'//'@/utils/apiCache'是自己放apiCache.ts的位置
const ctx = ''
class GetCommonApi {
@ApiCache(60 * 60 * 3)//时间单位是秒,其60*60*3代表是3小时
gradeList(params?: any) {// 年级下拉
return https.request<any>(ctx + 'biz-common/grade-list', Method.POST, params, ContentType.json)
}
// @ApiCache(60) 注释之后意味着每次都要请求
collegeList(params?: any) {// 学院列表
return https.request<any>(ctx + 'biz-common/college-list', Method.POST, params, ContentType.json)
}
}
export const getCommonApi = new GetCommonApi()
3.在页面中使用:
import { getCommonApi } from '@/apis/common'
const { gradeList ,collegeList} = getCommonApi;
const getGradeList = () => {
gradeList({b:0,a:9}).then((res: any) => {
if (res.code == '0') {
// console.log("接口获取的数据", res.data);
}
})
}
const getCollegeList = () => {
collegeList().then((res: any) => {
if (res.code == '0') {
// console.log("接口获取的数据", res.data);
}
})
}
onMounted(()=>{
getGradeList()//项目里,只要没超过缓存时间,就不会调用新接口
getCollegeList()//这个没加缓存,每次都要请求
})
注1:ts装饰器里用的是全局缓存,所以一刷新,所有缓存过的接口会重新请求。
注2:使用了接口缓存后,只有接口名与传递的参数相同时才会取缓存数据,当不同的参数,会重新请求接口,另外缓存
4.实际效果
(1)当相同接口名,不同参数时:各自缓存,互不干扰
(2)从其它页面回来:已缓存的数据,直接调用缓存数据
5.感谢
如果觉得写得好,记得收藏哦 ~ (O_O) ~