♻️ Refactor document selector and cache management logic
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
export { AsyncOperationManager } from './manager';
|
||||
export * from './types';
|
||||
export { AsyncOperationManager as default } from './manager';
|
||||
@@ -1,296 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* 异步操作竞态条件控制相关类型定义
|
||||
*/
|
||||
|
||||
/**
|
||||
* 操作状态枚举
|
||||
*/
|
||||
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;
|
||||
}
|
||||
285
frontend/src/common/async/README.md
Normal file
285
frontend/src/common/async/README.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# 异步操作管理器 (AsyncOperationManager)
|
||||
|
||||
一个用于控制异步操作竞态条件的 TypeScript 模块,确保操作的正确性和一致性。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🚀 **竞态条件控制**: 自动取消同一资源的过时操作
|
||||
- 🔄 **操作生命周期管理**: 完整的状态跟踪和回调支持
|
||||
- 🎯 **资源隔离**: 基于资源ID的操作隔离机制
|
||||
- ⚡ **并发控制**: 支持最大并发数限制
|
||||
- ⏰ **超时处理**: 可配置的操作超时机制
|
||||
- 🧹 **内存管理**: 自动清理已完成的操作
|
||||
- 🐛 **调试友好**: 内置日志系统
|
||||
|
||||
## 安装和导入
|
||||
|
||||
```typescript
|
||||
import { AsyncOperationManager } from '@/common/async';
|
||||
// 或者
|
||||
import AsyncOperationManager from '@/common/async';
|
||||
```
|
||||
|
||||
## 基本用法
|
||||
|
||||
### 创建管理器实例
|
||||
|
||||
```typescript
|
||||
const operationManager = new AsyncOperationManager({
|
||||
timeout: 5000, // 5秒超时
|
||||
autoCleanup: true, // 自动清理已完成操作
|
||||
maxConcurrent: 3, // 最大并发数
|
||||
debug: true // 启用调试日志
|
||||
});
|
||||
```
|
||||
|
||||
### 执行异步操作
|
||||
|
||||
```typescript
|
||||
const result = await operationManager.executeOperation(
|
||||
'document-123', // 资源ID
|
||||
async (signal, operationId) => {
|
||||
// 检查操作是否被取消
|
||||
if (signal.aborted) {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
|
||||
// 执行异步操作
|
||||
const data = await fetchData();
|
||||
|
||||
// 再次检查取消状态
|
||||
if (signal.aborted) {
|
||||
throw new Error('Operation cancelled');
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
'fetch-data' // 操作类型(可选,用于调试)
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log('操作成功:', result.data);
|
||||
} else {
|
||||
console.error('操作失败:', result.error);
|
||||
}
|
||||
```
|
||||
|
||||
## API 文档
|
||||
|
||||
### 构造函数
|
||||
|
||||
```typescript
|
||||
new AsyncOperationManager(config?, callbacks?)
|
||||
```
|
||||
|
||||
#### 配置选项 (AsyncOperationManagerConfig)
|
||||
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `timeout` | `number` | `0` | 操作超时时间(毫秒),0表示不超时 |
|
||||
| `autoCleanup` | `boolean` | `true` | 是否自动清理已完成的操作 |
|
||||
| `maxConcurrent` | `number` | `0` | 最大并发操作数,0表示无限制 |
|
||||
| `debug` | `boolean` | `false` | 是否启用调试模式 |
|
||||
|
||||
#### 回调函数 (OperationCallbacks)
|
||||
|
||||
```typescript
|
||||
{
|
||||
onStart?: (operation: OperationInfo) => void; // 操作开始
|
||||
onComplete?: (operation: OperationInfo) => void; // 操作完成
|
||||
onCancel?: (operation: OperationInfo) => void; // 操作取消
|
||||
onError?: (operation: OperationInfo, error: Error) => void; // 操作失败
|
||||
}
|
||||
```
|
||||
|
||||
### 主要方法
|
||||
|
||||
#### executeOperation<T>(resourceId, executor, operationType?)
|
||||
|
||||
执行异步操作的核心方法。
|
||||
|
||||
**参数:**
|
||||
- `resourceId: string | number` - 资源标识符
|
||||
- `executor: OperationExecutor<T>` - 操作执行函数
|
||||
- `operationType?: string` - 操作类型(可选)
|
||||
|
||||
**返回:** `Promise<OperationResult<T>>`
|
||||
|
||||
#### cancelResourceOperations(resourceId, excludeOperationId?)
|
||||
|
||||
取消指定资源的所有操作。
|
||||
|
||||
#### cancelOperation(operationId)
|
||||
|
||||
取消指定的操作。
|
||||
|
||||
#### cancelAllOperations()
|
||||
|
||||
取消所有正在进行的操作。
|
||||
|
||||
#### isOperationValid(operationId, resourceId?)
|
||||
|
||||
检查操作是否仍然有效。
|
||||
|
||||
### 查询方法
|
||||
|
||||
- `getOperation(operationId)` - 获取操作信息
|
||||
- `getCurrentOperationId(resourceId)` - 获取资源的当前操作ID
|
||||
- `getPendingOperations()` - 获取所有待处理操作
|
||||
- `getRunningOperationsCount()` - 获取运行中的操作数量
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. 编辑器文档切换
|
||||
|
||||
```typescript
|
||||
// 防止快速切换文档时的内容混乱
|
||||
const loadDocument = async (documentId: number) => {
|
||||
const result = await operationManager.executeOperation(
|
||||
documentId,
|
||||
async (signal) => {
|
||||
// 保存当前文档
|
||||
await saveCurrentDocument();
|
||||
|
||||
if (signal.aborted) return;
|
||||
|
||||
// 加载新文档
|
||||
const content = await loadDocumentContent(documentId);
|
||||
|
||||
if (signal.aborted) return;
|
||||
|
||||
return content;
|
||||
},
|
||||
'load-document'
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
updateEditor(result.data);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 搜索功能
|
||||
|
||||
```typescript
|
||||
// 取消过时的搜索请求
|
||||
const search = async (query: string) => {
|
||||
const result = await operationManager.executeOperation(
|
||||
'search',
|
||||
async (signal) => {
|
||||
const results = await searchAPI(query, { signal });
|
||||
return results;
|
||||
},
|
||||
'search-query'
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
displaySearchResults(result.data);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 数据加载
|
||||
|
||||
```typescript
|
||||
// 避免页面切换时的数据竞态
|
||||
const loadPageData = async (pageId: string) => {
|
||||
const result = await operationManager.executeOperation(
|
||||
`page-${pageId}`,
|
||||
async (signal) => {
|
||||
const [userData, pageContent, settings] = await Promise.all([
|
||||
fetchUserData(signal),
|
||||
fetchPageContent(pageId, signal),
|
||||
fetchSettings(signal)
|
||||
]);
|
||||
|
||||
return { userData, pageContent, settings };
|
||||
},
|
||||
'load-page'
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
renderPage(result.data);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 操作状态
|
||||
|
||||
操作在生命周期中会经历以下状态:
|
||||
|
||||
- `PENDING` - 待处理
|
||||
- `RUNNING` - 运行中
|
||||
- `COMPLETED` - 已完成
|
||||
- `CANCELLED` - 已取消
|
||||
- `FAILED` - 失败
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 合理使用资源ID
|
||||
|
||||
```typescript
|
||||
// ✅ 好的做法:使用具体的资源标识
|
||||
operationManager.executeOperation('document-123', executor);
|
||||
operationManager.executeOperation('user-456', executor);
|
||||
|
||||
// ❌ 避免:使用过于宽泛的标识
|
||||
operationManager.executeOperation('global', executor);
|
||||
```
|
||||
|
||||
### 2. 及时检查取消状态
|
||||
|
||||
```typescript
|
||||
// ✅ 在关键点检查取消状态
|
||||
const executor = async (signal) => {
|
||||
const step1 = await longRunningTask1();
|
||||
if (signal.aborted) throw new Error('Cancelled');
|
||||
|
||||
const step2 = await longRunningTask2();
|
||||
if (signal.aborted) throw new Error('Cancelled');
|
||||
|
||||
return processResults(step1, step2);
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 使用有意义的操作类型
|
||||
|
||||
```typescript
|
||||
// ✅ 使用描述性的操作类型
|
||||
operationManager.executeOperation(docId, executor, 'auto-save');
|
||||
operationManager.executeOperation(docId, executor, 'manual-save');
|
||||
operationManager.executeOperation(docId, executor, 'export-pdf');
|
||||
```
|
||||
|
||||
### 4. 适当的错误处理
|
||||
|
||||
```typescript
|
||||
const result = await operationManager.executeOperation(resourceId, executor);
|
||||
|
||||
if (!result.success) {
|
||||
if (result.operation.status === OperationStatus.CANCELLED) {
|
||||
// 操作被取消,通常不需要显示错误
|
||||
console.log('Operation was cancelled');
|
||||
} else {
|
||||
// 真正的错误,需要处理
|
||||
console.error('Operation failed:', result.error);
|
||||
showErrorMessage(result.error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **内存管理**: 启用 `autoCleanup` 以防止内存泄漏
|
||||
2. **并发控制**: 根据应用需求设置合适的 `maxConcurrent` 值
|
||||
3. **超时设置**: 为长时间运行的操作设置合理的超时时间
|
||||
4. **错误处理**: 区分取消和真正的错误,提供适当的用户反馈
|
||||
5. **调试模式**: 在开发环境启用 `debug` 模式以便排查问题
|
||||
|
||||
## 类型定义
|
||||
|
||||
完整的类型定义请参考 `types.ts` 文件。
|
||||
|
||||
## 许可证
|
||||
|
||||
本模块遵循项目的整体许可证。
|
||||
78
frontend/src/common/async/index.ts
Normal file
78
frontend/src/common/async/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 异步操作管理模块
|
||||
*
|
||||
* 该模块提供了用于管理异步操作竞态条件的完整解决方案。
|
||||
* 主要用于防止同一资源上的并发操作冲突,确保操作的正确性和一致性。
|
||||
*
|
||||
* @fileoverview 异步操作管理模块入口文件
|
||||
* @author VoidRaft Team
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { AsyncOperationManager, OperationStatus } from '@/common/async';
|
||||
*
|
||||
* // 创建管理器实例
|
||||
* const manager = new AsyncOperationManager({
|
||||
* timeout: 30000,
|
||||
* maxConcurrent: 5,
|
||||
* debug: true
|
||||
* });
|
||||
*
|
||||
* // 执行异步操作
|
||||
* const result = await manager.executeOperation(
|
||||
* 'document-123',
|
||||
* async (signal, operationId) => {
|
||||
* if (signal.aborted) throw new Error('Cancelled');
|
||||
* return await saveDocument(data);
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* if (result.success) {
|
||||
* console.log('Operation completed:', result.data);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* 导出异步操作管理器类
|
||||
*
|
||||
* AsyncOperationManager 是该模块的核心类,提供了完整的异步操作管理功能。
|
||||
*
|
||||
* @see {@link AsyncOperationManager} 异步操作管理器类的详细文档
|
||||
*/
|
||||
export { AsyncOperationManager } from './manager';
|
||||
|
||||
/**
|
||||
* 导出所有类型定义
|
||||
*
|
||||
* 包括操作状态枚举、接口定义、配置选项等所有相关类型。
|
||||
* 这些类型为使用异步操作管理器提供了完整的 TypeScript 类型支持。
|
||||
*
|
||||
* 导出的类型包括:
|
||||
* - OperationStatus: 操作状态枚举
|
||||
* - OperationInfo: 操作信息接口
|
||||
* - AsyncOperationManagerConfig: 管理器配置接口
|
||||
* - OperationCallbacks: 操作回调函数接口
|
||||
* - OperationExecutor: 操作执行器函数类型
|
||||
* - OperationResult: 操作结果接口
|
||||
*
|
||||
* @see {@link ./types} 类型定义文件
|
||||
*/
|
||||
export * from './types';
|
||||
|
||||
/**
|
||||
* 默认导出异步操作管理器类
|
||||
*
|
||||
* 提供默认导出,方便使用 `import AsyncOperationManager from '@/common/async'` 的方式导入。
|
||||
*
|
||||
* @default AsyncOperationManager
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import AsyncOperationManager from '@/common/async';
|
||||
*
|
||||
* const manager = new AsyncOperationManager();
|
||||
* ```
|
||||
*/
|
||||
export { AsyncOperationManager as default } from './manager';
|
||||
574
frontend/src/common/async/manager.ts
Normal file
574
frontend/src/common/async/manager.ts
Normal file
@@ -0,0 +1,574 @@
|
||||
import {
|
||||
OperationStatus,
|
||||
OperationInfo,
|
||||
AsyncOperationManagerConfig,
|
||||
OperationCallbacks,
|
||||
OperationExecutor,
|
||||
OperationResult
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 异步操作管理器
|
||||
*
|
||||
* 用于控制异步操作的竞态条件,确保操作的正确性和一致性。
|
||||
* 该管理器提供了操作的生命周期管理、并发控制、超时处理等功能。
|
||||
*
|
||||
* @class AsyncOperationManager
|
||||
* @author VoidRaft Team
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { AsyncOperationManager } from './manager';
|
||||
*
|
||||
* const manager = new AsyncOperationManager({
|
||||
* timeout: 30000,
|
||||
* maxConcurrent: 5,
|
||||
* debug: true
|
||||
* });
|
||||
*
|
||||
* // 执行异步操作
|
||||
* const result = await manager.executeOperation(
|
||||
* 'document-123',
|
||||
* async (signal, operationId) => {
|
||||
* // 执行实际的异步操作
|
||||
* return await saveDocument(documentData);
|
||||
* },
|
||||
* 'save-document'
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export class AsyncOperationManager {
|
||||
/**
|
||||
* 操作序列号生成器
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
private operationSequence = 0;
|
||||
|
||||
/**
|
||||
* 待处理操作映射表
|
||||
* @private
|
||||
* @type {Map<number, OperationInfo>}
|
||||
*/
|
||||
private pendingOperations = new Map<number, OperationInfo>();
|
||||
|
||||
/**
|
||||
* 当前资源操作映射表
|
||||
* 记录每个资源当前正在执行的操作ID
|
||||
* @private
|
||||
* @type {Map<string | number, number>}
|
||||
*/
|
||||
private currentResourceOperation = new Map<string | number, number>();
|
||||
|
||||
/**
|
||||
* 管理器配置
|
||||
* @private
|
||||
* @type {Required<AsyncOperationManagerConfig>}
|
||||
*/
|
||||
private config: Required<AsyncOperationManagerConfig>;
|
||||
|
||||
/**
|
||||
* 操作回调函数集合
|
||||
* @private
|
||||
* @type {OperationCallbacks}
|
||||
*/
|
||||
private callbacks: OperationCallbacks;
|
||||
|
||||
/**
|
||||
* 创建异步操作管理器实例
|
||||
*
|
||||
* @param {AsyncOperationManagerConfig} config - 管理器配置选项
|
||||
* @param {OperationCallbacks} callbacks - 操作生命周期回调函数
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const manager = new AsyncOperationManager(
|
||||
* {
|
||||
* timeout: 30000,
|
||||
* autoCleanup: true,
|
||||
* maxConcurrent: 10,
|
||||
* debug: false
|
||||
* },
|
||||
* {
|
||||
* onStart: (op) => console.log(`Operation ${op.id} started`),
|
||||
* onComplete: (op) => console.log(`Operation ${op.id} completed`),
|
||||
* onError: (op, err) => console.error(`Operation ${op.id} failed:`, err)
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
config: AsyncOperationManagerConfig = {},
|
||||
callbacks: OperationCallbacks = {}
|
||||
) {
|
||||
this.config = {
|
||||
timeout: 0,
|
||||
autoCleanup: true,
|
||||
maxConcurrent: 0,
|
||||
debug: false,
|
||||
...config
|
||||
};
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成新的操作ID
|
||||
*
|
||||
* @private
|
||||
* @returns {number} 新的操作ID
|
||||
*/
|
||||
private getNextOperationId(): number {
|
||||
return ++this.operationSequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录调试日志
|
||||
*
|
||||
* 仅在调试模式下输出日志信息。
|
||||
*
|
||||
* @private
|
||||
* @param {string} message - 日志消息
|
||||
* @param {...any} args - 额外的日志参数
|
||||
*/
|
||||
private log(message: string, ...args: any[]): void {
|
||||
if (this.config.debug) {
|
||||
console.log(`[AsyncOperationManager] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建操作信息对象
|
||||
*
|
||||
* @private
|
||||
* @param {string | number} resourceId - 资源ID
|
||||
* @param {string} [type] - 操作类型标识
|
||||
* @returns {OperationInfo} 新创建的操作信息
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
* @param {string | number} resourceId - 要取消操作的资源ID
|
||||
* @param {number} [excludeOperationId] - 要排除的操作ID(不会被取消)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 取消文档 'doc-123' 上的所有操作,除了操作 456
|
||||
* manager.cancelResourceOperations('doc-123', 456);
|
||||
* ```
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消指定的操作
|
||||
*
|
||||
* 通过操作ID取消正在运行的操作。只有状态为 RUNNING 的操作才能被取消。
|
||||
*
|
||||
* @public
|
||||
* @param {number} operationId - 要取消的操作ID
|
||||
* @returns {boolean} 是否成功取消操作
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const cancelled = manager.cancelOperation(123);
|
||||
* if (cancelled) {
|
||||
* console.log('Operation 123 was cancelled');
|
||||
* } else {
|
||||
* console.log('Operation 123 could not be cancelled (not found or not running)');
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 在应用关闭或重置时取消所有操作
|
||||
* manager.cancelAllOperations();
|
||||
* ```
|
||||
*/
|
||||
public cancelAllOperations(): void {
|
||||
this.log('Cancelling all operations');
|
||||
|
||||
for (const [id] of this.pendingOperations.entries()) {
|
||||
this.cancelOperation(id);
|
||||
}
|
||||
|
||||
this.currentResourceOperation.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查操作是否仍然有效
|
||||
*
|
||||
* 验证操作是否存在、未被取消、状态为运行中,以及(如果指定了资源ID)
|
||||
* 是否为该资源的当前活跃操作。
|
||||
*
|
||||
* @public
|
||||
* @param {number} operationId - 要检查的操作ID
|
||||
* @param {string | number} [resourceId] - 可选的资源ID,用于验证是否为当前活跃操作
|
||||
* @returns {boolean} 操作是否有效
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 在长时间运行的操作中定期检查有效性
|
||||
* const saveDocument = async (signal: AbortSignal, operationId: number) => {
|
||||
* for (let i = 0; i < 100; i++) {
|
||||
* if (!manager.isOperationValid(operationId, 'doc-123')) {
|
||||
* throw new Error('Operation is no longer valid');
|
||||
* }
|
||||
* await processChunk(i);
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
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
|
||||
* @template T - 操作返回值的类型
|
||||
* @param {string | number} resourceId - 操作关联的资源ID
|
||||
* @param {OperationExecutor<T>} executor - 实际执行操作的函数
|
||||
* @param {string} [operationType] - 操作类型标识,用于调试和日志
|
||||
* @returns {Promise<OperationResult<T>>} 操作执行结果
|
||||
*
|
||||
* @throws {Error} 当达到最大并发限制时抛出错误
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 执行文档保存操作
|
||||
* const result = await manager.executeOperation(
|
||||
* 'document-123',
|
||||
* async (signal, operationId) => {
|
||||
* // 检查操作是否被取消
|
||||
* if (signal.aborted) {
|
||||
* throw new Error('Operation was cancelled');
|
||||
* }
|
||||
*
|
||||
* // 执行实际的保存逻辑
|
||||
* const saved = await api.saveDocument(documentData);
|
||||
* return saved;
|
||||
* },
|
||||
* 'save-document'
|
||||
* );
|
||||
*
|
||||
* if (result.success) {
|
||||
* console.log('Document saved successfully:', result.data);
|
||||
* } else {
|
||||
* console.error('Failed to save document:', result.error);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作信息
|
||||
*
|
||||
* 根据操作ID获取操作的详细信息。
|
||||
*
|
||||
* @public
|
||||
* @param {number} operationId - 操作ID
|
||||
* @returns {OperationInfo | undefined} 操作信息,如果操作不存在则返回 undefined
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const operation = manager.getOperation(123);
|
||||
* if (operation) {
|
||||
* console.log(`Operation ${operation.id} status: ${operation.status}`);
|
||||
* } else {
|
||||
* console.log('Operation not found');
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public getOperation(operationId: number): OperationInfo | undefined {
|
||||
return this.pendingOperations.get(operationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源的当前操作ID
|
||||
*
|
||||
* 获取指定资源当前正在执行的操作ID。
|
||||
*
|
||||
* @public
|
||||
* @param {string | number} resourceId - 资源ID
|
||||
* @returns {number | undefined} 当前操作ID,如果没有正在执行的操作则返回 undefined
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const currentOpId = manager.getCurrentOperationId('document-123');
|
||||
* if (currentOpId) {
|
||||
* console.log(`Document 123 is currently being processed by operation ${currentOpId}`);
|
||||
* } else {
|
||||
* console.log('No active operation for document 123');
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public getCurrentOperationId(resourceId: string | number): number | undefined {
|
||||
return this.currentResourceOperation.get(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有待处理操作
|
||||
*
|
||||
* 返回管理器中所有待处理操作的列表,包括待执行、正在执行和已完成的操作。
|
||||
*
|
||||
* @public
|
||||
* @returns {OperationInfo[]} 所有待处理操作的数组
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const allOperations = manager.getPendingOperations();
|
||||
* console.log(`Total operations: ${allOperations.length}`);
|
||||
*
|
||||
* allOperations.forEach(op => {
|
||||
* console.log(`Operation ${op.id}: ${op.status} (${op.type || 'unknown'})`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public getPendingOperations(): OperationInfo[] {
|
||||
return Array.from(this.pendingOperations.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行中的操作数量
|
||||
*
|
||||
* 返回当前正在执行的操作数量,用于监控并发情况。
|
||||
*
|
||||
* @public
|
||||
* @returns {number} 正在运行的操作数量
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const runningCount = manager.getRunningOperationsCount();
|
||||
* console.log(`Currently running operations: ${runningCount}`);
|
||||
*
|
||||
* if (runningCount >= maxConcurrent) {
|
||||
* console.warn('Approaching maximum concurrent operations limit');
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public getRunningOperationsCount(): number {
|
||||
return Array.from(this.pendingOperations.values())
|
||||
.filter(op => op.status === OperationStatus.RUNNING).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*
|
||||
* 清理管理器的所有资源,取消所有正在运行的操作,清空所有映射表。
|
||||
* 通常在应用关闭或组件卸载时调用。
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 在组件卸载时清理资源
|
||||
* useEffect(() => {
|
||||
* return () => {
|
||||
* manager.destroy();
|
||||
* };
|
||||
* }, []);
|
||||
*
|
||||
* // 或在应用关闭时
|
||||
* window.addEventListener('beforeunload', () => {
|
||||
* manager.destroy();
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.log('Destroying AsyncOperationManager');
|
||||
this.cancelAllOperations();
|
||||
this.pendingOperations.clear();
|
||||
this.currentResourceOperation.clear();
|
||||
}
|
||||
}
|
||||
289
frontend/src/common/async/types.ts
Normal file
289
frontend/src/common/async/types.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* 异步操作竞态条件控制相关类型定义
|
||||
*
|
||||
* 该模块提供了用于管理异步操作竞态条件的类型定义,
|
||||
* 包括操作状态、配置选项、回调函数等核心类型。
|
||||
*
|
||||
* @fileoverview 异步操作管理器类型定义
|
||||
* @author VoidRaft Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 操作状态枚举
|
||||
*
|
||||
* 定义异步操作在其生命周期中可能的状态。
|
||||
*
|
||||
* @enum {string}
|
||||
* @readonly
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { OperationStatus } from './types';
|
||||
*
|
||||
* const status = OperationStatus.RUNNING;
|
||||
* console.log(status); // 'running'
|
||||
* ```
|
||||
*/
|
||||
export enum OperationStatus {
|
||||
/** 操作已创建但尚未开始执行 */
|
||||
PENDING = 'pending',
|
||||
/** 操作正在执行中 */
|
||||
RUNNING = 'running',
|
||||
/** 操作已成功完成 */
|
||||
COMPLETED = 'completed',
|
||||
/** 操作已被取消 */
|
||||
CANCELLED = 'cancelled',
|
||||
/** 操作执行失败 */
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作信息接口
|
||||
*
|
||||
* 描述单个异步操作的完整信息,包括标识符、状态、控制器等。
|
||||
*
|
||||
* @interface OperationInfo
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const operation: OperationInfo = {
|
||||
* id: 1,
|
||||
* resourceId: 'document-123',
|
||||
* status: OperationStatus.RUNNING,
|
||||
* abortController: new AbortController(),
|
||||
* createdAt: Date.now(),
|
||||
* type: 'save-document'
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export interface OperationInfo {
|
||||
/**
|
||||
* 操作的唯一标识符
|
||||
* @type {number}
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* 关联的资源ID(如文档ID、用户ID等)
|
||||
* 用于标识操作所作用的资源,支持字符串或数字类型
|
||||
* @type {string | number}
|
||||
*/
|
||||
resourceId: string | number;
|
||||
|
||||
/**
|
||||
* 当前操作状态
|
||||
* @type {OperationStatus}
|
||||
*/
|
||||
status: OperationStatus;
|
||||
|
||||
/**
|
||||
* 用于取消操作的控制器
|
||||
* 通过调用 abortController.abort() 可以取消正在执行的操作
|
||||
* @type {AbortController}
|
||||
*/
|
||||
abortController: AbortController;
|
||||
|
||||
/**
|
||||
* 操作创建时间戳(毫秒)
|
||||
* @type {number}
|
||||
*/
|
||||
createdAt: number;
|
||||
|
||||
/**
|
||||
* 操作类型标识(可选)
|
||||
* 用于调试和日志记录,帮助识别不同类型的操作
|
||||
* @type {string}
|
||||
* @optional
|
||||
*/
|
||||
type?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步操作管理器配置接口
|
||||
*
|
||||
* 定义异步操作管理器的配置选项,用于控制操作的行为和限制。
|
||||
*
|
||||
* @interface AsyncOperationManagerConfig
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const config: AsyncOperationManagerConfig = {
|
||||
* timeout: 30000, // 30秒超时
|
||||
* autoCleanup: true, // 自动清理已完成操作
|
||||
* maxConcurrent: 5, // 最多5个并发操作
|
||||
* debug: true // 启用调试模式
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export interface AsyncOperationManagerConfig {
|
||||
/**
|
||||
* 操作超时时间(毫秒)
|
||||
* 设置为 0 表示不设置超时限制
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @optional
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
/**
|
||||
* 是否自动清理已完成的操作
|
||||
* 启用后会自动从内存中移除已完成、已取消或失败的操作
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @optional
|
||||
*/
|
||||
autoCleanup?: boolean;
|
||||
|
||||
/**
|
||||
* 最大并发操作数
|
||||
* 设置为 0 表示无并发限制
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @optional
|
||||
*/
|
||||
maxConcurrent?: number;
|
||||
|
||||
/**
|
||||
* 调试模式开关
|
||||
* 启用后会在控制台输出详细的操作日志
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @optional
|
||||
*/
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作回调函数集合接口
|
||||
*
|
||||
* 定义在操作生命周期的不同阶段可以执行的回调函数。
|
||||
*
|
||||
* @interface OperationCallbacks
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const callbacks: OperationCallbacks = {
|
||||
* onStart: (operation) => console.log(`Operation ${operation.id} started`),
|
||||
* onComplete: (operation) => console.log(`Operation ${operation.id} completed`),
|
||||
* onCancel: (operation) => console.log(`Operation ${operation.id} cancelled`),
|
||||
* onError: (operation, error) => console.error(`Operation ${operation.id} failed:`, error)
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export interface OperationCallbacks {
|
||||
/**
|
||||
* 操作开始时的回调函数
|
||||
* @param {OperationInfo} operation - 操作信息
|
||||
* @optional
|
||||
*/
|
||||
onStart?: (operation: OperationInfo) => void;
|
||||
|
||||
/**
|
||||
* 操作成功完成时的回调函数
|
||||
* @param {OperationInfo} operation - 操作信息
|
||||
* @optional
|
||||
*/
|
||||
onComplete?: (operation: OperationInfo) => void;
|
||||
|
||||
/**
|
||||
* 操作被取消时的回调函数
|
||||
* @param {OperationInfo} operation - 操作信息
|
||||
* @optional
|
||||
*/
|
||||
onCancel?: (operation: OperationInfo) => void;
|
||||
|
||||
/**
|
||||
* 操作执行失败时的回调函数
|
||||
* @param {OperationInfo} operation - 操作信息
|
||||
* @param {Error} error - 错误对象
|
||||
* @optional
|
||||
*/
|
||||
onError?: (operation: OperationInfo, error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作执行器函数类型
|
||||
*
|
||||
* 定义实际执行异步操作的函数签名。
|
||||
*
|
||||
* @template T - 操作返回值的类型
|
||||
* @param {AbortSignal} signal - 用于检测操作是否被取消的信号
|
||||
* @param {number} operationId - 操作的唯一标识符
|
||||
* @returns {Promise<T>} 操作执行结果的 Promise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const saveDocument: OperationExecutor<boolean> = async (signal, operationId) => {
|
||||
* // 检查操作是否被取消
|
||||
* if (signal.aborted) {
|
||||
* throw new Error('Operation was cancelled');
|
||||
* }
|
||||
*
|
||||
* // 执行实际的保存操作
|
||||
* const result = await api.saveDocument(documentData);
|
||||
* return result.success;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export type OperationExecutor<T = any> = (
|
||||
signal: AbortSignal,
|
||||
operationId: number
|
||||
) => Promise<T>;
|
||||
|
||||
/**
|
||||
* 操作执行结果接口
|
||||
*
|
||||
* 封装异步操作的执行结果,包括成功状态、数据和错误信息。
|
||||
*
|
||||
* @template T - 操作结果数据的类型
|
||||
* @interface OperationResult
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 成功的操作结果
|
||||
* const successResult: OperationResult<string> = {
|
||||
* success: true,
|
||||
* data: 'Operation completed successfully',
|
||||
* operation: operationInfo
|
||||
* };
|
||||
*
|
||||
* // 失败的操作结果
|
||||
* const failureResult: OperationResult<never> = {
|
||||
* success: false,
|
||||
* error: new Error('Operation failed'),
|
||||
* operation: operationInfo
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export interface OperationResult<T = any> {
|
||||
/**
|
||||
* 操作是否成功执行
|
||||
* true 表示操作成功完成,false 表示操作失败或被取消
|
||||
* @type {boolean}
|
||||
*/
|
||||
success: boolean;
|
||||
|
||||
/**
|
||||
* 操作成功时的结果数据
|
||||
* 仅在 success 为 true 时有值
|
||||
* @type {T}
|
||||
* @optional
|
||||
*/
|
||||
data?: T;
|
||||
|
||||
/**
|
||||
* 操作失败时的错误信息
|
||||
* 仅在 success 为 false 时有值
|
||||
* @type {Error}
|
||||
* @optional
|
||||
*/
|
||||
error?: Error;
|
||||
|
||||
/**
|
||||
* 关联的操作信息
|
||||
* 包含操作的完整元数据
|
||||
* @type {OperationInfo}
|
||||
*/
|
||||
operation: OperationInfo;
|
||||
}
|
||||
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