Maple7F/build-server
0
1const express = require('express');
2const WebSocket = require('ws');
3const http = require('http');
4const { EventEmitter } = require('events');
5const fs = require('fs');
6const path = require('path');
7const { firefox } = require('playwright');
8const os = require('os');
9
10
11// ===================================================================================
12// 认证源管理模块 (已升级以支持动态管理)
13// ===================================================================================
14
15class AuthSource {
16 constructor(logger) {
17 this.logger = logger;
18 this.authMode = 'file'; // 默认模式
19 this.initialIndices = []; // 启动时发现的索引
20 this.runtimeAuths = new Map(); // 用于动态添加的账号
21
22 if (process.env.AUTH_JSON_1) {
23 this.authMode = 'env';
24 this.logger.info('[认证] 检测到 AUTH_JSON_1 环境变量,切换到环境变量认证模式。');
25 } else {
26 this.logger.info('[认证] 未检测到环境变量认证,将使用 "auth/" 目录下的文件。');
27 }
28
29 this._discoverAvailableIndices();
30
31 if (this.getAvailableIndices().length === 0) {
32 this.logger.error(`[认证] 致命错误:在 '${this.authMode}' 模式下未找到任何有效的认证源。`);
33 throw new Error("未找到有效的认证源。");
34 }
35 }
36
37 _discoverAvailableIndices() {
38 let indices = [];
39 if (this.authMode === 'env') {
40 const regex = /^AUTH_JSON_(\d+)$/;
41 for (const key in process.env) {
42 const match = key.match(regex);
43 // 修正:正确解析捕获组 (match[1]) 而不是整个匹配对象
44 if (match && match[1]) {
45 indices.push(parseInt(match[1], 10));
46 }
47 }
48 } else { // 'file' 模式
49 const authDir = path.join(__dirname, 'auth');
50 if (!fs.existsSync(authDir)) {
51 this.logger.warn('[认证] "auth/" 目录不存在。');
52 this.initialIndices = [];
53 return;
54 }
55 try {
56 const files = fs.readdirSync(authDir);
57 const authFiles = files.filter(file => /^auth-\d+\.json$/.test(file));
58 // 修正:正确解析文件名中的捕获组 (match[1])
59 indices = authFiles.map(file => {
60 const match = file.match(/^auth-(\d+)\.json$/);
61 return parseInt(match[1], 10);
62 });
63 } catch (error) {
64 this.logger.error(`[认证] 扫描 "auth/" 目录失败: ${error.message}`);
65 this.initialIndices = [];
66 return;
67 }
68 }
69 this.initialIndices = [...new Set(indices)].sort((a, b) => a - b);
70 this.logger.info(`[认证] 在 '${this.authMode}' 模式下,检测到 ${this.initialIndices.length} 个认证源。`);
71 if (this.initialIndices.length > 0) {
72 this.logger.info(`[认证] 可用初始索引: [${this.initialIndices.join(', ')}]`);
73 }
74 }
75
76 getAvailableIndices() {
77 const runtimeIndices = Array.from(this.runtimeAuths.keys());
78 const allIndices = [...new Set([...this.initialIndices, ...runtimeIndices])].sort((a, b) => a - b);
79 return allIndices;
80 }
81
82 // 新增方法:为仪表盘获取详细信息
83 getAccountDetails() {
84 const allIndices = this.getAvailableIndices();
85 return allIndices.map(index => ({
86 index,
87 source: this.runtimeAuths.has(index) ? 'temporary' : this.authMode
88 }));
89 }
90
91
92 getFirstAvailableIndex() {
93 const indices = this.getAvailableIndices();
94 return indices.length > 0 ? indices[0] : null;
95 }
96
97 getAuth(index) {
98 if (!this.getAvailableIndices().includes(index)) {
99 this.logger.error(`[认证] 请求了无效或不存在的认证索引: ${index}`);
100 return null;
101 }
102
103 // 优先使用运行时(临时)的认证信息
104 if (this.runtimeAuths.has(index)) {
105 this.logger.info(`[认证] 使用索引 ${index} 的临时认证源。`);
106 return this.runtimeAuths.get(index);
107 }
108
109 let jsonString;
110 let sourceDescription;
111
112 if (this.authMode === 'env') {
113 jsonString = process.env[`AUTH_JSON_${index}`];
114 sourceDescription = `环境变量 AUTH_JSON_${index}`;
115 } else {
116 const authFilePath = path.join(__dirname, 'auth', `auth-${index}.json`);
117 sourceDescription = `文件 ${authFilePath}`;
118 if (!fs.existsSync(authFilePath)) {
119 this.logger.error(`[认证] ${sourceDescription} 在读取时突然消失。`);
120 return null;
121 }
122 try {
123 jsonString = fs.readFileSync(authFilePath, 'utf-8');
124 } catch (e) {
125 this.logger.error(`[认证] 读取 ${sourceDescription} 失败: ${e.message}`);
126 return null;
127 }
128 }
129
130 try {
131 return JSON.parse(jsonString);
132 } catch (e) {
133 this.logger.error(`[认证] 解析来自 ${sourceDescription} 的JSON内容失败: ${e.message}`);
134 return null;
135 }
136 }
137
138 // 新增方法:动态添加账号
139 addAccount(index, authData) {
140 if (typeof index !== 'number' || index <= 0) {
141 return { success: false, message: "索引必须是一个正数。" };
142 }
143 if (this.initialIndices.includes(index)) {
144 return { success: false, message: `索引 ${index} 已作为永久账号存在。` };
145 }
146 try {
147 // 验证 authData 是否为有效的JSON对象
148 if(typeof authData !== 'object' || authData === null) {
149 throw new Error("提供的数据不是一个有效的对象。");
150 }
151 this.runtimeAuths.set(index, authData);
152 this.logger.info(`[认证] 成功添加索引为 ${index} 的临时账号。`);
153 return { success: true, message: `账号 ${index} 已临时添加。` };
154 } catch (e) {
155 this.logger.error(`[认证] 添加临时账号 ${index} 失败: ${e.message}`);
156 return { success: false, message: `添加账号失败: ${e.message}` };
157 }
158 }
159
160 // 新增方法:动态删除账号
161 removeAccount(index) {
162 if (!this.runtimeAuths.has(index)) {
163 return { success: false, message: `索引 ${index} 不是一个临时账号,无法移除。` };
164 }
165 this.runtimeAuths.delete(index);
166 this.logger.info(`[认证] 成功移除索引为 ${index} 的临时账号。`);
167 return { success: true, message: `账号 ${index} 已移除。` };
168 }
169}
170
171
172// ===================================================================================
173// 浏览器管理模块
174// ===================================================================================
175
176class BrowserManager {
177 constructor(logger, config, authSource) {
178 this.logger = logger;
179 this.config = config;
180 this.authSource = authSource;
181 this.browser = null;
182 this.context = null;
183 this.page = null;
184 this.currentAuthIndex = 0;
185 this.scriptFileName = 'dark-browser.js';
186
187 if (this.config.browserExecutablePath) {
188 this.browserExecutablePath = this.config.browserExecutablePath;
189 this.logger.info(`[系统] 使用环境变量 CAMOUFOX_EXECUTABLE_PATH 指定的浏览器路径。`);
190 } else {
191 const platform = os.platform();
192 if (platform === 'win32') {
193 this.browserExecutablePath = path.join(__dirname, 'camoufox', 'camoufox.exe');
194 this.logger.info(`[系统] 检测到操作系统: Windows. 将使用 'camoufox' 目录下的浏览器。`);
195 } else if (platform === 'linux') {
196 this.browserExecutablePath = path.join(__dirname, 'camoufox-linux', 'camoufox');
197 this.logger.info(`[系统] 检测到操作系统: Linux. 将使用 'camoufox-linux' 目录下的浏览器。`);
198 } else {
199 this.logger.error(`[系统] 不支持的操作系统: ${platform}.`);
200 throw new Error(`不支持的操作系统: ${platform}`);
201 }
202 }
203 }
204
205 async launchBrowser(authIndex) {
206 if (this.browser) {
207 this.logger.warn('尝试启动一个已在运行的浏览器实例,操作已取消。');
208 return;
209 }
210
211 const sourceDescription = this.authSource.authMode === 'env' ? `环境变量 AUTH_JSON_${authIndex}` : `文件 auth-${authIndex}.json`;
212 this.logger.info('==================================================');
213 this.logger.info(`🚀 [浏览器] 准备启动浏览器`);
214 this.logger.info(` • 认证源: ${sourceDescription}`);
215 this.logger.info(` • 浏览器路径: ${this.browserExecutablePath}`);
216 this.logger.info('==================================================');
217
218 if (!fs.existsSync(this.browserExecutablePath)) {
219 this.logger.error(`❌ [浏览器] 找不到浏览器可执行文件: ${this.browserExecutablePath}`);
220 throw new Error(`找不到浏览器可执行文件路径: ${this.browserExecutablePath}`);
221 }
222
223 const storageStateObject = this.authSource.getAuth(authIndex);
224 if (!storageStateObject) {
225 this.logger.error(`❌ [浏览器] 无法获取或解析索引为 ${authIndex} 的认证信息。`);
226 throw new Error(`获取或解析索引 ${authIndex} 的认证源失败。`);
227 }
228
229 if (storageStateObject.cookies && Array.isArray(storageStateObject.cookies)) {
230 let fixedCount = 0;
231 const validSameSiteValues = ['Lax', 'Strict', 'None'];
232 storageStateObject.cookies.forEach(cookie => {
233 if (!validSameSiteValues.includes(cookie.sameSite)) {
234 this.logger.warn(`[认证] 发现无效的 sameSite 值: '${cookie.sameSite}',正在自动修正为 'None'。`);
235 cookie.sameSite = 'None';
236 fixedCount++;
237 }
238 });
239 if (fixedCount > 0) {
240 this.logger.info(`[认证] 自动修正了 ${fixedCount} 个无效的 Cookie 'sameSite' 属性。`);
241 }
242 }
243
244 let buildScriptContent;
245 try {
246 const scriptFilePath = path.join(__dirname, this.scriptFileName);
247 if(fs.existsSync(scriptFilePath)){
248 buildScriptContent = fs.readFileSync(scriptFilePath, 'utf-8');
249 this.logger.info(`✅ [浏览器] 成功读取注入脚本 "${this.scriptFileName}"`);
250 } else {
251 this.logger.warn(`[浏览器] 未找到注入脚本 "${this.scriptFileName}"。将无注入继续运行。`);
252 buildScriptContent = "console.log('dark-browser.js not found, running without injection.');";
253 }
254 } catch (error) {
255 this.logger.error(`❌ [浏览器] 无法读取注入脚本 "${this.scriptFileName}"!`);
256 throw error;
257 }
258
259 try {
260 this.browser = await firefox.launch({
261 headless: true,
262 executablePath: this.browserExecutablePath,
263 });
264 this.browser.on('disconnected', () => {
265 this.logger.error('❌ [浏览器] 浏览器意外断开连接!服务器可能需要重启。');
266 this.browser = null; this.context = null; this.page = null;
267 });
268 this.context = await this.browser.newContext({
269 storageState: storageStateObject,
270 viewport: { width: 1280, height: 720 },
271 });
272 this.page = await this.context.newPage();
273 this.logger.info(`[浏览器] 正在加载账号 ${authIndex} 并访问目标网页...`);
274 const targetUrl = 'https://aistudio.google.com/u/0/apps/bundled/blank?showPreview=true&showCode=true&showAssistant=true';
275 await this.page.goto(targetUrl, { timeout: 120000, waitUntil: 'networkidle' });
276 this.logger.info('[浏览器] 网页加载完成,正在注入客户端脚本...');
277
278 const editorContainerLocator = this.page.locator('div.monaco-editor').first();
279
280 this.logger.info('[浏览器] 等待编辑器出现,最长120秒...');
281 await editorContainerLocator.waitFor({ state: 'visible', timeout: 120000 });
282 this.logger.info('[浏览器] 编辑器已出现,准备粘贴脚本。');
283
284 this.logger.info('[浏览器] 等待5秒,之后将在页面下方执行一次模拟点击以确保页面激活...');
285 await this.page.waitForTimeout(5000);
286
287 const viewport = this.page.viewportSize();
288 if (viewport) {
289 const clickX = viewport.width / 2;
290 const clickY = viewport.height - 120;
291 this.logger.info(`[浏览器] 在页面底部中心位置 (x≈${Math.round(clickX)}, y=${clickY}) 执行点击。`);
292 await this.page.mouse.click(clickX, clickY);
293 } else {
294 this.logger.warn('[浏览器] 无法获取视窗大小,跳过页面底部模拟点击。');
295 }
296
297 await editorContainerLocator.click({ timeout: 120000 });
298 await this.page.evaluate(text => navigator.clipboard.writeText(text), buildScriptContent);
299 const isMac = os.platform() === 'darwin';
300 const pasteKey = isMac ? 'Meta+V' : 'Control+V';
301 await this.page.keyboard.press(pasteKey);
302 this.logger.info('[浏览器] 脚本已粘贴。浏览器端初始化完成。');
303
304
305 this.currentAuthIndex = authIndex;
306 this.logger.info('==================================================');
307 this.logger.info(`✅ [浏览器] 账号 ${authIndex} 初始化成功!`);
308 this.logger.info('✅ [浏览器] 浏览器客户端已准备就绪。');
309 this.logger.info('==================================================');
310 } catch (error) {
311 this.logger.error(`❌ [浏览器] 账号 ${authIndex} 初始化失败: ${error.message}`);
312 if (this.browser) {
313 await this.browser.close();
314 this.browser = null;
315 }
316 throw error;
317 }
318 }
319
320 async closeBrowser() {
321 if (this.browser) {
322 this.logger.info('[浏览器] 正在关闭当前浏览器实例...');
323 await this.browser.close();
324 this.browser = null; this.context = null; this.page = null;
325 this.logger.info('[浏览器] 浏览器已关闭。');
326 }
327 }
328
329 async switchAccount(newAuthIndex) {
330 this.logger.info(`🔄 [浏览器] 开始账号切换: 从 ${this.currentAuthIndex} 到 ${newAuthIndex}`);
331 await this.closeBrowser();
332 await this.launchBrowser(newAuthIndex);
333 this.logger.info(`✅ [浏览器] 账号切换完成,当前账号: ${this.currentAuthIndex}`);
334 }
335}
336
337// ===================================================================================
338// 代理服务模块
339// ===================================================================================
340
341class LoggingService {
342 constructor(serviceName = 'ProxyServer') {
343 this.serviceName = serviceName;
344 }
345
346 _getFormattedTime() {
347 // 使用 toLocaleTimeString 并指定 en-GB 区域来保证输出为 HH:mm:ss 格式
348 return new Date().toLocaleTimeString('en-GB', { hour12: false });
349 }
350
351 // 用于 ERROR, WARN, DEBUG 等带有级别标签的日志
352 _formatMessage(level, message) {
353 const time = this._getFormattedTime();
354 return `[${level}] ${time} [${this.serviceName}] - ${message}`;
355 }
356
357 // info 级别使用特殊格式,不显示 [INFO]
358 info(message) {
359 const time = this._getFormattedTime();
360 console.log(`${time} [${this.serviceName}] - ${message}`);
361 }
362
363 error(message) {
364 console.error(this._formatMessage('ERROR', message));
365 }
366
367 warn(message) {
368 console.warn(this._formatMessage('WARN', message));
369 }
370
371 debug(message) {
372 if(process.env.DEBUG_MODE === 'true') {
373 console.debug(this._formatMessage('DEBUG', message));
374 }
375 }
376}
377
378class MessageQueue extends EventEmitter {
379 constructor(timeoutMs = 1200000) {
380 super();
381 this.messages = [];
382 this.waitingResolvers = [];
383 this.defaultTimeout = timeoutMs;
384 this.closed = false;
385 }
386 enqueue(message) {
387 if (this.closed) return;
388 if (this.waitingResolvers.length > 0) {
389 const resolver = this.waitingResolvers.shift();
390 resolver.resolve(message);
391 } else {
392 this.messages.push(message);
393 }
394 }
395 async dequeue(timeoutMs = this.defaultTimeout) {
396 if (this.closed) {
397 throw new Error('队列已关闭');
398 }
399 return new Promise((resolve, reject) => {
400 if (this.messages.length > 0) {
401 resolve(this.messages.shift());
402 return;
403 }
404 const resolver = { resolve, reject };
405 this.waitingResolvers.push(resolver);
406 const timeoutId = setTimeout(() => {
407 const index = this.waitingResolvers.indexOf(resolver);
408 if (index !== -1) {
409 this.waitingResolvers.splice(index, 1);
410 reject(new Error('队列超时'));
411 }
412 }, timeoutMs);
413 resolver.timeoutId = timeoutId;
414 });
415 }
416 close() {
417 this.closed = true;
418 this.waitingResolvers.forEach(resolver => {
419 clearTimeout(resolver.timeoutId);
420 resolver.reject(new Error('队列已关闭'));
421 });
422 this.waitingResolvers = [];
423 this.messages = [];
424 }
425}
426
427class ConnectionRegistry extends EventEmitter {
428 constructor(logger) {
429 super();
430 this.logger = logger;
431 this.connections = new Set();
432 this.messageQueues = new Map();
433 }
434 addConnection(websocket, clientInfo) {
435 this.connections.add(websocket);
436 this.logger.info(`[服务器] 内部WebSocket客户端已连接 (来自: ${clientInfo.address})`);
437 websocket.on('message', (data) => this._handleIncomingMessage(data.toString()));
438 websocket.on('close', () => this._removeConnection(websocket));
439 websocket.on('error', (error) => this.logger.error(`[服务器] 内部WebSocket连接错误: ${error.message}`));
440 this.emit('connectionAdded', websocket);
441 }
442 _removeConnection(websocket) {
443 this.connections.delete(websocket);
444 this.logger.warn('[服务器] 内部WebSocket客户端连接断开');
445 this.messageQueues.forEach(queue => queue.close());
446 this.messageQueues.clear();
447 this.emit('connectionRemoved', websocket);
448 }
449 _handleIncomingMessage(messageData) {
450 try {
451 const parsedMessage = JSON.parse(messageData);
452 const requestId = parsedMessage.request_id;
453 if (!requestId) {
454 this.logger.warn('[服务器] 收到无效消息:缺少request_id');
455 return;
456 }
457 const queue = this.messageQueues.get(requestId);
458 if (queue) {
459 this._routeMessage(parsedMessage, queue);
460 }
461 } catch (error) {
462 this.logger.error('[服务器] 解析内部WebSocket消息失败');
463 }
464 }
465 _routeMessage(message, queue) {
466 const { event_type } = message;
467 switch (event_type) {
468 case 'response_headers': case 'chunk': case 'error':
469 queue.enqueue(message);
470 break;
471 case 'stream_close':
472 queue.enqueue({ type: 'STREAM_END' });
473 break;
474 default:
475 this.logger.warn(`[服务器] 未知的内部事件类型: ${event_type}`);
476 }
477 }
478 hasActiveConnections() { return this.connections.size > 0; }
479 getFirstConnection() { return this.connections.values().next().value; }
480 createMessageQueue(requestId) {
481 const queue = new MessageQueue();
482 this.messageQueues.set(requestId, queue);
483 return queue;
484 }
485 removeMessageQueue(requestId) {
486 const queue = this.messageQueues.get(requestId);
487 if (queue) {
488 queue.close();
489 this.messageQueues.delete(requestId);
490 }
491 }
492}
493
494class RequestHandler {
495 constructor(serverSystem, connectionRegistry, logger, browserManager, config, authSource) {
496 this.serverSystem = serverSystem;
497 this.connectionRegistry = connectionRegistry;
498 this.logger = logger;
499 this.browserManager = browserManager;
500 this.config = config;
501 this.authSource = authSource;
502 this.maxRetries = this.config.maxRetries;
503 this.retryDelay = this.config.retryDelay;
504 this.failureCount = 0;
505 this.isAuthSwitching = false;
506 }
507
508 get currentAuthIndex() {
509 return this.browserManager.currentAuthIndex;
510 }
511
512 _getNextAuthIndex() {
513 const available = this.authSource.getAvailableIndices();
514 if (available.length === 0) return null;
515 if (available.length === 1) return available[0];
516
517 const currentIndexInArray = available.indexOf(this.currentAuthIndex);
518
519 if (currentIndexInArray === -1) {
520 this.logger.warn(`[认证] 当前索引 ${this.currentAuthIndex} 不在可用列表中,将切换到第一个可用索引。`);
521 return available[0];
522 }
523
524 const nextIndexInArray = (currentIndexInArray + 1) % available.length;
525 return available[nextIndexInArray];
526 }
527
528 async _switchToNextAuth() {
529 if (this.isAuthSwitching) {
530 this.logger.info('🔄 [认证] 正在切换账号,跳过重复切换');
531 return;
532 }
533
534 this.isAuthSwitching = true;
535 const nextAuthIndex = this._getNextAuthIndex();
536 const totalAuthCount = this.authSource.getAvailableIndices().length;
537
538 if (nextAuthIndex === null) {
539 this.logger.error('🔴 [认证] 无法切换账号,因为没有可用的认证源!');
540 this.isAuthSwitching = false;
541 throw new Error('没有可用的认证源可以切换。');
542 }
543
544 this.logger.info('==================================================');
545 this.logger.info(`🔄 [认证] 开始账号切换流程`);
546 this.logger.info(` • 失败次数: ${this.failureCount}/${this.config.failureThreshold > 0 ? this.config.failureThreshold : 'N/A'}`);
547 this.logger.info(` • 当前账号索引: ${this.currentAuthIndex}`);
548 this.logger.info(` • 目标账号索引: ${nextAuthIndex}`);
549 this.logger.info(` • 可用账号总数: ${totalAuthCount}`);
550 this.logger.info('==================================================');
551
552 try {
553 await this.browserManager.switchAccount(nextAuthIndex);
554 this.failureCount = 0;
555 this.logger.info('==================================================');
556 this.logger.info(`✅ [认证] 成功切换到账号索引 ${this.currentAuthIndex}`);
557 this.logger.info(`✅ [认证] 失败计数已重置为0`);
558 this.logger.info('==================================================');
559 } catch (error) {
560 this.logger.error('==================================================');
561 this.logger.error(`❌ [认证] 切换账号失败: ${error.message}`);
562 this.logger.error('==================================================');
563 throw error;
564 } finally {
565 this.isAuthSwitching = false;
566 }
567 }
568
569 _parseAndCorrectErrorDetails(errorDetails) {
570 const correctedDetails = { ...errorDetails };
571 this.logger.debug(`[错误解析器] 原始错误详情: status=${correctedDetails.status}, message="${correctedDetails.message}"`);
572
573 if (correctedDetails.message && typeof correctedDetails.message === 'string') {
574 const regex = /(?:HTTP|status code)\s+(\d{3})/;
575 const match = correctedDetails.message.match(regex);
576
577 if (match && match[1]) {
578 const parsedStatus = parseInt(match[1], 10);
579 if (parsedStatus >= 400 && parsedStatus <= 599) {
580 if (correctedDetails.status !== parsedStatus) {
581 this.logger.warn(`[错误解析器] 修正了错误状态码!原始: ${correctedDetails.status}, 从消息中解析得到: ${parsedStatus}`);
582 correctedDetails.status = parsedStatus;
583 } else {
584 this.logger.debug(`[错误解析器] 解析的状态码 (${parsedStatus}) 与原始状态码一致,无需修正。`);
585 }
586 }
587 }
588 }
589 return correctedDetails;
590 }
591
592 async _handleRequestFailureAndSwitch(errorDetails, res) {
593 const correctedDetails = { ...errorDetails };
594 if (correctedDetails.message && typeof correctedDetails.message === 'string') {
595 const regex = /(?:HTTP|status code)\s*(\d{3})|"code"\s*:\s*(\d{3})/;
596 const match = correctedDetails.message.match(regex);
597 const parsedStatusString = match ? (match[1] || match[2]) : null;
598
599 if (parsedStatusString) {
600 const parsedStatus = parseInt(parsedStatusString, 10);
601 if (parsedStatus >= 400 && parsedStatus <= 599 && correctedDetails.status !== parsedStatus) {
602 this.logger.warn(`[认证] 修正了错误状态码!原始: ${correctedDetails.status}, 从消息中解析得到: ${parsedStatus}`);
603 correctedDetails.status = parsedStatus;
604 }
605 }
606 }
607
608 const isImmediateSwitch = this.config.immediateSwitchStatusCodes.includes(correctedDetails.status);
609
610 if (isImmediateSwitch) {
611 this.logger.warn(`🔴 [认证] 收到状态码 ${correctedDetails.status} (已修正),触发立即切换账号...`);
612 if (res) this._sendErrorChunkToClient(res, `收到状态码 ${correctedDetails.status},正在尝试切换账号...`);
613 try {
614 await this._switchToNextAuth();
615 if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
616 } catch (switchError) {
617 this.logger.error(`🔴 [认证] 账号切换失败: ${switchError.message}`);
618 if (res) this._sendErrorChunkToClient(res, `切换账号失败: ${switchError.message}`);
619 }
620 return;
621 }
622
623 if (this.config.failureThreshold > 0) {
624 this.failureCount++;
625 this.logger.warn(`⚠️ [认证] 请求失败 - 失败计数: ${this.failureCount}/${this.config.failureThreshold} (当前账号索引: ${this.currentAuthIndex}, 状态码: ${correctedDetails.status})`);
626 if (this.failureCount >= this.config.failureThreshold) {
627 this.logger.warn(`🔴 [认证] 达到失败阈值!准备切换账号...`);
628 if (res) this._sendErrorChunkToClient(res, `连续失败${this.failureCount}次,正在尝试切换账号...`);
629 try {
630 await this._switchToNextAuth();
631 if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
632 } catch (switchError) {
633 this.logger.error(`🔴 [认证] 账号切换失败: ${switchError.message}`);
634 if (res) this._sendErrorChunkToClient(res, `切换账号失败: ${switchError.message}`);
635 }
636 }
637 } else {
638 this.logger.warn(`[认证] 请求失败 (状态码: ${correctedDetails.status})。基于计数的自动切换已禁用 (failureThreshold=0)`);
639 }
640 }
641
642 _getModelFromRequest(req) {
643 let body = req.body;
644
645 if (Buffer.isBuffer(body)) {
646 try {
647 body = JSON.parse(body.toString('utf-8'));
648 } catch (e) { body = {}; }
649 } else if (typeof body === 'string') {
650 try {
651 body = JSON.parse(body);
652 } catch(e) { body = {}; }
653 }
654
655 if (body && typeof body === 'object') {
656 if (body.model) return body.model;
657 if (body.generation_config && body.generation_config.model) return body.generation_config.model;
658 }
659
660 const match = req.path.match(/\/models\/([^/:]+)/);
661 if (match && match[1]) {
662 return match[1];
663 }
664 return 'unknown_model';
665 }
666
667 async processRequest(req, res) {
668 // 提前获取模型名称和当前账号
669 const modelName = this._getModelFromRequest(req);
670 const currentAccount = this.currentAuthIndex;
671
672 // 新增的合并日志行,报告路径、账号和模型
673 this.logger.info(`[请求] ${req.method} ${req.path} | 账号: ${currentAccount} | 模型: 🤖 ${modelName}`);
674
675 // --- 升级的统计逻辑 ---
676 this.serverSystem.stats.totalCalls++;
677 if (this.serverSystem.stats.accountCalls[currentAccount]) {
678 this.serverSystem.stats.accountCalls[currentAccount].total = (this.serverSystem.stats.accountCalls[currentAccount].total || 0) + 1;
679 this.serverSystem.stats.accountCalls[currentAccount].models[modelName] = (this.serverSystem.stats.accountCalls[currentAccount].models[modelName] || 0) + 1;
680 } else {
681 this.serverSystem.stats.accountCalls[currentAccount] = {
682 total: 1,
683 models: { [modelName]: 1 }
684 };
685 }
686
687 if (!this.connectionRegistry.hasActiveConnections()) {
688 return this._sendErrorResponse(res, 503, '没有可用的浏览器连接');
689 }
690 const requestId = this._generateRequestId();
691 const proxyRequest = this._buildProxyRequest(req, requestId);
692 const messageQueue = this.connectionRegistry.createMessageQueue(requestId);
693 try {
694 if (this.serverSystem.streamingMode === 'fake') {
695 await this._handlePseudoStreamResponse(proxyRequest, messageQueue, req, res);
696 } else {
697 await this._handleRealStreamResponse(proxyRequest, messageQueue, res);
698 }
699 } catch (error) {
700 this._handleRequestError(error, res);
701 } finally {
702 this.connectionRegistry.removeMessageQueue(requestId);
703 }
704 }
705 _generateRequestId() { return `${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; }
706 _buildProxyRequest(req, requestId) {
707 let requestBodyString;
708 if (typeof req.body === 'object' && req.body !== null) {
709 requestBodyString = JSON.stringify(req.body);
710 } else if (typeof req.body === 'string') {
711 requestBodyString = req.body;
712 } else if (Buffer.isBuffer(req.body)) {
713 requestBodyString = req.body.toString('utf-8');
714 } else {
715 requestBodyString = '';
716 }
717
718 return {
719 path: req.path, method: req.method, headers: req.headers, query_params: req.query,
720 body: requestBodyString,
721 request_id: requestId, streaming_mode: this.serverSystem.streamingMode
722 };
723 }
724 _forwardRequest(proxyRequest) {
725 const connection = this.connectionRegistry.getFirstConnection();
726 if (connection) {
727 connection.send(JSON.stringify(proxyRequest));
728 } else {
729 throw new Error("无法转发请求:没有可用的WebSocket连接。");
730 }
731 }
732 _sendErrorChunkToClient(res, errorMessage) {
733 const errorPayload = {
734 error: { message: `[代理系统提示] ${errorMessage}`, type: 'proxy_error', code: 'proxy_error' }
735 };
736 const chunk = `data: ${JSON.stringify(errorPayload)}\n\n`;
737 if (res && !res.writableEnded) {
738 res.write(chunk);
739 this.logger.info(`[请求] 已向客户端发送标准错误信号: ${errorMessage}`);
740 }
741 }
742
743 _getKeepAliveChunk(req) {
744 if (req.path.includes('chat/completions')) {
745 const payload = { id: `chatcmpl-${this._generateRequestId()}`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: "gpt-4", choices: [{ index: 0, delta: {}, finish_reason: null }] };
746 return `data: ${JSON.stringify(payload)}\n\n`;
747 }
748 if (req.path.includes('generateContent') || req.path.includes('streamGenerateContent')) {
749 const payload = { candidates: [{ content: { parts: [{ text: "" }], role: "model" }, finishReason: null, index: 0, safetyRatings: [] }] };
750 return `data: ${JSON.stringify(payload)}\n\n`;
751 }
752 return 'data: {}\n\n';
753 }
754
755 async _handlePseudoStreamResponse(proxyRequest, messageQueue, req, res) {
756 const originalPath = req.path;
757 const isStreamRequest = originalPath.includes(':stream');
758
759 this.logger.info(`[请求] 假流式处理流程启动,路径: "${originalPath}",判定为: ${isStreamRequest ? '流式请求' : '非流式请求'}`);
760
761 let connectionMaintainer = null;
762
763 if (isStreamRequest) {
764 res.status(200).set({
765 'Content-Type': 'text/event-stream',
766 'Cache-Control': 'no-cache',
767 'Connection': 'keep-alive'
768 });
769 const keepAliveChunk = this._getKeepAliveChunk(req);
770 connectionMaintainer = setInterval(() => { if (!res.writableEnded) res.write(keepAliveChunk); }, 2000);
771 }
772
773 try {
774 let lastMessage, requestFailed = false;
775 for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
776 this.logger.info(`[请求] 请求尝试 #${attempt}/${this.maxRetries}...`);
777 this._forwardRequest(proxyRequest);
778 lastMessage = await messageQueue.dequeue();
779
780 if (lastMessage.event_type === 'error' && lastMessage.status >= 400 && lastMessage.status <= 599) {
781 const correctedMessage = this._parseAndCorrectErrorDetails(lastMessage);
782 await this._handleRequestFailureAndSwitch(correctedMessage, isStreamRequest ? res : null);
783
784 const errorText = `收到 ${correctedMessage.status} 错误。${attempt < this.maxRetries ? `将在 ${this.retryDelay / 1000}秒后重试...` : '已达到最大重试次数。'}`;
785 this.logger.warn(`[请求] ${errorText}`);
786
787 if (isStreamRequest) {
788 this._sendErrorChunkToClient(res, errorText);
789 }
790
791 if (attempt < this.maxRetries) {
792 await new Promise(resolve => setTimeout(resolve, this.retryDelay));
793 continue;
794 }
795 requestFailed = true;
796 }
797 break;
798 }
799
800 if (lastMessage.event_type === 'error' || requestFailed) {
801 const finalError = this._parseAndCorrectErrorDetails(lastMessage);
802 if (!res.headersSent) {
803 this._sendErrorResponse(res, finalError.status, `请求失败: ${finalError.message}`);
804 } else {
805 this._sendErrorChunkToClient(res, `请求最终失败 (状态码: ${finalError.status}): ${finalError.message}`);
806 }
807 return;
808 }
809
810 if (this.failureCount > 0) {
811 this.logger.info(`✅ [认证] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
812 }
813 this.failureCount = 0;
814
815 const dataMessage = await messageQueue.dequeue();
816 const endMessage = await messageQueue.dequeue();
817 if (endMessage.type !== 'STREAM_END') this.logger.warn('[请求] 未收到预期的流结束信号。');
818
819 if (isStreamRequest) {
820 if (dataMessage.data) {
821 res.write(`data: ${dataMessage.data}\n\n`);
822 }
823 res.write('data: [DONE]\n\n');
824 this.logger.info('[请求] 已将完整响应作为模拟SSE事件发送。');
825 } else {
826 this.logger.info('[请求] 准备发送 application/json 响应。');
827 if (dataMessage.data) {
828 try {
829 const jsonData = JSON.parse(dataMessage.data);
830 res.status(200).json(jsonData);
831 } catch (e) {
832 this.logger.error(`[请求] 无法将来自浏览器的响应解析为JSON: ${e.message}`);
833 this._sendErrorResponse(res, 500, '代理内部错误:无法解析来自后端的响应。');
834 }
835 } else {
836 this._sendErrorResponse(res, 500, '代理内部错误:后端未返回有效数据。');
837 }
838 }
839
840 } catch (error) {
841 this.logger.error(`[请求] 假流式处理期间发生意外错误: ${error.message}`);
842 if (!res.headersSent) {
843 this._handleRequestError(error, res);
844 } else {
845 this._sendErrorChunkToClient(res, `处理失败: ${error.message}`);
846 }
847 } finally {
848 if (connectionMaintainer) clearInterval(connectionMaintainer);
849 if (!res.writableEnded) res.end();
850 this.logger.info('[请求] 假流式响应处理结束。');
851 }
852 }
853
854 async _handleRealStreamResponse(proxyRequest, messageQueue, res) {
855 let headerMessage, requestFailed = false;
856 for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
857 this.logger.info(`[请求] 请求尝试 #${attempt}/${this.maxRetries}...`);
858 this._forwardRequest(proxyRequest);
859 headerMessage = await messageQueue.dequeue();
860 if (headerMessage.event_type === 'error' && headerMessage.status >= 400 && headerMessage.status <= 599) {
861
862 const correctedMessage = this._parseAndCorrectErrorDetails(headerMessage);
863 await this._handleRequestFailureAndSwitch(correctedMessage, null);
864 this.logger.warn(`[请求] 收到 ${correctedMessage.status} 错误,将在 ${this.retryDelay / 1000}秒后重试...`);
865
866 if (attempt < this.maxRetries) {
867 await new Promise(resolve => setTimeout(resolve, this.retryDelay));
868 continue;
869 }
870 requestFailed = true;
871 }
872 break;
873 }
874 if (headerMessage.event_type === 'error' || requestFailed) {
875 const finalError = this._parseAndCorrectErrorDetails(headerMessage);
876 return this._sendErrorResponse(res, finalError.status, finalError.message);
877 }
878 if (this.failureCount > 0) {
879 this.logger.info(`✅ [认证] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
880 }
881 this.failureCount = 0;
882 this._setResponseHeaders(res, headerMessage);
883 this.logger.info('[请求] 已向客户端发送真实响应头,开始流式传输...');
884 try {
885 while (true) {
886 const dataMessage = await messageQueue.dequeue(30000);
887 if (dataMessage.type === 'STREAM_END') { this.logger.info('[请求] 收到流结束信号。'); break; }
888 if (dataMessage.data) res.write(dataMessage.data);
889 }
890 } catch (error) {
891 if (error.message !== '队列超时') throw error;
892 this.logger.warn('[请求] 真流式响应超时,可能流已正常结束。');
893 } finally {
894 if (!res.writableEnded) res.end();
895 this.logger.info('[请求] 真流式响应连接已关闭。');
896 }
897 }
898
899 _setResponseHeaders(res, headerMessage) {
900 res.status(headerMessage.status || 200);
901 const headers = headerMessage.headers || {};
902 Object.entries(headers).forEach(([name, value]) => {
903 if (name.toLowerCase() !== 'content-length') res.set(name, value);
904 });
905 }
906 _handleRequestError(error, res) {
907 if (res.headersSent) {
908 this.logger.error(`[请求] 请求处理错误 (头已发送): ${error.message}`);
909 if (this.serverSystem.streamingMode === 'fake') this._sendErrorChunkToClient(res, `处理失败: ${error.message}`);
910 if (!res.writableEnded) res.end();
911 } else {
912 this.logger.error(`[请求] 请求处理错误: ${error.message}`);
913 const status = error.message.includes('超时') ? 504 : 500;
914 this._sendErrorResponse(res, status, `代理错误: ${error.message}`);
915 }
916 }
917 _sendErrorResponse(res, status, message) {
918 if (!res.headersSent) res.status(status || 500).type('text/plain').send(message);
919 }
920}
921
922class ProxyServerSystem extends EventEmitter {
923 constructor() {
924 super();
925 this.logger = new LoggingService('ProxySystem');
926 this._loadConfiguration();
927 this.streamingMode = this.config.streamingMode;
928
929 // 升级后的统计结构
930 this.stats = {
931 totalCalls: 0,
932 accountCalls: {} // e.g., { "1": { total: 10, models: { "gemini-pro": 5, "gpt-4": 5 } } }
933 };
934
935 this.authSource = new AuthSource(this.logger);
936 this.browserManager = new BrowserManager(this.logger, this.config, this.authSource);
937 this.connectionRegistry = new ConnectionRegistry(this.logger);
938 this.requestHandler = new RequestHandler(this, this.connectionRegistry, this.logger, this.browserManager, this.config, this.authSource);
939
940 this.httpServer = null;
941 this.wsServer = null;
942 }
943
944 _loadConfiguration() {
945 let config = {
946 httpPort: 8889, host: '0.0.0.0', wsPort: 9998, streamingMode: 'real',
947 failureThreshold: 0,
948 maxRetries: 3, retryDelay: 2000, browserExecutablePath: null,
949 apiKeys: [],
950 immediateSwitchStatusCodes: [],
951 initialAuthIndex: null,
952 debugMode: false,
953 };
954
955 const configPath = path.join(__dirname, 'config.json');
956 try {
957 if (fs.existsSync(configPath)) {
958 const fileConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
959 config = { ...config, ...fileConfig };
960 this.logger.info('[系统] 已从 config.json 加载配置。');
961 }
962 } catch (error) {
963 this.logger.warn(`[系统] 无法读取或解析 config.json: ${error.message}`);
964 }
965
966 if (process.env.PORT) config.httpPort = parseInt(process.env.PORT, 10) || config.httpPort;
967 if (process.env.HOST) config.host = process.env.HOST;
968 if (process.env.STREAMING_MODE) config.streamingMode = process.env.STREAMING_MODE;
969 if (process.env.FAILURE_THRESHOLD) config.failureThreshold = parseInt(process.env.FAILURE_THRESHOLD, 10) || config.failureThreshold;
970 if (process.env.MAX_RETRIES) config.maxRetries = parseInt(process.env.MAX_RETRIES, 10) || config.maxRetries;
971 if (process.env.RETRY_DELAY) config.retryDelay = parseInt(process.env.RETRY_DELAY, 10) || config.retryDelay;
972 if (process.env.CAMOUFOX_EXECUTABLE_PATH) config.browserExecutablePath = process.env.CAMOUFOX_EXECUTABLE_PATH;
973 if (process.env.API_KEYS) {
974 config.apiKeys = process.env.API_KEYS.split(',');
975 }
976 if (process.env.DEBUG_MODE) {
977 config.debugMode = process.env.DEBUG_MODE === 'true';
978 }
979 if (process.env.INITIAL_AUTH_INDEX) {
980 const envIndex = parseInt(process.env.INITIAL_AUTH_INDEX, 10);
981 if (!isNaN(envIndex) && envIndex > 0) {
982 config.initialAuthIndex = envIndex;
983 }
984 }
985
986 let rawCodes = process.env.IMMEDIATE_SWITCH_STATUS_CODES;
987 let codesSource = '环境变量';
988
989 if (!rawCodes && config.immediateSwitchStatusCodes && Array.isArray(config.immediateSwitchStatusCodes)) {
990 rawCodes = config.immediateSwitchStatusCodes.join(',');
991 codesSource = 'config.json 文件';
992 }
993
994 if (rawCodes && typeof rawCodes === 'string') {
995 config.immediateSwitchStatusCodes = rawCodes
996 .split(',')
997 .map(code => parseInt(String(code).trim(), 10))
998 .filter(code => !isNaN(code) && code >= 400 && code <= 599);
999 if (config.immediateSwitchStatusCodes.length > 0) {
1000 this.logger.info(`[系统] 已从 ${codesSource} 加载“立即切换状态码”。`);
1001 }
1002 } else {
1003 config.immediateSwitchStatusCodes = [];
1004 }
1005
1006 if (Array.isArray(config.apiKeys)) {
1007 config.apiKeys = config.apiKeys.map(k => String(k).trim()).filter(k => k);
1008 } else {
1009 config.apiKeys = [];
1010 }
1011
1012 this.config = config;
1013 this.logger.info('================ [ 生效配置 ] ================');
1014 this.logger.info(` HTTP 服务端口: ${this.config.httpPort}`);
1015 this.logger.info(` 监听地址: ${this.config.host}`);
1016 this.logger.info(` 流式模式: ${this.config.streamingMode}`);
1017 this.logger.info(` 调试模式: ${this.config.debugMode ? '已开启' : '已关闭'}`);
1018 if (this.config.initialAuthIndex) {
1019 this.logger.info(` 指定初始认证索引: ${this.config.initialAuthIndex}`);
1020 }
1021 this.logger.info(` 失败计数切换: ${this.config.failureThreshold > 0 ? `连续 ${this.config.failureThreshold} 次失败后切换` : '已禁用'}`);
1022 this.logger.info(` 立即切换状态码: ${this.config.immediateSwitchStatusCodes.length > 0 ? this.config.immediateSwitchStatusCodes.join(', ') : '已禁用'}`);
1023 this.logger.info(` 单次请求最大重试: ${this.config.maxRetries}次`);
1024 this.logger.info(` 重试间隔: ${this.config.retryDelay}ms`);
1025 if (this.config.apiKeys && this.config.apiKeys.length > 0) {
1026 this.logger.info(` API 密钥认证: 已启用 (${this.config.apiKeys.length} 个密钥)`);
1027 } else {
1028 this.logger.info(` API 密钥认证: 已禁用`);
1029 }
1030 this.logger.info('=============================================================');
1031 }
1032
1033 async start() {
1034 try {
1035 // 初始化统计对象
1036 this.authSource.getAvailableIndices().forEach(index => {
1037 this.stats.accountCalls[index] = { total: 0, models: {} };
1038 });
1039
1040 let startupIndex = this.authSource.getFirstAvailableIndex();
1041 const suggestedIndex = this.config.initialAuthIndex;
1042
1043 if (suggestedIndex) {
1044 if (this.authSource.getAvailableIndices().includes(suggestedIndex)) {
1045 this.logger.info(`[系统] 使用配置中指定的有效启动索引: ${suggestedIndex}`);
1046 startupIndex = suggestedIndex;
1047 } else {
1048 this.logger.warn(`[系统] 配置中指定的启动索引 ${suggestedIndex} 无效或不存在,将使用第一个可用索引: ${startupIndex}`);
1049 }
1050 } else {
1051 this.logger.info(`[系统] 未指定启动索引,将自动使用第一个可用索引: ${startupIndex}`);
1052 }
1053
1054 await this.browserManager.launchBrowser(startupIndex);
1055 await this._startHttpServer();
1056 await this._startWebSocketServer();
1057 this.logger.info(`[系统] 代理服务器系统启动完成。`);
1058 this.emit('started');
1059 } catch (error) {
1060 this.logger.error(`[系统] 启动失败: ${error.message}`);
1061 this.emit('error', error);
1062 process.exit(1); // 启动失败时退出
1063 }
1064 }
1065
1066 _createDebugLogMiddleware() {
1067 return (req, res, next) => {
1068 if (!this.config.debugMode) {
1069 return next();
1070 }
1071
1072 const requestId = this.requestHandler._generateRequestId();
1073 const log = this.logger.info.bind(this.logger);
1074
1075 log(`\n\n--- [调试] 开始处理入站请求 (${requestId}) ---`);
1076 log(`[调试][${requestId}] 客户端 IP: ${req.ip}`);
1077 log(`[调试][${requestId}] 方法: ${req.method}`);
1078 log(`[调试][${requestId}] URL: ${req.originalUrl}`);
1079 log(`[调试][${requestId}] 请求头: ${JSON.stringify(req.headers, null, 2)}`);
1080
1081 let bodyContent = '无或空';
1082 if (req.body) {
1083 if (Buffer.isBuffer(req.body) && req.body.length > 0) {
1084 try {
1085 bodyContent = JSON.stringify(JSON.parse(req.body.toString('utf-8')), null, 2);
1086 } catch (e) {
1087 bodyContent = `[无法解析为JSON的Buffer, 大小: ${req.body.length} 字节]`;
1088 }
1089 } else if (typeof req.body === 'object' && Object.keys(req.body).length > 0) {
1090 bodyContent = JSON.stringify(req.body, null, 2);
1091 }
1092 }
1093
1094 log(`[调试][${requestId}] 请求体:\n${bodyContent}`);
1095 log(`--- [调试] 结束处理入站请求 (${requestId}) ---\n\n`);
1096
1097 next();
1098 };
1099 }
1100
1101
1102 _createAuthMiddleware() {
1103 return (req, res, next) => {
1104 const serverApiKeys = this.config.apiKeys;
1105 if (!serverApiKeys || serverApiKeys.length === 0) {
1106 return next();
1107 }
1108
1109 let clientKey = null;
1110 let keySource = null;
1111
1112 const headers = req.headers;
1113 const xGoogApiKey = headers['x-goog-api-key'] || headers['x_goog_api_key'];
1114 const xApiKey = headers['x-api-key'] || headers['x_api_key'];
1115 const authHeader = headers.authorization;
1116
1117 if (xGoogApiKey) {
1118 clientKey = xGoogApiKey;
1119 keySource = 'x-goog-api-key 请求头';
1120 } else if (authHeader && authHeader.startsWith('Bearer ')) {
1121 clientKey = authHeader.substring(7);
1122 keySource = 'Authorization 请求头';
1123 } else if (xApiKey) {
1124 clientKey = xApiKey;
1125 keySource = 'X-API-Key 请求头';
1126 } else if (req.query.key) {
1127 clientKey = req.query.key;
1128 keySource = '查询参数';
1129 }
1130
1131 if (clientKey) {
1132 if (serverApiKeys.includes(clientKey)) {
1133 if (this.config.debugMode) {
1134 this.logger.debug(`[认证][调试] 在 '${keySource}' 中找到API密钥,验证通过。`);
1135 }
1136 if (keySource === '查询参数') {
1137 delete req.query.key;
1138 }
1139 return next();
1140 } else {
1141 if (this.config.debugMode) {
1142 this.logger.warn(`[认证][调试] 拒绝请求: 无效的API密钥。IP: ${req.ip}, 路径: ${req.path}`);
1143 this.logger.debug(`[认证][调试] 来源: ${keySource}`);
1144 this.logger.debug(`[认证][调试] 提供的错误密钥: '${clientKey}'`);
1145 this.logger.debug(`[认证][调试] 已加载的有效密钥: [${serverApiKeys.join(', ')}]`);
1146 } else {
1147 this.logger.warn(`[认证] 拒绝请求: 无效的API密钥。IP: ${req.ip}, 路径: ${req.path}`);
1148 }
1149 return res.status(401).json({ error: { message: "提供了无效的API密钥。" } });
1150 }
1151 }
1152
1153 this.logger.warn(`[认证] 拒绝受保护的请求: 缺少API密钥。IP: ${req.ip}, 路径: ${req.path}`);
1154
1155 if (this.config.debugMode) {
1156 this.logger.debug(`[认证][调试] 未在任何标准位置找到API密钥。`);
1157 this.logger.debug(`[认证][调试] 搜索的请求头: ${JSON.stringify(headers, null, 2)}`);
1158 this.logger.debug(`[认证][调试] 搜索的查询参数: ${JSON.stringify(req.query)}`);
1159 this.logger.debug(`[认证][调试] 已加载的有效密钥: [${serverApiKeys.join(', ')}]`);
1160 }
1161
1162 return res.status(401).json({ error: { message: "访问被拒绝。未在请求头或查询参数中找到有效的API密钥。" } });
1163 };
1164 }
1165
1166 async _startHttpServer() {
1167 const app = this._createExpressApp();
1168 this.httpServer = http.createServer(app);
1169 return new Promise((resolve) => {
1170 this.httpServer.listen(this.config.httpPort, this.config.host, () => {
1171 this.logger.info(`[系统] HTTP服务器已在 http://${this.config.host}:${this.config.httpPort} 上监听`);
1172 this.logger.info(`[系统] 仪表盘可在 http://${this.config.host}:${this.config.httpPort}/dashboard 访问`);
1173 resolve();
1174 });
1175 });
1176 }
1177
1178 // [可复制并覆盖]
1179// 请用此版本完整替换您文件中的 _createExpressApp 方法
1180 _createExpressApp() {
1181 const app = express();
1182 app.use(express.json({ limit: '100mb' }));
1183 app.use(express.raw({ type: '*/*', limit: '100mb' }));
1184 app.use((req, res, next) => {
1185 if (req.is('application/json') && typeof req.body === 'object' && !Buffer.isBuffer(req.body)) {
1186 // Already parsed correctly by express.json()
1187 } else if (Buffer.isBuffer(req.body)) {
1188 const bodyStr = req.body.toString('utf-8');
1189 if (bodyStr) {
1190 try {
1191 req.body = JSON.parse(bodyStr);
1192 } catch (e) {
1193 // Not JSON, leave as buffer.
1194 }
1195 }
1196 }
1197 next();
1198 });
1199
1200 app.use(this._createDebugLogMiddleware());
