| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- import express from 'express';
- import cors from 'cors';
- import axios from 'axios';
- import nodeRSA from 'node-rsa';
- import cookieParser from 'cookie-parser';
- import { readFileSync } from 'fs';
- import { fileURLToPath } from 'url';
- import { dirname, join } from 'path';
- // node-rsa 是 CommonJS 模块,需要使用默认导入
- const NodeRSA = nodeRSA;
- const __filename = fileURLToPath(import.meta.url);
- const __dirname = dirname(__filename);
- const app = express();
- const PORT = process.env.PORT || 8889;
- // 中间件
- app.use(cors({
- origin: true,
- credentials: true
- }));
- app.use(express.json());
- app.use(express.urlencoded({ extended: true }));
- app.use(cookieParser());
- // 请求日志中间件(用于调试)
- app.use((req, res, next) => {
- console.log(`[请求] ${req.method} ${req.path} - ${new Date().toISOString()}`);
- next();
- });
- // 加载自动登录配置
- let autoLoginConfig = {};
- try {
- const configPath = join(__dirname, 'auto-login-config.json');
- console.log('正在加载自动登录配置文件:', configPath);
- const configData = readFileSync(configPath, 'utf-8');
- autoLoginConfig = JSON.parse(configData);
- console.log('✓ 已加载自动登录配置');
- console.log(' 配置的网站数量:', Object.keys(autoLoginConfig).length);
- console.log(' 网站列表:', Object.keys(autoLoginConfig).join(', '));
- Object.keys(autoLoginConfig).forEach(siteId => {
- const site = autoLoginConfig[siteId];
- console.log(` - ${siteId}: ${site.name} (${site.loginMethod})`);
- });
- } catch (error) {
- console.error('✗ 加载自动登录配置失败:', error.message);
- console.error(' 错误堆栈:', error.stack);
- console.log('将使用默认配置');
- }
- // RSA 加密函数
- function encryptWithRSA(text, publicKey) {
- const key = new NodeRSA(publicKey);
- return key.encrypt(text, 'base64');
- }
- // 解析 Cookie
- function parseCookies(setCookieHeaders) {
- return setCookieHeaders.map(cookie => {
- const match = cookie.match(/^([^=]+)=([^;]+)/);
- if (match) {
- const name = match[1];
- const value = match[2];
-
- // 提取其他属性
- const pathMatch = cookie.match(/Path=([^;]+)/);
- const expiresMatch = cookie.match(/Expires=([^;]+)/);
- const maxAgeMatch = cookie.match(/Max-Age=([^;]+)/);
- const httpOnlyMatch = cookie.match(/HttpOnly/);
- const secureMatch = cookie.match(/Secure/);
- const sameSiteMatch = cookie.match(/SameSite=([^;]+)/);
-
- return {
- name,
- value,
- path: pathMatch ? pathMatch[1] : '/',
- expires: expiresMatch ? expiresMatch[1] : null,
- maxAge: maxAgeMatch ? maxAgeMatch[1] : null,
- httpOnly: !!httpOnlyMatch,
- secure: !!secureMatch,
- sameSite: sameSiteMatch ? sameSiteMatch[1] : null
- };
- }
- return null;
- }).filter(Boolean);
- }
- // 生成跳转 HTML
- function generateRedirectHTML(cookieData, targetHost, targetDomain, requestId = '') {
- return `
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>自动登录中...</title>
- <style>
- body {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100vh;
- margin: 0;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
- }
- .loading {
- text-align: center;
- }
- .spinner {
- border: 4px solid #f3f3f3;
- border-top: 4px solid #3498db;
- border-radius: 50%;
- width: 50px;
- height: 50px;
- animation: spin 1s linear infinite;
- margin: 0 auto 20px;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- .message {
- color: #333;
- font-size: 18px;
- }
- </style>
- </head>
- <body>
- <div class="loading">
- <div class="spinner"></div>
- <div class="message">正在自动登录,请稍候...</div>
- </div>
- <iframe id="cookieFrame" style="display:none;"></iframe>
- <script>
- (function() {
- const requestId = '${requestId}';
- const cookies = ${JSON.stringify(cookieData)};
- const targetUrl = 'http://${targetHost}/';
- const targetDomain = '${targetDomain}';
-
- console.log('========================================');
- console.log('[浏览器端] 自动登录脚本开始执行');
- console.log('[浏览器端] 请求ID:', requestId);
- console.log('[浏览器端] 目标URL:', targetUrl);
- console.log('[浏览器端] 目标域名:', targetDomain);
- console.log('[浏览器端] Cookie 数量:', cookies.length);
- console.log('[浏览器端] Cookie 详情:', cookies);
-
- // 方法1: 尝试直接设置 Cookie(可能因为跨域限制而失败)
- console.log('[浏览器端] 开始尝试设置 Cookie...');
- let successCount = 0;
- let failCount = 0;
-
- cookies.forEach(function(cookie) {
- try {
- // 构建 Cookie 字符串
- let cookieStr = cookie.name + '=' + cookie.value;
- cookieStr += '; path=' + cookie.path;
- if (cookie.maxAge) {
- cookieStr += '; max-age=' + cookie.maxAge;
- }
- if (cookie.expires) {
- cookieStr += '; expires=' + cookie.expires;
- }
- if (cookie.secure) {
- cookieStr += '; secure';
- }
- if (cookie.sameSite) {
- cookieStr += '; samesite=' + cookie.sameSite;
- }
- // 注意:Domain 属性无法通过 JavaScript 设置跨域 Cookie
- // 但我们可以尝试设置(浏览器会忽略跨域的 Domain)
- cookieStr += '; domain=' + targetDomain;
-
- document.cookie = cookieStr;
- console.log('[浏览器端] ✓ 尝试设置 Cookie:', cookie.name);
- successCount++;
-
- // 验证 Cookie 是否设置成功
- const allCookies = document.cookie;
- if (allCookies.indexOf(cookie.name + '=') !== -1) {
- console.log('[浏览器端] ✓ Cookie 设置成功:', cookie.name);
- } else {
- console.warn('[浏览器端] ⚠ Cookie 可能未设置成功:', cookie.name, '(可能是跨域限制)');
- }
- } catch(e) {
- console.error('[浏览器端] ✗ 设置 Cookie 失败:', cookie.name, e);
- failCount++;
- }
- });
-
- console.log('[浏览器端] Cookie 设置结果: 成功 ' + successCount + ', 失败 ' + failCount);
-
- // 方法2: 使用隐藏的 iframe 加载目标站点,让服务器设置 Cookie
- // 然后跳转到目标站点
- console.log('[浏览器端] 创建隐藏 iframe 加载目标站点...');
- const iframe = document.getElementById('cookieFrame');
-
- iframe.onload = function() {
- console.log('[浏览器端] iframe 加载完成');
- };
-
- iframe.onerror = function(error) {
- console.error('[浏览器端] iframe 加载失败:', error);
- };
-
- iframe.src = targetUrl;
-
- // 延迟跳转,确保 iframe 加载完成
- setTimeout(function() {
- console.log('[浏览器端] 准备跳转到目标站点:', targetUrl);
- console.log('[浏览器端] 当前页面 Cookie:', document.cookie);
- console.log('========================================');
- window.location.href = targetUrl;
- }, 1500);
- })();
- </script>
- </body>
- </html>
- `;
- }
- // 处理 RSA 加密表单登录
- async function handleRSAEncryptedFormLogin(config, credentials) {
- const { targetBaseUrl, loginUrl, loginMethodConfig } = config;
- const { publicKey, usernameField, passwordField, captchaField, captchaRequired, contentType, successCode, successField } = loginMethodConfig;
-
- console.log('=== RSA 加密表单登录 ===');
- console.log(`目标URL: ${targetBaseUrl}${loginUrl}`);
- console.log(`用户名: ${credentials.username}`);
- console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
- console.log(`内容类型: ${contentType}`);
- console.log(`成功标识字段: ${successField || 'code'}, 成功值: ${successCode}`);
-
- // 加密用户名和密码
- const usernameEncrypted = encryptWithRSA(credentials.username, publicKey);
- const passwordEncrypted = encryptWithRSA(credentials.password, publicKey);
-
- console.log('用户名和密码已加密');
- console.log(`加密后用户名长度: ${usernameEncrypted.length}`);
- console.log(`加密后密码长度: ${passwordEncrypted.length}`);
-
- // 构建请求数据
- const requestData = {
- [usernameField]: usernameEncrypted,
- [passwordField]: passwordEncrypted
- };
-
- if (captchaField) {
- requestData[captchaField] = captchaRequired ? '' : '';
- }
-
- // 发送登录请求
- const headers = {};
- let requestBody;
-
- if (contentType === 'application/x-www-form-urlencoded') {
- headers['Content-Type'] = 'application/x-www-form-urlencoded';
- requestBody = new URLSearchParams(requestData).toString();
- } else if (contentType === 'application/json') {
- headers['Content-Type'] = 'application/json';
- requestBody = JSON.stringify(requestData);
- } else {
- requestBody = requestData;
- }
-
- console.log(`发送登录请求到: ${targetBaseUrl}${loginUrl}`);
- console.log(`请求头:`, JSON.stringify(headers, null, 2));
- console.log(`请求体长度: ${requestBody.length} 字符`);
-
- const loginResponse = await axios.post(
- `${targetBaseUrl}${loginUrl}`,
- requestBody,
- {
- headers,
- withCredentials: true,
- maxRedirects: 0,
- validateStatus: function (status) {
- return status >= 200 && status < 400;
- }
- }
- );
-
- console.log(`登录响应状态码: ${loginResponse.status}`);
- console.log(`响应头:`, JSON.stringify(loginResponse.headers, null, 2));
- console.log(`响应数据:`, JSON.stringify(loginResponse.data, null, 2));
-
- // 检查登录是否成功
- const responseData = loginResponse.data || {};
- const successValue = successField ? responseData[successField] : responseData.code;
-
- console.log(`成功标识值: ${successValue}, 期望值: ${successCode}`);
-
- if (successValue === successCode) {
- const cookies = loginResponse.headers['set-cookie'] || [];
- console.log(`登录成功!获取到 ${cookies.length} 个 Cookie`);
- cookies.forEach((cookie, index) => {
- console.log(`Cookie ${index + 1}: ${cookie.substring(0, 100)}...`);
- });
- return {
- success: true,
- cookies: cookies,
- response: loginResponse.data
- };
- } else {
- console.error(`登录失败!响应:`, responseData);
- return {
- success: false,
- message: responseData.msg || responseData.message || '登录失败',
- response: responseData
- };
- }
- }
- // 处理普通表单登录(未加密)
- async function handlePlainFormLogin(config, credentials) {
- const { targetBaseUrl, loginUrl, loginMethodConfig } = config;
- const { usernameField, passwordField, captchaField, contentType, successCode, successField } = loginMethodConfig;
-
- console.log('=== 普通表单登录 ===');
- console.log(`目标URL: ${targetBaseUrl}${loginUrl}`);
- console.log(`用户名: ${credentials.username}`);
- console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
- console.log(`内容类型: ${contentType}`);
- console.log(`成功标识字段: ${successField || 'code'}, 成功值: ${successCode}`);
-
- // 构建请求数据
- const requestData = {
- [usernameField]: credentials.username,
- [passwordField]: credentials.password
- };
-
- if (captchaField) {
- requestData[captchaField] = '';
- }
-
- // 发送登录请求
- const headers = {};
- let requestBody;
-
- if (contentType === 'application/x-www-form-urlencoded') {
- headers['Content-Type'] = 'application/x-www-form-urlencoded';
- requestBody = new URLSearchParams(requestData).toString();
- } else if (contentType === 'application/json') {
- headers['Content-Type'] = 'application/json';
- requestBody = JSON.stringify(requestData);
- } else {
- requestBody = requestData;
- }
-
- console.log(`发送登录请求到: ${targetBaseUrl}${loginUrl}`);
- console.log(`请求头:`, JSON.stringify(headers, null, 2));
- console.log(`请求体:`, contentType === 'application/json' ? requestBody : requestBody.substring(0, 200) + '...');
-
- const loginResponse = await axios.post(
- `${targetBaseUrl}${loginUrl}`,
- requestBody,
- {
- headers,
- withCredentials: true,
- maxRedirects: 0,
- validateStatus: function (status) {
- return status >= 200 && status < 400;
- }
- }
- );
-
- console.log(`登录响应状态码: ${loginResponse.status}`);
- console.log(`响应数据:`, JSON.stringify(loginResponse.data, null, 2));
-
- // 检查登录是否成功
- const responseData = loginResponse.data || {};
- const successValue = successField ? responseData[successField] : responseData.code;
-
- console.log(`成功标识值: ${successValue}, 期望值: ${successCode}`);
-
- if (successValue === successCode) {
- const cookies = loginResponse.headers['set-cookie'] || [];
- console.log(`登录成功!获取到 ${cookies.length} 个 Cookie`);
- cookies.forEach((cookie, index) => {
- console.log(`Cookie ${index + 1}: ${cookie.substring(0, 100)}...`);
- });
- return {
- success: true,
- cookies: cookies,
- response: loginResponse.data
- };
- } else {
- console.error(`登录失败!响应:`, responseData);
- return {
- success: false,
- message: responseData.msg || responseData.message || '登录失败',
- response: responseData
- };
- }
- }
- // 通用的自动登录端点
- app.get('/api/auto-login/:siteId', async (req, res) => {
- const startTime = Date.now();
- const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
-
- // 立即输出日志,确认请求已到达
- console.log('\n' + '='.repeat(80));
- console.log(`[${requestId}] ⚡⚡⚡ 收到自动登录请求!⚡⚡⚡`);
- console.log(`[${requestId}] 时间: ${new Date().toISOString()}`);
- console.log(`[${requestId}] 请求路径: ${req.path}`);
- console.log(`[${requestId}] 请求方法: ${req.method}`);
- console.log(`[${requestId}] 完整URL: ${req.protocol}://${req.get('host')}${req.originalUrl}`);
- console.log(`[${requestId}] 客户端IP: ${req.ip || req.connection.remoteAddress || req.socket.remoteAddress}`);
- console.log(`[${requestId}] User-Agent: ${req.get('user-agent') || 'Unknown'}`);
-
- try {
- const { siteId } = req.params;
- console.log(`[${requestId}] 网站ID: ${siteId}`);
-
- // 获取网站配置
- const config = autoLoginConfig[siteId];
- if (!config) {
- console.error(`[${requestId}] 错误: 未找到网站ID "${siteId}" 的配置`);
- console.error(`[${requestId}] 可用的网站ID: ${Object.keys(autoLoginConfig).join(', ') || '无'}`);
- return res.status(404).json({
- success: false,
- message: `未找到网站ID "${siteId}" 的配置`,
- availableSites: Object.keys(autoLoginConfig)
- });
- }
-
- console.log(`[${requestId}] 网站名称: ${config.name}`);
- console.log(`[${requestId}] 目标地址: ${config.targetBaseUrl}`);
- console.log(`[${requestId}] 登录方法: ${config.loginMethod}`);
-
- // 获取登录凭据(优先使用环境变量)
- const envUsername = process.env[config.credentials.envUsername];
- const envPassword = process.env[config.credentials.envPassword];
- const credentials = {
- username: envUsername || config.credentials.username,
- password: envPassword || config.credentials.password
- };
-
- console.log(`[${requestId}] 凭据来源: ${envUsername ? '环境变量' : '配置文件'}`);
- console.log(`[${requestId}] 用户名: ${credentials.username}`);
- console.log(`[${requestId}] 密码: ${'*'.repeat(credentials.password.length)}`);
-
- if (!credentials.username || !credentials.password) {
- console.error(`[${requestId}] 错误: 登录凭据未配置`);
- return res.status(400).json({
- success: false,
- message: '登录凭据未配置'
- });
- }
-
- // 根据登录方法处理登录
- let loginResult;
- console.log(`[${requestId}] 开始执行登录...`);
- switch (config.loginMethod) {
- case 'rsa-encrypted-form':
- loginResult = await handleRSAEncryptedFormLogin(config, credentials);
- break;
- case 'plain-form':
- loginResult = await handlePlainFormLogin(config, credentials);
- break;
- default:
- console.error(`[${requestId}] 错误: 不支持的登录方法: ${config.loginMethod}`);
- return res.status(400).json({
- success: false,
- message: `不支持的登录方法: ${config.loginMethod}`
- });
- }
-
- if (!loginResult.success) {
- console.error(`[${requestId}] 登录失败:`, loginResult.message);
- console.error(`[${requestId}] 失败响应:`, JSON.stringify(loginResult.response, null, 2));
- const duration = Date.now() - startTime;
- console.log(`[${requestId}] 总耗时: ${duration}ms`);
- console.log('='.repeat(80) + '\n');
-
- // 返回错误页面而不是 JSON
- const errorHtml = `
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>自动登录失败</title>
- <style>
- body {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100vh;
- margin: 0;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
- }
- .error-container {
- background: white;
- padding: 40px;
- border-radius: 12px;
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
- max-width: 600px;
- text-align: center;
- }
- .error-icon {
- font-size: 64px;
- margin-bottom: 20px;
- }
- .error-title {
- font-size: 24px;
- color: #e74c3c;
- margin-bottom: 15px;
- }
- .error-message {
- font-size: 16px;
- color: #666;
- margin-bottom: 20px;
- line-height: 1.6;
- }
- .error-details {
- background: #f8f9fa;
- padding: 15px;
- border-radius: 8px;
- margin-top: 20px;
- text-align: left;
- font-size: 14px;
- color: #555;
- }
- .error-details pre {
- margin: 0;
- white-space: pre-wrap;
- word-wrap: break-word;
- }
- </style>
- </head>
- <body>
- <div class="error-container">
- <div class="error-icon">❌</div>
- <div class="error-title">自动登录失败</div>
- <div class="error-message">${loginResult.message}</div>
- <div class="error-details">
- <strong>请求ID:</strong> ${requestId}<br>
- <strong>网站:</strong> ${config.name}<br>
- <strong>详细信息:</strong>
- <pre>${JSON.stringify(loginResult.response, null, 2)}</pre>
- </div>
- <button onclick="window.history.back()" style="margin-top: 20px; padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 6px; cursor: pointer;">返回</button>
- </div>
- </body>
- </html>
- `;
- return res.status(500).send(errorHtml);
- }
-
- console.log(`[${requestId}] 登录成功!`);
-
- // 解析 Cookie
- const cookieData = parseCookies(loginResult.cookies);
- console.log(`[${requestId}] 解析到 ${cookieData.length} 个 Cookie:`);
- cookieData.forEach((cookie, index) => {
- console.log(`[${requestId}] Cookie ${index + 1}: ${cookie.name} = ${cookie.value.substring(0, 20)}...`);
- });
-
- // 生成跳转 HTML(添加更多调试信息)
- console.log(`[${requestId}] 生成跳转页面,目标: http://${config.targetHost}/`);
- const html = generateRedirectHTML(
- cookieData,
- config.targetHost,
- config.targetDomain,
- requestId
- );
-
- // 在响应头中设置 Cookie
- console.log(`[${requestId}] 设置响应头 Cookie...`);
- loginResult.cookies.forEach((cookie, index) => {
- // 修改 Cookie 的 Domain,移除端口号
- let modifiedCookie = cookie.replace(/Domain=[^;]+/i, `Domain=${config.targetDomain}`);
- res.setHeader('Set-Cookie', modifiedCookie);
- console.log(`[${requestId}] 设置 Cookie ${index + 1}: ${modifiedCookie.substring(0, 80)}...`);
- });
-
- const duration = Date.now() - startTime;
- console.log(`[${requestId}] 总耗时: ${duration}ms`);
- console.log(`[${requestId}] 返回跳转页面`);
- console.log('='.repeat(80) + '\n');
-
- res.send(html);
- } catch (error) {
- const duration = Date.now() - startTime;
- console.error(`[${requestId}] 自动登录异常:`, error.message);
- console.error(`[${requestId}] 错误堆栈:`, error.stack);
- if (error.response) {
- console.error(`[${requestId}] 响应状态:`, error.response.status);
- console.error(`[${requestId}] 响应头:`, JSON.stringify(error.response.headers, null, 2));
- console.error(`[${requestId}] 响应数据:`, JSON.stringify(error.response.data, null, 2));
- }
- if (error.request) {
- console.error(`[${requestId}] 请求信息:`, {
- url: error.config?.url,
- method: error.config?.method,
- headers: error.config?.headers
- });
- }
- console.log(`[${requestId}] 总耗时: ${duration}ms`);
- console.log('='.repeat(80) + '\n');
- res.status(500).json({
- success: false,
- message: '自动登录失败: ' + error.message,
- error: process.env.NODE_ENV === 'development' ? error.stack : undefined
- });
- }
- });
- // 获取所有配置的网站列表
- app.get('/api/auto-login', (req, res) => {
- const sites = Object.keys(autoLoginConfig).map(siteId => ({
- id: siteId,
- name: autoLoginConfig[siteId].name,
- endpoint: `/api/auto-login/${siteId}`
- }));
- res.json({ sites });
- });
- // 健康检查端点
- app.get('/api/health', (req, res) => {
- res.json({
- status: 'ok',
- timestamp: new Date().toISOString(),
- port: PORT,
- configuredSites: Object.keys(autoLoginConfig)
- });
- });
- // 测试端点 - 用于验证配置
- app.get('/api/test/:siteId', (req, res) => {
- const { siteId } = req.params;
- const config = autoLoginConfig[siteId];
-
- if (!config) {
- return res.json({
- success: false,
- message: `未找到网站ID "${siteId}" 的配置`,
- availableSites: Object.keys(autoLoginConfig)
- });
- }
-
- const envUsername = process.env[config.credentials.envUsername];
- const envPassword = process.env[config.credentials.envPassword];
- const credentials = {
- username: envUsername || config.credentials.username,
- password: envPassword || config.credentials.password
- };
-
- res.json({
- success: true,
- siteId,
- config: {
- name: config.name,
- targetBaseUrl: config.targetBaseUrl,
- loginMethod: config.loginMethod,
- loginUrl: config.loginUrl,
- hasCredentials: !!(credentials.username && credentials.password),
- credentialsSource: envUsername ? '环境变量' : '配置文件',
- username: credentials.username,
- passwordLength: credentials.password ? credentials.password.length : 0
- }
- });
- });
- app.listen(PORT, '0.0.0.0', () => {
- console.log('\n' + '='.repeat(80));
- console.log('🚀 后端服务器启动成功!');
- console.log('='.repeat(80));
- console.log(`📍 本地地址: http://localhost:${PORT}`);
- console.log(`📍 服务器地址: http://0.0.0.0:${PORT}`);
- console.log(`📍 外部访问: http://222.243.138.146:${PORT} (通过防火墙端口映射)`);
- console.log(`\n📋 已配置的自动登录网站: ${Object.keys(autoLoginConfig).join(', ') || '无'}`);
- console.log(`\n🔗 可用端点:`);
- console.log(` - 健康检查: http://localhost:${PORT}/api/health`);
- console.log(` - 测试配置: http://localhost:${PORT}/api/test/:siteId`);
- console.log(` - 自动登录: http://localhost:${PORT}/api/auto-login/:siteId`);
- console.log(`\n💡 提示: 确保防火墙已配置端口映射 (前端:8888, 后端:8889 -> 外网)`);
- console.log('='.repeat(80) + '\n');
- });
|