/** * 内存存储适配器实现 * 将数据存储在内存中的适配器,适用于测试和开发环境 */ import { StorageAdapter, StorageConfig, QueryCondition, SortOption } from '../../interfaces/StorageAdapter'; /** * 简单的ID生成函数替代nanoid */ function generateSimpleId(): string { return Date.now().toString(36) + Math.random().toString(36).substr(2, 5); } /** * 内存存储适配器配置接口 */ export interface MemoryStorageConfig extends StorageConfig { /** * 是否在初始化时清空数据 */ clearOnInitialize?: boolean; /** * 数据项的默认TTL(毫秒),设置为0表示永不过期 */ defaultTTL?: number; /** * 是否启用数据项过期检查 */ enableTTLCheck?: boolean; } /** * 内存存储中的数据项 */ interface MemoryItem { /** * 数据对象 */ data: T; /** * 创建时间戳 */ createdAt: number; /** * 更新时间戳 */ updatedAt: number; /** * 过期时间戳,0表示永不过期 */ expiresAt?: number; } /** * 内存存储适配器类 */ export class MemoryStorageAdapter implements StorageAdapter { /** * 内存数据存储,以namespace为键,Map为值 */ private dataStore: Map>> = new Map(); /** * 配置项 */ private config: any = { type: 'memory', clearOnInitialize: true, defaultTTL: 3600000, // 1 hour in milliseconds enableTTLCheck: false, }; /** * 是否已初始化 */ private initialized: boolean = false; /** * TTL清理定时器ID */ private ttlCleanupTimer: NodeJS.Timeout | null = null; /** * 初始化适配器 * @param config 适配器配置 */ public async initialize(config: any = {}): Promise { // 合并配置 this.config = { ...this.config, ...config }; // 如果配置了在初始化时清空数据,则清空 if (this.config.clearOnInitialize) { this.dataStore.clear(); } this.initialized = true; // 如果启用了TTL检查,则启动定期清理任务 if (this.config.enableTTLCheck) { this.startTTLCleanup(); } } /** * 关闭适配器 */ public async close(): Promise { if (!this.initialized) { return; } // 清除TTL清理定时器 if (this.ttlCleanupTimer) { clearInterval(this.ttlCleanupTimer); this.ttlCleanupTimer = null; } // 清空数据 this.dataStore.clear(); this.initialized = false; } /** * 创建新数据项 * @param namespace 命名空间 * @param data 数据对象 * @returns 创建的数据对象,包含ID */ public async create(namespace: string, data: Partial): Promise { this.ensureInitialized(); this.ensureNamespaceExists(namespace); // 生成唯一ID const id = (data as any).id || generateSimpleId(); const timestamp = Date.now(); // 创建完整的数据对象 const item = { ...data, id: id, createdAt: timestamp, updatedAt: timestamp }; // 计算过期时间 const expiresAt = this.config?.defaultTTL && this.config.defaultTTL > 0 ? timestamp + this.config.defaultTTL : undefined; // 存储数据项 this.dataStore.get(namespace)!.set(id, { data: item, createdAt: timestamp, updatedAt: timestamp, expiresAt: expiresAt !== undefined && expiresAt > 0 ? expiresAt : undefined }); return item; } /** * 根据ID获取数据项 * @param namespace 命名空间 * @param id 数据项ID * @returns 数据项,如果不存在则返回null */ public async getById(namespace: string, id: string): Promise { this.ensureInitialized(); if (!this.dataStore.has(namespace)) { return null; } const item = this.dataStore.get(namespace)!.get(id); // 检查是否已过期 if (!item || (item.expiresAt && Date.now() > item.expiresAt)) { return null; } return item.data as T; } /** * 查找符合条件的数据项 * @param namespace 命名空间 * @param query 查询条件 * @param skip 跳过的记录数 * @param limit 返回的最大记录数 * @param sort 排序条件 * @returns 查询结果数组和总数 */ public async find( namespace: string, query?: QueryCondition, skip: number = 0, limit: number = 100, sort?: SortOption ): Promise<{ data: T[]; total: number }> { this.ensureInitialized(); if (!this.dataStore.has(namespace)) { return { data: [], total: 0 }; } // 获取所有有效数据项 const now = Date.now(); const items = Array.from(this.dataStore.get(namespace)!.values()) .filter(item => !item.expiresAt || now <= item.expiresAt) .map(item => item.data); // 应用查询条件 const filteredItems = this.applyQuery(items, query); const total = filteredItems.length; // 应用排序 const sortedItems = this.applySort(filteredItems, sort); // 应用分页 const paginatedItems = sortedItems.slice(skip, skip + limit); return { data: paginatedItems, total }; } /** * 更新数据项 * @param namespace 命名空间 * @param id 数据项ID * @param data 更新的数据 * @returns 更新后的数据项,如果不存在则返回null */ public async update(namespace: string, id: string, data: Partial): Promise { this.ensureInitialized(); if (!this.dataStore.has(namespace)) { return null; } const namespaceStore = this.dataStore.get(namespace)!; const existingItem = namespaceStore.get(id); if (!existingItem || (existingItem.expiresAt && Date.now() > existingItem.expiresAt)) { return null; } const timestamp = Date.now(); // 创建更新后的数据对象 const updatedItem: T = { ...existingItem.data, ...data, id, updatedAt: timestamp } as T; // 更新存储 namespaceStore.set(id, { ...existingItem, data: updatedItem, updatedAt: timestamp }); return updatedItem; } /** * 删除数据项 * @param namespace 命名空间 * @param id 数据项ID * @returns 是否成功删除 */ public async delete(namespace: string, id: string): Promise { this.ensureInitialized(); if (!this.dataStore.has(namespace)) { return false; } return this.dataStore.get(namespace)!.delete(id); } /** * 批量创建数据项 * @param namespace 命名空间 * @param dataList 数据对象数组 * @returns 创建的数据对象数组,包含ID */ public async bulkCreate(namespace: string, dataList: Partial[]): Promise { this.ensureInitialized(); this.ensureNamespaceExists(namespace); const results: T[] = []; const timestamp = Date.now(); const namespaceStore = this.dataStore.get(namespace)!; for (const data of dataList) { // 生成唯一ID const id = (data as any).id || generateSimpleId(); // 创建完整的数据对象 const item = { ...data, id, createdAt: timestamp, updatedAt: timestamp }; // 计算过期时间 const expiresAt = this.config?.defaultTTL && this.config.defaultTTL > 0 ? timestamp + this.config.defaultTTL : undefined; // 存储数据项 namespaceStore.set(id, { data: item, createdAt: timestamp, updatedAt: timestamp, expiresAt: expiresAt !== undefined && expiresAt > 0 ? expiresAt : undefined }); results.push(item as any); } return results; } /** * 检查适配器是否可用 * @returns 是否可用 */ public async isAvailable(): Promise { return this.initialized; } /** * 确保适配器已初始化 */ private ensureInitialized(): void { if (!this.initialized) { throw new Error('MemoryStorageAdapter is not initialized'); } } /** * 确保命名空间存在 * @param namespace 命名空间 */ private ensureNamespaceExists(namespace: string): void { if (!this.dataStore.has(namespace)) { this.dataStore.set(namespace, new Map()); } } /** * 应用查询条件过滤数据 * @param items 数据项数组 * @param query 查询条件 * @returns 过滤后的数据项数组 */ private applyQuery(items: T[], query?: QueryCondition): T[] { if (!query || Object.keys(query).length === 0) { return items; } return items.filter(item => { for (const [key, value] of Object.entries(query)) { const itemValue = (item as any)[key]; if (value === null || value === undefined) { if (itemValue !== null && itemValue !== undefined) { return false; } } else if (typeof value === 'object' && !Array.isArray(value)) { // 处理比较操作符 for (const [operator, compareValue] of Object.entries(value)) { switch (operator) { case '$eq': if (itemValue !== compareValue) return false; break; case '$neq': if (itemValue === compareValue) return false; break; case '$gt': if (itemValue <= (compareValue as number)) return false; break; case '$gte': if (itemValue < (compareValue as number)) return false; break; case '$lt': if (itemValue >= (compareValue as number)) return false; break; case '$lte': if (itemValue > (compareValue as number)) return false; break; case '$in': if (!Array.isArray(compareValue) || !compareValue.includes(itemValue)) { return false; } break; case '$nin': if (!Array.isArray(compareValue) || compareValue.includes(itemValue)) { return false; } break; case '$like': if (typeof compareValue === 'string' && typeof itemValue === 'string') { const regex = new RegExp(compareValue.replace(/%/g, '.*')); if (!regex.test(itemValue)) return false; } else { return false; } break; default: // 未知操作符,忽略 break; } } } else if (Array.isArray(value)) { // 数组包含查询 if (!Array.isArray(itemValue) || !value.every(v => itemValue.includes(v))) { return false; } } else { // 直接相等比较 if (itemValue !== value) { return false; } } } return true; }); } /** * 应用排序条件 * @param items 数据项数组 * @param sort 排序条件 * @returns 排序后的数据项数组 */ private applySort(items: T[], sort?: SortOption): T[] { if (!sort || Object.keys(sort).length === 0) { return [...items]; } return [...items].sort((a, b) => { for (const [key, direction] of Object.entries(sort)) { const aValue = (a as any)[key]; const bValue = (b as any)[key]; const dirValue = typeof direction === 'string' ? (direction === 'asc' ? 1 : -1) : direction; if (aValue < bValue) return dirValue === 1 ? -1 : 1; if (aValue > bValue) return dirValue === 1 ? 1 : -1; } return 0; }); } /** * 启动TTL清理任务 */ private startTTLCleanup(): void { // 每分钟执行一次清理 this.ttlCleanupTimer = setInterval(() => { this.cleanupExpiredItems(); }, 60000); } /** * 清理过期的数据项 */ private cleanupExpiredItems(): void { if (!this.initialized) return; const now = Date.now(); for (const [namespace, items] of this.dataStore.entries()) { for (const [id, item] of items.entries()) { if (item.expiresAt && now > item.expiresAt) { items.delete(id); } } } } }