Files
front/src/utils/safeStorage.ts

159 lines
4.3 KiB
TypeScript
Raw Normal View History

2026-03-07 20:11:25 +08:00
/**
* localStorage key
*/
2026-05-02 09:59:06 +08:00
2026-03-07 20:11:25 +08:00
// 1. 定义应用程序使用的存储键名(防止使用任意字符串作为 key
export enum AppStorageKey {
2026-05-02 09:59:06 +08:00
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,
2026-03-07 20:11:25 +08:00
}
2026-05-02 09:59:06 +08:00
// 设置过期时间(如果提供)
if (ttl !== undefined && ttl > 0) {
storageValue.__expiry = Date.now() + ttl
2026-03-07 20:11:25 +08:00
}
2026-05-02 09:59:06 +08:00
// 自动序列化并存储
localStorage.setItem(key, JSON.stringify(storageValue))
} catch (error) {
console.error(`[存储] 保存失败 (key: ${key})`, error)
this.handleStorageError(key, error)
2026-03-07 20:11:25 +08:00
}
2026-05-02 09:59:06 +08:00
}
/**
*
* @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
2026-03-07 20:11:25 +08:00
}
2026-05-02 09:59:06 +08:00
}
/**
*
* @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
2026-03-07 20:11:25 +08:00
}
}
2026-05-02 09:59:06 +08:00
} catch (retryError) {
console.error('重试存储失败', retryError)
2026-03-07 20:11:25 +08:00
}
}
2026-05-02 09:59:06 +08:00
}
/**
*
*/
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)
2026-03-07 20:11:25 +08:00
}
}
2026-05-02 09:59:06 +08:00
} catch (e) {
// 忽略无效数据
}
})
2026-03-07 20:11:25 +08:00
}
2026-05-02 09:59:06 +08:00
}
export default SafeStorage