♻️ Refactor document selector and cache management logic

This commit is contained in:
2025-09-29 00:26:05 +08:00
parent bc0569af93
commit 3077d5a7c5
31 changed files with 3660 additions and 1382 deletions

View File

@@ -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)` - 创建缓存项

View File

@@ -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 分钟
});

View 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;
}
}

View File

@@ -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
View 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;
}

View File

@@ -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
View 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
View 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 分钟
});

View File

@@ -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;
}

View File

@@ -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,