/** * 类型安全的 localStorage 封装,具备严格的 key 管理 */ // 1. 定义应用程序使用的存储键名(防止使用任意字符串作为 key) export enum AppStorageKey { PASSPORT_DATA = 'passportData', PASSPORT_TOKEN = 'passportToken', MENU_DATA = 'menuData', // 全部的菜单数据 SLIDER_MENU = 'sliderMenu', // 侧边栏菜单数据 USER_INFO = 'userInfo', TOKEN = 'token', MODE = 'mode', // 日间夜间模式 } // 2. 存储值类型定义(用于内部处理) type StorageValue = { __data: T; // 实际存储的数据 __expiry?: number; // 可选的过期时间戳 }; // 3. 主存储类 class SafeStorage { /** * 存储数据(自动处理 JSON 序列化) * @param key 预定义的存储键 * @param value 要存储的值(支持所有 JSON 安全类型) * @param ttl 可选的时间有效期(毫秒) */ static set(key: AppStorageKey, value: T, ttl?: number): void { if (typeof window === "undefined") return; try { // 创建存储对象(包含数据值和过期时间) const storageValue: StorageValue = { __data: value, }; // 设置过期时间(如果提供) if (ttl !== undefined && ttl > 0) { storageValue.__expiry = Date.now() + ttl; } // 自动序列化并存储 localStorage.setItem(key, JSON.stringify(storageValue)); } catch (error) { console.error(`[存储] 保存失败 (key: ${key})`, error); this.handleStorageError(key, error); } } /** * 获取数据(自动反序列化并检查过期) * @param key 预定义的存储键 * @returns 存储的值或 null(如果不存在或过期) */ static get(key: AppStorageKey): T | null { if (typeof window === "undefined") return null; try { // 获取原始数据 const rawData = localStorage.getItem(key); if (!rawData) return null; // 解析为内部存储结构 const storageValue = JSON.parse(rawData) as StorageValue; // 检查是否过期 if (this.isExpired(storageValue)) { this.remove(key); return null; } // 返回实际数据 return storageValue.__data; } catch (error) { console.error(`[存储] 解析失败 (key: ${key})`, error); this.remove(key); // 移除无效数据 return null; } } /** * 删除指定存储项 * @param key 要删除的存储键 */ static remove(key: AppStorageKey): void { if (typeof window === "undefined") return; localStorage.removeItem(key); } /** * 清除应用相关的所有存储 */ static clearAppStorage(): void { if (typeof window === "undefined") return; // 遍历所有预定义的 key 进行删除 Object.values(AppStorageKey).forEach(key => { localStorage.removeItem(key); }); } // ----------------------------- 私有辅助方法 ----------------------------- /** * 检查存储值是否过期 */ private static isExpired(value: StorageValue): boolean { return value.__expiry !== undefined && Date.now() > value.__expiry; } /** * 处理存储错误(配额不足等) */ private static handleStorageError(key: AppStorageKey, error: unknown): void { // 处理存储空间不足错误 if (error instanceof DOMException && error.name === 'QuotaExceededError') { console.warn('存储空间不足,尝试清理过期数据'); this.clearExpiredItems(); // 尝试重新存储(最多尝试一次) try { const raw = localStorage.getItem(key); if (raw) { const value = JSON.parse(raw) as StorageValue; if (!this.isExpired(value)) { localStorage.setItem(key, raw); return; } } } catch (retryError) { console.error('重试存储失败', retryError); } } } /** * 清理所有过期的存储项 */ private static clearExpiredItems(): void { Object.values(AppStorageKey).forEach(key => { try { const raw = localStorage.getItem(key); if (raw) { const value = JSON.parse(raw) as StorageValue; if (this.isExpired(value)) { localStorage.removeItem(key); } } } catch (e) { // 忽略无效数据 } }); } } export default SafeStorage;