/** * 表格缓存管理模块 * * 提供高性能的表格数据缓存机制 * * ## 主要功能 * * - 基于参数的智能缓存键生成(使用 ohash) * - LRU(最近最少使用)缓存淘汰策略 * - 缓存过期时间管理 * - 缓存大小限制和自动清理 * - 基于标签的缓存分组管理 * - 多种缓存失效策略(清空所有、清空当前、清空分页等) * - 缓存访问统计和命中率分析 * - 缓存大小估算 * * ## 使用场景 * * - 表格数据的分页缓存 * - 减少重复的 API 请求 * - 提升表格切换和返回的响应速度 * - 搜索条件变化时的智能缓存管理 * - 数据更新后的缓存失效处理 * * ## 缓存策略 * * - CLEAR_ALL: 清空所有缓存(适用于全局数据更新) * - CLEAR_CURRENT: 仅清空当前查询条件的缓存(适用于单条数据更新) * - CLEAR_PAGINATION: 清空所有分页缓存但保留不同搜索条件(适用于批量操作) * - KEEP_ALL: 不清除缓存(适用于只读操作) * * @module utils/table/tableCache * @author Art Design Pro Team */ import { hash } from 'ohash' // 缓存失效策略枚举 export enum CacheInvalidationStrategy { /** 清空所有缓存 */ CLEAR_ALL = 'clear_all', /** 仅清空当前查询条件的缓存 */ CLEAR_CURRENT = 'clear_current', /** 清空所有分页缓存(保留不同搜索条件的缓存) */ CLEAR_PAGINATION = 'clear_pagination', /** 不清除缓存 */ KEEP_ALL = 'keep_all' } // 通用 API 响应接口(兼容不同的后端响应格式) export interface ApiResponse { records?: T[] data?: T[] total?: number current?: number size?: number [key: string]: unknown } // 缓存存储接口 export interface CacheItem { data: T[] response: ApiResponse timestamp: number params: string // 缓存标签,用于分组管理 tags: Set // 访问次数(用于 LRU 算法) accessCount: number // 最后访问时间 lastAccessTime: number } // 增强的缓存管理类 export class TableCache { private cache = new Map>() private cacheTime: number private maxSize: number private enableLog: boolean constructor(cacheTime = 5 * 60 * 1000, maxSize = 50, enableLog = false) { // 默认5分钟,最多50条缓存 this.cacheTime = cacheTime this.maxSize = maxSize this.enableLog = enableLog } // 内部日志工具 private log(message: string, ...args: any[]) { if (this.enableLog) { console.log(`[TableCache] ${message}`, ...args) } } // 生成稳定的缓存键 private generateKey(params: unknown): string { return hash(params) } // 🔧 优化:增强类型安全性 private generateTags(params: Record): Set { const tags = new Set() // 添加搜索条件标签 const searchKeys = Object.keys(params).filter( (key) => !['current', 'size', 'total'].includes(key) && params[key] !== undefined && params[key] !== '' && params[key] !== null ) if (searchKeys.length > 0) { const searchTag = searchKeys.map((key) => `${key}:${String(params[key])}`).join('|') tags.add(`search:${searchTag}`) } else { tags.add('search:default') } // 添加分页标签 tags.add(`pagination:${params.size || 10}`) // 添加通用分页标签,用于清理所有分页缓存 tags.add('pagination') return tags } // 🔧 优化:LRU 缓存清理 private evictLRU(): void { if (this.cache.size <= this.maxSize) return // 找到最少使用的缓存项 let lruKey = '' let minAccessCount = Infinity let oldestTime = Infinity for (const [key, item] of this.cache.entries()) { if ( item.accessCount < minAccessCount || (item.accessCount === minAccessCount && item.lastAccessTime < oldestTime) ) { lruKey = key minAccessCount = item.accessCount oldestTime = item.lastAccessTime } } if (lruKey) { this.cache.delete(lruKey) this.log(`LRU 清理缓存: ${lruKey}`) } } // 设置缓存 set(params: unknown, data: T[], response: ApiResponse): void { const key = this.generateKey(params) const tags = this.generateTags(params as Record) const now = Date.now() // 检查是否需要清理 this.evictLRU() this.cache.set(key, { data, response, timestamp: now, params: key, tags, accessCount: 1, lastAccessTime: now }) } // 获取缓存 get(params: unknown): CacheItem | null { const key = this.generateKey(params) const item = this.cache.get(key) if (!item) return null // 检查是否过期 if (Date.now() - item.timestamp > this.cacheTime) { this.cache.delete(key) return null } // 更新访问统计 item.accessCount++ item.lastAccessTime = Date.now() return item } // 根据标签清除缓存 clearByTags(tags: string[]): number { let clearedCount = 0 for (const [key, item] of this.cache.entries()) { // 检查是否包含任意一个标签 const hasMatchingTag = tags.some((tag) => Array.from(item.tags).some((itemTag) => itemTag.includes(tag)) ) if (hasMatchingTag) { this.cache.delete(key) clearedCount++ } } return clearedCount } // 清除当前搜索条件的缓存 clearCurrentSearch(params: unknown): number { const key = this.generateKey(params) const deleted = this.cache.delete(key) return deleted ? 1 : 0 } // 清除分页缓存 clearPagination(): number { return this.clearByTags(['pagination']) } // 清空所有缓存 clear(): void { this.cache.clear() } // 获取缓存统计信息 getStats(): { total: number; size: string; hitRate: string } { const total = this.cache.size let totalSize = 0 let totalAccess = 0 for (const item of this.cache.values()) { // 粗略估算大小(JSON字符串长度) totalSize += JSON.stringify(item.data).length totalAccess += item.accessCount } // 转换为人类可读的大小 const sizeInKB = (totalSize / 1024).toFixed(2) const avgHits = total > 0 ? (totalAccess / total).toFixed(1) : '0' return { total, size: `${sizeInKB}KB`, hitRate: `${avgHits} avg hits` } } // 清理过期缓存 cleanupExpired(): number { let cleanedCount = 0 const now = Date.now() for (const [key, item] of this.cache.entries()) { if (now - item.timestamp > this.cacheTime) { this.cache.delete(key) cleanedCount++ } } return cleanedCount } }