Spaces:
Paused
Paused
| import fs from 'fs'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| import { dirname } from 'path'; | |
| import { createLogger } from './logger.js'; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = dirname(__filename); | |
| const logger = createLogger('Storage'); | |
| /** | |
| * 持久化存储管理器 | |
| * 用于保存和加载Cookie数据(包括Thread ID) | |
| */ | |
| class StorageManager { | |
| constructor() { | |
| // 数据文件路径 | |
| this.dataFilePath = path.join(dirname(dirname(__dirname)), 'data', 'cookies-data.json'); | |
| this.backupFilePath = path.join(dirname(dirname(__dirname)), 'data', 'cookies-data.backup.json'); | |
| // 确保数据目录存在 | |
| this.ensureDataDirectory(); | |
| } | |
| /** | |
| * 确保数据目录存在 | |
| */ | |
| ensureDataDirectory() { | |
| const dataDir = path.dirname(this.dataFilePath); | |
| if (!fs.existsSync(dataDir)) { | |
| fs.mkdirSync(dataDir, { recursive: true }); | |
| logger.info(`创建数据目录: ${dataDir}`); | |
| } | |
| } | |
| /** | |
| * 保存Cookie数据到文件 | |
| * @param {Array} cookieEntries - Cookie条目数组 | |
| * @returns {boolean} - 是否保存成功 | |
| */ | |
| saveCookieData(cookieEntries) { | |
| try { | |
| // 准备要保存的数据 | |
| const dataToSave = { | |
| version: '1.0', | |
| lastUpdated: new Date().toISOString(), | |
| cookies: cookieEntries.map(entry => ({ | |
| userId: entry.userId, | |
| spaceId: entry.spaceId, | |
| threadId: entry.threadId, | |
| enabled: entry.enabled, | |
| valid: entry.valid, | |
| lastUsed: entry.lastUsed, | |
| // 不保存实际的cookie值,只保存其哈希或标识 | |
| cookieHash: this.hashCookie(entry.cookie) | |
| })) | |
| }; | |
| // 先备份现有文件 | |
| if (fs.existsSync(this.dataFilePath)) { | |
| fs.copyFileSync(this.dataFilePath, this.backupFilePath); | |
| } | |
| // 保存新数据 | |
| fs.writeFileSync(this.dataFilePath, JSON.stringify(dataToSave, null, 2), 'utf8'); | |
| logger.info(`成功保存 ${cookieEntries.length} 个Cookie的数据`); | |
| return true; | |
| } catch (error) { | |
| logger.error(`保存Cookie数据失败: ${error.message}`); | |
| return false; | |
| } | |
| } | |
| /** | |
| * 加载Cookie数据 | |
| * @returns {Object|null} - 加载的数据或null | |
| */ | |
| loadCookieData() { | |
| try { | |
| if (!fs.existsSync(this.dataFilePath)) { | |
| logger.info('Cookie数据文件不存在'); | |
| return null; | |
| } | |
| const fileContent = fs.readFileSync(this.dataFilePath, 'utf8'); | |
| const data = JSON.parse(fileContent); | |
| logger.info(`成功加载 ${data.cookies?.length || 0} 个Cookie的数据`); | |
| return data; | |
| } catch (error) { | |
| logger.error(`加载Cookie数据失败: ${error.message}`); | |
| // 尝试从备份恢复 | |
| if (fs.existsSync(this.backupFilePath)) { | |
| try { | |
| logger.info('尝试从备份文件恢复...'); | |
| const backupContent = fs.readFileSync(this.backupFilePath, 'utf8'); | |
| const backupData = JSON.parse(backupContent); | |
| // 将备份恢复为主文件 | |
| fs.copyFileSync(this.backupFilePath, this.dataFilePath); | |
| logger.success('成功从备份恢复数据'); | |
| return backupData; | |
| } catch (backupError) { | |
| logger.error(`从备份恢复失败: ${backupError.message}`); | |
| } | |
| } | |
| return null; | |
| } | |
| } | |
| /** | |
| * 合并保存的数据和内存中的Cookie条目 | |
| * @param {Array} cookieEntries - 内存中的Cookie条目 | |
| * @param {Object} savedData - 保存的数据 | |
| * @returns {Array} - 合并后的Cookie条目 | |
| */ | |
| mergeCookieData(cookieEntries, savedData) { | |
| if (!savedData || !savedData.cookies) { | |
| return cookieEntries; | |
| } | |
| const mergedEntries = []; | |
| // 为每个内存中的cookie条目恢复保存的数据 | |
| for (const entry of cookieEntries) { | |
| const savedEntry = savedData.cookies.find(saved => | |
| saved.userId === entry.userId || | |
| saved.cookieHash === this.hashCookie(entry.cookie) | |
| ); | |
| if (savedEntry) { | |
| // 恢复保存的数据 | |
| entry.threadId = savedEntry.threadId || entry.threadId; | |
| entry.enabled = savedEntry.enabled !== undefined ? savedEntry.enabled : entry.enabled; | |
| entry.lastUsed = savedEntry.lastUsed || entry.lastUsed; | |
| logger.info(`恢复用户 ${entry.userId} 的数据: threadId=${entry.threadId}`); | |
| } | |
| mergedEntries.push(entry); | |
| } | |
| return mergedEntries; | |
| } | |
| /** | |
| * 生成Cookie的哈希值(用于匹配,不存储实际cookie) | |
| * @param {string} cookie - Cookie字符串 | |
| * @returns {string} - 哈希值 | |
| */ | |
| hashCookie(cookie) { | |
| if (!cookie) return ''; | |
| // 简单的哈希实现,取cookie的前20个字符和后20个字符 | |
| const prefix = cookie.substring(0, 20); | |
| const suffix = cookie.substring(Math.max(0, cookie.length - 20)); | |
| return `${prefix}...${suffix}`; | |
| } | |
| /** | |
| * 清理过期数据 | |
| * @param {number} daysToKeep - 保留多少天的数据 | |
| */ | |
| cleanupOldData(daysToKeep = 30) { | |
| try { | |
| const dataDir = path.dirname(this.dataFilePath); | |
| const files = fs.readdirSync(dataDir); | |
| const now = Date.now(); | |
| const maxAge = daysToKeep * 24 * 60 * 60 * 1000; | |
| files.forEach(file => { | |
| if (file.startsWith('cookies-data') && file.endsWith('.backup.json')) { | |
| const filePath = path.join(dataDir, file); | |
| const stats = fs.statSync(filePath); | |
| if (now - stats.mtime.getTime() > maxAge) { | |
| fs.unlinkSync(filePath); | |
| logger.info(`删除过期备份文件: ${file}`); | |
| } | |
| } | |
| }); | |
| } catch (error) { | |
| logger.error(`清理过期数据失败: ${error.message}`); | |
| } | |
| } | |
| } | |
| export const storageManager = new StorageManager(); | |