Spaces:
Running
Running
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(); | |