Spaces:
Paused
Paused
Upload 27 files
Browse files- Dockerfile +5 -0
- src/utils/storage.js +191 -203
Dockerfile
CHANGED
|
@@ -14,6 +14,11 @@ RUN npm install --production
|
|
| 14 |
# 复制项目源代码
|
| 15 |
COPY . .
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# 确保代理服务器可执行 (Hugging Face Spaces 通常运行在 linux/amd64 架构上)
|
| 18 |
RUN chmod +x src/proxy/chrome_proxy_server_linux_amd64
|
| 19 |
|
|
|
|
| 14 |
# 复制项目源代码
|
| 15 |
COPY . .
|
| 16 |
|
| 17 |
+
# 解决权限问题:将工作目录的所有权交给 node 用户
|
| 18 |
+
# node 镜像默认使用非 root 的 'node' 用户运行应用
|
| 19 |
+
# /app 目录默认由 root 创建,导致应用没有写入权限
|
| 20 |
+
RUN chown -R node:node /app
|
| 21 |
+
|
| 22 |
# 确保代理服务器可执行 (Hugging Face Spaces 通常运行在 linux/amd64 架构上)
|
| 23 |
RUN chmod +x src/proxy/chrome_proxy_server_linux_amd64
|
| 24 |
|
src/utils/storage.js
CHANGED
|
@@ -1,203 +1,191 @@
|
|
| 1 |
-
import fs from 'fs';
|
| 2 |
-
import path from 'path';
|
| 3 |
-
import { fileURLToPath } from 'url';
|
| 4 |
-
import { dirname } from 'path';
|
| 5 |
-
import { createLogger } from './logger.js';
|
| 6 |
-
|
| 7 |
-
const __filename = fileURLToPath(import.meta.url);
|
| 8 |
-
const __dirname = dirname(__filename);
|
| 9 |
-
|
| 10 |
-
const logger = createLogger('Storage');
|
| 11 |
-
|
| 12 |
-
/**
|
| 13 |
-
* 持久化存储管理器
|
| 14 |
-
* 用于保存和加载Cookie数据(包括Thread ID)
|
| 15 |
-
*/
|
| 16 |
-
class StorageManager {
|
| 17 |
-
constructor() {
|
| 18 |
-
//
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
};
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
return
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
*
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
fs.unlinkSync(filePath);
|
| 193 |
-
logger.info(`删除过期备份文件: ${file}`);
|
| 194 |
-
}
|
| 195 |
-
}
|
| 196 |
-
});
|
| 197 |
-
} catch (error) {
|
| 198 |
-
logger.error(`清理过期数据失败: ${error.message}`);
|
| 199 |
-
}
|
| 200 |
-
}
|
| 201 |
-
}
|
| 202 |
-
|
| 203 |
-
export const storageManager = new StorageManager();
|
|
|
|
| 1 |
+
import fs from 'fs';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
import { fileURLToPath } from 'url';
|
| 4 |
+
import { dirname } from 'path';
|
| 5 |
+
import { createLogger } from './logger.js';
|
| 6 |
+
|
| 7 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 8 |
+
const __dirname = dirname(__filename);
|
| 9 |
+
|
| 10 |
+
const logger = createLogger('Storage');
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* 持久化存储管理器
|
| 14 |
+
* 用于保存和加载Cookie数据(包括Thread ID)
|
| 15 |
+
*/
|
| 16 |
+
class StorageManager {
|
| 17 |
+
constructor() {
|
| 18 |
+
// 数据文件路径
|
| 19 |
+
this.dataFilePath = path.join(dirname(dirname(__dirname)), 'data', 'cookies-data.json');
|
| 20 |
+
this.backupFilePath = path.join(dirname(dirname(__dirname)), 'data', 'cookies-data.backup.json');
|
| 21 |
+
|
| 22 |
+
// 确保数据目录存在
|
| 23 |
+
this.ensureDataDirectory();
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
/**
|
| 27 |
+
* 确保数据目录存在
|
| 28 |
+
*/
|
| 29 |
+
ensureDataDirectory() {
|
| 30 |
+
const dataDir = path.dirname(this.dataFilePath);
|
| 31 |
+
if (!fs.existsSync(dataDir)) {
|
| 32 |
+
fs.mkdirSync(dataDir, { recursive: true });
|
| 33 |
+
logger.info(`创建数据目录: ${dataDir}`);
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
/**
|
| 38 |
+
* 保存Cookie数据到文件
|
| 39 |
+
* @param {Array} cookieEntries - Cookie条目数组
|
| 40 |
+
* @returns {boolean} - 是否保存成功
|
| 41 |
+
*/
|
| 42 |
+
saveCookieData(cookieEntries) {
|
| 43 |
+
try {
|
| 44 |
+
// 准备要保存的数据
|
| 45 |
+
const dataToSave = {
|
| 46 |
+
version: '1.0',
|
| 47 |
+
lastUpdated: new Date().toISOString(),
|
| 48 |
+
cookies: cookieEntries.map(entry => ({
|
| 49 |
+
userId: entry.userId,
|
| 50 |
+
spaceId: entry.spaceId,
|
| 51 |
+
threadId: entry.threadId,
|
| 52 |
+
enabled: entry.enabled,
|
| 53 |
+
valid: entry.valid,
|
| 54 |
+
lastUsed: entry.lastUsed,
|
| 55 |
+
// 不保存实际的cookie值,只保存其哈希或标识
|
| 56 |
+
cookieHash: this.hashCookie(entry.cookie)
|
| 57 |
+
}))
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
// 先备份现有文件
|
| 61 |
+
if (fs.existsSync(this.dataFilePath)) {
|
| 62 |
+
fs.copyFileSync(this.dataFilePath, this.backupFilePath);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// 保存新数据
|
| 66 |
+
fs.writeFileSync(this.dataFilePath, JSON.stringify(dataToSave, null, 2), 'utf8');
|
| 67 |
+
logger.info(`成功保存 ${cookieEntries.length} 个Cookie的数据`);
|
| 68 |
+
return true;
|
| 69 |
+
} catch (error) {
|
| 70 |
+
logger.error(`保存Cookie数据失败: ${error.message}`);
|
| 71 |
+
return false;
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/**
|
| 76 |
+
* 加载Cookie数据
|
| 77 |
+
* @returns {Object|null} - 加载的数据或null
|
| 78 |
+
*/
|
| 79 |
+
loadCookieData() {
|
| 80 |
+
try {
|
| 81 |
+
if (!fs.existsSync(this.dataFilePath)) {
|
| 82 |
+
logger.info('Cookie数据文件不存在');
|
| 83 |
+
return null;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
const fileContent = fs.readFileSync(this.dataFilePath, 'utf8');
|
| 87 |
+
const data = JSON.parse(fileContent);
|
| 88 |
+
|
| 89 |
+
logger.info(`成功加载 ${data.cookies?.length || 0} 个Cookie的数据`);
|
| 90 |
+
return data;
|
| 91 |
+
} catch (error) {
|
| 92 |
+
logger.error(`加载Cookie数据失败: ${error.message}`);
|
| 93 |
+
|
| 94 |
+
// 尝试从备份恢复
|
| 95 |
+
if (fs.existsSync(this.backupFilePath)) {
|
| 96 |
+
try {
|
| 97 |
+
logger.info('尝试从备份文件恢复...');
|
| 98 |
+
const backupContent = fs.readFileSync(this.backupFilePath, 'utf8');
|
| 99 |
+
const backupData = JSON.parse(backupContent);
|
| 100 |
+
|
| 101 |
+
// 将备份恢复为主文件
|
| 102 |
+
fs.copyFileSync(this.backupFilePath, this.dataFilePath);
|
| 103 |
+
logger.success('成功从备份恢复数据');
|
| 104 |
+
return backupData;
|
| 105 |
+
} catch (backupError) {
|
| 106 |
+
logger.error(`从备份恢复失败: ${backupError.message}`);
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
return null;
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
/**
|
| 115 |
+
* 合并保存的数据和内存中的Cookie条目
|
| 116 |
+
* @param {Array} cookieEntries - 内存中的Cookie条目
|
| 117 |
+
* @param {Object} savedData - 保存的数据
|
| 118 |
+
* @returns {Array} - 合并后的Cookie条目
|
| 119 |
+
*/
|
| 120 |
+
mergeCookieData(cookieEntries, savedData) {
|
| 121 |
+
if (!savedData || !savedData.cookies) {
|
| 122 |
+
return cookieEntries;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
const mergedEntries = [];
|
| 126 |
+
|
| 127 |
+
// 为每个内存中的cookie条目恢复保存的数据
|
| 128 |
+
for (const entry of cookieEntries) {
|
| 129 |
+
const savedEntry = savedData.cookies.find(saved =>
|
| 130 |
+
saved.userId === entry.userId ||
|
| 131 |
+
saved.cookieHash === this.hashCookie(entry.cookie)
|
| 132 |
+
);
|
| 133 |
+
|
| 134 |
+
if (savedEntry) {
|
| 135 |
+
// 恢复保存的数据
|
| 136 |
+
entry.threadId = savedEntry.threadId || entry.threadId;
|
| 137 |
+
entry.enabled = savedEntry.enabled !== undefined ? savedEntry.enabled : entry.enabled;
|
| 138 |
+
entry.lastUsed = savedEntry.lastUsed || entry.lastUsed;
|
| 139 |
+
|
| 140 |
+
logger.info(`恢复用户 ${entry.userId} 的数据: threadId=${entry.threadId}`);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
mergedEntries.push(entry);
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
return mergedEntries;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
/**
|
| 150 |
+
* 生成Cookie的哈希值(用于匹配,不存储实际cookie)
|
| 151 |
+
* @param {string} cookie - Cookie字符串
|
| 152 |
+
* @returns {string} - 哈希值
|
| 153 |
+
*/
|
| 154 |
+
hashCookie(cookie) {
|
| 155 |
+
if (!cookie) return '';
|
| 156 |
+
|
| 157 |
+
// 简单的哈希实现,取cookie的前20个字符和后20个字符
|
| 158 |
+
const prefix = cookie.substring(0, 20);
|
| 159 |
+
const suffix = cookie.substring(Math.max(0, cookie.length - 20));
|
| 160 |
+
return `${prefix}...${suffix}`;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
/**
|
| 164 |
+
* 清理过期数据
|
| 165 |
+
* @param {number} daysToKeep - 保留多少天的数据
|
| 166 |
+
*/
|
| 167 |
+
cleanupOldData(daysToKeep = 30) {
|
| 168 |
+
try {
|
| 169 |
+
const dataDir = path.dirname(this.dataFilePath);
|
| 170 |
+
const files = fs.readdirSync(dataDir);
|
| 171 |
+
const now = Date.now();
|
| 172 |
+
const maxAge = daysToKeep * 24 * 60 * 60 * 1000;
|
| 173 |
+
|
| 174 |
+
files.forEach(file => {
|
| 175 |
+
if (file.startsWith('cookies-data') && file.endsWith('.backup.json')) {
|
| 176 |
+
const filePath = path.join(dataDir, file);
|
| 177 |
+
const stats = fs.statSync(filePath);
|
| 178 |
+
|
| 179 |
+
if (now - stats.mtime.getTime() > maxAge) {
|
| 180 |
+
fs.unlinkSync(filePath);
|
| 181 |
+
logger.info(`删除过期备份文件: ${file}`);
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
});
|
| 185 |
+
} catch (error) {
|
| 186 |
+
logger.error(`清理过期数据失败: ${error.message}`);
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
export const storageManager = new StorageManager();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|