import fs from 'fs'; import path from 'path'; import { log } from '../utils/logger.js'; import memoryManager, { MemoryPressure } from '../utils/memoryManager.js'; import { getDataDir } from '../utils/paths.js'; import { QUOTA_CACHE_TTL, QUOTA_CLEANUP_INTERVAL } from '../constants/index.js'; class QuotaManager { /** * @param {string} filePath - 额度数据文件路径 */ constructor(filePath = path.join(getDataDir(), 'quotas.json')) { this.filePath = filePath; /** @type {Map} */ this.cache = new Map(); this.CACHE_TTL = QUOTA_CACHE_TTL; this.CLEANUP_INTERVAL = QUOTA_CLEANUP_INTERVAL; this.cleanupTimer = null; this.ensureFileExists(); this.loadFromFile(); this.startCleanupTimer(); this.registerMemoryCleanup(); } ensureFileExists() { const dir = path.dirname(this.filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } if (!fs.existsSync(this.filePath)) { fs.writeFileSync(this.filePath, JSON.stringify({ meta: { lastCleanup: Date.now(), ttl: this.CLEANUP_INTERVAL }, quotas: {} }, null, 2), 'utf8'); } } loadFromFile() { try { const data = fs.readFileSync(this.filePath, 'utf8'); const parsed = JSON.parse(data); Object.entries(parsed.quotas || {}).forEach(([key, value]) => { this.cache.set(key, value); }); } catch (error) { log.error('加载额度文件失败:', error.message); } } saveToFile() { try { const quotas = {}; this.cache.forEach((value, key) => { quotas[key] = value; }); const data = { meta: { lastCleanup: Date.now(), ttl: this.CLEANUP_INTERVAL }, quotas }; fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8'); } catch (error) { log.error('保存额度文件失败:', error.message); } } updateQuota(refreshToken, quotas) { this.cache.set(refreshToken, { lastUpdated: Date.now(), models: quotas }); this.saveToFile(); } getQuota(refreshToken) { const data = this.cache.get(refreshToken); if (!data) return null; // 检查缓存是否过期 if (Date.now() - data.lastUpdated > this.CACHE_TTL) { return null; } return data; } cleanup() { const now = Date.now(); let cleaned = 0; this.cache.forEach((value, key) => { if (now - value.lastUpdated > this.CLEANUP_INTERVAL) { this.cache.delete(key); cleaned++; } }); if (cleaned > 0) { log.info(`清理了 ${cleaned} 个过期的额度记录`); this.saveToFile(); } } startCleanupTimer() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); } this.cleanupTimer = setInterval(() => this.cleanup(), this.CLEANUP_INTERVAL); } stopCleanupTimer() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = null; } } // 注册内存清理回调 registerMemoryCleanup() { memoryManager.registerCleanup((pressure) => { // 根据压力级别调整缓存 TTL if (pressure === MemoryPressure.CRITICAL) { // 紧急时清理所有缓存 const size = this.cache.size; if (size > 0) { this.cache.clear(); log.info(`紧急清理 ${size} 个额度缓存`); } } else if (pressure === MemoryPressure.HIGH) { // 高压力时清理过期缓存 this.cleanup(); } }); } convertToBeijingTime(utcTimeStr) { if (!utcTimeStr) return 'N/A'; try { const utcDate = new Date(utcTimeStr); return utcDate.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', timeZone: 'Asia/Shanghai' }); } catch (error) { return 'N/A'; } } } const quotaManager = new QuotaManager(); export default quotaManager;