import globalConfig from '@/config'; import { stringMd5, rabbitEncryptString, rabbitDecryptString } from '@/utils/crypto'; // 用一样的 key 和 iv 来加密 storage 的 key, 保证每次加密后的 storage 的 key 的结果一样,否则每次获取数据都需要把 storage 中的所有 key 解密一遍才知道哪个是我们需要的 key const storageKeyIV = stringMd5(`${globalConfig.app.title}_${globalConfig.app.version}`).slice(0, 8); export function encryptKey(key: string) { return rabbitEncryptString(key, globalConfig.security.storageEncryptionKey, storageKeyIV); } export function decryptKey(key: string) { return rabbitDecryptString(key, globalConfig.security.storageEncryptionKey, storageKeyIV); } export function encryptData(data: string) { return rabbitEncryptString(data, globalConfig.security.storageEncryptionKey); } export function decryptData(data: string) { return rabbitDecryptString(data, globalConfig.security.storageEncryptionKey); } export interface StorageOptions { /** * 过期时间(秒), 0 为永远不过期 * @default 0 */ expire?: number; /** * 是否加密key * @default false */ encryptKey?: boolean; /** * 是否加密数据 * @default false */ encryptData?: boolean; } interface StorageData { data: T; expire: number; } /** * 是否处于可访问 localStorage 的浏览器环境。 * 预渲染(SSG)在 Node 端执行,无 window/localStorage,此时所有 storage 操作应安全降级为空操作/返回 null, * 而非抛错。hydrate 后回到浏览器环境即恢复正常读写。 */ const canUseStorage = () => typeof window !== 'undefined' && !!window.localStorage; function shouldEncryptKey(functionOptions?: StorageOptions, instanceOptions?: StorageOptions) { if (globalConfig.security.enableStorageEncryption) { return functionOptions?.encryptKey ?? instanceOptions?.encryptKey ?? false; } return false; } function shouldEncryptData(funcOpts?: StorageOptions, instanceOpts?: StorageOptions) { if (globalConfig.security.enableStorageEncryption) { return funcOpts?.encryptData ?? instanceOpts?.encryptData ?? false; } return false; } /** * 创建 localStorage 工具实例 * @param opts {StorageOptions} 本实例使用的全局配置 * @returns */ export function createLocalStorage({ expire: confExpire = 0, encryptKey: confEncryptKey = false, encryptData: confEncryptData = false, }: StorageOptions = {}) { /** * 保存数据 * @param key * @param data * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。 */ function set(key: string, data: T, opts?: StorageOptions) { if (!canUseStorage()) return; const expire = opts?.expire ?? confExpire; const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey }); const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData }); const storageData: StorageData = { data, expire: expire !== 0 ? new Date().getTime() + expire * 1000 : 0, }; const json = JSON.stringify(storageData); const finalKey = isEncryptKey ? encryptKey(key) : key; const finalData = isEncryptData ? encryptData(json) : json; window.localStorage.setItem(finalKey, finalData); } /** * 读取数据 * @param key * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。 * @returns */ function get(key: string, opts?: StorageOptions) { if (!canUseStorage()) return null; const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey }); const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData }); const finalKey = isEncryptKey ? encryptKey(key) : key; let data = window.localStorage.getItem(finalKey); if (!data) return null; if (isEncryptData) { data = decryptData(data); } if (!data) return null; let storageData: StorageData | null = null; try { storageData = JSON.parse(data); } catch { // Prevent failure } if (storageData) { const { data, expire = 0 } = storageData; if (expire === 0 || expire >= Date.now()) return data as T; } return null; } /** * 删除数据 * @param key * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。 */ function remove(key: string, opts?: StorageOptions) { if (!canUseStorage()) return; const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey }); const finalKey = isEncryptKey ? encryptKey(key) : key; window.localStorage.removeItem(finalKey); } /** * 清除 localStorage 中的所有数据。**慎用** */ function clear() { if (!canUseStorage()) return; window.localStorage.clear(); } return { set, get, remove, clear }; } /** * 创建 sessionStorage 工具实例 * @param opts {StorageOptions} 本实例使用的全局配置 * @returns */ export function createSessionStorage({ expire: confExpire = 0, encryptKey: confEncryptKey = false, encryptData: confEncryptData = false, }: StorageOptions = {}) { /** * 保存数据 * @param key * @param data * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。 */ function set(key: string, data: T, opts?: StorageOptions) { if (!canUseStorage()) return; const expire = opts?.expire ?? confExpire; const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey }); const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData }); const storageData: StorageData = { data, expire: expire !== 0 ? new Date().getTime() + expire * 1000 : 0, }; const json = JSON.stringify(storageData); const finalKey = isEncryptKey ? encryptKey(key) : key; const finalData = isEncryptData ? encryptData(json) : json; window.sessionStorage.setItem(finalKey, finalData); } /** * 读取数据 * @param key * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。 * @returns */ function get(key: string, opts?: StorageOptions) { if (!canUseStorage()) return null; const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey }); const isEncryptData = shouldEncryptData(opts, { encryptData: confEncryptData }); const finalKey = isEncryptKey ? encryptKey(key) : key; let data = window.sessionStorage.getItem(finalKey); if (!data) return null; if (isEncryptData) { data = decryptData(data); } if (!data) return null; let storageData: StorageData | null = null; try { storageData = JSON.parse(data); } catch { // Prevent failure } if (storageData) { const { data, expire = 0 } = storageData; if (expire === 0 || expire >= Date.now()) return data as T; } return null; } /** * 删除数据 * @param key * @param opts 当传递了 opts 时,opts 中存在的配置项会覆盖当前实例的全局配置中的配置项。 */ function remove(key: string, opts?: StorageOptions) { if (!canUseStorage()) return; const isEncryptKey = shouldEncryptKey(opts, { encryptKey: confEncryptKey }); const finalKey = isEncryptKey ? encryptKey(key) : key; window.sessionStorage.removeItem(finalKey); } /** * 清除 sessionStorage 中的所有数据。**慎用** */ function clear() { if (!canUseStorage()) return; window.sessionStorage.clear(); } return { set, get, remove, clear }; } export const ls = createLocalStorage({ expire: 60 * 60 * 24 * 7, encryptKey: true, encryptData: true, }); export const ss = createSessionStorage({ expire: 60 * 60 * 24 * 7, encryptKey: true, encryptData: true, });