clash-linux commited on
Commit
825e3f3
·
verified ·
1 Parent(s): 2fef15e

Upload 27 files

Browse files
Files changed (2) hide show
  1. Dockerfile +5 -0
  2. 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
- // 优先使用环境变量 DATA_DIR,如果未设置,则默认为项目根目录下的 'data' 文件夹
19
- // 这样可以灵活配置数据存储路径,以适应不同的部署环境(如 Hugging Face Spaces)
20
- const dataDir = process.env.DATA_DIR || path.join(process.cwd(), 'data');
21
-
22
- logger.info(`Data directory is set to: ${dataDir}`);
23
-
24
- // 数据文件路径
25
- this.dataFilePath = path.join(dataDir, 'cookies-data.json');
26
- this.backupFilePath = path.join(dataDir, 'cookies-data.backup.json');
27
-
28
- // 确保数据目录存在
29
- this.ensureDataDirectory();
30
- }
31
-
32
- /**
33
- * 确保数据目录存在
34
- */
35
- ensureDataDirectory() {
36
- const dataDir = path.dirname(this.dataFilePath);
37
- if (!fs.existsSync(dataDir)) {
38
- try {
39
- fs.mkdirSync(dataDir, { recursive: true });
40
- logger.info(`创建数据目录: ${dataDir}`);
41
- } catch (error) {
42
- logger.error(`创建数据目录失败: ${dataDir}. 请确保此路径具有写入权限。`);
43
- logger.error('在 Hugging Face Spaces 等环境中,您可能需要设置 DATA_DIR 环境变量。');
44
- throw error;
45
- }
46
- }
47
- }
48
-
49
- /**
50
- * 保存Cookie数据到文件
51
- * @param {Array} cookieEntries - Cookie条目数组
52
- * @returns {boolean} - 是否保存成功
53
- */
54
- saveCookieData(cookieEntries) {
55
- try {
56
- // 准备要保存的数据
57
- const dataToSave = {
58
- version: '1.0',
59
- lastUpdated: new Date().toISOString(),
60
- cookies: cookieEntries.map(entry => ({
61
- userId: entry.userId,
62
- spaceId: entry.spaceId,
63
- threadId: entry.threadId,
64
- enabled: entry.enabled,
65
- valid: entry.valid,
66
- lastUsed: entry.lastUsed,
67
- // 不保存实际的cookie值,只保存其哈希或标识
68
- cookieHash: this.hashCookie(entry.cookie)
69
- }))
70
- };
71
-
72
- // 先备份现有文件
73
- if (fs.existsSync(this.dataFilePath)) {
74
- fs.copyFileSync(this.dataFilePath, this.backupFilePath);
75
- }
76
-
77
- // 保存新数据
78
- fs.writeFileSync(this.dataFilePath, JSON.stringify(dataToSave, null, 2), 'utf8');
79
- logger.info(`成功保存 ${cookieEntries.length} 个Cookie的数据`);
80
- return true;
81
- } catch (error) {
82
- logger.error(`保存Cookie数据失败: ${error.message}`);
83
- return false;
84
- }
85
- }
86
-
87
- /**
88
- * 加载Cookie数据
89
- * @returns {Object|null} - 加载的数据或null
90
- */
91
- loadCookieData() {
92
- try {
93
- if (!fs.existsSync(this.dataFilePath)) {
94
- logger.info('Cookie数据文件不存在');
95
- return null;
96
- }
97
-
98
- const fileContent = fs.readFileSync(this.dataFilePath, 'utf8');
99
- const data = JSON.parse(fileContent);
100
-
101
- logger.info(`成功加载 ${data.cookies?.length || 0} 个Cookie的数据`);
102
- return data;
103
- } catch (error) {
104
- logger.error(`加载Cookie数据失败: ${error.message}`);
105
-
106
- // 尝试从备份恢复
107
- if (fs.existsSync(this.backupFilePath)) {
108
- try {
109
- logger.info('尝试从备份文件恢复...');
110
- const backupContent = fs.readFileSync(this.backupFilePath, 'utf8');
111
- const backupData = JSON.parse(backupContent);
112
-
113
- // 将备份恢复为主文件
114
- fs.copyFileSync(this.backupFilePath, this.dataFilePath);
115
- logger.success('成功从备份恢复数据');
116
- return backupData;
117
- } catch (backupError) {
118
- logger.error(`从备份恢复失败: ${backupError.message}`);
119
- }
120
- }
121
-
122
- return null;
123
- }
124
- }
125
-
126
- /**
127
- * 合并保存的数据和内存中的Cookie条目
128
- * @param {Array} cookieEntries - 内存中的Cookie条目
129
- * @param {Object} savedData - 保存的数据
130
- * @returns {Array} - 合并后的Cookie条目
131
- */
132
- mergeCookieData(cookieEntries, savedData) {
133
- if (!savedData || !savedData.cookies) {
134
- return cookieEntries;
135
- }
136
-
137
- const mergedEntries = [];
138
-
139
- // 为每个内存中的cookie条目恢复保存的数据
140
- for (const entry of cookieEntries) {
141
- const savedEntry = savedData.cookies.find(saved =>
142
- saved.userId === entry.userId ||
143
- saved.cookieHash === this.hashCookie(entry.cookie)
144
- );
145
-
146
- if (savedEntry) {
147
- // 恢复保存的数据
148
- entry.threadId = savedEntry.threadId || entry.threadId;
149
- entry.enabled = savedEntry.enabled !== undefined ? savedEntry.enabled : entry.enabled;
150
- entry.lastUsed = savedEntry.lastUsed || entry.lastUsed;
151
-
152
- logger.info(`恢复用户 ${entry.userId} 的数据: threadId=${entry.threadId}`);
153
- }
154
-
155
- mergedEntries.push(entry);
156
- }
157
-
158
- return mergedEntries;
159
- }
160
-
161
- /**
162
- * 生成Cookie的哈希值(用于匹配,不存储实际cookie)
163
- * @param {string} cookie - Cookie字符串
164
- * @returns {string} - 哈希值
165
- */
166
- hashCookie(cookie) {
167
- if (!cookie) return '';
168
-
169
- // 简单的哈希实现,取cookie的前20个字符和后20个字符
170
- const prefix = cookie.substring(0, 20);
171
- const suffix = cookie.substring(Math.max(0, cookie.length - 20));
172
- return `${prefix}...${suffix}`;
173
- }
174
-
175
- /**
176
- * 清理过期数据
177
- * @param {number} daysToKeep - 保留多少天的数据
178
- */
179
- cleanupOldData(daysToKeep = 30) {
180
- try {
181
- const dataDir = path.dirname(this.dataFilePath);
182
- const files = fs.readdirSync(dataDir);
183
- const now = Date.now();
184
- const maxAge = daysToKeep * 24 * 60 * 60 * 1000;
185
-
186
- files.forEach(file => {
187
- if (file.startsWith('cookies-data') && file.endsWith('.backup.json')) {
188
- const filePath = path.join(dataDir, file);
189
- const stats = fs.statSync(filePath);
190
-
191
- if (now - stats.mtime.getTime() > maxAge) {
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();