import { Injectable, Logger } from '@nestjs/common'; import { promises as fs } from 'fs'; import * as path from 'path'; import { IRedisService } from './redis.interface'; /** * 文件模拟Redis服务 * 在本地开发环境中使用文件系统模拟Redis功能 */ @Injectable() export class FileRedisService implements IRedisService { private readonly logger = new Logger(FileRedisService.name); private readonly dataDir = path.join(process.cwd(), 'redis-data'); private readonly dataFile = path.join(this.dataDir, 'redis.json'); private data: Map = new Map(); constructor() { this.initializeStorage(); } /** * 初始化存储 */ private async initializeStorage(): Promise { try { // 确保数据目录存在 await fs.mkdir(this.dataDir, { recursive: true }); // 尝试加载现有数据 await this.loadData(); // 启动过期清理任务 this.startExpirationCleanup(); this.logger.log('文件Redis服务初始化完成'); } catch (error) { this.logger.error('初始化文件Redis服务失败', error); } } /** * 从文件加载数据 */ private async loadData(): Promise { try { const fileContent = await fs.readFile(this.dataFile, 'utf-8'); const jsonData = JSON.parse(fileContent); this.data = new Map(); for (const [key, item] of Object.entries(jsonData)) { const typedItem = item as { value: string; expireAt?: number }; // 检查是否已过期 if (!typedItem.expireAt || typedItem.expireAt > Date.now()) { this.data.set(key, typedItem); } } this.logger.log(`从文件加载了 ${this.data.size} 条Redis数据`); } catch (error) { // 文件不存在或格式错误,使用空数据 this.data = new Map(); this.logger.log('初始化空的Redis数据存储'); } } /** * 保存数据到文件 */ private async saveData(): Promise { try { const jsonData = Object.fromEntries(this.data); await fs.writeFile(this.dataFile, JSON.stringify(jsonData, null, 2)); } catch (error) { this.logger.error('保存Redis数据到文件失败', error); } } /** * 启动过期清理任务 */ private startExpirationCleanup(): void { setInterval(() => { this.cleanExpiredKeys(); }, 60000); // 每分钟清理一次过期键 } /** * 清理过期的键 */ private cleanExpiredKeys(): void { const now = Date.now(); let cleanedCount = 0; for (const [key, item] of this.data.entries()) { if (item.expireAt && item.expireAt <= now) { this.data.delete(key); cleanedCount++; } } if (cleanedCount > 0) { this.logger.log(`清理了 ${cleanedCount} 个过期的Redis键`); this.saveData(); // 保存清理后的数据 } } async set(key: string, value: string, ttl?: number): Promise { const item: { value: string; expireAt?: number } = { value }; if (ttl && ttl > 0) { item.expireAt = Date.now() + ttl * 1000; } this.data.set(key, item); await this.saveData(); this.logger.debug(`设置Redis键: ${key}, TTL: ${ttl || '永不过期'}`); } async get(key: string): Promise { const item = this.data.get(key); if (!item) { return null; } // 检查是否过期 if (item.expireAt && item.expireAt <= Date.now()) { this.data.delete(key); await this.saveData(); return null; } return item.value; } async del(key: string): Promise { const existed = this.data.has(key); this.data.delete(key); if (existed) { await this.saveData(); this.logger.debug(`删除Redis键: ${key}`); } return existed; } async exists(key: string): Promise { const item = this.data.get(key); if (!item) { return false; } // 检查是否过期 if (item.expireAt && item.expireAt <= Date.now()) { this.data.delete(key); await this.saveData(); return false; } return true; } async expire(key: string, ttl: number): Promise { const item = this.data.get(key); if (item) { item.expireAt = Date.now() + ttl * 1000; await this.saveData(); this.logger.debug(`设置Redis键过期时间: ${key}, TTL: ${ttl}秒`); } } async ttl(key: string): Promise { const item = this.data.get(key); if (!item) { return -2; // 键不存在 } if (!item.expireAt) { return -1; // 永不过期 } const remaining = Math.ceil((item.expireAt - Date.now()) / 1000); if (remaining <= 0) { // 已过期,删除键 this.data.delete(key); await this.saveData(); return -2; } return remaining; } async flushall(): Promise { this.data.clear(); await this.saveData(); this.logger.log('清空所有Redis数据'); } }