🎨 Optimize code structure
This commit is contained in:
3
frontend/src/common/async-operation/index.ts
Normal file
3
frontend/src/common/async-operation/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { AsyncOperationManager } from './manager';
|
||||
export * from './types';
|
||||
export { AsyncOperationManager as default } from './manager';
|
||||
296
frontend/src/common/async-operation/manager.ts
Normal file
296
frontend/src/common/async-operation/manager.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
import {
|
||||
OperationStatus,
|
||||
OperationInfo,
|
||||
AsyncOperationManagerConfig,
|
||||
OperationCallbacks,
|
||||
OperationExecutor,
|
||||
OperationResult
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 异步操作管理器
|
||||
* 用于控制异步操作的竞态条件,确保操作的正确性和一致性
|
||||
*/
|
||||
export class AsyncOperationManager {
|
||||
private operationSequence = 0;
|
||||
private pendingOperations = new Map<number, OperationInfo>();
|
||||
private currentResourceOperation = new Map<string | number, number>();
|
||||
private config: Required<AsyncOperationManagerConfig>;
|
||||
private callbacks: OperationCallbacks;
|
||||
|
||||
constructor(
|
||||
config: AsyncOperationManagerConfig = {},
|
||||
callbacks: OperationCallbacks = {}
|
||||
) {
|
||||
this.config = {
|
||||
timeout: 0,
|
||||
autoCleanup: true,
|
||||
maxConcurrent: 0,
|
||||
debug: false,
|
||||
...config
|
||||
};
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成新的操作ID
|
||||
*/
|
||||
private getNextOperationId(): number {
|
||||
return ++this.operationSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志(调试模式下)
|
||||
*/
|
||||
private log(message: string, ...args: any[]): void {
|
||||
if (this.config.debug) {
|
||||
console.log(`[AsyncOperationManager] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建操作信息
|
||||
*/
|
||||
private createOperation(
|
||||
resourceId: string | number,
|
||||
type?: string
|
||||
): OperationInfo {
|
||||
const operation: OperationInfo = {
|
||||
id: this.getNextOperationId(),
|
||||
resourceId,
|
||||
status: OperationStatus.PENDING,
|
||||
abortController: new AbortController(),
|
||||
createdAt: Date.now(),
|
||||
type
|
||||
};
|
||||
|
||||
this.log(`Created operation ${operation.id} for resource ${resourceId}`, operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已完成的操作
|
||||
*/
|
||||
private cleanupCompletedOperations(): void {
|
||||
if (!this.config.autoCleanup) return;
|
||||
|
||||
const completedStatuses = [
|
||||
OperationStatus.COMPLETED,
|
||||
OperationStatus.CANCELLED,
|
||||
OperationStatus.FAILED
|
||||
];
|
||||
|
||||
for (const [id, operation] of this.pendingOperations.entries()) {
|
||||
if (completedStatuses.includes(operation.status)) {
|
||||
this.pendingOperations.delete(id);
|
||||
this.log(`Cleaned up operation ${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消指定资源的所有操作(除了指定的操作ID)
|
||||
*/
|
||||
public cancelResourceOperations(
|
||||
resourceId: string | number,
|
||||
excludeOperationId?: number
|
||||
): void {
|
||||
this.log(`Cancelling operations for resource ${resourceId}, exclude: ${excludeOperationId}`);
|
||||
|
||||
for (const [id, operation] of this.pendingOperations.entries()) {
|
||||
if (
|
||||
operation.resourceId === resourceId &&
|
||||
id !== excludeOperationId &&
|
||||
operation.status === OperationStatus.RUNNING
|
||||
) {
|
||||
this.cancelOperation(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消指定操作
|
||||
*/
|
||||
public cancelOperation(operationId: number): boolean {
|
||||
const operation = this.pendingOperations.get(operationId);
|
||||
if (!operation) return false;
|
||||
|
||||
if (operation.status === OperationStatus.RUNNING) {
|
||||
operation.abortController.abort();
|
||||
operation.status = OperationStatus.CANCELLED;
|
||||
this.log(`Cancelled operation ${operationId}`);
|
||||
|
||||
this.callbacks.onCancel?.(operation);
|
||||
this.cleanupCompletedOperations();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有操作
|
||||
*/
|
||||
public cancelAllOperations(): void {
|
||||
this.log('Cancelling all operations');
|
||||
|
||||
for (const [id] of this.pendingOperations.entries()) {
|
||||
this.cancelOperation(id);
|
||||
}
|
||||
|
||||
this.currentResourceOperation.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查操作是否仍然有效
|
||||
*/
|
||||
public isOperationValid(
|
||||
operationId: number,
|
||||
resourceId?: string | number
|
||||
): boolean {
|
||||
const operation = this.pendingOperations.get(operationId);
|
||||
|
||||
if (!operation) return false;
|
||||
if (operation.abortController.signal.aborted) return false;
|
||||
if (operation.status !== OperationStatus.RUNNING) return false;
|
||||
|
||||
// 如果指定了资源ID,检查是否为当前资源的活跃操作
|
||||
if (resourceId !== undefined) {
|
||||
const currentOperationId = this.currentResourceOperation.get(resourceId);
|
||||
if (currentOperationId !== operationId) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行异步操作
|
||||
*/
|
||||
public async executeOperation<T = any>(
|
||||
resourceId: string | number,
|
||||
executor: OperationExecutor<T>,
|
||||
operationType?: string
|
||||
): Promise<OperationResult<T>> {
|
||||
// 检查并发限制
|
||||
if (this.config.maxConcurrent > 0) {
|
||||
const runningCount = Array.from(this.pendingOperations.values())
|
||||
.filter(op => op.status === OperationStatus.RUNNING).length;
|
||||
|
||||
if (runningCount >= this.config.maxConcurrent) {
|
||||
throw new Error(`Maximum concurrent operations limit reached: ${this.config.maxConcurrent}`);
|
||||
}
|
||||
}
|
||||
|
||||
const operation = this.createOperation(resourceId, operationType);
|
||||
|
||||
try {
|
||||
// 取消同一资源的其他操作
|
||||
this.cancelResourceOperations(resourceId, operation.id);
|
||||
|
||||
// 设置当前资源的活跃操作
|
||||
this.currentResourceOperation.set(resourceId, operation.id);
|
||||
|
||||
// 添加到待处理操作列表
|
||||
this.pendingOperations.set(operation.id, operation);
|
||||
|
||||
// 设置超时
|
||||
if (this.config.timeout > 0) {
|
||||
setTimeout(() => {
|
||||
if (this.isOperationValid(operation.id)) {
|
||||
this.cancelOperation(operation.id);
|
||||
}
|
||||
}, this.config.timeout);
|
||||
}
|
||||
|
||||
// 更新状态为运行中
|
||||
operation.status = OperationStatus.RUNNING;
|
||||
this.callbacks.onStart?.(operation);
|
||||
this.log(`Started operation ${operation.id} for resource ${resourceId}`);
|
||||
|
||||
// 执行操作
|
||||
const result = await executor(operation.abortController.signal, operation.id);
|
||||
|
||||
// 检查操作是否仍然有效
|
||||
if (!this.isOperationValid(operation.id, resourceId)) {
|
||||
throw new Error('Operation was cancelled');
|
||||
}
|
||||
|
||||
// 操作成功完成
|
||||
operation.status = OperationStatus.COMPLETED;
|
||||
this.callbacks.onComplete?.(operation);
|
||||
this.log(`Completed operation ${operation.id}`);
|
||||
|
||||
this.cleanupCompletedOperations();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
operation
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
|
||||
if (operation.abortController.signal.aborted) {
|
||||
operation.status = OperationStatus.CANCELLED;
|
||||
this.log(`Operation ${operation.id} was cancelled`);
|
||||
} else {
|
||||
operation.status = OperationStatus.FAILED;
|
||||
this.callbacks.onError?.(operation, err);
|
||||
this.log(`Operation ${operation.id} failed:`, err.message);
|
||||
}
|
||||
|
||||
this.cleanupCompletedOperations();
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: err,
|
||||
operation
|
||||
};
|
||||
} finally {
|
||||
// 清理当前资源操作记录
|
||||
if (this.currentResourceOperation.get(resourceId) === operation.id) {
|
||||
this.currentResourceOperation.delete(resourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作信息
|
||||
*/
|
||||
public getOperation(operationId: number): OperationInfo | undefined {
|
||||
return this.pendingOperations.get(operationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源的当前操作ID
|
||||
*/
|
||||
public getCurrentOperationId(resourceId: string | number): number | undefined {
|
||||
return this.currentResourceOperation.get(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有待处理操作
|
||||
*/
|
||||
public getPendingOperations(): OperationInfo[] {
|
||||
return Array.from(this.pendingOperations.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行中的操作数量
|
||||
*/
|
||||
public getRunningOperationsCount(): number {
|
||||
return Array.from(this.pendingOperations.values())
|
||||
.filter(op => op.status === OperationStatus.RUNNING).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器,取消所有操作
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.log('Destroying AsyncOperationManager');
|
||||
this.cancelAllOperations();
|
||||
this.pendingOperations.clear();
|
||||
this.currentResourceOperation.clear();
|
||||
}
|
||||
}
|
||||
82
frontend/src/common/async-operation/types.ts
Normal file
82
frontend/src/common/async-operation/types.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 异步操作竞态条件控制相关类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 操作状态枚举
|
||||
*/
|
||||
export enum OperationStatus {
|
||||
PENDING = 'pending',
|
||||
RUNNING = 'running',
|
||||
COMPLETED = 'completed',
|
||||
CANCELLED = 'cancelled',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作信息接口
|
||||
*/
|
||||
export interface OperationInfo {
|
||||
/** 操作ID */
|
||||
id: number;
|
||||
/** 资源ID(如文档ID、用户ID等) */
|
||||
resourceId: string | number;
|
||||
/** 操作状态 */
|
||||
status: OperationStatus;
|
||||
/** 取消控制器 */
|
||||
abortController: AbortController;
|
||||
/** 创建时间 */
|
||||
createdAt: number;
|
||||
/** 操作类型(可选,用于调试) */
|
||||
type?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作管理器配置
|
||||
*/
|
||||
export interface AsyncOperationManagerConfig {
|
||||
/** 操作超时时间(毫秒),0表示不超时 */
|
||||
timeout?: number;
|
||||
/** 是否自动清理已完成的操作 */
|
||||
autoCleanup?: boolean;
|
||||
/** 最大并发操作数,0表示无限制 */
|
||||
maxConcurrent?: number;
|
||||
/** 调试模式 */
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作回调函数类型
|
||||
*/
|
||||
export interface OperationCallbacks {
|
||||
/** 操作开始回调 */
|
||||
onStart?: (operation: OperationInfo) => void;
|
||||
/** 操作完成回调 */
|
||||
onComplete?: (operation: OperationInfo) => void;
|
||||
/** 操作取消回调 */
|
||||
onCancel?: (operation: OperationInfo) => void;
|
||||
/** 操作失败回调 */
|
||||
onError?: (operation: OperationInfo, error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作执行器函数类型
|
||||
*/
|
||||
export type OperationExecutor<T = any> = (
|
||||
signal: AbortSignal,
|
||||
operationId: number
|
||||
) => Promise<T>;
|
||||
|
||||
/**
|
||||
* 操作结果类型
|
||||
*/
|
||||
export interface OperationResult<T = any> {
|
||||
/** 操作是否成功 */
|
||||
success: boolean;
|
||||
/** 操作结果数据 */
|
||||
data?: T;
|
||||
/** 错误信息 */
|
||||
error?: Error;
|
||||
/** 操作信息 */
|
||||
operation: OperationInfo;
|
||||
}
|
||||
106
frontend/src/common/cache/README.md
vendored
Normal file
106
frontend/src/common/cache/README.md
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
# 缓存系统
|
||||
|
||||
简洁高效的 LRU 缓存实现,支持泛型和自动清理。
|
||||
|
||||
## 特性
|
||||
|
||||
- 🚀 高性能 LRU 缓存算法
|
||||
- 🔧 TypeScript 泛型支持
|
||||
- 🧹 自动资源清理
|
||||
- 📊 缓存统计信息
|
||||
- ⏰ TTL 过期支持
|
||||
- 🎯 简洁易用的 API
|
||||
|
||||
## 基础用法
|
||||
|
||||
### 创建缓存
|
||||
|
||||
```typescript
|
||||
import { LRUCache, CacheManager, createCacheItem } from '@/common/cache';
|
||||
|
||||
// 直接创建缓存
|
||||
const cache = new LRUCache({
|
||||
maxSize: 100,
|
||||
ttl: 5 * 60 * 1000, // 5 分钟
|
||||
onEvict: (item) => console.log('Evicted:', item)
|
||||
});
|
||||
|
||||
// 使用缓存管理器
|
||||
const manager = new CacheManager();
|
||||
const myCache = manager.createCache('myCache', { maxSize: 50 });
|
||||
```
|
||||
|
||||
### 缓存操作
|
||||
|
||||
```typescript
|
||||
// 创建缓存项
|
||||
const item = createCacheItem('key1', {
|
||||
name: 'example',
|
||||
data: { foo: 'bar' }
|
||||
});
|
||||
|
||||
// 设置缓存
|
||||
cache.set('key1', item);
|
||||
|
||||
// 获取缓存
|
||||
const cached = cache.get('key1');
|
||||
|
||||
// 检查存在
|
||||
if (cache.has('key1')) {
|
||||
// 处理逻辑
|
||||
}
|
||||
|
||||
// 移除缓存
|
||||
cache.remove('key1');
|
||||
```
|
||||
|
||||
### 自动清理资源
|
||||
|
||||
```typescript
|
||||
interface MyItem extends CacheItem, DisposableCacheItem {
|
||||
resource: SomeResource;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
const item: MyItem = {
|
||||
id: 'resource1',
|
||||
lastAccessed: new Date(),
|
||||
createdAt: new Date(),
|
||||
resource: new SomeResource(),
|
||||
dispose() {
|
||||
this.resource.cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
// 当项被驱逐或移除时,dispose 方法会自动调用
|
||||
cache.set('resource1', item);
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### LRUCache
|
||||
|
||||
- `get(id)` - 获取缓存项
|
||||
- `set(id, item)` - 设置缓存项
|
||||
- `remove(id)` - 移除缓存项
|
||||
- `has(id)` - 检查是否存在
|
||||
- `clear()` - 清空缓存
|
||||
- `size()` - 获取缓存大小
|
||||
- `getStats()` - 获取统计信息
|
||||
- `cleanup()` - 清理过期项
|
||||
|
||||
### CacheManager
|
||||
|
||||
- `getCache(name, config?)` - 获取或创建缓存
|
||||
- `createCache(name, config)` - 创建新缓存
|
||||
- `removeCache(name)` - 删除缓存
|
||||
- `clearAll()` - 清空所有缓存
|
||||
- `getAllStats()` - 获取所有统计信息
|
||||
|
||||
## 工具函数
|
||||
|
||||
- `generateCacheKey(...parts)` - 生成缓存键
|
||||
- `createCacheItem(id, data)` - 创建缓存项
|
||||
- `createContentHash(content)` - 创建内容哈希
|
||||
- `debounce(func, wait)` - 防抖函数
|
||||
- `throttle(func, limit)` - 节流函数
|
||||
133
frontend/src/common/cache/cache-manager.ts
vendored
Normal file
133
frontend/src/common/cache/cache-manager.ts
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
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 分钟
|
||||
});
|
||||
19
frontend/src/common/cache/index.ts
vendored
Normal file
19
frontend/src/common/cache/index.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
export type {
|
||||
CacheItem,
|
||||
DisposableCacheItem,
|
||||
CacheConfig,
|
||||
CacheStats
|
||||
} from './types';
|
||||
export { LRUCache } from './lru-cache';
|
||||
export { CacheManager, globalCacheManager } from './cache-manager';
|
||||
|
||||
export {
|
||||
generateCacheKey,
|
||||
createCacheItem,
|
||||
calculateHitRate,
|
||||
formatCacheSize,
|
||||
isExpired,
|
||||
createContentHash,
|
||||
debounce,
|
||||
throttle
|
||||
} from './utils';
|
||||
200
frontend/src/common/cache/lru-cache.ts
vendored
Normal file
200
frontend/src/common/cache/lru-cache.ts
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
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';
|
||||
}
|
||||
}
|
||||
39
frontend/src/common/cache/types.ts
vendored
Normal file
39
frontend/src/common/cache/types.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// 缓存项基础接口
|
||||
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;
|
||||
}
|
||||
98
frontend/src/common/cache/utils.ts
vendored
Normal file
98
frontend/src/common/cache/utils.ts
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import type { CacheItem } from './types';
|
||||
|
||||
/**
|
||||
* 生成缓存键
|
||||
*/
|
||||
export function generateCacheKey(...parts: (string | number)[]): string {
|
||||
return parts.join(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基础缓存项
|
||||
*/
|
||||
export function createCacheItem<T extends Record<string, any>>(
|
||||
id: string | number,
|
||||
data: T
|
||||
): CacheItem & T {
|
||||
const now = new Date();
|
||||
return {
|
||||
id,
|
||||
lastAccessed: now,
|
||||
createdAt: now,
|
||||
...data
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算缓存命中率
|
||||
*/
|
||||
export function calculateHitRate(hits: number, misses: number): number {
|
||||
const total = hits + misses;
|
||||
return total > 0 ? hits / total : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化缓存大小
|
||||
*/
|
||||
export function formatCacheSize(size: number): string {
|
||||
if (size < 1024) return `${size} B`;
|
||||
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
|
||||
if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查项是否过期
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数,用于缓存操作
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: number | undefined;
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = window.setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数,用于缓存清理
|
||||
*/
|
||||
export function throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
limit: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let inThrottle: boolean;
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (!inThrottle) {
|
||||
func(...args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user