server.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. import express from 'express';
  2. import cors from 'cors';
  3. import axios from 'axios';
  4. import nodeRSA from 'node-rsa';
  5. import cookieParser from 'cookie-parser';
  6. import { readFileSync } from 'fs';
  7. import { fileURLToPath } from 'url';
  8. import { dirname, join } from 'path';
  9. // node-rsa 是 CommonJS 模块,需要使用默认导入
  10. const NodeRSA = nodeRSA;
  11. const __filename = fileURLToPath(import.meta.url);
  12. const __dirname = dirname(__filename);
  13. const app = express();
  14. const PORT = process.env.PORT || 8889;
  15. // 中间件
  16. app.use(cors({
  17. origin: true,
  18. credentials: true
  19. }));
  20. app.use(express.json());
  21. app.use(express.urlencoded({ extended: true }));
  22. app.use(cookieParser());
  23. // 请求日志中间件(用于调试)
  24. app.use((req, res, next) => {
  25. console.log(`[请求] ${req.method} ${req.path} - ${new Date().toISOString()}`);
  26. next();
  27. });
  28. // 加载自动登录配置
  29. let autoLoginConfig = {};
  30. try {
  31. const configPath = join(__dirname, 'auto-login-config.json');
  32. console.log('正在加载自动登录配置文件:', configPath);
  33. const configData = readFileSync(configPath, 'utf-8');
  34. autoLoginConfig = JSON.parse(configData);
  35. console.log('✓ 已加载自动登录配置');
  36. console.log(' 配置的网站数量:', Object.keys(autoLoginConfig).length);
  37. console.log(' 网站列表:', Object.keys(autoLoginConfig).join(', '));
  38. Object.keys(autoLoginConfig).forEach(siteId => {
  39. const site = autoLoginConfig[siteId];
  40. console.log(` - ${siteId}: ${site.name} (${site.loginMethod})`);
  41. });
  42. } catch (error) {
  43. console.error('✗ 加载自动登录配置失败:', error.message);
  44. console.error(' 错误堆栈:', error.stack);
  45. console.log('将使用默认配置');
  46. }
  47. // RSA 加密函数
  48. function encryptWithRSA(text, publicKey) {
  49. const key = new NodeRSA(publicKey);
  50. return key.encrypt(text, 'base64');
  51. }
  52. // 解析 Cookie
  53. function parseCookies(setCookieHeaders) {
  54. return setCookieHeaders.map(cookie => {
  55. const match = cookie.match(/^([^=]+)=([^;]+)/);
  56. if (match) {
  57. const name = match[1];
  58. const value = match[2];
  59. // 提取其他属性
  60. const pathMatch = cookie.match(/Path=([^;]+)/);
  61. const expiresMatch = cookie.match(/Expires=([^;]+)/);
  62. const maxAgeMatch = cookie.match(/Max-Age=([^;]+)/);
  63. const httpOnlyMatch = cookie.match(/HttpOnly/);
  64. const secureMatch = cookie.match(/Secure/);
  65. const sameSiteMatch = cookie.match(/SameSite=([^;]+)/);
  66. return {
  67. name,
  68. value,
  69. path: pathMatch ? pathMatch[1] : '/',
  70. expires: expiresMatch ? expiresMatch[1] : null,
  71. maxAge: maxAgeMatch ? maxAgeMatch[1] : null,
  72. httpOnly: !!httpOnlyMatch,
  73. secure: !!secureMatch,
  74. sameSite: sameSiteMatch ? sameSiteMatch[1] : null
  75. };
  76. }
  77. return null;
  78. }).filter(Boolean);
  79. }
  80. // 生成跳转 HTML
  81. function generateRedirectHTML(cookieData, targetHost, targetDomain, requestId = '') {
  82. return `
  83. <!DOCTYPE html>
  84. <html lang="zh-CN">
  85. <head>
  86. <meta charset="UTF-8">
  87. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  88. <title>自动登录中...</title>
  89. <style>
  90. body {
  91. display: flex;
  92. justify-content: center;
  93. align-items: center;
  94. height: 100vh;
  95. margin: 0;
  96. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  97. background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  98. }
  99. .loading {
  100. text-align: center;
  101. }
  102. .spinner {
  103. border: 4px solid #f3f3f3;
  104. border-top: 4px solid #3498db;
  105. border-radius: 50%;
  106. width: 50px;
  107. height: 50px;
  108. animation: spin 1s linear infinite;
  109. margin: 0 auto 20px;
  110. }
  111. @keyframes spin {
  112. 0% { transform: rotate(0deg); }
  113. 100% { transform: rotate(360deg); }
  114. }
  115. .message {
  116. color: #333;
  117. font-size: 18px;
  118. }
  119. </style>
  120. </head>
  121. <body>
  122. <div class="loading">
  123. <div class="spinner"></div>
  124. <div class="message">正在自动登录,请稍候...</div>
  125. </div>
  126. <iframe id="cookieFrame" style="display:none;"></iframe>
  127. <script>
  128. (function() {
  129. const requestId = '${requestId}';
  130. const cookies = ${JSON.stringify(cookieData)};
  131. const targetUrl = 'http://${targetHost}/';
  132. const targetDomain = '${targetDomain}';
  133. console.log('========================================');
  134. console.log('[浏览器端] 自动登录脚本开始执行');
  135. console.log('[浏览器端] 请求ID:', requestId);
  136. console.log('[浏览器端] 目标URL:', targetUrl);
  137. console.log('[浏览器端] 目标域名:', targetDomain);
  138. console.log('[浏览器端] Cookie 数量:', cookies.length);
  139. console.log('[浏览器端] Cookie 详情:', cookies);
  140. // 方法1: 尝试直接设置 Cookie(可能因为跨域限制而失败)
  141. console.log('[浏览器端] 开始尝试设置 Cookie...');
  142. let successCount = 0;
  143. let failCount = 0;
  144. cookies.forEach(function(cookie) {
  145. try {
  146. // 构建 Cookie 字符串
  147. let cookieStr = cookie.name + '=' + cookie.value;
  148. cookieStr += '; path=' + cookie.path;
  149. if (cookie.maxAge) {
  150. cookieStr += '; max-age=' + cookie.maxAge;
  151. }
  152. if (cookie.expires) {
  153. cookieStr += '; expires=' + cookie.expires;
  154. }
  155. if (cookie.secure) {
  156. cookieStr += '; secure';
  157. }
  158. if (cookie.sameSite) {
  159. cookieStr += '; samesite=' + cookie.sameSite;
  160. }
  161. // 注意:Domain 属性无法通过 JavaScript 设置跨域 Cookie
  162. // 但我们可以尝试设置(浏览器会忽略跨域的 Domain)
  163. cookieStr += '; domain=' + targetDomain;
  164. document.cookie = cookieStr;
  165. console.log('[浏览器端] ✓ 尝试设置 Cookie:', cookie.name);
  166. successCount++;
  167. // 验证 Cookie 是否设置成功
  168. const allCookies = document.cookie;
  169. if (allCookies.indexOf(cookie.name + '=') !== -1) {
  170. console.log('[浏览器端] ✓ Cookie 设置成功:', cookie.name);
  171. } else {
  172. console.warn('[浏览器端] ⚠ Cookie 可能未设置成功:', cookie.name, '(可能是跨域限制)');
  173. }
  174. } catch(e) {
  175. console.error('[浏览器端] ✗ 设置 Cookie 失败:', cookie.name, e);
  176. failCount++;
  177. }
  178. });
  179. console.log('[浏览器端] Cookie 设置结果: 成功 ' + successCount + ', 失败 ' + failCount);
  180. // 方法2: 使用隐藏的 iframe 加载目标站点,让服务器设置 Cookie
  181. // 然后跳转到目标站点
  182. console.log('[浏览器端] 创建隐藏 iframe 加载目标站点...');
  183. const iframe = document.getElementById('cookieFrame');
  184. iframe.onload = function() {
  185. console.log('[浏览器端] iframe 加载完成');
  186. };
  187. iframe.onerror = function(error) {
  188. console.error('[浏览器端] iframe 加载失败:', error);
  189. };
  190. iframe.src = targetUrl;
  191. // 延迟跳转,确保 iframe 加载完成
  192. setTimeout(function() {
  193. console.log('[浏览器端] 准备跳转到目标站点:', targetUrl);
  194. console.log('[浏览器端] 当前页面 Cookie:', document.cookie);
  195. console.log('========================================');
  196. window.location.href = targetUrl;
  197. }, 1500);
  198. })();
  199. </script>
  200. </body>
  201. </html>
  202. `;
  203. }
  204. // 处理 RSA 加密表单登录
  205. async function handleRSAEncryptedFormLogin(config, credentials) {
  206. const { targetBaseUrl, loginUrl, loginMethodConfig } = config;
  207. const { publicKey, usernameField, passwordField, captchaField, captchaRequired, contentType, successCode, successField } = loginMethodConfig;
  208. console.log('=== RSA 加密表单登录 ===');
  209. console.log(`目标URL: ${targetBaseUrl}${loginUrl}`);
  210. console.log(`用户名: ${credentials.username}`);
  211. console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
  212. console.log(`内容类型: ${contentType}`);
  213. console.log(`成功标识字段: ${successField || 'code'}, 成功值: ${successCode}`);
  214. // 加密用户名和密码
  215. const usernameEncrypted = encryptWithRSA(credentials.username, publicKey);
  216. const passwordEncrypted = encryptWithRSA(credentials.password, publicKey);
  217. console.log('用户名和密码已加密');
  218. console.log(`加密后用户名长度: ${usernameEncrypted.length}`);
  219. console.log(`加密后密码长度: ${passwordEncrypted.length}`);
  220. // 构建请求数据
  221. const requestData = {
  222. [usernameField]: usernameEncrypted,
  223. [passwordField]: passwordEncrypted
  224. };
  225. if (captchaField) {
  226. requestData[captchaField] = captchaRequired ? '' : '';
  227. }
  228. // 发送登录请求
  229. const headers = {};
  230. let requestBody;
  231. if (contentType === 'application/x-www-form-urlencoded') {
  232. headers['Content-Type'] = 'application/x-www-form-urlencoded';
  233. requestBody = new URLSearchParams(requestData).toString();
  234. } else if (contentType === 'application/json') {
  235. headers['Content-Type'] = 'application/json';
  236. requestBody = JSON.stringify(requestData);
  237. } else {
  238. requestBody = requestData;
  239. }
  240. console.log(`发送登录请求到: ${targetBaseUrl}${loginUrl}`);
  241. console.log(`请求头:`, JSON.stringify(headers, null, 2));
  242. console.log(`请求体长度: ${requestBody.length} 字符`);
  243. const loginResponse = await axios.post(
  244. `${targetBaseUrl}${loginUrl}`,
  245. requestBody,
  246. {
  247. headers,
  248. withCredentials: true,
  249. maxRedirects: 0,
  250. validateStatus: function (status) {
  251. return status >= 200 && status < 400;
  252. }
  253. }
  254. );
  255. console.log(`登录响应状态码: ${loginResponse.status}`);
  256. console.log(`响应头:`, JSON.stringify(loginResponse.headers, null, 2));
  257. console.log(`响应数据:`, JSON.stringify(loginResponse.data, null, 2));
  258. // 检查登录是否成功
  259. const responseData = loginResponse.data || {};
  260. const successValue = successField ? responseData[successField] : responseData.code;
  261. console.log(`成功标识值: ${successValue}, 期望值: ${successCode}`);
  262. if (successValue === successCode) {
  263. const cookies = loginResponse.headers['set-cookie'] || [];
  264. console.log(`登录成功!获取到 ${cookies.length} 个 Cookie`);
  265. cookies.forEach((cookie, index) => {
  266. console.log(`Cookie ${index + 1}: ${cookie.substring(0, 100)}...`);
  267. });
  268. return {
  269. success: true,
  270. cookies: cookies,
  271. response: loginResponse.data
  272. };
  273. } else {
  274. console.error(`登录失败!响应:`, responseData);
  275. return {
  276. success: false,
  277. message: responseData.msg || responseData.message || '登录失败',
  278. response: responseData
  279. };
  280. }
  281. }
  282. // 处理普通表单登录(未加密)
  283. async function handlePlainFormLogin(config, credentials) {
  284. const { targetBaseUrl, loginUrl, loginMethodConfig } = config;
  285. const { usernameField, passwordField, captchaField, contentType, successCode, successField } = loginMethodConfig;
  286. console.log('=== 普通表单登录 ===');
  287. console.log(`目标URL: ${targetBaseUrl}${loginUrl}`);
  288. console.log(`用户名: ${credentials.username}`);
  289. console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
  290. console.log(`内容类型: ${contentType}`);
  291. console.log(`成功标识字段: ${successField || 'code'}, 成功值: ${successCode}`);
  292. // 构建请求数据
  293. const requestData = {
  294. [usernameField]: credentials.username,
  295. [passwordField]: credentials.password
  296. };
  297. if (captchaField) {
  298. requestData[captchaField] = '';
  299. }
  300. // 发送登录请求
  301. const headers = {};
  302. let requestBody;
  303. if (contentType === 'application/x-www-form-urlencoded') {
  304. headers['Content-Type'] = 'application/x-www-form-urlencoded';
  305. requestBody = new URLSearchParams(requestData).toString();
  306. } else if (contentType === 'application/json') {
  307. headers['Content-Type'] = 'application/json';
  308. requestBody = JSON.stringify(requestData);
  309. } else {
  310. requestBody = requestData;
  311. }
  312. console.log(`发送登录请求到: ${targetBaseUrl}${loginUrl}`);
  313. console.log(`请求头:`, JSON.stringify(headers, null, 2));
  314. console.log(`请求体:`, contentType === 'application/json' ? requestBody : requestBody.substring(0, 200) + '...');
  315. const loginResponse = await axios.post(
  316. `${targetBaseUrl}${loginUrl}`,
  317. requestBody,
  318. {
  319. headers,
  320. withCredentials: true,
  321. maxRedirects: 0,
  322. validateStatus: function (status) {
  323. return status >= 200 && status < 400;
  324. }
  325. }
  326. );
  327. console.log(`登录响应状态码: ${loginResponse.status}`);
  328. console.log(`响应数据:`, JSON.stringify(loginResponse.data, null, 2));
  329. // 检查登录是否成功
  330. const responseData = loginResponse.data || {};
  331. const successValue = successField ? responseData[successField] : responseData.code;
  332. console.log(`成功标识值: ${successValue}, 期望值: ${successCode}`);
  333. if (successValue === successCode) {
  334. const cookies = loginResponse.headers['set-cookie'] || [];
  335. console.log(`登录成功!获取到 ${cookies.length} 个 Cookie`);
  336. cookies.forEach((cookie, index) => {
  337. console.log(`Cookie ${index + 1}: ${cookie.substring(0, 100)}...`);
  338. });
  339. return {
  340. success: true,
  341. cookies: cookies,
  342. response: loginResponse.data
  343. };
  344. } else {
  345. console.error(`登录失败!响应:`, responseData);
  346. return {
  347. success: false,
  348. message: responseData.msg || responseData.message || '登录失败',
  349. response: responseData
  350. };
  351. }
  352. }
  353. // 通用的自动登录端点
  354. app.get('/api/auto-login/:siteId', async (req, res) => {
  355. const startTime = Date.now();
  356. const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  357. // 立即输出日志,确认请求已到达
  358. console.log('\n' + '='.repeat(80));
  359. console.log(`[${requestId}] ⚡⚡⚡ 收到自动登录请求!⚡⚡⚡`);
  360. console.log(`[${requestId}] 时间: ${new Date().toISOString()}`);
  361. console.log(`[${requestId}] 请求路径: ${req.path}`);
  362. console.log(`[${requestId}] 请求方法: ${req.method}`);
  363. console.log(`[${requestId}] 完整URL: ${req.protocol}://${req.get('host')}${req.originalUrl}`);
  364. console.log(`[${requestId}] 客户端IP: ${req.ip || req.connection.remoteAddress || req.socket.remoteAddress}`);
  365. console.log(`[${requestId}] User-Agent: ${req.get('user-agent') || 'Unknown'}`);
  366. try {
  367. const { siteId } = req.params;
  368. console.log(`[${requestId}] 网站ID: ${siteId}`);
  369. // 获取网站配置
  370. const config = autoLoginConfig[siteId];
  371. if (!config) {
  372. console.error(`[${requestId}] 错误: 未找到网站ID "${siteId}" 的配置`);
  373. console.error(`[${requestId}] 可用的网站ID: ${Object.keys(autoLoginConfig).join(', ') || '无'}`);
  374. return res.status(404).json({
  375. success: false,
  376. message: `未找到网站ID "${siteId}" 的配置`,
  377. availableSites: Object.keys(autoLoginConfig)
  378. });
  379. }
  380. console.log(`[${requestId}] 网站名称: ${config.name}`);
  381. console.log(`[${requestId}] 目标地址: ${config.targetBaseUrl}`);
  382. console.log(`[${requestId}] 登录方法: ${config.loginMethod}`);
  383. // 获取登录凭据(优先使用环境变量)
  384. const envUsername = process.env[config.credentials.envUsername];
  385. const envPassword = process.env[config.credentials.envPassword];
  386. const credentials = {
  387. username: envUsername || config.credentials.username,
  388. password: envPassword || config.credentials.password
  389. };
  390. console.log(`[${requestId}] 凭据来源: ${envUsername ? '环境变量' : '配置文件'}`);
  391. console.log(`[${requestId}] 用户名: ${credentials.username}`);
  392. console.log(`[${requestId}] 密码: ${'*'.repeat(credentials.password.length)}`);
  393. if (!credentials.username || !credentials.password) {
  394. console.error(`[${requestId}] 错误: 登录凭据未配置`);
  395. return res.status(400).json({
  396. success: false,
  397. message: '登录凭据未配置'
  398. });
  399. }
  400. // 根据登录方法处理登录
  401. let loginResult;
  402. console.log(`[${requestId}] 开始执行登录...`);
  403. switch (config.loginMethod) {
  404. case 'rsa-encrypted-form':
  405. loginResult = await handleRSAEncryptedFormLogin(config, credentials);
  406. break;
  407. case 'plain-form':
  408. loginResult = await handlePlainFormLogin(config, credentials);
  409. break;
  410. default:
  411. console.error(`[${requestId}] 错误: 不支持的登录方法: ${config.loginMethod}`);
  412. return res.status(400).json({
  413. success: false,
  414. message: `不支持的登录方法: ${config.loginMethod}`
  415. });
  416. }
  417. if (!loginResult.success) {
  418. console.error(`[${requestId}] 登录失败:`, loginResult.message);
  419. console.error(`[${requestId}] 失败响应:`, JSON.stringify(loginResult.response, null, 2));
  420. const duration = Date.now() - startTime;
  421. console.log(`[${requestId}] 总耗时: ${duration}ms`);
  422. console.log('='.repeat(80) + '\n');
  423. // 返回错误页面而不是 JSON
  424. const errorHtml = `
  425. <!DOCTYPE html>
  426. <html lang="zh-CN">
  427. <head>
  428. <meta charset="UTF-8">
  429. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  430. <title>自动登录失败</title>
  431. <style>
  432. body {
  433. display: flex;
  434. justify-content: center;
  435. align-items: center;
  436. height: 100vh;
  437. margin: 0;
  438. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  439. background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  440. }
  441. .error-container {
  442. background: white;
  443. padding: 40px;
  444. border-radius: 12px;
  445. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  446. max-width: 600px;
  447. text-align: center;
  448. }
  449. .error-icon {
  450. font-size: 64px;
  451. margin-bottom: 20px;
  452. }
  453. .error-title {
  454. font-size: 24px;
  455. color: #e74c3c;
  456. margin-bottom: 15px;
  457. }
  458. .error-message {
  459. font-size: 16px;
  460. color: #666;
  461. margin-bottom: 20px;
  462. line-height: 1.6;
  463. }
  464. .error-details {
  465. background: #f8f9fa;
  466. padding: 15px;
  467. border-radius: 8px;
  468. margin-top: 20px;
  469. text-align: left;
  470. font-size: 14px;
  471. color: #555;
  472. }
  473. .error-details pre {
  474. margin: 0;
  475. white-space: pre-wrap;
  476. word-wrap: break-word;
  477. }
  478. </style>
  479. </head>
  480. <body>
  481. <div class="error-container">
  482. <div class="error-icon">❌</div>
  483. <div class="error-title">自动登录失败</div>
  484. <div class="error-message">${loginResult.message}</div>
  485. <div class="error-details">
  486. <strong>请求ID:</strong> ${requestId}<br>
  487. <strong>网站:</strong> ${config.name}<br>
  488. <strong>详细信息:</strong>
  489. <pre>${JSON.stringify(loginResult.response, null, 2)}</pre>
  490. </div>
  491. <button onclick="window.history.back()" style="margin-top: 20px; padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 6px; cursor: pointer;">返回</button>
  492. </div>
  493. </body>
  494. </html>
  495. `;
  496. return res.status(500).send(errorHtml);
  497. }
  498. console.log(`[${requestId}] 登录成功!`);
  499. // 解析 Cookie
  500. const cookieData = parseCookies(loginResult.cookies);
  501. console.log(`[${requestId}] 解析到 ${cookieData.length} 个 Cookie:`);
  502. cookieData.forEach((cookie, index) => {
  503. console.log(`[${requestId}] Cookie ${index + 1}: ${cookie.name} = ${cookie.value.substring(0, 20)}...`);
  504. });
  505. // 生成跳转 HTML(添加更多调试信息)
  506. console.log(`[${requestId}] 生成跳转页面,目标: http://${config.targetHost}/`);
  507. const html = generateRedirectHTML(
  508. cookieData,
  509. config.targetHost,
  510. config.targetDomain,
  511. requestId
  512. );
  513. // 在响应头中设置 Cookie
  514. console.log(`[${requestId}] 设置响应头 Cookie...`);
  515. loginResult.cookies.forEach((cookie, index) => {
  516. // 修改 Cookie 的 Domain,移除端口号
  517. let modifiedCookie = cookie.replace(/Domain=[^;]+/i, `Domain=${config.targetDomain}`);
  518. res.setHeader('Set-Cookie', modifiedCookie);
  519. console.log(`[${requestId}] 设置 Cookie ${index + 1}: ${modifiedCookie.substring(0, 80)}...`);
  520. });
  521. const duration = Date.now() - startTime;
  522. console.log(`[${requestId}] 总耗时: ${duration}ms`);
  523. console.log(`[${requestId}] 返回跳转页面`);
  524. console.log('='.repeat(80) + '\n');
  525. res.send(html);
  526. } catch (error) {
  527. const duration = Date.now() - startTime;
  528. console.error(`[${requestId}] 自动登录异常:`, error.message);
  529. console.error(`[${requestId}] 错误堆栈:`, error.stack);
  530. if (error.response) {
  531. console.error(`[${requestId}] 响应状态:`, error.response.status);
  532. console.error(`[${requestId}] 响应头:`, JSON.stringify(error.response.headers, null, 2));
  533. console.error(`[${requestId}] 响应数据:`, JSON.stringify(error.response.data, null, 2));
  534. }
  535. if (error.request) {
  536. console.error(`[${requestId}] 请求信息:`, {
  537. url: error.config?.url,
  538. method: error.config?.method,
  539. headers: error.config?.headers
  540. });
  541. }
  542. console.log(`[${requestId}] 总耗时: ${duration}ms`);
  543. console.log('='.repeat(80) + '\n');
  544. res.status(500).json({
  545. success: false,
  546. message: '自动登录失败: ' + error.message,
  547. error: process.env.NODE_ENV === 'development' ? error.stack : undefined
  548. });
  549. }
  550. });
  551. // 获取所有配置的网站列表
  552. app.get('/api/auto-login', (req, res) => {
  553. const sites = Object.keys(autoLoginConfig).map(siteId => ({
  554. id: siteId,
  555. name: autoLoginConfig[siteId].name,
  556. endpoint: `/api/auto-login/${siteId}`
  557. }));
  558. res.json({ sites });
  559. });
  560. // 健康检查端点
  561. app.get('/api/health', (req, res) => {
  562. res.json({
  563. status: 'ok',
  564. timestamp: new Date().toISOString(),
  565. port: PORT,
  566. configuredSites: Object.keys(autoLoginConfig)
  567. });
  568. });
  569. // 测试端点 - 用于验证配置
  570. app.get('/api/test/:siteId', (req, res) => {
  571. const { siteId } = req.params;
  572. const config = autoLoginConfig[siteId];
  573. if (!config) {
  574. return res.json({
  575. success: false,
  576. message: `未找到网站ID "${siteId}" 的配置`,
  577. availableSites: Object.keys(autoLoginConfig)
  578. });
  579. }
  580. const envUsername = process.env[config.credentials.envUsername];
  581. const envPassword = process.env[config.credentials.envPassword];
  582. const credentials = {
  583. username: envUsername || config.credentials.username,
  584. password: envPassword || config.credentials.password
  585. };
  586. res.json({
  587. success: true,
  588. siteId,
  589. config: {
  590. name: config.name,
  591. targetBaseUrl: config.targetBaseUrl,
  592. loginMethod: config.loginMethod,
  593. loginUrl: config.loginUrl,
  594. hasCredentials: !!(credentials.username && credentials.password),
  595. credentialsSource: envUsername ? '环境变量' : '配置文件',
  596. username: credentials.username,
  597. passwordLength: credentials.password ? credentials.password.length : 0
  598. }
  599. });
  600. });
  601. app.listen(PORT, '0.0.0.0', () => {
  602. console.log('\n' + '='.repeat(80));
  603. console.log('🚀 后端服务器启动成功!');
  604. console.log('='.repeat(80));
  605. console.log(`📍 本地地址: http://localhost:${PORT}`);
  606. console.log(`📍 服务器地址: http://0.0.0.0:${PORT}`);
  607. console.log(`📍 外部访问: http://222.243.138.146:${PORT} (通过防火墙端口映射)`);
  608. console.log(`\n📋 已配置的自动登录网站: ${Object.keys(autoLoginConfig).join(', ') || '无'}`);
  609. console.log(`\n🔗 可用端点:`);
  610. console.log(` - 健康检查: http://localhost:${PORT}/api/health`);
  611. console.log(` - 测试配置: http://localhost:${PORT}/api/test/:siteId`);
  612. console.log(` - 自动登录: http://localhost:${PORT}/api/auto-login/:siteId`);
  613. console.log(`\n💡 提示: 确保防火墙已配置端口映射 (前端:8888, 后端:8889 -> 外网)`);
  614. console.log('='.repeat(80) + '\n');
  615. });