holyterra commited on
Commit
f30f29d
·
verified ·
1 Parent(s): 9600dc2

Upload unified-server.js

Browse files
Files changed (1) hide show
  1. unified-server.js +243 -219
unified-server.js CHANGED
@@ -57,8 +57,8 @@ class AuthSource {
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}`);
@@ -78,14 +78,14 @@ class AuthSource {
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
 
@@ -99,11 +99,11 @@ class AuthSource {
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;
@@ -134,37 +134,37 @@ class AuthSource {
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
 
@@ -244,7 +244,7 @@ class BrowserManager {
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 {
@@ -280,7 +280,7 @@ class BrowserManager {
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
 
@@ -353,24 +353,24 @@ class LoggingService {
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
  }
@@ -640,28 +640,28 @@ class RequestHandler {
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) {
@@ -671,19 +671,19 @@ class RequestHandler {
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
  }
@@ -925,11 +925,11 @@ class ProxyServerSystem extends EventEmitter {
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);
@@ -1034,9 +1034,9 @@ class ProxyServerSystem extends EventEmitter {
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
 
@@ -1081,13 +1081,13 @@ class ProxyServerSystem extends EventEmitter {
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
 
@@ -1131,7 +1131,7 @@ class ProxyServerSystem extends EventEmitter {
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;
@@ -1151,7 +1151,7 @@ class ProxyServerSystem extends EventEmitter {
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)}`);
@@ -1176,162 +1176,162 @@ class ProxyServerSystem extends EventEmitter {
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());
1201
-
1202
  // --- 仪表盘和API端点 ---
1203
-
1204
  // 公开端点:提供仪表盘HTML
1205
  app.get('/dashboard', (req, res) => {
1206
- res.send(this._getDashboardHtml());
1207
  });
1208
-
1209
  // 公开端点:用于仪表盘验证API密钥
1210
  app.post('/dashboard/verify-key', (req, res) => {
1211
- const { key } = req.body;
1212
- const serverApiKeys = this.config.apiKeys;
1213
 
1214
- if (!serverApiKeys || serverApiKeys.length === 0) {
1215
- this.logger.info('[管理] 服务器未配置API密钥,自动授予仪表盘访问权限。');
1216
- return res.json({ success: true });
1217
- }
1218
 
1219
- if (key && serverApiKeys.includes(key)) {
1220
- this.logger.info('[管理] 仪表盘API密钥验证成功。');
1221
- return res.json({ success: true });
1222
- }
1223
-
1224
- this.logger.warn(`[管理] 仪表盘API密��验证失败。`);
1225
- res.status(401).json({ success: false, message: '无效的API密钥。' });
1226
  });
1227
 
1228
  // 中间件:保护仪表盘API路由
1229
  const dashboardApiAuth = (req, res, next) => {
1230
- const serverApiKeys = this.config.apiKeys;
1231
- if (!serverApiKeys || serverApiKeys.length === 0) {
1232
- return next(); // 未配置密钥,跳过认证
1233
- }
1234
 
1235
- const clientKey = req.headers['x-dashboard-auth'];
1236
- if (clientKey && serverApiKeys.includes(clientKey)) {
1237
- return next();
1238
- }
1239
-
1240
- this.logger.warn(`[管理] 拒绝未经授权的仪表盘API请求。IP: ${req.ip}, 路径: ${req.path}`);
1241
- res.status(401).json({ error: { message: 'Unauthorized dashboard access' } });
1242
  };
1243
 
1244
  const dashboardApiRouter = express.Router();
1245
  dashboardApiRouter.use(dashboardApiAuth);
1246
 
1247
  dashboardApiRouter.get('/data', (req, res) => {
1248
- res.json({
1249
- status: {
1250
- uptime: process.uptime(),
1251
- streamingMode: this.streamingMode,
1252
- debugMode: this.config.debugMode,
1253
- authMode: this.authSource.authMode,
1254
- apiKeyAuth: (this.config.apiKeys && this.config.apiKeys.length > 0) ? '已启用' : '已禁用',
1255
- isAuthSwitching: this.requestHandler.isAuthSwitching,
1256
- browserConnected: !!this.browserManager.browser,
1257
- internalWsClients: this.connectionRegistry.connections.size
1258
- },
1259
- auth: {
1260
- currentAuthIndex: this.requestHandler.currentAuthIndex,
1261
- accounts: this.authSource.getAccountDetails(),
1262
- failureCount: this.requestHandler.failureCount,
1263
- },
1264
- stats: this.stats,
1265
- config: this.config
1266
- });
1267
  });
1268
-
1269
  dashboardApiRouter.post('/config', (req, res) => {
1270
- const newConfig = req.body;
1271
- try {
1272
- if (newConfig.hasOwnProperty('streamingMode') && ['real', 'fake'].includes(newConfig.streamingMode)) {
1273
- this.config.streamingMode = newConfig.streamingMode;
1274
- this.streamingMode = newConfig.streamingMode;
1275
- this.requestHandler.serverSystem.streamingMode = newConfig.streamingMode;
1276
- }
1277
- if (newConfig.hasOwnProperty('debugMode') && typeof newConfig.debugMode === 'boolean') {
1278
- this.config.debugMode = newConfig.debugMode;
1279
- }
1280
- if (newConfig.hasOwnProperty('failureThreshold')) {
1281
- this.config.failureThreshold = parseInt(newConfig.failureThreshold, 10) || 0;
1282
- }
1283
- if (newConfig.hasOwnProperty('maxRetries')) {
1284
- const retries = parseInt(newConfig.maxRetries, 10);
1285
- this.config.maxRetries = retries >= 0 ? retries : 3;
1286
- this.requestHandler.maxRetries = this.config.maxRetries;
1287
- }
1288
- if (newConfig.hasOwnProperty('retryDelay')) {
1289
- this.config.retryDelay = parseInt(newConfig.retryDelay, 10) || 2000;
1290
- this.requestHandler.retryDelay = this.config.retryDelay;
1291
- }
1292
- if (newConfig.hasOwnProperty('immediateSwitchStatusCodes')) {
1293
- if (Array.isArray(newConfig.immediateSwitchStatusCodes)) {
1294
- this.config.immediateSwitchStatusCodes = newConfig.immediateSwitchStatusCodes
1295
- .map(c => parseInt(c, 10))
1296
- .filter(c => !isNaN(c));
1297
- }
1298
- }
1299
- this.logger.info('[管理] 配置已通过仪表盘动态更新。');
1300
- res.status(200).json({ success: true, message: '配置已临时更新。' });
1301
- } catch (error) {
1302
- this.logger.error(`[管理] 更新配置失败: ${error.message}`);
1303
- res.status(500).json({ success: false, message: error.message });
1304
  }
 
 
 
 
 
 
1305
  });
1306
 
1307
  dashboardApiRouter.post('/accounts', (req, res) => {
1308
- const { index, authData } = req.body;
1309
- if (!index || !authData) {
1310
- return res.status(400).json({ success: false, message: "必须提供索引和认证数据。" });
1311
- }
1312
-
1313
- let parsedData;
1314
- try {
1315
- parsedData = (typeof authData === 'string') ? JSON.parse(authData) : authData;
1316
- } catch(e) {
1317
- return res.status(400).json({ success: false, message: "认证数据的JSON格式无效。" });
1318
- }
1319
 
1320
- const result = this.authSource.addAccount(parseInt(index, 10), parsedData);
1321
- if (result.success) {
1322
- if(!this.stats.accountCalls.hasOwnProperty(index)) {
1323
- this.stats.accountCalls[index] = { total: 0, models: {} };
1324
- }
 
 
 
 
 
 
1325
  }
1326
- res.status(result.success ? 200 : 400).json(result);
 
1327
  });
1328
 
1329
  dashboardApiRouter.delete('/accounts/:index', (req, res) => {
1330
- const index = parseInt(req.params.index, 10);
1331
- const result = this.authSource.removeAccount(index);
1332
- res.status(result.success ? 200 : 400).json(result);
1333
  });
1334
-
1335
  // 挂载受保护的仪表盘API路由
1336
  app.use('/dashboard', dashboardApiRouter);
1337
 
@@ -1356,7 +1356,7 @@ class ProxyServerSystem extends EventEmitter {
1356
  res.status(500).send(errorMessage);
1357
  }
1358
  });
1359
-
1360
  app.get('/health', (req, res) => {
1361
  res.status(200).json({
1362
  status: 'healthy',
@@ -1398,7 +1398,7 @@ class ProxyServerSystem extends EventEmitter {
1398
 
1399
  return app;
1400
  }
1401
-
1402
  _getDashboardHtml() {
1403
  return `
1404
  <!DOCTYPE html>
@@ -1721,29 +1721,53 @@ class ProxyServerSystem extends EventEmitter {
1721
  });
1722
  }
1723
 
1724
- async function checkAndInitiate() {
1725
- let apiKey = sessionStorage.getItem(API_KEY_SESSION_STORAGE);
1726
- if (!apiKey) {
1727
- apiKey = prompt("请输入API密钥以访问仪表盘:");
1728
- if (!apiKey) { document.body.innerHTML = '<h1>访问被拒绝</h1>'; return; }
1729
- }
1730
-
1731
  try {
1732
- const response = await fetch(\`\${API_BASE}/verify-key\`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key: apiKey }) });
 
 
 
 
1733
  const result = await response.json();
1734
 
1735
  if (response.ok && result.success) {
1736
- sessionStorage.setItem(API_KEY_SESSION_STORAGE, apiKey);
 
 
1737
  mainContainer.style.display = 'block';
1738
  initializeDashboardListeners();
1739
  fetchData();
1740
  setInterval(fetchData, 5000);
 
1741
  } else {
1742
  sessionStorage.removeItem(API_KEY_SESSION_STORAGE);
1743
- document.body.innerHTML = \`<h1>认证失败: \${result.message || '无效的密钥'}</h1>\`;
1744
  }
1745
  } catch (err) {
1746
  document.body.innerHTML = \`<h1>认证时发生错误: \${err.message}</h1>\`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1747
  }
1748
  }
1749
 
@@ -1754,7 +1778,7 @@ class ProxyServerSystem extends EventEmitter {
1754
  </html>
1755
  `;
1756
  }
1757
-
1758
 
1759
 
1760
  async _startWebSocketServer() {
 
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}`);
 
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
 
 
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;
 
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
 
 
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 {
 
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
 
 
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
  }
 
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) {
 
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
  }
 
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);
 
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
 
 
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
 
 
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;
 
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)}`);
 
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());
1201
+
1202
  // --- 仪表盘和API端点 ---
1203
+
1204
  // 公开端点:提供仪表盘HTML
1205
  app.get('/dashboard', (req, res) => {
1206
+ res.send(this._getDashboardHtml());
1207
  });
1208
+
1209
  // 公开端点:用于仪表盘验证API密钥
1210
  app.post('/dashboard/verify-key', (req, res) => {
1211
+ const { key } = req.body;
1212
+ const serverApiKeys = this.config.apiKeys;
1213
 
1214
+ if (!serverApiKeys || serverApiKeys.length === 0) {
1215
+ this.logger.info('[管理] 服务器未配置API密钥,自动授予仪表盘访问权限。');
1216
+ return res.json({ success: true });
1217
+ }
1218
 
1219
+ if (key && serverApiKeys.includes(key)) {
1220
+ this.logger.info('[管理] 仪表盘API密钥验证成功。');
1221
+ return res.json({ success: true });
1222
+ }
1223
+
1224
+ this.logger.warn(`[管理] 仪表盘API密钥验证失败。`);
1225
+ res.status(401).json({ success: false, message: '无效的API密钥。' });
1226
  });
1227
 
1228
  // 中间件:保护仪表盘API路由
1229
  const dashboardApiAuth = (req, res, next) => {
1230
+ const serverApiKeys = this.config.apiKeys;
1231
+ if (!serverApiKeys || serverApiKeys.length === 0) {
1232
+ return next(); // 未配置密钥,跳过认证
1233
+ }
1234
 
1235
+ const clientKey = req.headers['x-dashboard-auth'];
1236
+ if (clientKey && serverApiKeys.includes(clientKey)) {
1237
+ return next();
1238
+ }
1239
+
1240
+ this.logger.warn(`[管理] 拒绝未经授权的仪表盘API请求。IP: ${req.ip}, 路径: ${req.path}`);
1241
+ res.status(401).json({ error: { message: 'Unauthorized dashboard access' } });
1242
  };
1243
 
1244
  const dashboardApiRouter = express.Router();
1245
  dashboardApiRouter.use(dashboardApiAuth);
1246
 
1247
  dashboardApiRouter.get('/data', (req, res) => {
1248
+ res.json({
1249
+ status: {
1250
+ uptime: process.uptime(),
1251
+ streamingMode: this.streamingMode,
1252
+ debugMode: this.config.debugMode,
1253
+ authMode: this.authSource.authMode,
1254
+ apiKeyAuth: (this.config.apiKeys && this.config.apiKeys.length > 0) ? '已启用' : '已禁用',
1255
+ isAuthSwitching: this.requestHandler.isAuthSwitching,
1256
+ browserConnected: !!this.browserManager.browser,
1257
+ internalWsClients: this.connectionRegistry.connections.size
1258
+ },
1259
+ auth: {
1260
+ currentAuthIndex: this.requestHandler.currentAuthIndex,
1261
+ accounts: this.authSource.getAccountDetails(),
1262
+ failureCount: this.requestHandler.failureCount,
1263
+ },
1264
+ stats: this.stats,
1265
+ config: this.config
1266
+ });
1267
  });
1268
+
1269
  dashboardApiRouter.post('/config', (req, res) => {
1270
+ const newConfig = req.body;
1271
+ try {
1272
+ if (newConfig.hasOwnProperty('streamingMode') && ['real', 'fake'].includes(newConfig.streamingMode)) {
1273
+ this.config.streamingMode = newConfig.streamingMode;
1274
+ this.streamingMode = newConfig.streamingMode;
1275
+ this.requestHandler.serverSystem.streamingMode = newConfig.streamingMode;
1276
+ }
1277
+ if (newConfig.hasOwnProperty('debugMode') && typeof newConfig.debugMode === 'boolean') {
1278
+ this.config.debugMode = newConfig.debugMode;
1279
+ }
1280
+ if (newConfig.hasOwnProperty('failureThreshold')) {
1281
+ this.config.failureThreshold = parseInt(newConfig.failureThreshold, 10) || 0;
1282
+ }
1283
+ if (newConfig.hasOwnProperty('maxRetries')) {
1284
+ const retries = parseInt(newConfig.maxRetries, 10);
1285
+ this.config.maxRetries = retries >= 0 ? retries : 3;
1286
+ this.requestHandler.maxRetries = this.config.maxRetries;
1287
+ }
1288
+ if (newConfig.hasOwnProperty('retryDelay')) {
1289
+ this.config.retryDelay = parseInt(newConfig.retryDelay, 10) || 2000;
1290
+ this.requestHandler.retryDelay = this.config.retryDelay;
1291
+ }
1292
+ if (newConfig.hasOwnProperty('immediateSwitchStatusCodes')) {
1293
+ if (Array.isArray(newConfig.immediateSwitchStatusCodes)) {
1294
+ this.config.immediateSwitchStatusCodes = newConfig.immediateSwitchStatusCodes
1295
+ .map(c => parseInt(c, 10))
1296
+ .filter(c => !isNaN(c));
1297
+ }
 
 
 
 
 
 
1298
  }
1299
+ this.logger.info('[管理] 配置已通过仪表盘动态更新。');
1300
+ res.status(200).json({ success: true, message: '配置已临时更新。' });
1301
+ } catch (error) {
1302
+ this.logger.error(`[管理] 更新配置失败: ${error.message}`);
1303
+ res.status(500).json({ success: false, message: error.message });
1304
+ }
1305
  });
1306
 
1307
  dashboardApiRouter.post('/accounts', (req, res) => {
1308
+ const { index, authData } = req.body;
1309
+ if (!index || !authData) {
1310
+ return res.status(400).json({ success: false, message: "必须提供索引和认证数据。" });
1311
+ }
 
 
 
 
 
 
 
1312
 
1313
+ let parsedData;
1314
+ try {
1315
+ parsedData = (typeof authData === 'string') ? JSON.parse(authData) : authData;
1316
+ } catch (e) {
1317
+ return res.status(400).json({ success: false, message: "认证数据的JSON格式无效。" });
1318
+ }
1319
+
1320
+ const result = this.authSource.addAccount(parseInt(index, 10), parsedData);
1321
+ if (result.success) {
1322
+ if (!this.stats.accountCalls.hasOwnProperty(index)) {
1323
+ this.stats.accountCalls[index] = { total: 0, models: {} };
1324
  }
1325
+ }
1326
+ res.status(result.success ? 200 : 400).json(result);
1327
  });
1328
 
1329
  dashboardApiRouter.delete('/accounts/:index', (req, res) => {
1330
+ const index = parseInt(req.params.index, 10);
1331
+ const result = this.authSource.removeAccount(index);
1332
+ res.status(result.success ? 200 : 400).json(result);
1333
  });
1334
+
1335
  // 挂载受保护的仪表盘API路由
1336
  app.use('/dashboard', dashboardApiRouter);
1337
 
 
1356
  res.status(500).send(errorMessage);
1357
  }
1358
  });
1359
+
1360
  app.get('/health', (req, res) => {
1361
  res.status(200).json({
1362
  status: 'healthy',
 
1398
 
1399
  return app;
1400
  }
1401
+
1402
  _getDashboardHtml() {
1403
  return `
1404
  <!DOCTYPE html>
 
1721
  });
1722
  }
1723
 
1724
+ async function verifyAndLoad(keyToVerify) {
 
 
 
 
 
 
1725
  try {
1726
+ const response = await fetch(\`\${API_BASE}/verify-key\`, {
1727
+ method: 'POST',
1728
+ headers: { 'Content-Type': 'application/json' },
1729
+ body: JSON.stringify({ key: keyToVerify || '' })
1730
+ });
1731
  const result = await response.json();
1732
 
1733
  if (response.ok && result.success) {
1734
+ if (keyToVerify) {
1735
+ sessionStorage.setItem(API_KEY_SESSION_STORAGE, keyToVerify);
1736
+ }
1737
  mainContainer.style.display = 'block';
1738
  initializeDashboardListeners();
1739
  fetchData();
1740
  setInterval(fetchData, 5000);
1741
+ return true;
1742
  } else {
1743
  sessionStorage.removeItem(API_KEY_SESSION_STORAGE);
1744
+ return false;
1745
  }
1746
  } catch (err) {
1747
  document.body.innerHTML = \`<h1>认证时发生错误: \${err.message}</h1>\`;
1748
+ return false;
1749
+ }
1750
+ }
1751
+
1752
+ async function checkAndInitiate() {
1753
+ const storedApiKey = sessionStorage.getItem(API_KEY_SESSION_STORAGE);
1754
+
1755
+ // 尝试使用已存储的密钥或空密钥进行验证
1756
+ const initialCheckSuccess = await verifyAndLoad(storedApiKey);
1757
+
1758
+ // 如果初次验证失败,说明服务器需要密钥,而我们没有提供或提供了错误的密钥
1759
+ if (!initialCheckSuccess) {
1760
+ const newApiKey = prompt("请输入API密钥以访问仪表盘 (服务器需要认证):");
1761
+ if (newApiKey) {
1762
+ // 使用用户新输入的密钥再次尝试
1763
+ const secondCheckSuccess = await verifyAndLoad(newApiKey);
1764
+ if (!secondCheckSuccess) {
1765
+ document.body.innerHTML = \`<h1>认证失败: 无效的API密钥</h1>\`;
1766
+ }
1767
+ } else {
1768
+ // 用户取消了输入
1769
+ document.body.innerHTML = '<h1>访问被拒绝</h1>';
1770
+ }
1771
  }
1772
  }
1773
 
 
1778
  </html>
1779
  `;
1780
  }
1781
+
1782
 
1783
 
1784
  async _startWebSocketServer() {