holyterra commited on
Commit
bd53b1a
·
verified ·
1 Parent(s): f813dc0

Upload unified-server.js

Browse files
Files changed (1) hide show
  1. unified-server.js +207 -116
unified-server.js CHANGED
@@ -5,7 +5,7 @@ const { EventEmitter } = require('events');
5
  const fs = require('fs');
6
  const path = require('path');
7
  const { firefox } = require('playwright');
8
- const os =require('os');
9
 
10
 
11
  // ===================================================================================
@@ -60,13 +60,13 @@ class AuthSource {
60
  return;
61
  }
62
  }
63
-
64
  // 排序并去重,确保索引列表干净有序
65
  this.availableIndices = [...new Set(indices)].sort((a, b) => a - b);
66
 
67
  this.logger.info(`[Auth] 在 '${this.authMode}' 模式下,检测到 ${this.availableIndices.length} 个认证源。`);
68
- if(this.availableIndices.length > 0) {
69
- this.logger.info(`[Auth] 可用索引列表: [${this.availableIndices.join(', ')}]`);
70
  }
71
  }
72
 
@@ -443,16 +443,16 @@ class RequestHandler {
443
  if (available.length === 1) return available[0]; // 只有一个,切给自己
444
 
445
  const currentIndexInArray = available.indexOf(this.currentAuthIndex);
446
-
447
  // 如果当前索引不知为何不在可用列表里,安全起见返回第一个
448
  if (currentIndexInArray === -1) {
449
- this.logger.warn(`[Auth] 当前索引 ${this.currentAuthIndex} 不在可用列表中,将切换到第一个可用索引。`);
450
- return available[0];
451
  }
452
-
453
  // 计算下一个索引在数组中的位置,使用模运算实现循环
454
  const nextIndexInArray = (currentIndexInArray + 1) % available.length;
455
-
456
  return available[nextIndexInArray];
457
  }
458
 
@@ -467,10 +467,10 @@ class RequestHandler {
467
  const totalAuthCount = this.authSource.getAvailableIndices().length;
468
 
469
  if (nextAuthIndex === null) {
470
- this.logger.error('🔴 [Auth] 无法切换账号,因为没有可用的认证源!');
471
- this.isAuthSwitching = false;
472
- // 抛出错误以便调用者可以捕获它
473
- throw new Error('No available authentication sources to switch to.');
474
  }
475
 
476
  this.logger.info('==================================================');
@@ -526,25 +526,25 @@ class RequestHandler {
526
  return correctedDetails;
527
  }
528
 
529
- async _handleRequestFailureAndSwitch(errorDetails, res) {
530
  // 创建一个副本进行操作,并进行深度解析
531
  const correctedDetails = { ...errorDetails };
532
  if (correctedDetails.message && typeof correctedDetails.message === 'string') {
533
- // 增强版正则表达式,能匹配 "HTTP 429" 或 JSON 中的 "code":429 等多种模式
534
- const regex = /(?:HTTP|status code)\s*(\d{3})|"code"\s*:\s*(\d{3})/;
535
- const match = correctedDetails.message.match(regex);
536
-
537
- // match[1] 对应 (?:HTTP|status code)\s*(\d{3})
538
- // match[2] 对应 "code"\s*:\s*(\d{3})
539
- const parsedStatusString = match ? (match[1] || match[2]) : null;
540
-
541
- if (parsedStatusString) {
542
- const parsedStatus = parseInt(parsedStatusString, 10);
543
- if (parsedStatus >= 400 && parsedStatus <= 599 && correctedDetails.status !== parsedStatus) {
544
- this.logger.warn(`[Auth] 修正了错误状态码!原始: ${correctedDetails.status}, 从消息中解析得到: ${parsedStatus}`);
545
- correctedDetails.status = parsedStatus;
546
- }
547
  }
 
548
  }
549
 
550
  // --- 后续逻辑使用修正后的 correctedDetails ---
@@ -563,24 +563,24 @@ class RequestHandler {
563
  }
564
  return; // 结束函数,外层循环将进行重试
565
  }
566
-
567
  // 基于失败计数的切换逻辑
568
  if (this.config.failureThreshold > 0) {
569
- this.failureCount++;
570
- this.logger.warn(`⚠️ [Auth] 请求失败 - 失败计数: ${this.failureCount}/${this.config.failureThreshold} (当前账号索引: ${this.currentAuthIndex}, 状态码: ${correctedDetails.status})`);
571
- if (this.failureCount >= this.config.failureThreshold) {
572
- this.logger.warn(`🔴 [Auth] 达到失败阈值!准备切换账号...`);
573
- if (res) this._sendErrorChunkToClient(res, `连续失败${this.failureCount}次,正在尝试切换账号...`);
574
- try {
575
- await this._switchToNextAuth();
576
- if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
577
- } catch (switchError) {
578
- this.logger.error(`🔴 [Auth] 账号切换失败: ${switchError.message}`);
579
- if (res) this._sendErrorChunkToClient(res, `切换账号失败: ${switchError.message}`);
580
- }
581
  }
 
582
  } else {
583
- this.logger.warn(`[Auth] 请求失败 (状态码: ${correctedDetails.status})。基于计数的自动切换已禁用 (failureThreshold=0)`);
584
  }
585
  }
586
 
@@ -634,94 +634,128 @@ class RequestHandler {
634
  this.logger.info(`[Request] 已向客户端发送标准错误信号: ${errorMessage}`);
635
  }
636
  }
637
- async _handlePseudoStreamResponse(proxyRequest, messageQueue, req, res) {
638
- // 注意:我们不再立即发送响应头,因为响应类型(SSE或JSON)尚未确定。
639
- this.logger.info('[Request] 进入假流式处理流程,将根据原始请求路径决定最终响应格式。');
640
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
  let connectionMaintainer = null;
642
- try {
643
- // 为了防止连接过早断开,可以先启动一个通用的心跳计时器
644
- // 这个计时器只发送注释行,对SSE和JSON客户端都无害,但能保持连接
645
- connectionMaintainer = setInterval(() => { if (!res.writableEnded) { res.write(': keep-alive\n\n'); } }, 15000);
646
 
 
 
 
 
 
 
 
 
 
 
 
 
647
  let lastMessage, requestFailed = false;
648
  for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
649
  this.logger.info(`[Request] 请求尝试 #${attempt}/${this.maxRetries}...`);
650
  this._forwardRequest(proxyRequest);
651
  lastMessage = await messageQueue.dequeue();
 
652
  if (lastMessage.event_type === 'error' && lastMessage.status >= 400 && lastMessage.status <= 599) {
653
  const correctedMessage = this._parseAndCorrectErrorDetails(lastMessage);
654
- await this._handleRequestFailureAndSwitch(correctedMessage, res);
 
655
  const errorText = `收到 ${correctedMessage.status} 错误。${attempt < this.maxRetries ? `将在 ${this.retryDelay / 1000}秒后重试...` : '已达到最大重试次数。'}`;
656
- this._sendErrorChunkToClient(res, errorText);
 
 
 
 
 
 
657
  if (attempt < this.maxRetries) {
658
  await new Promise(resolve => setTimeout(resolve, this.retryDelay));
659
  continue;
660
  }
661
  requestFailed = true;
662
  }
663
- break;
664
  }
 
 
665
  if (lastMessage.event_type === 'error' || requestFailed) {
666
  const finalError = this._parseAndCorrectErrorDetails(lastMessage);
667
- throw new Error(`请求失败 (状态码: ${finalError.status}): ${finalError.message}`);
 
 
 
 
 
 
668
  }
669
-
 
670
  if (this.failureCount > 0) {
671
  this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
672
  }
673
  this.failureCount = 0;
674
-
675
  const dataMessage = await messageQueue.dequeue();
676
  const endMessage = await messageQueue.dequeue();
677
  if (endMessage.type !== 'STREAM_END') this.logger.warn('[Request] 未收到预期的流结束信号。');
678
 
679
- // 停止心跳计时器,因为我们即将发送最终数据
680
- if (connectionMaintainer) clearInterval(connectionMaintainer);
681
-
682
- if (dataMessage.data) {
683
- // ======================= START: CORE LOGIC CHANGE =======================
684
- // 检查原始请求路径,判断客户端期望的是流还是普通JSON
685
- const originalPath = req.path;
686
- const isStreamRequest = originalPath.includes(':stream');
687
-
688
- if (isStreamRequest) {
689
- // 客户端想要一个流,我们模拟它 (保持原有逻辑)
690
- this.logger.info(`[Request] 原始请求路径 "${originalPath}" 是流式请求,将模拟SSE响应。`);
691
- res.status(200).set({
692
- 'Content-Type': 'text/event-stream',
693
- 'Cache-Control': 'no-cache',
694
- 'Connection': 'keep-alive'
695
- });
696
- // 发送数据块
697
  res.write(`data: ${dataMessage.data}\n\n`);
698
- // 为提高兼容性,模拟一个 [DONE] 结束标志
699
- res.write('data: [DONE]\n\n');
700
- this.logger.info('[Request] 已将完整响应体作为模拟SSE事件发送。');
701
- } else {
702
- // 客户端想要一个普通JSON,我们直接返回它
703
- this.logger.info(`[Request] 原始请求路径 "${originalPath}" 是非流式请求,将返回 application/json 响应。`);
 
704
  try {
705
  // 确保我们发送的是有效的JSON
706
  const jsonData = JSON.parse(dataMessage.data);
707
  res.status(200).json(jsonData);
708
  } catch (e) {
709
  this.logger.error(`[Request] 无法将来自浏览器的响应解析为JSON: ${e.message}`);
710
- this._sendErrorResponse(res, 500, '代理内部错误:无法解析来自浏览器的响应。');
711
  }
 
 
712
  }
713
- // ======================== END: CORE LOGIC CHANGE ========================
714
  }
 
715
 
716
  } catch (error) {
717
- // 如果出错时头还没发送,我们可以安全地发送一个错误状态码
718
- if (!res.headersSent) {
719
- this._handleRequestError(error, res);
720
- } else {
721
- // 如果头已发送(比如在模拟流时),我们只能在现有连接上发送错误块
722
- this.logger.error(`[Request] 请求处理错误 (头已发送): ${error.message}`);
723
- this._sendErrorChunkToClient(res, `处理失败: ${error.message}`);
724
- }
725
  } finally {
726
  if (connectionMaintainer) clearInterval(connectionMaintainer);
727
  if (!res.writableEnded) res.end();
@@ -736,13 +770,13 @@ class RequestHandler {
736
  this._forwardRequest(proxyRequest);
737
  headerMessage = await messageQueue.dequeue();
738
  if (headerMessage.event_type === 'error' && headerMessage.status >= 400 && headerMessage.status <= 599) {
739
-
740
  // --- START: MODIFICATION ---
741
  const correctedMessage = this._parseAndCorrectErrorDetails(headerMessage);
742
  await this._handleRequestFailureAndSwitch(correctedMessage, null); // res is not available
743
  this.logger.warn(`[Request] 收到 ${correctedMessage.status} 错误,将在 ${this.retryDelay / 1000}秒后重试...`);
744
  // --- END: MODIFICATION ---
745
-
746
  if (attempt < this.maxRetries) {
747
  await new Promise(resolve => setTimeout(resolve, this.retryDelay));
748
  continue;
@@ -778,17 +812,7 @@ class RequestHandler {
778
  this.logger.info('[Request] 真流式响应连接已关闭。');
779
  }
780
  }
781
- _getKeepAliveChunk(req) {
782
- if (req.path.includes('chat/completions')) {
783
- 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 }] };
784
- return `data: ${JSON.stringify(payload)}\n\n`;
785
- }
786
- if (req.path.includes('generateContent') || req.path.includes('streamGenerateContent')) {
787
- const payload = { candidates: [{ content: { parts: [{ text: "" }], role: "model" }, finishReason: null, index: 0, safetyRatings: [] }] };
788
- return `data: ${JSON.stringify(payload)}\n\n`;
789
- }
790
- return 'data: {}\n\n';
791
- }
792
  _setResponseHeaders(res, headerMessage) {
793
  res.status(headerMessage.status || 200);
794
  const headers = headerMessage.headers || {};
@@ -830,12 +854,13 @@ class ProxyServerSystem extends EventEmitter {
830
 
831
  _loadConfiguration() {
832
  let config = {
833
- httpPort: 7860, host: '0.0.0.0', wsPort: 9998, streamingMode: 'real',
834
  failureThreshold: 0,
835
  maxRetries: 3, retryDelay: 2000, browserExecutablePath: null,
836
  apiKeys: [],
837
  immediateSwitchStatusCodes: [],
838
- initialAuthIndex: null, // 新增:为 initialAuthIndex 提供默认值
 
839
  };
840
 
841
  const configPath = path.join(__dirname, 'config.json');
@@ -859,12 +884,15 @@ class ProxyServerSystem extends EventEmitter {
859
  if (process.env.API_KEYS) {
860
  config.apiKeys = process.env.API_KEYS.split(',');
861
  }
 
 
 
862
  // 新增:处理环境变量,它会覆盖 config.json 中的设置
863
  if (process.env.INITIAL_AUTH_INDEX) {
864
- const envIndex = parseInt(process.env.INITIAL_AUTH_INDEX, 10);
865
- if (!isNaN(envIndex) && envIndex > 0) {
866
- config.initialAuthIndex = envIndex;
867
- }
868
  }
869
 
870
 
@@ -900,9 +928,10 @@ class ProxyServerSystem extends EventEmitter {
900
  this.logger.info(` HTTP 服务端口: ${this.config.httpPort}`);
901
  this.logger.info(` 监听地址: ${this.config.host}`);
902
  this.logger.info(` 流式模式: ${this.config.streamingMode}`);
 
903
  // 新增:在日志中显示初始索引的配置
904
  if (this.config.initialAuthIndex) {
905
- this.logger.info(` 指定初始认证索引: ${this.config.initialAuthIndex}`);
906
  }
907
  // MODIFIED: 日志输出已汉化
908
  this.logger.info(` 失败计数切换: ${this.config.failureThreshold > 0 ? `连续 ${this.config.failureThreshold} 次失败后切换` : '已禁用'}`);
@@ -926,10 +955,10 @@ class ProxyServerSystem extends EventEmitter {
926
 
927
  if (suggestedIndex) {
928
  if (this.authSource.getAvailableIndices().includes(suggestedIndex)) {
929
- this.logger.info(`[System] 使用配置中指定的有效启动索引: ${suggestedIndex}`);
930
- startupIndex = suggestedIndex;
931
  } else {
932
- this.logger.warn(`[System] 配置中指定的启动索引 ${suggestedIndex} 无效或不存在,将使用第一个可用索引: ${startupIndex}`);
933
  }
934
  } else {
935
  this.logger.info(`[System] 未指定启动索引,将自动使用第一个可用索引: ${startupIndex}`);
@@ -940,14 +969,54 @@ class ProxyServerSystem extends EventEmitter {
940
  await this._startWebSocketServer();
941
  this.logger.info(`[System] 代理服务器系统启动完成。`);
942
  this.emit('started');
943
- } catch (error)
944
- {
945
  this.logger.error(`[System] 启动失败: ${error.message}`);
946
  this.emit('error', error);
947
  throw error;
948
  }
949
  }
950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  _createAuthMiddleware() {
952
  return (req, res, next) => {
953
  const serverApiKeys = this.config.apiKeys;
@@ -1018,25 +1087,47 @@ class ProxyServerSystem extends EventEmitter {
1018
 
1019
  _createExpressApp() {
1020
  const app = express();
 
1021
  app.use(express.json({ limit: '100mb' }));
1022
  app.use(express.raw({ type: '*/*', limit: '100mb' }));
1023
 
 
 
 
1024
  app.get('/admin/set-mode', (req, res) => {
1025
  const newMode = req.query.mode;
1026
  if (newMode === 'fake' || newMode === 'real') {
1027
  this.streamingMode = newMode;
 
1028
  res.status(200).send(`流式模式已切换为: ${this.streamingMode}`);
1029
  } else {
1030
  res.status(400).send('无效模式. 请用 "fake" 或 "real".');
1031
  }
1032
  });
1033
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1034
  app.get('/health', (req, res) => {
1035
  res.status(200).json({
1036
  status: 'healthy',
1037
  uptime: process.uptime(),
1038
  config: {
1039
  streamingMode: this.streamingMode,
 
1040
  failureThreshold: this.config.failureThreshold,
1041
  immediateSwitchStatusCodes: this.config.immediateSwitchStatusCodes,
1042
  maxRetries: this.config.maxRetries,
@@ -1074,7 +1165,7 @@ class ProxyServerSystem extends EventEmitter {
1074
  try {
1075
  await this.requestHandler._switchToNextAuth();
1076
  const newIndex = this.requestHandler.currentAuthIndex;
1077
-
1078
  const message = `成功将账号从索引 ${oldIndex} 切换到 ${newIndex}。`;
1079
  this.logger.info(`[Admin] 手动切换成功。 ${message}`);
1080
  res.status(200).send(message);
 
5
  const fs = require('fs');
6
  const path = require('path');
7
  const { firefox } = require('playwright');
8
+ const os = require('os');
9
 
10
 
11
  // ===================================================================================
 
60
  return;
61
  }
62
  }
63
+
64
  // 排序并去重,确保索引列表干净有序
65
  this.availableIndices = [...new Set(indices)].sort((a, b) => a - b);
66
 
67
  this.logger.info(`[Auth] 在 '${this.authMode}' 模式下,检测到 ${this.availableIndices.length} 个认证源。`);
68
+ if (this.availableIndices.length > 0) {
69
+ this.logger.info(`[Auth] 可用索引列表: [${this.availableIndices.join(', ')}]`);
70
  }
71
  }
72
 
 
443
  if (available.length === 1) return available[0]; // 只有一个,切给自己
444
 
445
  const currentIndexInArray = available.indexOf(this.currentAuthIndex);
446
+
447
  // 如果当前索引不知为何不在可用列表里,安全起见返回第一个
448
  if (currentIndexInArray === -1) {
449
+ this.logger.warn(`[Auth] 当前索引 ${this.currentAuthIndex} 不在可用列表中,将切换到第一个可用索引。`);
450
+ return available[0];
451
  }
452
+
453
  // 计算下一个索引在数组中的位置,使用模运算实现循环
454
  const nextIndexInArray = (currentIndexInArray + 1) % available.length;
455
+
456
  return available[nextIndexInArray];
457
  }
458
 
 
467
  const totalAuthCount = this.authSource.getAvailableIndices().length;
468
 
469
  if (nextAuthIndex === null) {
470
+ this.logger.error('🔴 [Auth] 无法切换账号,因为没有可用的认证源!');
471
+ this.isAuthSwitching = false;
472
+ // 抛出错误以便调用者可以捕获它
473
+ throw new Error('No available authentication sources to switch to.');
474
  }
475
 
476
  this.logger.info('==================================================');
 
526
  return correctedDetails;
527
  }
528
 
529
+ async _handleRequestFailureAndSwitch(errorDetails, res) {
530
  // 创建一个副本进行操作,并进行深度解析
531
  const correctedDetails = { ...errorDetails };
532
  if (correctedDetails.message && typeof correctedDetails.message === 'string') {
533
+ // 增强版正则表达式,能匹配 "HTTP 429" 或 JSON 中的 "code":429 等多种模式
534
+ const regex = /(?:HTTP|status code)\s*(\d{3})|"code"\s*:\s*(\d{3})/;
535
+ const match = correctedDetails.message.match(regex);
536
+
537
+ // match[1] 对应 (?:HTTP|status code)\s*(\d{3})
538
+ // match[2] 对应 "code"\s*:\s*(\d{3})
539
+ const parsedStatusString = match ? (match[1] || match[2]) : null;
540
+
541
+ if (parsedStatusString) {
542
+ const parsedStatus = parseInt(parsedStatusString, 10);
543
+ if (parsedStatus >= 400 && parsedStatus <= 599 && correctedDetails.status !== parsedStatus) {
544
+ this.logger.warn(`[Auth] 修正了错误状态码!原始: ${correctedDetails.status}, 从消息中解析得到: ${parsedStatus}`);
545
+ correctedDetails.status = parsedStatus;
 
546
  }
547
+ }
548
  }
549
 
550
  // --- 后续逻辑使用修正后的 correctedDetails ---
 
563
  }
564
  return; // 结束函数,外层循环将进行重试
565
  }
566
+
567
  // 基于失败计数的切换逻辑
568
  if (this.config.failureThreshold > 0) {
569
+ this.failureCount++;
570
+ this.logger.warn(`⚠️ [Auth] 请求失败 - 失败计数: ${this.failureCount}/${this.config.failureThreshold} (当前账号索引: ${this.currentAuthIndex}, 状态码: ${correctedDetails.status})`);
571
+ if (this.failureCount >= this.config.failureThreshold) {
572
+ this.logger.warn(`🔴 [Auth] 达到失败阈值!准备切换账号...`);
573
+ if (res) this._sendErrorChunkToClient(res, `连续失败${this.failureCount}次,正在尝试切换账号...`);
574
+ try {
575
+ await this._switchToNextAuth();
576
+ if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
577
+ } catch (switchError) {
578
+ this.logger.error(`🔴 [Auth] 账号切换失败: ${switchError.message}`);
579
+ if (res) this._sendErrorChunkToClient(res, `切换账号失败: ${switchError.message}`);
 
580
  }
581
+ }
582
  } else {
583
+ this.logger.warn(`[Auth] 请求失败 (状态码: ${correctedDetails.status})。基于计数的自动切换已禁用 (failureThreshold=0)`);
584
  }
585
  }
586
 
 
634
  this.logger.info(`[Request] 已向客户端发送标准错误信号: ${errorMessage}`);
635
  }
636
  }
637
+
638
+ //========================================================
639
+ // START: MODIFIED SECTION
640
+ //========================================================
641
+
642
+ _getKeepAliveChunk(req) {
643
+ if (req.path.includes('chat/completions')) {
644
+ 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 }] };
645
+ return `data: ${JSON.stringify(payload)}\n\n`;
646
+ }
647
+ if (req.path.includes('generateContent') || req.path.includes('streamGenerateContent')) {
648
+ const payload = { candidates: [{ content: { parts: [{ text: "" }], role: "model" }, finishReason: null, index: 0, safetyRatings: [] }] };
649
+ return `data: ${JSON.stringify(payload)}\n\n`;
650
+ }
651
+ // Provide a generic, harmless default
652
+ return 'data: {}\n\n';
653
+ }
654
+
655
+ async _handlePseudoStreamResponse(proxyRequest, messageQueue, req, res) {
656
+ // 关键决策点: 通过请求路径判断客户端期望的是流还是普通JSON
657
+ const originalPath = req.path;
658
+ const isStreamRequest = originalPath.includes(':stream');
659
+
660
+ this.logger.info(`[Request] 假流式处理流程启动,路径: "${originalPath}",判定为: ${isStreamRequest ? '流式请求' : '非流式请求'}`);
661
+
662
  let connectionMaintainer = null;
 
 
 
 
663
 
664
+ // 只有在确定是流式请求时,才立即发送头并启动心跳
665
+ if (isStreamRequest) {
666
+ res.status(200).set({
667
+ 'Content-Type': 'text/event-stream',
668
+ 'Cache-Control': 'no-cache',
669
+ 'Connection': 'keep-alive'
670
+ });
671
+ const keepAliveChunk = this._getKeepAliveChunk(req);
672
+ connectionMaintainer = setInterval(() => { if (!res.writableEnded) res.write(keepAliveChunk); }, 2000);
673
+ }
674
+
675
+ try {
676
  let lastMessage, requestFailed = false;
677
  for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
678
  this.logger.info(`[Request] 请求尝试 #${attempt}/${this.maxRetries}...`);
679
  this._forwardRequest(proxyRequest);
680
  lastMessage = await messageQueue.dequeue();
681
+
682
  if (lastMessage.event_type === 'error' && lastMessage.status >= 400 && lastMessage.status <= 599) {
683
  const correctedMessage = this._parseAndCorrectErrorDetails(lastMessage);
684
+ await this._handleRequestFailureAndSwitch(correctedMessage, isStreamRequest ? res : null); // 仅在流模式下才向客户端发送错误块
685
+
686
  const errorText = `收到 ${correctedMessage.status} 错误。${attempt < this.maxRetries ? `将在 ${this.retryDelay / 1000}秒后重试...` : '已达到最大重试次数。'}`;
687
+ this.logger.warn(`[Request] ${errorText}`);
688
+
689
+ // 如果是流式请求,则通过数据块通知客户端错误
690
+ if (isStreamRequest) {
691
+ this._sendErrorChunkToClient(res, errorText);
692
+ }
693
+
694
  if (attempt < this.maxRetries) {
695
  await new Promise(resolve => setTimeout(resolve, this.retryDelay));
696
  continue;
697
  }
698
  requestFailed = true;
699
  }
700
+ break; // 成功则跳出循环
701
  }
702
+
703
+ // 如果所有重试都失败
704
  if (lastMessage.event_type === 'error' || requestFailed) {
705
  const finalError = this._parseAndCorrectErrorDetails(lastMessage);
706
+ // 对于非流式请求,现在可以安全地发送一个完整的错误响应
707
+ if (!res.headersSent) {
708
+ this._sendErrorResponse(res, finalError.status, `请求失败: ${finalError.message}`);
709
+ } else { // 对于流式请求,只能发送最后一个错误块
710
+ this._sendErrorChunkToClient(res, `请求最终失败 (状态码: ${finalError.status}): ${finalError.message}`);
711
+ }
712
+ return; // 结束函数
713
  }
714
+
715
+ // 请求成功
716
  if (this.failureCount > 0) {
717
  this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
718
  }
719
  this.failureCount = 0;
720
+
721
  const dataMessage = await messageQueue.dequeue();
722
  const endMessage = await messageQueue.dequeue();
723
  if (endMessage.type !== 'STREAM_END') this.logger.warn('[Request] 未收到预期的流结束信号。');
724
 
725
+ // ======================= 核心逻辑:根据请求类型格式化最终响应 =======================
726
+ if (isStreamRequest) {
727
+ // 客户端想要一个流,我们发送SSE数据块
728
+ if (dataMessage.data) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
729
  res.write(`data: ${dataMessage.data}\n\n`);
730
+ }
731
+ res.write('data: [DONE]\n\n');
732
+ this.logger.info('[Request] 已将完整响应作为模拟SSE事件发送。');
733
+ } else {
734
+ // 客户端想要一个普通JSON,我们直接返回它
735
+ this.logger.info('[Request] 准备发送 application/json 响应。');
736
+ if (dataMessage.data) {
737
  try {
738
  // 确保我们发送的是有效的JSON
739
  const jsonData = JSON.parse(dataMessage.data);
740
  res.status(200).json(jsonData);
741
  } catch (e) {
742
  this.logger.error(`[Request] 无法将来自浏览器的响应解析为JSON: ${e.message}`);
743
+ this._sendErrorResponse(res, 500, '代理内部错误:无法解析来自后端的响应。');
744
  }
745
+ } else {
746
+ this._sendErrorResponse(res, 500, '代理内部错误:后端未返回有效数据。');
747
  }
 
748
  }
749
+ // =================================================================================
750
 
751
  } catch (error) {
752
+ // 这个 catch 块处理意外错误,比如队列超时
753
+ this.logger.error(`[Request] 假流式处理期间发生意外错误: ${error.message}`);
754
+ if (!res.headersSent) {
755
+ this._handleRequestError(error, res);
756
+ } else {
757
+ this._sendErrorChunkToClient(res, `处理失败: ${error.message}`);
758
+ }
 
759
  } finally {
760
  if (connectionMaintainer) clearInterval(connectionMaintainer);
761
  if (!res.writableEnded) res.end();
 
770
  this._forwardRequest(proxyRequest);
771
  headerMessage = await messageQueue.dequeue();
772
  if (headerMessage.event_type === 'error' && headerMessage.status >= 400 && headerMessage.status <= 599) {
773
+
774
  // --- START: MODIFICATION ---
775
  const correctedMessage = this._parseAndCorrectErrorDetails(headerMessage);
776
  await this._handleRequestFailureAndSwitch(correctedMessage, null); // res is not available
777
  this.logger.warn(`[Request] 收到 ${correctedMessage.status} 错误,将在 ${this.retryDelay / 1000}秒后重试...`);
778
  // --- END: MODIFICATION ---
779
+
780
  if (attempt < this.maxRetries) {
781
  await new Promise(resolve => setTimeout(resolve, this.retryDelay));
782
  continue;
 
812
  this.logger.info('[Request] 真流式响应连接已关闭。');
813
  }
814
  }
815
+
 
 
 
 
 
 
 
 
 
 
816
  _setResponseHeaders(res, headerMessage) {
817
  res.status(headerMessage.status || 200);
818
  const headers = headerMessage.headers || {};
 
854
 
855
  _loadConfiguration() {
856
  let config = {
857
+ httpPort: 8889, host: '0.0.0.0', wsPort: 9998, streamingMode: 'real',
858
  failureThreshold: 0,
859
  maxRetries: 3, retryDelay: 2000, browserExecutablePath: null,
860
  apiKeys: [],
861
  immediateSwitchStatusCodes: [],
862
+ initialAuthIndex: null,
863
+ debugMode: false, // [新增] 调试模式默认关闭
864
  };
865
 
866
  const configPath = path.join(__dirname, 'config.json');
 
884
  if (process.env.API_KEYS) {
885
  config.apiKeys = process.env.API_KEYS.split(',');
886
  }
887
+ if (process.env.DEBUG_MODE) { // [新增] 从环境变量读取调试模式
888
+ config.debugMode = process.env.DEBUG_MODE === 'true';
889
+ }
890
  // 新增:处理环境变量,它会覆盖 config.json 中的设置
891
  if (process.env.INITIAL_AUTH_INDEX) {
892
+ const envIndex = parseInt(process.env.INITIAL_AUTH_INDEX, 10);
893
+ if (!isNaN(envIndex) && envIndex > 0) {
894
+ config.initialAuthIndex = envIndex;
895
+ }
896
  }
897
 
898
 
 
928
  this.logger.info(` HTTP 服务端口: ${this.config.httpPort}`);
929
  this.logger.info(` 监听地址: ${this.config.host}`);
930
  this.logger.info(` 流式模式: ${this.config.streamingMode}`);
931
+ this.logger.info(` 调试模式: ${this.config.debugMode ? '已开启' : '已关闭'}`); // [新增] 打印调试模式状态
932
  // 新增:在日志中显示初始索引的配置
933
  if (this.config.initialAuthIndex) {
934
+ this.logger.info(` 指定初始认证索引: ${this.config.initialAuthIndex}`);
935
  }
936
  // MODIFIED: 日志输出已汉化
937
  this.logger.info(` 失败计数切换: ${this.config.failureThreshold > 0 ? `连续 ${this.config.failureThreshold} 次失败后切换` : '已禁用'}`);
 
955
 
956
  if (suggestedIndex) {
957
  if (this.authSource.getAvailableIndices().includes(suggestedIndex)) {
958
+ this.logger.info(`[System] 使用配置中指定的有效启动索引: ${suggestedIndex}`);
959
+ startupIndex = suggestedIndex;
960
  } else {
961
+ this.logger.warn(`[System] 配置中指定的启动索引 ${suggestedIndex} 无效或不存在,将使用第一个可用索引: ${startupIndex}`);
962
  }
963
  } else {
964
  this.logger.info(`[System] 未指定启动索引,将自动使用第一个可用索引: ${startupIndex}`);
 
969
  await this._startWebSocketServer();
970
  this.logger.info(`[System] 代理服务器系统启动完成。`);
971
  this.emit('started');
972
+ } catch (error) {
 
973
  this.logger.error(`[System] 启动失败: ${error.message}`);
974
  this.emit('error', error);
975
  throw error;
976
  }
977
  }
978
 
979
+ // [新增] 调试日志中间件
980
+ _createDebugLogMiddleware() {
981
+ return (req, res, next) => {
982
+ if (!this.config.debugMode) {
983
+ return next();
984
+ }
985
+
986
+ const requestId = this.requestHandler._generateRequestId();
987
+ const log = this.logger.info.bind(this.logger); // 使用 info 级别以保证显示
988
+
989
+ log(`\n\n--- [DEBUG] START INCOMING REQUEST (${requestId}) ---`);
990
+ log(`[DEBUG][${requestId}] Client IP: ${req.ip}`);
991
+ log(`[DEBUG][${requestId}] Method: ${req.method}`);
992
+ log(`[DEBUG][${requestId}] URL: ${req.originalUrl}`);
993
+ log(`[DEBUG][${requestId}] Headers: ${JSON.stringify(req.headers, null, 2)}`);
994
+
995
+ // 智能处理请求体
996
+ let bodyContent = 'N/A or empty';
997
+ if (req.body) {
998
+ if (Buffer.isBuffer(req.body) && req.body.length > 0) {
999
+ // 对于 buffer,尝试以 utf-8 解码,如果失败则显示原始 buffer 信息
1000
+ try {
1001
+ bodyContent = req.body.toString('utf-8');
1002
+ } catch (e) {
1003
+ bodyContent = `[Non-UTF8 Buffer, size: ${req.body.length} bytes]`;
1004
+ }
1005
+ } else if (typeof req.body === 'object' && Object.keys(req.body).length > 0) {
1006
+ bodyContent = JSON.stringify(req.body, null, 2);
1007
+ } else if (typeof req.body === 'string' && req.body.length > 0) {
1008
+ bodyContent = req.body;
1009
+ }
1010
+ }
1011
+
1012
+ log(`[DEBUG][${requestId}] Body:\n${bodyContent}`);
1013
+ log(`--- [DEBUG] END INCOMING REQUEST (${requestId}) ---\n\n`);
1014
+
1015
+ next();
1016
+ };
1017
+ }
1018
+
1019
+
1020
  _createAuthMiddleware() {
1021
  return (req, res, next) => {
1022
  const serverApiKeys = this.config.apiKeys;
 
1087
 
1088
  _createExpressApp() {
1089
  const app = express();
1090
+ // [修改] body-parser 中间件需要先于我们的调试中间件
1091
  app.use(express.json({ limit: '100mb' }));
1092
  app.use(express.raw({ type: '*/*', limit: '100mb' }));
1093
 
1094
+ // [新增] 插入调试日志中间件。它会在body解析后,但在任何业务逻辑之前运行。
1095
+ app.use(this._createDebugLogMiddleware());
1096
+
1097
  app.get('/admin/set-mode', (req, res) => {
1098
  const newMode = req.query.mode;
1099
  if (newMode === 'fake' || newMode === 'real') {
1100
  this.streamingMode = newMode;
1101
+ this.logger.info(`[Admin] 流式模式已切换为: ${this.streamingMode}`);
1102
  res.status(200).send(`流式模式已切换为: ${this.streamingMode}`);
1103
  } else {
1104
  res.status(400).send('无效模式. 请用 "fake" 或 "real".');
1105
  }
1106
  });
1107
 
1108
+ // [新增] 切换调试模式的管理端点
1109
+ app.get('/admin/set-debug', (req, res) => {
1110
+ const enable = req.query.enable;
1111
+ if (enable === 'true') {
1112
+ this.config.debugMode = true;
1113
+ this.logger.info('[Admin] 调试模式已开启 (Debug Mode ON)');
1114
+ res.status(200).send('调试模式已开启 (Debug Mode ON)');
1115
+ } else if (enable === 'false') {
1116
+ this.config.debugMode = false;
1117
+ this.logger.info('[Admin] 调试模式已关闭 (Debug Mode OFF)');
1118
+ res.status(200).send('调试模式已关闭 (Debug Mode OFF)');
1119
+ } else {
1120
+ res.status(400).send('无效的参数. 请使用 ?enable=true 或 ?enable=false');
1121
+ }
1122
+ });
1123
+
1124
  app.get('/health', (req, res) => {
1125
  res.status(200).json({
1126
  status: 'healthy',
1127
  uptime: process.uptime(),
1128
  config: {
1129
  streamingMode: this.streamingMode,
1130
+ debugMode: this.config.debugMode, // [新增] 在健康检查中报告调试模式状态
1131
  failureThreshold: this.config.failureThreshold,
1132
  immediateSwitchStatusCodes: this.config.immediateSwitchStatusCodes,
1133
  maxRetries: this.config.maxRetries,
 
1165
  try {
1166
  await this.requestHandler._switchToNextAuth();
1167
  const newIndex = this.requestHandler.currentAuthIndex;
1168
+
1169
  const message = `成功将账号从索引 ${oldIndex} 切换到 ${newIndex}。`;
1170
  this.logger.info(`[Admin] 手动切换成功。 ${message}`);
1171
  res.status(200).send(message);