Skip to content

缓存工具 (cache.ts)

浏览器缓存工具集合,提供对浏览器存储机制的完整封装,支持会话级和持久化的数据存储操作。所有缓存键会自动添加应用ID前缀,避免多应用冲突。

📖 概述

缓存工具提供两套完整的存储方案:

会话缓存 (sessionCache)

基于 sessionStorage 的临时数据存储,页面关闭后数据会被清除:

  • 基础操作:存取字符串数据
  • JSON处理:存取JSON对象数据
  • 数据清理:移除指定缓存数据

本地缓存 (localCache)

基于 localStorage 的持久化数据存储,具有高级功能:

  • 基础操作:存取字符串和对象数据
  • 过期控制:支持设置缓存有效期
  • 自动清理:定期清理过期或损坏的缓存
  • 存储统计:提供缓存使用情况统计

🔧 核心特性

自动前缀机制

所有缓存键都会自动添加应用ID前缀,避免不同应用之间的数据冲突:

typescript
const KEY_PREFIX = `${SystemConfig.app.id}:`

// 实际存储的键名
// 用户输入:'userInfo'
// 实际存储:'myApp:userInfo'

自动清理机制

本地缓存支持自动清理过期和损坏的数据:

typescript
// 应用启动时自动清理
setTimeout(() => {
  autoCleanup()
}, 1000)

// 每小时清理一次过期缓存
setInterval(autoCleanup, 60 * 60 * 1000)

📦 会话缓存 (sessionCache)

基于 sessionStorage 的临时存储,页面关闭后数据会被清除。

set

设置会话缓存。

typescript
set(key: string, value: string): void

参数:

  • key - 缓存键(自动添加应用ID前缀)
  • value - 缓存值(仅支持字符串)

示例:

typescript
// 存储用户名
sessionCache.set('userName', 'admin')
// 实际存储的键为 'appId:userName'

// 存储数字需要转为字符串
sessionCache.set('userId', '12345')
sessionCache.set('count', String(42))

get

获取会话缓存。

typescript
get(key: string): string | null

参数:

  • key - 缓存键(自动添加应用ID前缀)

返回值:

  • string | null - 缓存值或 null

示例:

typescript
// 获取用户名
const userName = sessionCache.get('userName')
if (userName) {
  console.log('用户名:', userName)
}

getNumber

获取数字类型缓存(便捷转换方法)。

typescript
getNumber(key: string): number | null

参数:

  • key - 缓存键(自动添加应用ID前缀)

返回值:

  • number | null - 数字或 null

示例:

typescript
// 获取数字
sessionCache.set('count', '42')
const count = sessionCache.getNumber('count') // 42

setJSON

设置 JSON 对象到会话缓存。

typescript
setJSON<T>(key: string, jsonValue: T): void

参数:

  • key - 缓存键(自动添加应用ID前缀)
  • jsonValue - 要缓存的 JSON 对象

示例:

typescript
// 存储用户信息对象
const userInfo = { id: 1, name: 'admin', role: 'administrator' }
sessionCache.setJSON('userInfo', userInfo)

getJSON

从会话缓存获取 JSON 对象。

typescript
getJSON<T = any>(key: string): T | null

参数:

  • key - 缓存键(自动添加应用ID前缀)

返回值:

  • T | null - 解析后的 JSON 对象或 null

示例:

typescript
// 获取用户信息对象
const userInfo = sessionCache.getJSON<UserInfo>('userInfo')
if (userInfo) {
  console.log(userInfo.name) // TypeScript 类型安全
}

remove

移除会话缓存项。

typescript
remove(key: string): void

参数:

  • key - 要移除的缓存键(自动添加应用ID前缀)

示例:

typescript
// 移除用户信息
sessionCache.remove('userInfo')

has

检查会话缓存是否存在。

typescript
has(key: string): boolean

参数:

  • key - 缓存键

返回值:

  • boolean - 是否存在

示例:

typescript
if (sessionCache.has('userToken')) {
  // 令牌存在
  proceedWithAuthentication()
}

clearAll

清除所有带有当前应用前缀的会话缓存。

typescript
clearAll(): void

示例:

typescript
// 清除当前应用的所有会话缓存
sessionCache.clearAll()

💾 本地缓存 (localCache)

基于 localStorage 的持久化存储,数据将永久保存,支持过期时间管理和自动清理。

set

设置本地缓存,支持过期时间。

typescript
set<T>(key: string, value: T, expireSeconds?: number): void

参数:

  • key - 缓存键(自动添加应用ID前缀)
  • value - 缓存值
  • expireSeconds - 过期时间(秒),不传则永不过期

示例:

typescript
// 存储主题设置(永久)
localCache.set('theme', 'dark')
// 实际存储的键为 'appId:theme'

// 存储 token(7天过期)
localCache.set('userToken', 'abc123', 7 * 24 * 3600)

get

获取本地缓存,自动处理过期数据。

typescript
get<T = any>(key: string): T | null

参数:

  • key - 缓存键(自动添加应用ID前缀)

返回值:

  • T | null - 缓存值或 null(过期或无效数据返回 null)

示例:

typescript
// 获取主题设置
const theme = localCache.get<string>('theme') // 'dark'

// 获取可能过期的token
const token = localCache.get<string>('userToken')
if (!token) {
  // token不存在或已过期,需要重新登录
  redirectToLogin()
}

setJSON / getJSON

JSON对象的便捷方法,与 set/get 功能相同。

typescript
setJSON<T>(key: string, jsonValue: T, expireSeconds?: number): void
getJSON<T = any>(key: string): T | null

示例:

typescript
// 存储系统配置
const config = { language: 'zh-CN', fontSize: 'medium', autoSave: true }
localCache.setJSON('sysConfig', config)

// 获取系统配置
const sysConfig = localCache.getJSON<SystemConfig>('sysConfig')
if (sysConfig) {
  console.log(sysConfig.language) // TypeScript 类型安全
}

remove

移除本地缓存项。

typescript
remove(key: string): void

参数:

  • key - 要移除的缓存键(自动添加应用ID前缀)

示例:

typescript
// 移除系统配置
localCache.remove('sysConfig')

has

检查本地缓存是否存在且未过期。

typescript
has(key: string): boolean

参数:

  • key - 缓存键

返回值:

  • boolean - 是否存在

示例:

typescript
if (localCache.has('userToken')) {
  // 用户已登录且token未过期
  showUserDashboard()
} else {
  // 需要登录
  redirectToLogin()
}

clearAll

清除所有带有当前应用前缀的本地缓存。

typescript
clearAll(): void

示例:

typescript
// 清除当前应用的所有本地缓存
localCache.clearAll()

cleanup

手动清理过期缓存。

typescript
cleanup(): void

示例:

typescript
// 清理所有过期的本地缓存
localCache.cleanup()

getStats

获取本地缓存统计信息。

typescript
getStats(): {
  totalKeys: number
  appKeys: number
  usagePercent: number
} | null

返回值:

  • 统计信息对象或 null
    • totalKeys - localStorage中的总键数
    • appKeys - 当前应用的键数
    • usagePercent - 估算的存储使用百分比

示例:

typescript
const stats = localCache.getStats()
if (stats) {
  console.log(`当前使用: ${stats.usagePercent}%`)
  console.log(`应用缓存数量: ${stats.appKeys}`)
  
  if (stats.usagePercent > 80) {
    console.warn('存储空间不足,建议清理缓存')
  }
}

🎯 实际应用场景

1. 用户认证状态管理

typescript
// 登录成功后存储用户信息
const handleLogin = async (credentials: LoginForm) => {
  const response = await loginApi(credentials)
  
  if (response.success) {
    // 存储用户信息(会话级)
    sessionCache.setJSON('userInfo', response.user)
    
    // 存储访问令牌(30分钟过期)
    localCache.set('accessToken', response.accessToken, 30 * 60)
    
    // 存储刷新令牌(7天过期)
    localCache.set('refreshToken', response.refreshToken, 7 * 24 * 60 * 60)
    
    // 记住登录状态(如果用户选择)
    if (credentials.rememberMe) {
      localCache.set('rememberUser', 'true', 30 * 24 * 60 * 60) // 30天
    }
  }
}

// 检查登录状态
const checkAuthStatus = () => {
  const accessToken = localCache.get('accessToken')
  const userInfo = sessionCache.getJSON('userInfo')
  
  if (accessToken && userInfo) {
    return { isAuthenticated: true, user: userInfo }
  }
  
  // 尝试使用刷新令牌
  const refreshToken = localCache.get('refreshToken')
  if (refreshToken) {
    return { isAuthenticated: false, canRefresh: true }
  }
  
  return { isAuthenticated: false, canRefresh: false }
}

// 登出处理
const handleLogout = () => {
  // 清除所有认证相关的缓存
  sessionCache.remove('userInfo')
  localCache.remove('accessToken')
  localCache.remove('refreshToken')
  
  // 检查是否需要保留记住登录
  if (!localCache.has('rememberUser')) {
    localCache.remove('lastLoginUser')
  }
}

2. 应用配置管理

typescript
interface AppConfig {
  theme: 'light' | 'dark'
  language: string
  pageSize: number
  autoSave: boolean
  notifications: boolean
}

class ConfigManager {
  private static readonly CONFIG_KEY = 'appConfig'
  private static readonly DEFAULT_CONFIG: AppConfig = {
    theme: 'light',
    language: 'zh-CN',
    pageSize: 20,
    autoSave: true,
    notifications: true
  }
  
  // 获取配置(带默认值)
  static getConfig(): AppConfig {
    const stored = localCache.getJSON<AppConfig>(this.CONFIG_KEY)
    return { ...this.DEFAULT_CONFIG, ...stored }
  }
  
  // 更新配置
  static updateConfig(updates: Partial<AppConfig>) {
    const current = this.getConfig()
    const updated = { ...current, ...updates }
    localCache.setJSON(this.CONFIG_KEY, updated)
    
    // 触发配置变更事件
    this.notifyConfigChange(updated)
  }
  
  // 重置为默认配置
  static resetConfig() {
    localCache.setJSON(this.CONFIG_KEY, this.DEFAULT_CONFIG)
    this.notifyConfigChange(this.DEFAULT_CONFIG)
  }
  
  private static notifyConfigChange(config: AppConfig) {
    window.dispatchEvent(new CustomEvent('configChanged', { detail: config }))
  }
}

// 使用示例
const config = ConfigManager.getConfig()
console.log('当前主题:', config.theme)

// 切换主题
ConfigManager.updateConfig({ theme: 'dark' })

// 监听配置变更
window.addEventListener('configChanged', (e: CustomEvent) => {
  console.log('配置已更新:', e.detail)
  applyTheme(e.detail.theme)
})

3. 表单数据自动保存

typescript
// 表单自动保存管理器
class FormAutoSave {
  private saveTimer: number | null = null
  private readonly SAVE_DELAY = 2000 // 2秒延迟保存
  
  constructor(
    private formId: string,
    private expireMinutes: number = 30
  ) {}
  
  // 保存表单数据
  saveFormData(data: Record<string, any>) {
    // 清除之前的定时器
    if (this.saveTimer) {
      clearTimeout(this.saveTimer)
    }
    
    // 延迟保存,避免频繁写入
    this.saveTimer = window.setTimeout(() => {
      const key = `form_draft_${this.formId}`
      const saveData = {
        data,
        timestamp: Date.now()
      }
      
      localCache.setJSON(key, saveData, this.expireMinutes * 60)
      console.log('表单数据已自动保存')
    }, this.SAVE_DELAY)
  }
  
  // 恢复表单数据
  restoreFormData(): Record<string, any> | null {
    const key = `form_draft_${this.formId}`
    const saved = localCache.getJSON<{
      data: Record<string, any>
      timestamp: number
    }>(key)
    
    if (saved) {
      const age = Date.now() - saved.timestamp
      console.log(`发现${Math.round(age / 1000)}秒前的草稿`)
      return saved.data
    }
    
    return null
  }
  
  // 清除草稿
  clearDraft() {
    const key = `form_draft_${this.formId}`
    localCache.remove(key)
    
    if (this.saveTimer) {
      clearTimeout(this.saveTimer)
      this.saveTimer = null
    }
  }
}

// 使用示例
const autoSave = new FormAutoSave('user_profile_form', 60) // 60分钟过期

// 表单输入时自动保存
const handleFormChange = (formData: any) => {
  autoSave.saveFormData(formData)
}

// 页面加载时恢复数据
const restoreData = autoSave.restoreFormData()
if (restoreData) {
  const shouldRestore = confirm('发现未保存的表单数据,是否恢复?')
  if (shouldRestore) {
    fillFormWithData(restoreData)
  } else {
    autoSave.clearDraft()
  }
}

4. API响应缓存

typescript
// API响应缓存管理器
class ApiCache {
  private static readonly CACHE_PREFIX = 'api_cache_'
  
  // 缓存API响应
  static cacheResponse(
    endpoint: string, 
    params: Record<string, any>, 
    response: any, 
    ttlMinutes: number = 5
  ) {
    const cacheKey = this.generateCacheKey(endpoint, params)
    const cacheData = {
      response,
      cachedAt: Date.now(),
      endpoint,
      params
    }
    
    localCache.setJSON(cacheKey, cacheData, ttlMinutes * 60)
  }
  
  // 获取缓存的响应
  static getCachedResponse(endpoint: string, params: Record<string, any>) {
    const cacheKey = this.generateCacheKey(endpoint, params)
    const cached = localCache.getJSON<{
      response: any
      cachedAt: number
      endpoint: string
      params: Record<string, any>
    }>(cacheKey)
    
    if (cached) {
      const age = Math.round((Date.now() - cached.cachedAt) / 1000)
      console.log(`使用${age}秒前的缓存数据`)
      return cached.response
    }
    
    return null
  }
  
  // 生成缓存键
  private static generateCacheKey(endpoint: string, params: Record<string, any>): string {
    const paramStr = JSON.stringify(params, Object.keys(params).sort())
    const hash = this.simpleHash(endpoint + paramStr)
    return `${this.CACHE_PREFIX}${hash}`
  }
  
  // 简单哈希函数
  private static simpleHash(str: string): string {
    let hash = 0
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i)
      hash = ((hash << 5) - hash) + char
      hash = hash & hash // 转换为32位整数
    }
    return Math.abs(hash).toString(36)
  }
  
  // 清理特定endpoint的缓存
  static clearEndpointCache(endpoint: string) {
    const stats = localCache.getStats()
    if (!stats) return
    
    // 这里需要遍历所有键,实际项目中可考虑更高效的实现
    console.log(`清理${endpoint}相关缓存`)
  }
}

// API调用包装器
const cachedApiCall = async <T>(
  endpoint: string, 
  params: Record<string, any> = {},
  options: { ttl?: number, useCache?: boolean } = {}
): Promise<T> => {
  const { ttl = 5, useCache = true } = options
  
  // 尝试获取缓存
  if (useCache) {
    const cached = ApiCache.getCachedResponse(endpoint, params)
    if (cached) {
      return cached
    }
  }
  
  // 发起API请求
  console.log('发起API请求:', endpoint)
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(params)
  }).then(res => res.json())
  
  // 缓存响应
  if (useCache && response.success) {
    ApiCache.cacheResponse(endpoint, params, response, ttl)
  }
  
  return response
}

// 使用示例
const getUserList = (page: number, pageSize: number) => {
  return cachedApiCall('/api/users', { page, pageSize }, { ttl: 10 })
}

const getUserDetail = (userId: number) => {
  return cachedApiCall(`/api/user/${userId}`, {}, { ttl: 30 })
}

5. 购物车状态管理

typescript
interface CartItem {
  id: number
  name: string
  price: number
  quantity: number
  image?: string
}

class ShoppingCart {
  private static readonly CART_KEY = 'shopping_cart'
  private static readonly EXPIRE_DAYS = 7
  
  // 获取购物车
  static getCart(): CartItem[] {
    return localCache.getJSON<CartItem[]>(this.CART_KEY) || []
  }
  
  // 添加商品
  static addItem(item: Omit<CartItem, 'quantity'>, quantity: number = 1) {
    const cart = this.getCart()
    const existingIndex = cart.findIndex(cartItem => cartItem.id === item.id)
    
    if (existingIndex >= 0) {
      // 更新数量
      cart[existingIndex].quantity += quantity
    } else {
      // 添加新商品
      cart.push({ ...item, quantity })
    }
    
    this.saveCart(cart)
    this.notifyCartChange()
  }
  
  // 更新商品数量
  static updateQuantity(itemId: number, quantity: number) {
    const cart = this.getCart()
    const itemIndex = cart.findIndex(item => item.id === itemId)
    
    if (itemIndex >= 0) {
      if (quantity <= 0) {
        cart.splice(itemIndex, 1) // 删除商品
      } else {
        cart[itemIndex].quantity = quantity
      }
      
      this.saveCart(cart)
      this.notifyCartChange()
    }
  }
  
  // 移除商品
  static removeItem(itemId: number) {
    const cart = this.getCart().filter(item => item.id !== itemId)
    this.saveCart(cart)
    this.notifyCartChange()
  }
  
  // 清空购物车
  static clearCart() {
    localCache.remove(this.CART_KEY)
    this.notifyCartChange()
  }
  
  // 获取统计信息
  static getCartStats() {
    const cart = this.getCart()
    return {
      itemCount: cart.length,
      totalQuantity: cart.reduce((sum, item) => sum + item.quantity, 0),
      totalAmount: cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
    }
  }
  
  // 保存购物车
  private static saveCart(cart: CartItem[]) {
    localCache.setJSON(this.CART_KEY, cart, this.EXPIRE_DAYS * 24 * 60 * 60)
  }
  
  // 通知购物车变更
  private static notifyCartChange() {
    const stats = this.getCartStats()
    window.dispatchEvent(new CustomEvent('cartChanged', { detail: stats }))
  }
}

// 使用示例
// 添加商品到购物车
ShoppingCart.addItem({
  id: 1,
  name: 'iPhone 15',
  price: 5999,
  image: 'iphone15.jpg'
}, 2)

// 监听购物车变更
window.addEventListener('cartChanged', (e: CustomEvent) => {
  const { itemCount, totalAmount } = e.detail
  updateCartBadge(itemCount)
  updateCartTotal(totalAmount)
})

// 获取购物车统计
const stats = ShoppingCart.getCartStats()
console.log(`购物车中有${stats.itemCount}种商品,共${stats.totalQuantity}件`)

🛠️ 高级功能

缓存同步机制

typescript
// 跨标签页缓存同步
class CacheSync {
  private static listeners = new Map<string, Function[]>()
  
  // 监听缓存变更
  static listen(key: string, callback: (newValue: any) => void) {
    if (!this.listeners.has(key)) {
      this.listeners.set(key, [])
    }
    this.listeners.get(key)!.push(callback)
    
    // 监听storage事件(跨标签页)
    window.addEventListener('storage', (e) => {
      if (e.key === `${SystemConfig.app.id}:${key}`) {
        const newValue = e.newValue ? JSON.parse(e.newValue) : null
        callback(newValue)
      }
    })
  }
  
  // 设置缓存并通知
  static setAndNotify(key: string, value: any, expire?: number) {
    localCache.set(key, value, expire)
    
    // 通知当前页面的监听器
    const callbacks = this.listeners.get(key) || []
    callbacks.forEach(callback => callback(value))
  }
}

// 使用示例
CacheSync.listen('theme', (newTheme) => {
  console.log('主题已在其他标签页中更改为:', newTheme)
  applyTheme(newTheme)
})

缓存压缩

typescript
// 大数据缓存压缩(概念示例)
class CompressedCache {
  // 压缩存储
  static setCompressed(key: string, data: any, expire?: number) {
    try {
      const jsonStr = JSON.stringify(data)
      
      // 简单压缩:移除多余空格
      const compressed = jsonStr.replace(/\s+/g, ' ')
      
      localCache.set(`compressed_${key}`, compressed, expire)
      
      console.log(`压缩率: ${((1 - compressed.length / jsonStr.length) * 100).toFixed(1)}%`)
    } catch (error) {
      console.error('压缩存储失败:', error)
    }
  }
  
  // 解压获取
  static getCompressed<T>(key: string): T | null {
    try {
      const compressed = localCache.get<string>(`compressed_${key}`)
      if (compressed) {
        return JSON.parse(compressed)
      }
      return null
    } catch (error) {
      console.error('解压获取失败:', error)
      return null
    }
  }
}

📊 性能监控

缓存性能分析

typescript
class CachePerformance {
  private static hitCount = 0
  private static missCount = 0
  
  // 记录缓存命中
  static recordHit() {
    this.hitCount++
  }
  
  // 记录缓存未命中
  static recordMiss() {
    this.missCount++
  }
  
  // 获取命中率
  static getHitRate(): number {
    const total = this.hitCount + this.missCount
    return total > 0 ? (this.hitCount / total) * 100 : 0
  }
  
  // 重置统计
  static resetStats() {
    this.hitCount = 0
    this.missCount = 0
  }
  
  // 输出性能报告
  static getReport() {
    const total = this.hitCount + this.missCount
    const hitRate = this.getHitRate()
    
    return {
      hitCount: this.hitCount,
      missCount: this.missCount,
      totalRequests: total,
      hitRate: hitRate.toFixed(2) + '%'
    }
  }
}

// 集成到缓存操作中
const enhancedGet = <T>(key: string): T | null => {
  const value = localCache.get<T>(key)
  
  if (value !== null) {
    CachePerformance.recordHit()
  } else {
    CachePerformance.recordMiss()
  }
  
  return value
}

⚠️ 注意事项

1. 存储限制

  • 容量限制:localStorage通常有5-10MB的限制
  • 监控使用量:定期检查 getStats() 返回的使用情况
  • 清理策略:及时清理不需要的缓存数据

2. 数据安全

  • 敏感数据:不要在缓存中存储密码等敏感信息
  • 加密存储:敏感数据应先加密再存储
  • 清理机制:登出时要清理相关缓存

3. 浏览器兼容性

  • 兼容性检查:在不支持localStorage的环境中会静默失败
  • 异常处理:存储空间不足时会抛出异常
  • 隐私模式:某些浏览器隐私模式下localStorage可能不可用

4. 性能考虑

  • JSON序列化:大对象的序列化/反序列化会影响性能
  • 频繁操作:避免频繁的存储操作
  • 数据大小:单个存储项不宜过大