第七十三章-VbenAdmin的utils-cache解析

1.说明

本章解析VbenAdmin中存储浏览器数据的四个地方:LocalStorageSessionStorageLocalMemorySessionMemory

  • src\settings\projectSetting-permissionCacheType只决定你的认证信息存储在LocalStorage中还是SessionStorage中。
  • 你可以调用src\utils\cache\persistent.ts中的Persistent的静态方法操作这些存储。
  • LocalStorage中的COMMON__LOCAL__KEY__存储着LocalMemory的所有对象。当页面刷新的时候,LocalMemory会获取LocalStorage中的COMMON__LOCAL__KEY__对应的值作为内存。
  • 每当你向LocalMeory设置一个新值的时候,就会同步写到LocalStorage中。
  • 同理SessionStorageSessionMemory的关系也是如此。
  • 为什么这么做,我觉得应该是LocalStorageSessionStorage中的数据都是生产环境加密的,这样可以减少运算吧。

2.index.ts

这个文件提供了一些方便我们创建 (操作Storage的类) 的方法。

src\utils\cache\index.ts

import { getStorageShortName } from '/@/utils/env';
import { createStorage as create, CreateStorageParams } from './storageCache';
import { enableStorageEncryption } from '/@/settings/encryptionSetting';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';

// Partial将类型变成可选的
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;

3.memory.ts

这个类定义Memory的数据结构和内部的操作方法。

src\utils\cache\memory.ts

// 缓存接口
export interface Cache<V = any> {
  // 值
  value?: V;
  // 计时器的ID
  timeoutId?: ReturnType<typeof setTimeout>;
  // 过期时间毫秒值(new Date + alive)
  time?: number;
  // 存活时间
  alive?: number;
}

// 不存活为0
const NOT_ALIVE = 0;

// 内存类
export class Memory<T = any, V = any> {
  // 缓存对象,一个Key一个缓存接口
  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];
  }

  // 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) {
      // 如果计时器ID存在
      if (item.timeoutId) {
        // 清除计时器
        clearTimeout(item.timeoutId);
        // 计时器ID置空
        item.timeoutId = undefined;
      }
      // 缓存赋值
      item.value = value;
    } else {
      // 原值不存在,赋值
      item = { value, alive: expires };
      this.cache[key] = item;
    }

    // 如果过期时间不存在
    if (!expires) {
      // 返回值
      return value;
    }
    item.time = new Date().getTime() + this.alive;
    item.timeoutId = setTimeout(() => {
      this.remove(key);
    }, expires);

    return value;
  }

  remove<K extends keyof T>(key: K) {
    const item = this.get(key);
    // 缓存对象上删除这个Key
    Reflect.deleteProperty(this.cache, key);
    // 如果缓存的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 = {};
  }
}

4.persistent.ts

这个文件提供了外界操作StorageMemory的方法。

src\utils\cache\persistent.ts

// import type { LockInfo, UserInfo } from '/@/store/types';

import { ProjectConfig } from '/#/config';

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,
} from '/@/enums/cacheEnum';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
import { toRaw } from 'vue';

// 基础存储接口
interface BasicStore {
  [TOKEN_KEY]: string | number | null | undefined;
  // [USER_INFO_KEY]: UserInfo;
  [USER_INFO_KEY]: any;
  [ROLES_KEY]: string[];
  // [LOCK_INFO_KEY]: LockInfo;
  [LOCK_INFO_KEY]: any;
  [PROJ_CFG_KEY]: ProjectConfig;
}

// LocalStore存储接口
type LocalStore = BasicStore;

// SessionStore存储接口
type SessionStore = BasicStore;

// 基础存储接口的Key
export type BasicKeys = keyof BasicStore;
// LocalStore存储接口Key
type LocalKeys = keyof LocalStore;
// SessionStore存储接口Key
type SessionKeys = keyof SessionStore;

// 创建本地存储
const ls = createLocalStorage();
// 创建Session存储
const ss = createSessionStorage();

// DEFAULT_CACHE_TIME为内存的默认存活时间
// 创建本地内存
const localMemory = new Memory(DEFAULT_CACHE_TIME);
// 创建Session内存
const sessionMemory = new Memory(DEFAULT_CACHE_TIME);

// 初始化持久化内存
function initPersistentMemory() {
  // 获取LocalStorage中的COMMON__LOCAL__KEY__的值
  const localCache = ls.get(APP_LOCAL_CACHE_KEY);
  //获取SessionStorage中的COMMON__SESSION__KEY__的值
  const sessionCache = ss.get(APP_SESSION_CACHE_KEY);
  // 如果存了值,就在内存中也存一份
  localCache && localMemory.resetCache(localCache);
  sessionCache && sessionMemory.resetCache(sessionCache);
}

// 导出一个持久化类,这里面的方法都是静态的
export class Persistent {
  // 获取LocalStorage的内存
  static getLocal<T>(key: LocalKeys) {
    return localMemory.get(key)?.value as Nullable<T>;
  }

  // 设置LocalStorage的内存
  static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void {
    localMemory.set(key, toRaw(value));
    immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
  }

  // 移出LocalStorage的内存
  static removeLocal(key: LocalKeys): void {
    localMemory.remove(key);
  }

  // 清除LocalStorage的内存
  static clearLocal(): void {
    localMemory.clear();
  }

  // 获取SessionStorage的内存
  static getSession<T>(key: SessionKeys) {
    return sessionMemory.get(key)?.value as Nullable<T>;
  }

  // 设置SessionStorage的内存
  static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void {
    sessionMemory.set(key, toRaw(value));
    immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory);
  }

  // 移出SessionStorage的内存
  static removeSession(key: SessionKeys): void {
    sessionMemory.remove(key);
  }

  // 清除SessionStorage的内存
  static clearSession(): void {
    sessionMemory.clear();
  }

  // 将两种内存都清除
  static clearAll() {
    sessionMemory.clear();
    localMemory.clear();
  }
}

// 当窗口刷新或者关闭窗口
window.addEventListener('beforeunload', function () {
  // 将内存加载到LocalStorage
  ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
  // 将内存加载到SessionStorage
  ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
});

// 当存储库修改时
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();
    }
  }
}

// https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
// https://zhuanlan.zhihu.com/p/70497745
// 只监听LocalStorage的变化
// 监听其他窗口是否改变了LocalStorage,当前窗口修改的不处理
window.addEventListener('storage', storageChange);

// 将LocalStorage和SessionStorage加载到内存中
initPersistentMemory();
// console.log('初始化localMemory', localMemory);
// console.log('初始化sessionMemory', sessionMemory);

5.storageCache.ts

这个类封装了对Storage的操作。

src\utils\cache\storageCache.ts

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>;
}

// 创建一个存储库
// Partial将类型的所有属性变成可选的
export const createStorage = ({
  prefixKey = '',
  storage = sessionStorage,
  key = cacheCipher.key,
  iv = cacheCipher.iv,
  timeout = null,
  hasEncrypt = true,
}: Partial<CreateStorageParams> = {}) => {
  // 如果需要加密,加密key和偏移量需要为16bit
  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!');
  }

  // 使用Aes对称加密
  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;
    // 前缀Key
    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
     * @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;
      // storage设置Key,Value
      this.storage.setItem(this.getKey(key), stringifyValue);
    }

    /**
     *Read cache
     * @param {string} key
     * @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;
        }
        // 如果过期,删除Key,但是这里没有返回了。
        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();
};

上一章

第七十二章-VbenAdmin的utils-auth解析

下一章

第七十四章-VbenAdmin的utils-event解析

# vben 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×