♻️ Refactor document selector and cache management logic
This commit is contained in:
35
frontend/src/common/cache/README.md
vendored
35
frontend/src/common/cache/README.md
vendored
@@ -10,16 +10,18 @@
|
||||
- 📊 缓存统计信息
|
||||
- ⏰ TTL 过期支持
|
||||
- 🎯 简洁易用的 API
|
||||
- 🔐 多种哈希算法支持
|
||||
- 🏗️ 模块化设计,易于扩展
|
||||
|
||||
## 基础用法
|
||||
|
||||
### 创建缓存
|
||||
|
||||
```typescript
|
||||
import { LRUCache, CacheManager, createCacheItem } from '@/common/cache';
|
||||
import { LruCache, CacheManager, createCacheItem } from '@/common/cache';
|
||||
|
||||
// 直接创建缓存
|
||||
const cache = new LRUCache({
|
||||
const cache = new LruCache({
|
||||
maxSize: 100,
|
||||
ttl: 5 * 60 * 1000, // 5 分钟
|
||||
onEvict: (item) => console.log('Evicted:', item)
|
||||
@@ -76,14 +78,26 @@ const item: MyItem = {
|
||||
cache.set('resource1', item);
|
||||
```
|
||||
|
||||
### 哈希工具使用
|
||||
|
||||
```typescript
|
||||
import { createHash, generateCacheKey } from '@/common/cache';
|
||||
|
||||
// 生成简单哈希
|
||||
const hash = createHash('some content');
|
||||
|
||||
// 生成缓存键
|
||||
const key = generateCacheKey('user', userId, 'profile');
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### LRUCache
|
||||
### LruCache
|
||||
|
||||
- `get(id)` - 获取缓存项
|
||||
- `set(id, item)` - 设置缓存项
|
||||
- `remove(id)` - 移除缓存项
|
||||
- `has(id)` - 检查是否存在
|
||||
- `get(id)` - 获取缓存项(O(1))
|
||||
- `set(id, item)` - 设置缓存项(O(1))
|
||||
- `remove(id)` - 移除缓存项(O(1))
|
||||
- `has(id)` - 检查是否存在(O(1))
|
||||
- `clear()` - 清空缓存
|
||||
- `size()` - 获取缓存大小
|
||||
- `getStats()` - 获取统计信息
|
||||
@@ -96,11 +110,10 @@ cache.set('resource1', item);
|
||||
- `removeCache(name)` - 删除缓存
|
||||
- `clearAll()` - 清空所有缓存
|
||||
- `getAllStats()` - 获取所有统计信息
|
||||
- `cleanupAll()` - 清理所有缓存的过期项
|
||||
|
||||
## 工具函数
|
||||
|
||||
- `generateCacheKey(...parts)` - 生成缓存键
|
||||
- `createCacheItem(id, data)` - 创建缓存项
|
||||
- `createContentHash(content)` - 创建内容哈希
|
||||
- `debounce(func, wait)` - 防抖函数
|
||||
- `throttle(func, limit)` - 节流函数
|
||||
- `createHash(content)` - 创建内容哈希
|
||||
- `createCacheItem(id, data)` - 创建缓存项
|
||||
133
frontend/src/common/cache/cache-manager.ts
vendored
133
frontend/src/common/cache/cache-manager.ts
vendored
@@ -1,133 +0,0 @@
|
||||
import type { CacheItem, CacheConfig, CacheStats } from './types';
|
||||
import { LRUCache } from './lru-cache';
|
||||
|
||||
export class CacheManager {
|
||||
private caches = new Map<string, LRUCache<any>>();
|
||||
private cleanupInterval?: number;
|
||||
|
||||
constructor(options?: {
|
||||
/** 自动清理间隔(毫秒),默认 5 分钟 */
|
||||
cleanupInterval?: number;
|
||||
}) {
|
||||
if (options?.cleanupInterval) {
|
||||
this.startAutoCleanup(options.cleanupInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或获取缓存实例
|
||||
*/
|
||||
getCache<T extends CacheItem>(name: string, config?: CacheConfig): LRUCache<T> {
|
||||
if (!this.caches.has(name)) {
|
||||
if (!config) {
|
||||
throw new Error(`Cache "${name}" does not exist and no config provided`);
|
||||
}
|
||||
this.caches.set(name, new LRUCache<T>(config));
|
||||
}
|
||||
return this.caches.get(name)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的缓存实例
|
||||
*/
|
||||
createCache<T extends CacheItem>(name: string, config: CacheConfig): LRUCache<T> {
|
||||
if (this.caches.has(name)) {
|
||||
throw new Error(`Cache "${name}" already exists`);
|
||||
}
|
||||
const cache = new LRUCache<T>(config);
|
||||
this.caches.set(name, cache);
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存实例
|
||||
*/
|
||||
removeCache(name: string): boolean {
|
||||
const cache = this.caches.get(name);
|
||||
if (cache) {
|
||||
cache.clear();
|
||||
this.caches.delete(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存是否存在
|
||||
*/
|
||||
hasCache(name: string): boolean {
|
||||
return this.caches.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有缓存名称
|
||||
*/
|
||||
getCacheNames(): string[] {
|
||||
return Array.from(this.caches.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有缓存
|
||||
*/
|
||||
clearAll(): void {
|
||||
for (const cache of this.caches.values()) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有缓存的统计信息
|
||||
*/
|
||||
getAllStats(): Record<string, CacheStats> {
|
||||
const stats: Record<string, CacheStats> = {};
|
||||
for (const [name, cache] of this.caches.entries()) {
|
||||
stats[name] = cache.getStats();
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有缓存中的过期项
|
||||
*/
|
||||
cleanupAll(): Record<string, number> {
|
||||
const results: Record<string, number> = {};
|
||||
for (const [name, cache] of this.caches.entries()) {
|
||||
results[name] = cache.cleanup();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动自动清理
|
||||
*/
|
||||
startAutoCleanup(interval: number): void {
|
||||
this.stopAutoCleanup();
|
||||
this.cleanupInterval = window.setInterval(() => {
|
||||
this.cleanupAll();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止自动清理
|
||||
*/
|
||||
stopAutoCleanup(): void {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
destroy(): void {
|
||||
this.stopAutoCleanup();
|
||||
this.clearAll();
|
||||
this.caches.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 全局缓存管理器实例
|
||||
export const globalCacheManager = new CacheManager({
|
||||
cleanupInterval: 5 * 60 * 1000 // 5 分钟
|
||||
});
|
||||
166
frontend/src/common/cache/doublyLinkedList.ts
vendored
Normal file
166
frontend/src/common/cache/doublyLinkedList.ts
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
import type { DoublyLinkedNode } from './interfaces';
|
||||
|
||||
/**
|
||||
* 双向链表实现
|
||||
* 用于高效管理LRU缓存的访问顺序,所有操作都是O(1)时间复杂度
|
||||
*
|
||||
* @template T 节点值的类型
|
||||
*/
|
||||
export class DoublyLinkedList<T> {
|
||||
/** 头节点(虚拟节点) */
|
||||
private head: DoublyLinkedNode<T>;
|
||||
/** 尾节点(虚拟节点) */
|
||||
private tail: DoublyLinkedNode<T>;
|
||||
/** 当前节点数量 */
|
||||
private count: number = 0;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* 创建头尾虚拟节点,简化边界处理
|
||||
*/
|
||||
constructor() {
|
||||
// 创建虚拟头节点
|
||||
this.head = {
|
||||
value: null as any,
|
||||
key: 'head',
|
||||
prev: null,
|
||||
next: null
|
||||
};
|
||||
|
||||
// 创建虚拟尾节点
|
||||
this.tail = {
|
||||
value: null as any,
|
||||
key: 'tail',
|
||||
prev: null,
|
||||
next: null
|
||||
};
|
||||
|
||||
// 连接头尾节点
|
||||
this.head.next = this.tail;
|
||||
this.tail.prev = this.head;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在头部添加节点
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @param node 要添加的节点
|
||||
*/
|
||||
addToHead(node: DoublyLinkedNode<T>): void {
|
||||
node.prev = this.head;
|
||||
node.next = this.head.next;
|
||||
|
||||
if (this.head.next) {
|
||||
this.head.next.prev = node;
|
||||
}
|
||||
this.head.next = node;
|
||||
|
||||
this.count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定节点
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @param node 要移除的节点
|
||||
*/
|
||||
removeNode(node: DoublyLinkedNode<T>): void {
|
||||
if (node.prev) {
|
||||
node.prev.next = node.next;
|
||||
}
|
||||
if (node.next) {
|
||||
node.next.prev = node.prev;
|
||||
}
|
||||
|
||||
this.count--;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除尾部节点
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @returns 被移除的节点,如果链表为空则返回null
|
||||
*/
|
||||
removeTail(): DoublyLinkedNode<T> | null {
|
||||
const lastNode = this.tail.prev;
|
||||
|
||||
if (lastNode === this.head) {
|
||||
return null; // 链表为空
|
||||
}
|
||||
|
||||
this.removeNode(lastNode!);
|
||||
return lastNode!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将节点移动到头部
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @param node 要移动的节点
|
||||
*/
|
||||
moveToHead(node: DoublyLinkedNode<T>): void {
|
||||
this.removeNode(node);
|
||||
this.addToHead(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新节点
|
||||
*
|
||||
* @param key 节点键
|
||||
* @param value 节点值
|
||||
* @returns 新创建的节点
|
||||
*/
|
||||
createNode(key: string | number, value: T): DoublyLinkedNode<T> {
|
||||
return {
|
||||
key,
|
||||
value,
|
||||
prev: null,
|
||||
next: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取链表大小
|
||||
*
|
||||
* @returns 当前节点数量
|
||||
*/
|
||||
size(): number {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查链表是否为空
|
||||
*
|
||||
* @returns 是否为空
|
||||
*/
|
||||
isEmpty(): boolean {
|
||||
return this.count === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空链表
|
||||
*/
|
||||
clear(): void {
|
||||
this.head.next = this.tail;
|
||||
this.tail.prev = this.head;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有节点的值(从头到尾)
|
||||
* 主要用于调试和测试
|
||||
*
|
||||
* @returns 所有节点值的数组
|
||||
*/
|
||||
toArray(): T[] {
|
||||
const result: T[] = [];
|
||||
let current = this.head.next;
|
||||
|
||||
while (current && current !== this.tail) {
|
||||
result.push(current.value);
|
||||
current = current.next;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
28
frontend/src/common/cache/index.ts
vendored
28
frontend/src/common/cache/index.ts
vendored
@@ -1,19 +1,21 @@
|
||||
export type {
|
||||
CacheItem,
|
||||
DisposableCacheItem,
|
||||
CacheConfig,
|
||||
CacheStats
|
||||
} from './types';
|
||||
export { LRUCache } from './lru-cache';
|
||||
export { CacheManager, globalCacheManager } from './cache-manager';
|
||||
|
||||
export {
|
||||
CacheItem,
|
||||
DisposableCacheItem,
|
||||
CacheConfig,
|
||||
CacheStats,
|
||||
CacheStrategy,
|
||||
DoublyLinkedNode
|
||||
} from './interfaces';
|
||||
export { LruCache } from './lruCache';
|
||||
export { CacheManager, globalCacheManager } from './manager';
|
||||
export { DoublyLinkedList } from './doublyLinkedList';
|
||||
export {
|
||||
createHash,
|
||||
generateCacheKey,
|
||||
createCacheItem,
|
||||
calculateHitRate,
|
||||
formatCacheSize,
|
||||
createCacheItem,
|
||||
calculateHitRate,
|
||||
formatCacheSize,
|
||||
isExpired,
|
||||
createContentHash,
|
||||
debounce,
|
||||
throttle
|
||||
} from './utils';
|
||||
124
frontend/src/common/cache/interfaces.ts
vendored
Normal file
124
frontend/src/common/cache/interfaces.ts
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 缓存项基础接口
|
||||
*/
|
||||
export interface CacheItem {
|
||||
/** 缓存项的唯一标识 */
|
||||
id: string | number;
|
||||
/** 最后访问时间 */
|
||||
lastAccessed: Date;
|
||||
/** 创建时间 */
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可清理的缓存项接口
|
||||
*/
|
||||
export interface DisposableCacheItem extends CacheItem {
|
||||
/** 清理资源的方法 */
|
||||
dispose(): void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存配置接口
|
||||
*/
|
||||
export interface CacheConfig {
|
||||
/** 最大缓存数量 */
|
||||
maxSize: number;
|
||||
/** 生存时间(毫秒),可选 */
|
||||
ttl?: number;
|
||||
/** 驱逐回调函数,可选 */
|
||||
onEvict?: (item: any) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存统计信息接口
|
||||
*/
|
||||
export interface CacheStats {
|
||||
/** 当前缓存项数量 */
|
||||
size: number;
|
||||
/** 最大容量 */
|
||||
maxSize: number;
|
||||
/** 命中次数 */
|
||||
hits: number;
|
||||
/** 未命中次数 */
|
||||
misses: number;
|
||||
/** 命中率 */
|
||||
hitRate: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用缓存策略接口
|
||||
* 所有缓存实现都应该实现这个接口
|
||||
*/
|
||||
export interface CacheStrategy<T extends CacheItem> {
|
||||
/**
|
||||
* 获取缓存项
|
||||
* @param id 缓存项ID
|
||||
* @returns 缓存项或null
|
||||
*/
|
||||
get(id: string | number): T | null;
|
||||
|
||||
/**
|
||||
* 设置缓存项
|
||||
* @param id 缓存项ID
|
||||
* @param item 缓存项
|
||||
*/
|
||||
set(id: string | number, item: T): void;
|
||||
|
||||
/**
|
||||
* 移除缓存项
|
||||
* @param id 缓存项ID
|
||||
* @returns 是否成功移除
|
||||
*/
|
||||
remove(id: string | number): boolean;
|
||||
|
||||
/**
|
||||
* 检查是否存在
|
||||
* @param id 缓存项ID
|
||||
* @returns 是否存在
|
||||
*/
|
||||
has(id: string | number): boolean;
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
clear(): void;
|
||||
|
||||
/**
|
||||
* 获取所有项
|
||||
* @returns 所有缓存项
|
||||
*/
|
||||
getAll(): T[];
|
||||
|
||||
/**
|
||||
* 获取缓存大小
|
||||
* @returns 当前缓存项数量
|
||||
*/
|
||||
size(): number;
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
* @returns 缓存统计信息
|
||||
*/
|
||||
getStats(): CacheStats;
|
||||
|
||||
/**
|
||||
* 清理过期项
|
||||
* @returns 清理的项数量
|
||||
*/
|
||||
cleanup(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 双向链表节点接口
|
||||
*/
|
||||
export interface DoublyLinkedNode<T> {
|
||||
/** 节点值 */
|
||||
value: T;
|
||||
/** 节点键 */
|
||||
key: string | number;
|
||||
/** 前一个节点 */
|
||||
prev: DoublyLinkedNode<T> | null;
|
||||
/** 下一个节点 */
|
||||
next: DoublyLinkedNode<T> | null;
|
||||
}
|
||||
200
frontend/src/common/cache/lru-cache.ts
vendored
200
frontend/src/common/cache/lru-cache.ts
vendored
@@ -1,200 +0,0 @@
|
||||
import type { CacheItem, CacheConfig, CacheStats, DisposableCacheItem } from './types';
|
||||
|
||||
export class LRUCache<T extends CacheItem> {
|
||||
private items = new Map<string | number, T>();
|
||||
private accessOrder: (string | number)[] = [];
|
||||
private config: CacheConfig;
|
||||
private stats = {
|
||||
hits: 0,
|
||||
misses: 0
|
||||
};
|
||||
|
||||
constructor(config: CacheConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存项
|
||||
*/
|
||||
get(id: string | number): T | null {
|
||||
const item = this.items.get(id);
|
||||
|
||||
if (!item) {
|
||||
this.stats.misses++;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (this.isExpired(item)) {
|
||||
this.remove(id);
|
||||
this.stats.misses++;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新访问时间和顺序
|
||||
item.lastAccessed = new Date();
|
||||
this.updateAccessOrder(id);
|
||||
this.stats.hits++;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存项
|
||||
*/
|
||||
set(id: string | number, item: T): void {
|
||||
// 如果已存在,先移除旧的
|
||||
if (this.items.has(id)) {
|
||||
this.remove(id);
|
||||
}
|
||||
|
||||
// 检查容量,必要时驱逐最旧的项
|
||||
while (this.items.size >= this.config.maxSize) {
|
||||
const oldestId = this.accessOrder.shift();
|
||||
if (oldestId !== undefined) {
|
||||
this.evict(oldestId);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加新项
|
||||
this.items.set(id, item);
|
||||
this.accessOrder.push(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除缓存项
|
||||
*/
|
||||
remove(id: string | number): boolean {
|
||||
const item = this.items.get(id);
|
||||
if (!item) return false;
|
||||
|
||||
this.items.delete(id);
|
||||
const index = this.accessOrder.indexOf(id);
|
||||
if (index > -1) {
|
||||
this.accessOrder.splice(index, 1);
|
||||
}
|
||||
|
||||
// 调用清理逻辑
|
||||
this.disposeItem(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否存在
|
||||
*/
|
||||
has(id: string | number): boolean {
|
||||
const item = this.items.get(id);
|
||||
if (!item) return false;
|
||||
|
||||
if (this.isExpired(item)) {
|
||||
this.remove(id);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
clear(): void {
|
||||
for (const item of this.items.values()) {
|
||||
this.disposeItem(item);
|
||||
}
|
||||
this.items.clear();
|
||||
this.accessOrder = [];
|
||||
this.stats.hits = 0;
|
||||
this.stats.misses = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有项
|
||||
*/
|
||||
getAll(): T[] {
|
||||
return Array.from(this.items.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存大小
|
||||
*/
|
||||
size(): number {
|
||||
return this.items.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): CacheStats {
|
||||
const total = this.stats.hits + this.stats.misses;
|
||||
return {
|
||||
size: this.items.size,
|
||||
maxSize: this.config.maxSize,
|
||||
hits: this.stats.hits,
|
||||
misses: this.stats.misses,
|
||||
hitRate: total > 0 ? this.stats.hits / total : 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期项
|
||||
*/
|
||||
cleanup(): number {
|
||||
let cleanedCount = 0;
|
||||
const now = Date.now();
|
||||
|
||||
if (!this.config.ttl) return cleanedCount;
|
||||
|
||||
for (const [id, item] of this.items.entries()) {
|
||||
if (now - item.lastAccessed.getTime() > this.config.ttl) {
|
||||
this.remove(id);
|
||||
cleanedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return cleanedCount;
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
private isExpired(item: T): boolean {
|
||||
if (!this.config.ttl) return false;
|
||||
return Date.now() - item.lastAccessed.getTime() > this.config.ttl;
|
||||
}
|
||||
|
||||
private updateAccessOrder(id: string | number): void {
|
||||
const index = this.accessOrder.indexOf(id);
|
||||
if (index > -1) {
|
||||
this.accessOrder.splice(index, 1);
|
||||
}
|
||||
this.accessOrder.push(id);
|
||||
}
|
||||
|
||||
private evict(id: string | number): void {
|
||||
const item = this.items.get(id);
|
||||
if (item) {
|
||||
if (this.config.onEvict) {
|
||||
this.config.onEvict(item);
|
||||
}
|
||||
this.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
private disposeItem(item: T): void {
|
||||
if (this.isDisposable(item)) {
|
||||
try {
|
||||
const result = item.dispose();
|
||||
if (result instanceof Promise) {
|
||||
result.catch(error => {
|
||||
console.warn('Failed to dispose cache item:', error);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to dispose cache item:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isDisposable(item: T): item is T & DisposableCacheItem {
|
||||
return 'dispose' in item && typeof (item as any).dispose === 'function';
|
||||
}
|
||||
}
|
||||
276
frontend/src/common/cache/lruCache.ts
vendored
Normal file
276
frontend/src/common/cache/lruCache.ts
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
import type { CacheItem, CacheConfig, CacheStats, DisposableCacheItem, CacheStrategy, DoublyLinkedNode } from './interfaces';
|
||||
import { DoublyLinkedList } from './doublyLinkedList';
|
||||
|
||||
/**
|
||||
* 高性能LRU缓存实现
|
||||
* 使用双向链表 + Map 的组合,所有核心操作都是O(1)时间复杂度
|
||||
*
|
||||
* @template T 缓存项类型,必须继承自CacheItem
|
||||
*/
|
||||
export class LruCache<T extends CacheItem> implements CacheStrategy<T> {
|
||||
/** 存储缓存项的Map,提供O(1)的查找性能 */
|
||||
private items = new Map<string | number, DoublyLinkedNode<T>>();
|
||||
|
||||
/** 双向链表,管理访问顺序,提供O(1)的插入/删除性能 */
|
||||
private accessList = new DoublyLinkedList<T>();
|
||||
|
||||
/** 缓存配置 */
|
||||
private config: CacheConfig;
|
||||
|
||||
/** 统计信息 */
|
||||
private stats = {
|
||||
hits: 0,
|
||||
misses: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param config 缓存配置
|
||||
*/
|
||||
constructor(config: CacheConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存项
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @param id 缓存项ID
|
||||
* @returns 缓存项或null
|
||||
*/
|
||||
get(id: string | number): T | null {
|
||||
const node = this.items.get(id);
|
||||
|
||||
if (!node) {
|
||||
this.stats.misses++;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (this.isExpired(node.value)) {
|
||||
this.remove(id);
|
||||
this.stats.misses++;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新访问时间
|
||||
node.value.lastAccessed = new Date();
|
||||
|
||||
// 将节点移动到链表头部(最近访问)
|
||||
this.accessList.moveToHead(node);
|
||||
|
||||
this.stats.hits++;
|
||||
return node.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置缓存项
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @param id 缓存项ID
|
||||
* @param item 缓存项
|
||||
*/
|
||||
set(id: string | number, item: T): void {
|
||||
const existingNode = this.items.get(id);
|
||||
|
||||
// 如果已存在,更新值并移动到头部
|
||||
if (existingNode) {
|
||||
existingNode.value = item;
|
||||
this.accessList.moveToHead(existingNode);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查容量,必要时驱逐最旧的项
|
||||
while (this.items.size >= this.config.maxSize) {
|
||||
this.evictLeastRecentlyUsed();
|
||||
}
|
||||
|
||||
// 创建新节点并添加到头部
|
||||
const newNode = this.accessList.createNode(id, item);
|
||||
this.accessList.addToHead(newNode);
|
||||
this.items.set(id, newNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除缓存项
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @param id 缓存项ID
|
||||
* @returns 是否成功移除
|
||||
*/
|
||||
remove(id: string | number): boolean {
|
||||
const node = this.items.get(id);
|
||||
if (!node) return false;
|
||||
|
||||
// 从链表中移除
|
||||
this.accessList.removeNode(node);
|
||||
|
||||
// 从Map中移除
|
||||
this.items.delete(id);
|
||||
|
||||
// 调用清理逻辑
|
||||
this.disposeItem(node.value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否存在
|
||||
* 时间复杂度: O(1)
|
||||
*
|
||||
* @param id 缓存项ID
|
||||
* @returns 是否存在
|
||||
*/
|
||||
has(id: string | number): boolean {
|
||||
const node = this.items.get(id);
|
||||
if (!node) return false;
|
||||
|
||||
if (this.isExpired(node.value)) {
|
||||
this.remove(id);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
clear(): void {
|
||||
// 清理所有项
|
||||
for (const node of this.items.values()) {
|
||||
this.disposeItem(node.value);
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
this.accessList.clear();
|
||||
this.stats.hits = 0;
|
||||
this.stats.misses = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有项
|
||||
* 按访问顺序返回(最近访问的在前)
|
||||
*
|
||||
* @returns 所有缓存项
|
||||
*/
|
||||
getAll(): T[] {
|
||||
return this.accessList.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存大小
|
||||
*
|
||||
* @returns 当前缓存项数量
|
||||
*/
|
||||
size(): number {
|
||||
return this.items.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*
|
||||
* @returns 缓存统计信息
|
||||
*/
|
||||
getStats(): CacheStats {
|
||||
const total = this.stats.hits + this.stats.misses;
|
||||
return {
|
||||
size: this.items.size,
|
||||
maxSize: this.config.maxSize,
|
||||
hits: this.stats.hits,
|
||||
misses: this.stats.misses,
|
||||
hitRate: total > 0 ? this.stats.hits / total : 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期项
|
||||
*
|
||||
* @returns 清理的项数量
|
||||
*/
|
||||
cleanup(): number {
|
||||
let cleanedCount = 0;
|
||||
|
||||
if (!this.config.ttl) return cleanedCount;
|
||||
|
||||
// 收集过期的键
|
||||
const expiredKeys: (string | number)[] = [];
|
||||
for (const [id, node] of this.items.entries()) {
|
||||
if (this.isExpired(node.value)) {
|
||||
expiredKeys.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除过期项
|
||||
for (const key of expiredKeys) {
|
||||
if (this.remove(key)) {
|
||||
cleanedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return cleanedCount;
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
/**
|
||||
* 检查项是否过期
|
||||
*
|
||||
* @param item 缓存项
|
||||
* @returns 是否过期
|
||||
*/
|
||||
private isExpired(item: T): boolean {
|
||||
if (!this.config.ttl) return false;
|
||||
return Date.now() - item.lastAccessed.getTime() > this.config.ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 驱逐最近最少使用的项
|
||||
*/
|
||||
private evictLeastRecentlyUsed(): void {
|
||||
const tailNode = this.accessList.removeTail();
|
||||
if (tailNode) {
|
||||
// 调用驱逐回调
|
||||
if (this.config.onEvict) {
|
||||
this.config.onEvict(tailNode.value);
|
||||
}
|
||||
|
||||
// 从Map中移除
|
||||
this.items.delete(tailNode.key);
|
||||
|
||||
// 清理资源
|
||||
this.disposeItem(tailNode.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存项资源
|
||||
*
|
||||
* @param item 要清理的缓存项
|
||||
*/
|
||||
private disposeItem(item: T): void {
|
||||
if (this.isDisposable(item)) {
|
||||
try {
|
||||
const result = item.dispose();
|
||||
if (result instanceof Promise) {
|
||||
result.catch(error => {
|
||||
console.warn('Failed to dispose cache item:', error);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to dispose cache item:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查项是否可清理
|
||||
*
|
||||
* @param item 缓存项
|
||||
* @returns 是否可清理
|
||||
*/
|
||||
private isDisposable(item: T): item is T & DisposableCacheItem {
|
||||
return 'dispose' in item && typeof (item as any).dispose === 'function';
|
||||
}
|
||||
}
|
||||
263
frontend/src/common/cache/manager.ts
vendored
Normal file
263
frontend/src/common/cache/manager.ts
vendored
Normal file
@@ -0,0 +1,263 @@
|
||||
import type { CacheItem, CacheConfig, CacheStats, CacheStrategy } from './interfaces';
|
||||
import { LruCache } from './lruCache';
|
||||
|
||||
/**
|
||||
* 缓存管理器
|
||||
* 统一管理多个缓存实例,提供全局缓存操作和自动清理功能
|
||||
* 支持不同的缓存策略,默认使用LRU缓存
|
||||
*/
|
||||
export class CacheManager {
|
||||
/** 存储所有缓存实例的Map */
|
||||
private caches = new Map<string, CacheStrategy<any>>();
|
||||
|
||||
/** 自动清理定时器ID */
|
||||
private cleanupInterval?: number;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param options 配置选项
|
||||
* @param options.cleanupInterval 自动清理间隔(毫秒),默认不启用
|
||||
*/
|
||||
constructor(options?: {
|
||||
/** 自动清理间隔(毫秒),默认 5 分钟 */
|
||||
cleanupInterval?: number;
|
||||
}) {
|
||||
if (options?.cleanupInterval) {
|
||||
this.startAutoCleanup(options.cleanupInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或获取缓存实例
|
||||
* 如果缓存不存在且提供了配置,则创建新的缓存实例
|
||||
*
|
||||
* @template T 缓存项类型
|
||||
* @param name 缓存名称
|
||||
* @param config 缓存配置(仅在创建新缓存时需要)
|
||||
* @param strategy 缓存策略构造函数,默认使用LruCache
|
||||
* @returns 缓存实例
|
||||
* @throws 如果缓存不存在且未提供配置
|
||||
* @example
|
||||
* ```typescript
|
||||
* const userCache = manager.getCache<UserCacheItem>('users', {
|
||||
* maxSize: 100,
|
||||
* ttl: 5 * 60 * 1000 // 5分钟
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
getCache<T extends CacheItem>(
|
||||
name: string,
|
||||
config?: CacheConfig,
|
||||
strategy: new (config: CacheConfig) => CacheStrategy<T> = LruCache
|
||||
): CacheStrategy<T> {
|
||||
if (!this.caches.has(name)) {
|
||||
if (!config) {
|
||||
throw new Error(`Cache "${name}" does not exist and no config provided`);
|
||||
}
|
||||
this.caches.set(name, new strategy(config));
|
||||
}
|
||||
return this.caches.get(name)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的缓存实例
|
||||
* 如果同名缓存已存在,则抛出错误
|
||||
*
|
||||
* @template T 缓存项类型
|
||||
* @param name 缓存名称
|
||||
* @param config 缓存配置
|
||||
* @param strategy 缓存策略构造函数,默认使用LruCache
|
||||
* @returns 新创建的缓存实例
|
||||
* @throws 如果同名缓存已存在
|
||||
* @example
|
||||
* ```typescript
|
||||
* const productCache = manager.createCache<ProductCacheItem>('products', {
|
||||
* maxSize: 200,
|
||||
* ttl: 10 * 60 * 1000 // 10分钟
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
createCache<T extends CacheItem>(
|
||||
name: string,
|
||||
config: CacheConfig,
|
||||
strategy: new (config: CacheConfig) => CacheStrategy<T> = LruCache
|
||||
): CacheStrategy<T> {
|
||||
if (this.caches.has(name)) {
|
||||
throw new Error(`Cache "${name}" already exists`);
|
||||
}
|
||||
const cache = new strategy(config);
|
||||
this.caches.set(name, cache);
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存实例
|
||||
* 会先清空缓存内容,然后从管理器中移除
|
||||
*
|
||||
* @param name 缓存名称
|
||||
* @returns 是否成功删除
|
||||
* @example
|
||||
* ```typescript
|
||||
* const removed = manager.removeCache('temp-cache');
|
||||
* console.log(removed); // true 或 false
|
||||
* ```
|
||||
*/
|
||||
removeCache(name: string): boolean {
|
||||
const cache = this.caches.get(name);
|
||||
if (cache) {
|
||||
cache.clear();
|
||||
this.caches.delete(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存是否存在
|
||||
*
|
||||
* @param name 缓存名称
|
||||
* @returns 是否存在
|
||||
* @example
|
||||
* ```typescript
|
||||
* if (manager.hasCache('users')) {
|
||||
* const userCache = manager.getCache('users');
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
hasCache(name: string): boolean {
|
||||
return this.caches.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有缓存名称
|
||||
*
|
||||
* @returns 缓存名称数组
|
||||
* @example
|
||||
* ```typescript
|
||||
* const cacheNames = manager.getCacheNames();
|
||||
* console.log('Active caches:', cacheNames);
|
||||
* ```
|
||||
*/
|
||||
getCacheNames(): string[] {
|
||||
return Array.from(this.caches.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有缓存
|
||||
* 清空所有缓存实例的内容,但不删除缓存实例本身
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* manager.clearAll(); // 清空所有缓存内容
|
||||
* ```
|
||||
*/
|
||||
clearAll(): void {
|
||||
for (const cache of this.caches.values()) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有缓存的统计信息
|
||||
*
|
||||
* @returns 包含所有缓存统计信息的对象
|
||||
* @example
|
||||
* ```typescript
|
||||
* const stats = manager.getAllStats();
|
||||
* console.log('Cache stats:', stats);
|
||||
* // 输出: { users: { size: 50, hits: 100, ... }, products: { ... } }
|
||||
* ```
|
||||
*/
|
||||
getAllStats(): Record<string, CacheStats> {
|
||||
const stats: Record<string, CacheStats> = {};
|
||||
for (const [name, cache] of this.caches.entries()) {
|
||||
stats[name] = cache.getStats();
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有缓存中的过期项
|
||||
*
|
||||
* @returns 包含每个缓存清理项数量的对象
|
||||
* @example
|
||||
* ```typescript
|
||||
* const results = manager.cleanupAll();
|
||||
* console.log('Cleanup results:', results);
|
||||
* // 输出: { users: 5, products: 2 } // 表示清理的项数量
|
||||
* ```
|
||||
*/
|
||||
cleanupAll(): Record<string, number> {
|
||||
const results: Record<string, number> = {};
|
||||
for (const [name, cache] of this.caches.entries()) {
|
||||
results[name] = cache.cleanup();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动自动清理
|
||||
* 定期清理所有缓存中的过期项
|
||||
*
|
||||
* @param interval 清理间隔(毫秒)
|
||||
* @example
|
||||
* ```typescript
|
||||
* manager.startAutoCleanup(5 * 60 * 1000); // 每5分钟清理一次
|
||||
* ```
|
||||
*/
|
||||
startAutoCleanup(interval: number): void {
|
||||
this.stopAutoCleanup();
|
||||
this.cleanupInterval = window.setInterval(() => {
|
||||
this.cleanupAll();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止自动清理
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* manager.stopAutoCleanup();
|
||||
* ```
|
||||
*/
|
||||
stopAutoCleanup(): void {
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
* 停止自动清理,清空所有缓存,并移除所有缓存实例
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* manager.destroy(); // 完全清理管理器
|
||||
* ```
|
||||
*/
|
||||
destroy(): void {
|
||||
this.stopAutoCleanup();
|
||||
this.clearAll();
|
||||
this.caches.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局缓存管理器实例
|
||||
* 提供开箱即用的缓存管理功能,默认启用5分钟自动清理
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { globalCacheManager } from './cache';
|
||||
*
|
||||
* const userCache = globalCacheManager.getCache('users', {
|
||||
* maxSize: 100,
|
||||
* ttl: 5 * 60 * 1000
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const globalCacheManager = new CacheManager({
|
||||
cleanupInterval: 5 * 60 * 1000 // 5 分钟
|
||||
});
|
||||
39
frontend/src/common/cache/types.ts
vendored
39
frontend/src/common/cache/types.ts
vendored
@@ -1,39 +0,0 @@
|
||||
// 缓存项基础接口
|
||||
export interface CacheItem {
|
||||
/** 缓存项的唯一标识 */
|
||||
id: string | number;
|
||||
/** 最后访问时间 */
|
||||
lastAccessed: Date;
|
||||
/** 创建时间 */
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// 可清理的缓存项接口
|
||||
export interface DisposableCacheItem extends CacheItem {
|
||||
/** 清理资源的方法 */
|
||||
dispose(): void | Promise<void>;
|
||||
}
|
||||
|
||||
// 缓存配置
|
||||
export interface CacheConfig {
|
||||
/** 最大缓存数量 */
|
||||
maxSize: number;
|
||||
/** 生存时间(毫秒),可选 */
|
||||
ttl?: number;
|
||||
/** 驱逐回调函数,可选 */
|
||||
onEvict?: (item: any) => void | Promise<void>;
|
||||
}
|
||||
|
||||
// 缓存统计信息
|
||||
export interface CacheStats {
|
||||
/** 当前缓存项数量 */
|
||||
size: number;
|
||||
/** 最大容量 */
|
||||
maxSize: number;
|
||||
/** 命中次数 */
|
||||
hits: number;
|
||||
/** 未命中次数 */
|
||||
misses: number;
|
||||
/** 命中率 */
|
||||
hitRate: number;
|
||||
}
|
||||
116
frontend/src/common/cache/utils.ts
vendored
116
frontend/src/common/cache/utils.ts
vendored
@@ -1,7 +1,42 @@
|
||||
import type { CacheItem } from './types';
|
||||
import type { CacheItem } from './interfaces';
|
||||
|
||||
/**
|
||||
* 简单哈希函数
|
||||
* 使用FNV-1a算法生成哈希值,提供良好的分布性
|
||||
*
|
||||
* @param content 要哈希的内容
|
||||
* @returns 哈希值字符串
|
||||
* @example
|
||||
* ```typescript
|
||||
* const hash = createHash('some content');
|
||||
* // 结果: 类似 '1a2b3c4d'
|
||||
* ```
|
||||
*/
|
||||
export function createHash(content: string): string {
|
||||
const FNV_OFFSET_BASIS = 2166136261;
|
||||
const FNV_PRIME = 16777619;
|
||||
|
||||
let hash = FNV_OFFSET_BASIS;
|
||||
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
hash ^= content.charCodeAt(i);
|
||||
hash = (hash * FNV_PRIME) >>> 0; // 无符号32位整数
|
||||
}
|
||||
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存键
|
||||
* 将多个部分组合成一个缓存键
|
||||
*
|
||||
* @param parts 键的各个部分
|
||||
* @returns 组合后的缓存键
|
||||
* @example
|
||||
* ```typescript
|
||||
* const key = generateCacheKey('user', 123, 'profile');
|
||||
* // 结果: 'user:123:profile'
|
||||
* ```
|
||||
*/
|
||||
export function generateCacheKey(...parts: (string | number)[]): string {
|
||||
return parts.join(':');
|
||||
@@ -9,6 +44,17 @@ export function generateCacheKey(...parts: (string | number)[]): string {
|
||||
|
||||
/**
|
||||
* 创建基础缓存项
|
||||
* 为任意数据创建符合CacheItem接口的缓存项
|
||||
*
|
||||
* @template T 数据类型
|
||||
* @param id 缓存项的唯一标识
|
||||
* @param data 要缓存的数据
|
||||
* @returns 包含缓存元数据的缓存项
|
||||
* @example
|
||||
* ```typescript
|
||||
* const cacheItem = createCacheItem('user:123', { name: 'John', age: 30 });
|
||||
* // 结果包含 id, lastAccessed, createdAt 以及原始数据
|
||||
* ```
|
||||
*/
|
||||
export function createCacheItem<T extends Record<string, any>>(
|
||||
id: string | number,
|
||||
@@ -25,6 +71,16 @@ export function createCacheItem<T extends Record<string, any>>(
|
||||
|
||||
/**
|
||||
* 计算缓存命中率
|
||||
* 根据命中次数和未命中次数计算命中率
|
||||
*
|
||||
* @param hits 命中次数
|
||||
* @param misses 未命中次数
|
||||
* @returns 命中率(0-1之间的数值)
|
||||
* @example
|
||||
* ```typescript
|
||||
* const hitRate = calculateHitRate(80, 20);
|
||||
* // 结果: 0.8 (80% 命中率)
|
||||
* ```
|
||||
*/
|
||||
export function calculateHitRate(hits: number, misses: number): number {
|
||||
const total = hits + misses;
|
||||
@@ -33,6 +89,16 @@ export function calculateHitRate(hits: number, misses: number): number {
|
||||
|
||||
/**
|
||||
* 格式化缓存大小
|
||||
* 将字节数格式化为人类可读的大小字符串
|
||||
*
|
||||
* @param size 大小(字节)
|
||||
* @returns 格式化后的大小字符串
|
||||
* @example
|
||||
* ```typescript
|
||||
* formatCacheSize(1024); // '1.0 KB'
|
||||
* formatCacheSize(1048576); // '1.0 MB'
|
||||
* formatCacheSize(500); // '500 B'
|
||||
* ```
|
||||
*/
|
||||
export function formatCacheSize(size: number): string {
|
||||
if (size < 1024) return `${size} B`;
|
||||
@@ -43,29 +109,34 @@ export function formatCacheSize(size: number): string {
|
||||
|
||||
/**
|
||||
* 检查项是否过期
|
||||
* 根据最后访问时间和TTL判断缓存项是否过期
|
||||
*
|
||||
* @param item 缓存项
|
||||
* @param ttl 生存时间(毫秒)
|
||||
* @returns 是否过期
|
||||
* @example
|
||||
* ```typescript
|
||||
* const item = createCacheItem('test', { data: 'value' });
|
||||
* const expired = isExpired(item, 5000); // 5秒TTL
|
||||
* ```
|
||||
*/
|
||||
export function isExpired(item: CacheItem, ttl: number): boolean {
|
||||
return Date.now() - item.lastAccessed.getTime() > ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建内容哈希(简单实现)
|
||||
*/
|
||||
export function createContentHash(content: string): string {
|
||||
let hash = 0;
|
||||
if (content.length === 0) return hash.toString();
|
||||
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const char = content.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // 转换为 32 位整数
|
||||
}
|
||||
|
||||
return Math.abs(hash).toString(36);
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数,用于缓存操作
|
||||
* 在指定时间内多次调用只执行最后一次
|
||||
*
|
||||
* @template T 函数类型
|
||||
* @param func 要防抖的函数
|
||||
* @param wait 等待时间(毫秒)
|
||||
* @returns 防抖后的函数
|
||||
* @example
|
||||
* ```typescript
|
||||
* const debouncedSave = debounce(saveToCache, 300);
|
||||
* debouncedSave(data); // 只有在300ms内没有新调用时才执行
|
||||
* ```
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
@@ -81,6 +152,17 @@ export function debounce<T extends (...args: any[]) => any>(
|
||||
|
||||
/**
|
||||
* 节流函数,用于缓存清理
|
||||
* 在指定时间内最多执行一次
|
||||
*
|
||||
* @template T 函数类型
|
||||
* @param func 要节流的函数
|
||||
* @param limit 限制时间间隔(毫秒)
|
||||
* @returns 节流后的函数
|
||||
* @example
|
||||
* ```typescript
|
||||
* const throttledCleanup = throttle(cleanupCache, 1000);
|
||||
* throttledCleanup(); // 1秒内最多执行一次
|
||||
* ```
|
||||
*/
|
||||
export function throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
|
||||
Reference in New Issue
Block a user