Files
front/src/utils/safeStorage.ts
2026-03-07 20:11:25 +08:00

158 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 类型安全的 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<T> = {
__data: T; // 实际存储的数据
__expiry?: number; // 可选的过期时间戳
};
// 3. 主存储类
class SafeStorage {
/**
* 存储数据(自动处理 JSON 序列化)
* @param key 预定义的存储键
* @param value 要存储的值(支持所有 JSON 安全类型)
* @param ttl 可选的时间有效期(毫秒)
*/
static set<T>(key: AppStorageKey, value: T, ttl?: number): void {
if (typeof window === "undefined") return;
try {
// 创建存储对象(包含数据值和过期时间)
const storageValue: StorageValue<T> = {
__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<T>(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<T>;
// 检查是否过期
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<T>(value: StorageValue<T>): 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<unknown>;
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<unknown>;
if (this.isExpired(value)) {
localStorage.removeItem(key);
}
}
} catch (e) {
// 忽略无效数据
}
});
}
}
export default SafeStorage;