初始版本提交
This commit is contained in:
32
src/utils/cache/index.ts
vendored
Normal file
32
src/utils/cache/index.ts
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
import { getStorageShortName } from '/@/utils/env';
|
||||
import { createStorage as create, CreateStorageParams } from './storageCache';
|
||||
import { enableStorageEncryption } from '/@/settings/encryptionSetting';
|
||||
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
|
||||
|
||||
export type Options = Partial<CreateStorageParams>;
|
||||
|
||||
const createOptions = (storage: Storage, options: Options = {}): Options => {
|
||||
return {
|
||||
// No encryption in debug mode
|
||||
hasEncrypt: enableStorageEncryption,
|
||||
storage,
|
||||
prefixKey: getStorageShortName(),
|
||||
...options,
|
||||
};
|
||||
};
|
||||
|
||||
export const WebStorage = create(createOptions(sessionStorage));
|
||||
|
||||
export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => {
|
||||
return create(createOptions(storage, options));
|
||||
};
|
||||
|
||||
export const createSessionStorage = (options: Options = {}) => {
|
||||
return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
|
||||
};
|
||||
|
||||
export const createLocalStorage = (options: Options = {}) => {
|
||||
return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
|
||||
};
|
||||
|
||||
export default WebStorage;
|
||||
107
src/utils/cache/memory.ts
vendored
Normal file
107
src/utils/cache/memory.ts
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
export interface Cache<V = any> {
|
||||
value?: V;
|
||||
timeoutId?: ReturnType<typeof setTimeout>;
|
||||
time?: number;
|
||||
alive?: number;
|
||||
}
|
||||
|
||||
const NOT_ALIVE = 0;
|
||||
|
||||
export class Memory<T = any, V = any> {
|
||||
private cache: { [key in keyof T]?: Cache<V> } = {};
|
||||
private alive: number;
|
||||
|
||||
constructor(alive = NOT_ALIVE) {
|
||||
// Unit second
|
||||
this.alive = alive * 1000;
|
||||
}
|
||||
|
||||
get getCache() {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
setCache(cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
// get<K extends keyof T>(key: K) {
|
||||
// const item = this.getItem(key);
|
||||
// const time = item?.time;
|
||||
// if (!isNullOrUnDef(time) && time < new Date().getTime()) {
|
||||
// this.remove(key);
|
||||
// }
|
||||
// return item?.value ?? undefined;
|
||||
// }
|
||||
|
||||
get<K extends keyof T>(key: K) {
|
||||
return this.cache[key];
|
||||
}
|
||||
|
||||
set<K extends keyof T>(key: K, value: V, expires?: number) {
|
||||
let item = this.get(key);
|
||||
|
||||
if (!expires || (expires as number) <= 0) {
|
||||
expires = this.alive;
|
||||
}
|
||||
if (item) {
|
||||
if (item.timeoutId) {
|
||||
clearTimeout(item.timeoutId);
|
||||
item.timeoutId = undefined;
|
||||
}
|
||||
item.value = value;
|
||||
} else {
|
||||
item = { value, alive: expires };
|
||||
this.cache[key] = item;
|
||||
}
|
||||
|
||||
if (!expires) {
|
||||
return value;
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
/**
|
||||
* Prevent overflow of the setTimeout Maximum delay value
|
||||
* Maximum delay value 2,147,483,647 ms
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value
|
||||
*/
|
||||
item.time = expires > now ? expires : now + expires;
|
||||
item.timeoutId = setTimeout(
|
||||
() => {
|
||||
this.remove(key);
|
||||
},
|
||||
expires > now ? expires - now : expires,
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
remove<K extends keyof T>(key: K) {
|
||||
const item = this.get(key);
|
||||
Reflect.deleteProperty(this.cache, key);
|
||||
if (item) {
|
||||
clearTimeout(item.timeoutId!);
|
||||
return item.value;
|
||||
}
|
||||
}
|
||||
|
||||
resetCache(cache: { [K in keyof T]: Cache }) {
|
||||
Object.keys(cache).forEach((key) => {
|
||||
const k = key as any as keyof T;
|
||||
const item = cache[k];
|
||||
if (item && item.time) {
|
||||
const now = new Date().getTime();
|
||||
const expire = item.time;
|
||||
if (expire > now) {
|
||||
this.set(k, item.value, expire);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
Object.keys(this.cache).forEach((key) => {
|
||||
const item = this.cache[key];
|
||||
item.timeoutId && clearTimeout(item.timeoutId);
|
||||
});
|
||||
this.cache = {};
|
||||
}
|
||||
}
|
||||
132
src/utils/cache/persistent.ts
vendored
Normal file
132
src/utils/cache/persistent.ts
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
import type { LockInfo, UserInfo } from '/#/store';
|
||||
import type { ProjectConfig } from '/#/config';
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
import { createLocalStorage, createSessionStorage } from '/@/utils/cache';
|
||||
import { Memory } from './memory';
|
||||
import {
|
||||
TOKEN_KEY,
|
||||
USER_INFO_KEY,
|
||||
ROLES_KEY,
|
||||
LOCK_INFO_KEY,
|
||||
PROJ_CFG_KEY,
|
||||
APP_LOCAL_CACHE_KEY,
|
||||
APP_SESSION_CACHE_KEY,
|
||||
MULTIPLE_TABS_KEY,
|
||||
} from '/@/enums/cacheEnum';
|
||||
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
|
||||
import { toRaw } from 'vue';
|
||||
import { pick, omit } from 'lodash-es';
|
||||
|
||||
interface BasicStore {
|
||||
[TOKEN_KEY]: string | number | null | undefined;
|
||||
[USER_INFO_KEY]: UserInfo;
|
||||
[ROLES_KEY]: string[];
|
||||
[LOCK_INFO_KEY]: LockInfo;
|
||||
[PROJ_CFG_KEY]: ProjectConfig;
|
||||
[MULTIPLE_TABS_KEY]: RouteLocationNormalized[];
|
||||
}
|
||||
|
||||
type LocalStore = BasicStore;
|
||||
|
||||
type SessionStore = BasicStore;
|
||||
|
||||
export type BasicKeys = keyof BasicStore;
|
||||
type LocalKeys = keyof LocalStore;
|
||||
type SessionKeys = keyof SessionStore;
|
||||
|
||||
const ls = createLocalStorage();
|
||||
const ss = createSessionStorage();
|
||||
|
||||
const localMemory = new Memory(DEFAULT_CACHE_TIME);
|
||||
const sessionMemory = new Memory(DEFAULT_CACHE_TIME);
|
||||
|
||||
function initPersistentMemory() {
|
||||
const localCache = ls.get(APP_LOCAL_CACHE_KEY);
|
||||
const sessionCache = ss.get(APP_SESSION_CACHE_KEY);
|
||||
localCache && localMemory.resetCache(localCache);
|
||||
sessionCache && sessionMemory.resetCache(sessionCache);
|
||||
}
|
||||
|
||||
export class Persistent {
|
||||
static getLocal<T>(key: LocalKeys) {
|
||||
return localMemory.get(key)?.value as Nullable<T>;
|
||||
}
|
||||
|
||||
static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void {
|
||||
localMemory.set(key, toRaw(value));
|
||||
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
|
||||
}
|
||||
|
||||
static removeLocal(key: LocalKeys, immediate = false): void {
|
||||
localMemory.remove(key);
|
||||
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
|
||||
}
|
||||
|
||||
static clearLocal(immediate = false): void {
|
||||
localMemory.clear();
|
||||
immediate && ls.clear();
|
||||
}
|
||||
|
||||
static getSession<T>(key: SessionKeys) {
|
||||
return sessionMemory.get(key)?.value as Nullable<T>;
|
||||
}
|
||||
|
||||
static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void {
|
||||
sessionMemory.set(key, toRaw(value));
|
||||
immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
|
||||
}
|
||||
|
||||
static removeSession(key: SessionKeys, immediate = false): void {
|
||||
sessionMemory.remove(key);
|
||||
immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
|
||||
}
|
||||
static clearSession(immediate = false): void {
|
||||
sessionMemory.clear();
|
||||
immediate && ss.clear();
|
||||
}
|
||||
|
||||
static clearAll(immediate = false) {
|
||||
sessionMemory.clear();
|
||||
localMemory.clear();
|
||||
if (immediate) {
|
||||
ls.clear();
|
||||
ss.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
// TOKEN_KEY 在登录或注销时已经写入到storage了,此处为了解决同时打开多个窗口时token不同步的问题
|
||||
// LOCK_INFO_KEY 在锁屏和解锁时写入,此处也不应修改
|
||||
ls.set(APP_LOCAL_CACHE_KEY, {
|
||||
...omit(localMemory.getCache, LOCK_INFO_KEY),
|
||||
...pick(ls.get(APP_LOCAL_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]),
|
||||
});
|
||||
ss.set(APP_SESSION_CACHE_KEY, {
|
||||
...omit(sessionMemory.getCache, LOCK_INFO_KEY),
|
||||
...pick(ss.get(APP_SESSION_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]),
|
||||
});
|
||||
});
|
||||
|
||||
function storageChange(e: any) {
|
||||
const { key, newValue, oldValue } = e;
|
||||
|
||||
if (!key) {
|
||||
Persistent.clearAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!!newValue && !!oldValue) {
|
||||
if (APP_LOCAL_CACHE_KEY === key) {
|
||||
Persistent.clearLocal();
|
||||
}
|
||||
if (APP_SESSION_CACHE_KEY === key) {
|
||||
Persistent.clearSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('storage', storageChange);
|
||||
|
||||
initPersistentMemory();
|
||||
111
src/utils/cache/storageCache.ts
vendored
Normal file
111
src/utils/cache/storageCache.ts
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
import { cacheCipher } from '/@/settings/encryptionSetting';
|
||||
import type { EncryptionParams } from '/@/utils/cipher';
|
||||
import { AesEncryption } from '/@/utils/cipher';
|
||||
import { isNullOrUnDef } from '/@/utils/is';
|
||||
|
||||
export interface CreateStorageParams extends EncryptionParams {
|
||||
prefixKey: string;
|
||||
storage: Storage;
|
||||
hasEncrypt: boolean;
|
||||
timeout?: Nullable<number>;
|
||||
}
|
||||
export const createStorage = ({
|
||||
prefixKey = '',
|
||||
storage = sessionStorage,
|
||||
key = cacheCipher.key,
|
||||
iv = cacheCipher.iv,
|
||||
timeout = null,
|
||||
hasEncrypt = true,
|
||||
}: Partial<CreateStorageParams> = {}) => {
|
||||
if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) {
|
||||
throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!');
|
||||
}
|
||||
|
||||
const encryption = new AesEncryption({ key, iv });
|
||||
|
||||
/**
|
||||
* Cache class
|
||||
* Construction parameters can be passed into sessionStorage, localStorage,
|
||||
* @class Cache
|
||||
* @example
|
||||
*/
|
||||
const WebStorage = class WebStorage {
|
||||
private storage: Storage;
|
||||
private prefixKey?: string;
|
||||
private encryption: AesEncryption;
|
||||
private hasEncrypt: boolean;
|
||||
/**
|
||||
*
|
||||
* @param {*} storage
|
||||
*/
|
||||
constructor() {
|
||||
this.storage = storage;
|
||||
this.prefixKey = prefixKey;
|
||||
this.encryption = encryption;
|
||||
this.hasEncrypt = hasEncrypt;
|
||||
}
|
||||
|
||||
private getKey(key: string) {
|
||||
return `${this.prefixKey}${key}`.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache
|
||||
* @param {string} key
|
||||
* @param {*} value
|
||||
* @param {*} expire Expiration time in seconds
|
||||
* @memberof Cache
|
||||
*/
|
||||
set(key: string, value: any, expire: number | null = timeout) {
|
||||
const stringData = JSON.stringify({
|
||||
value,
|
||||
time: Date.now(),
|
||||
expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null,
|
||||
});
|
||||
const stringifyValue = this.hasEncrypt
|
||||
? this.encryption.encryptByAES(stringData)
|
||||
: stringData;
|
||||
this.storage.setItem(this.getKey(key), stringifyValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read cache
|
||||
* @param {string} key
|
||||
* @param {*} def
|
||||
* @memberof Cache
|
||||
*/
|
||||
get(key: string, def: any = null): any {
|
||||
const val = this.storage.getItem(this.getKey(key));
|
||||
if (!val) return def;
|
||||
|
||||
try {
|
||||
const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val;
|
||||
const data = JSON.parse(decVal);
|
||||
const { value, expire } = data;
|
||||
if (isNullOrUnDef(expire) || expire >= new Date().getTime()) {
|
||||
return value;
|
||||
}
|
||||
this.remove(key);
|
||||
} catch (e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache based on key
|
||||
* @param {string} key
|
||||
* @memberof Cache
|
||||
*/
|
||||
remove(key: string) {
|
||||
this.storage.removeItem(this.getKey(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all caches of this instance
|
||||
*/
|
||||
clear(): void {
|
||||
this.storage.clear();
|
||||
}
|
||||
};
|
||||
return new WebStorage();
|
||||
};
|
||||
Reference in New Issue
Block a user