Skip to main content
Glama
cache.ts6.54 kB
import { CacheEntry } from '../types.js'; import { DEFAULT_CONFIG } from '../constants.js'; /** * LRU缓存管理器 * 支持TTL(生存时间)和LRU(最近最少使用)淘汰策略 */ export class CacheManager { private cache = new Map<string, CacheEntry>(); private readonly maxSize: number; private readonly defaultTTL: number; constructor(maxSize = DEFAULT_CONFIG.MAX_CACHE_SIZE, defaultTTL = DEFAULT_CONFIG.CACHE_DURATION) { this.maxSize = maxSize; this.defaultTTL = defaultTTL; } /** * 设置缓存项 * @param key 缓存键 * @param value 缓存值 * @param ttl 生存时间(毫秒),默认使用配置的TTL */ set(key: string, value: any, ttl?: number): void { const expiry = Date.now() + (ttl || this.defaultTTL); // LRU淘汰:如果缓存已满,删除最旧的项 if (this.cache.size >= this.maxSize && !this.cache.has(key)) { const firstKey = this.cache.keys().next().value; if (firstKey) { this.cache.delete(firstKey); } } // 如果key已存在,先删除(这样重新插入会更新顺序) if (this.cache.has(key)) { this.cache.delete(key); } this.cache.set(key, { value, expiry }); } /** * 获取缓存项 * @param key 缓存键 * @returns 缓存值或null(如果不存在或已过期) */ get(key: string): any | null { const entry = this.cache.get(key); if (!entry) { return null; } // 检查是否过期 if (Date.now() > entry.expiry) { this.cache.delete(key); return null; } // LRU更新:重新插入以更新访问顺序 this.cache.delete(key); this.cache.set(key, entry); return entry.value; } /** * 检查缓存项是否存在且未过期 * @param key 缓存键 * @returns 是否存在有效缓存 */ has(key: string): boolean { return this.get(key) !== null; } /** * 删除缓存项 * @param key 缓存键 * @returns 是否成功删除 */ delete(key: string): boolean { return this.cache.delete(key); } /** * 清空所有缓存 */ clear(): void { this.cache.clear(); } /** * 获取缓存统计信息 * @returns 缓存统计 */ getStats(): CacheStats { const now = Date.now(); let expiredCount = 0; let validCount = 0; for (const [, entry] of this.cache) { if (now > entry.expiry) { expiredCount++; } else { validCount++; } } return { totalSize: this.cache.size, validCount, expiredCount, maxSize: this.maxSize, hitRate: this.calculateHitRate() }; } /** * 清理过期缓存项 * @returns 清理的项目数量 */ cleanup(): number { const now = Date.now(); const expiredKeys: string[] = []; for (const [key, entry] of this.cache) { if (now > entry.expiry) { expiredKeys.push(key); } } expiredKeys.forEach(key => this.cache.delete(key)); return expiredKeys.length; } /** * 获取所有有效的缓存键 * @returns 有效缓存键列表 */ getValidKeys(): string[] { const now = Date.now(); const validKeys: string[] = []; for (const [key, entry] of this.cache) { if (now <= entry.expiry) { validKeys.push(key); } } return validKeys; } /** * 计算缓存命中率(简单实现) * 实际应用中可能需要更复杂的统计 */ private calculateHitRate(): number { // 简化实现,实际项目中可以维护更详细的统计 const stats = this.getBasicStats(); return stats.validCount / Math.max(stats.totalSize, 1); } private getBasicStats() { const now = Date.now(); let validCount = 0; for (const [, entry] of this.cache) { if (now <= entry.expiry) { validCount++; } } return { totalSize: this.cache.size, validCount }; } /** * 设置带条件的缓存(仅当key不存在时设置) * @param key 缓存键 * @param value 缓存值 * @param ttl 生存时间 * @returns 是否成功设置 */ setIfNotExists(key: string, value: any, ttl?: number): boolean { if (this.has(key)) { return false; } this.set(key, value, ttl); return true; } /** * 批量获取缓存项 * @param keys 缓存键列表 * @returns 键值对映射 */ mget(keys: string[]): Map<string, any> { const result = new Map<string, any>(); for (const key of keys) { const value = this.get(key); if (value !== null) { result.set(key, value); } } return result; } /** * 批量设置缓存项 * @param entries 键值对数组 * @param ttl 生存时间 */ mset(entries: Array<[string, any]>, ttl?: number): void { for (const [key, value] of entries) { this.set(key, value, ttl); } } } // 缓存统计接口 export interface CacheStats { totalSize: number; validCount: number; expiredCount: number; maxSize: number; hitRate: number; } // 全局缓存实例 export const globalCache = new CacheManager(); // 缓存键生成器 export class CacheKeyGenerator { private static readonly PREFIX = 'juyiting:'; static hero(source: string): string { return `${this.PREFIX}hero:${source}`; } static config(configId: string): string { return `${this.PREFIX}config:${configId}`; } static collaboration(sessionId: string): string { return `${this.PREFIX}collaboration:${sessionId}`; } static userConfigs(userKey: string): string { return `${this.PREFIX}user_configs:${userKey}`; } static custom(namespace: string, key: string): string { return `${this.PREFIX}${namespace}:${key}`; } } // 缓存装饰器(用于方法级缓存) export function cached(ttl?: number) { return function (target: any, propertyName: string, descriptor: PropertyDescriptor) { const method = descriptor.value; descriptor.value = async function (...args: any[]) { const cacheKey = CacheKeyGenerator.custom( target.constructor.name, `${propertyName}:${JSON.stringify(args)}` ); // 尝试从缓存获取 const cached = globalCache.get(cacheKey); if (cached !== null) { return cached; } // 执行原方法 const result = await method.apply(this, args); // 存入缓存 globalCache.set(cacheKey, result, ttl); return result; }; return descriptor; }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/juyitingmcp/juyitingmcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server