🎨 Optimize code structure

This commit is contained in:
2025-09-28 01:09:20 +08:00
parent 0188b618f2
commit bc0569af93
12 changed files with 1363 additions and 324 deletions

106
frontend/src/common/cache/README.md vendored Normal file
View 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)` - 节流函数

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