Files
whale-town-end/src/business/zulip/websocket_test.controller.ts
moyin d04ab7f75f CRITICAL ISSUES: Database management service with major problems
WARNING: This commit contains code with significant issues that need immediate attention:

1. Type Safety Issues:
   - Unused import ZulipAccountsService causing compilation warnings
   - Implicit 'any' type in formatZulipAccount method parameter
   - Type inconsistencies in service injections

2. Service Integration Problems:
   - Inconsistent service interface usage
   - Missing proper type definitions for injected services
   - Potential runtime errors due to type mismatches

3. Code Quality Issues:
   - Violation of TypeScript strict mode requirements
   - Inconsistent error handling patterns
   - Missing proper interface implementations

 Files affected:
   - src/business/admin/database_management.service.ts (main issue)
   - Multiple test files and service implementations
   - Configuration and documentation updates

 Next steps required:
   1. Fix TypeScript compilation errors
   2. Implement proper type safety
   3. Resolve service injection inconsistencies
   4. Add comprehensive error handling
   5. Update tests to match new implementations

 Impact: High - affects admin functionality and system stability
 Priority: Urgent - requires immediate review and fixes

Author: moyin
Date: 2026-01-10
2026-01-10 19:27:28 +08:00

1962 lines
88 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WebSocket 测试页面控制器
*
* 提供一个简单的WebSocket测试界面可以直接在浏览器中测试WebSocket连接
* 包含API调用监控功能帮助前端开发者了解接口调用情况
*
* @author moyin
* @version 1.1.0
* @since 2026-01-09
*/
import { Controller, Get, Res } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Response } from 'express';
@ApiTags('websocket')
@Controller('websocket-test')
export class WebSocketTestController {
@Get()
@ApiOperation({
summary: '🔌 WebSocket 测试页面 - 一键测试工具 + API监控',
description: `
**🚀 功能强大的WebSocket测试工具**
提供完整的WebSocket测试功能
- ✅ 自动获取JWT Token
- ✅ 一键建立WebSocket连接
- ✅ 用户认证和登录
- ✅ 聊天消息发送测试
- ✅ 位置更新测试
- ✅ 实时消息日志
- 📡 **新增API调用监控** - 实时显示所有HTTP请求
**使用方法**
1. 点击下方"Execute"按钮
2. 在响应中会返回完整的测试页面HTML
3. 或直接访问: /websocket-test
**推荐**:直接在新标签页中打开 [/websocket-test](/websocket-test)
`
})
@ApiResponse({
status: 200,
description: 'WebSocket测试页面HTML',
content: {
'text/html': {
schema: {
type: 'string'
}
}
}
})
getTestPage(@Res() res: Response) {
const html = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 测试工具 + API监控 - Pixel Game Server</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.status {
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
font-weight: bold;
}
.status.disconnected { background-color: #ffebee; color: #c62828; }
.status.connected { background-color: #e8f5e8; color: #2e7d32; }
.status.connecting { background-color: #fff3e0; color: #ef6c00; }
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input, textarea, select, button {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
button {
background-color: #1976d2;
color: white;
border: none;
cursor: pointer;
font-weight: 500;
}
button:hover { background-color: #1565c0; }
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.message-log, .api-log {
height: 300px;
overflow-y: auto;
overflow-x: hidden;
border: 1px solid #ddd;
padding: 10px;
background-color: #fafafa;
font-family: 'Courier New', monospace;
font-size: 12px;
word-wrap: break-word;
box-sizing: border-box;
max-width: 100%;
width: 100%;
}
.message-item {
margin-bottom: 8px;
padding: 4px 8px;
border-radius: 3px;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
box-sizing: border-box;
overflow: hidden;
white-space: pre-wrap;
}
.message-sent { background-color: #e3f2fd; }
.message-received { background-color: #f3e5f5; }
.message-system { background-color: #fff3e0; }
.message-error { background-color: #ffebee; }
.api-log {
background-color: #f8f9fa;
}
.api-item {
margin-bottom: 8px;
padding: 8px;
border-radius: 3px;
border-left: 4px solid #ddd;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
box-sizing: border-box;
overflow: hidden;
white-space: pre-wrap;
}
.api-request {
background-color: #e3f2fd;
border-left-color: #2196f3;
}
.api-response-success {
background-color: #e8f5e8;
border-left-color: #4caf50;
}
.api-response-error {
background-color: #ffebee;
border-left-color: #f44336;
}
.api-method {
font-weight: bold;
color: #1976d2;
}
.api-url {
color: #666;
word-break: break-all;
overflow-wrap: break-word;
white-space: normal;
max-width: 100%;
display: inline-block;
}
.api-status {
font-weight: bold;
}
.api-status.success { color: #4caf50; }
.api-status.error { color: #f44336; }
.api-body {
margin-top: 5px;
padding: 5px;
background-color: rgba(0,0,0,0.05);
border-radius: 3px;
font-size: 11px;
max-height: 100px;
overflow-y: auto;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: pre-wrap;
max-width: 100%;
overflow-x: hidden;
}
.quick-actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-top: 15px;
}
.quick-action {
padding: 8px 12px;
background-color: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
text-align: center;
font-size: 12px;
}
.quick-action:hover { background-color: #eeeeee; }
.info-panel {
background-color: #e8f4fd;
border: 1px solid #bbdefb;
border-radius: 4px;
padding: 15px;
margin-bottom: 20px;
}
.info-panel h3 {
margin-top: 0;
color: #1976d2;
}
.two-column {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.logs-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
max-width: 100%;
overflow: hidden;
}
.logs-section .container {
max-width: 100%;
min-width: 0;
overflow: hidden;
}
@media (max-width: 768px) {
.two-column, .logs-section {
grid-template-columns: 1fr;
}
.logs-section .container {
max-width: 100vw;
overflow: hidden;
}
.message-log, .api-log {
max-width: calc(100vw - 60px);
}
}
</style>
</head>
<body>
<h1>🎮 Pixel Game Server - WebSocket 测试工具 + API监控</h1>
<div class="info-panel">
<h3>📋 使用说明</h3>
<p><strong>🚀 一键测试:</strong> 点击"🚀 一键测试"按钮,系统会自动生成随机账号并完成所有步骤</p>
<p><strong>🔐 认证方式:</strong> 支持密码登录和验证码登录两种方式</p>
<p><strong>🎲 随机账号:</strong> 自动生成符合要求的随机测试账号,方便多用户测试</p>
<p><strong>🔧 灵活配置:</strong> 可以自定义用户信息、使用默认账号或生成随机账号</p>
<p><strong>📡 API监控:</strong> 实时显示所有HTTP请求帮助前端开发者了解接口调用情况</p>
<p><strong>WebSocket地址:</strong> <code>wss://whaletownend.xinghangee.icu/game</code></p>
<div style="margin-top: 15px; padding: 10px; background-color: #e8f5e8; border-radius: 4px;">
<strong>💡 新功能亮点:</strong><br>
• 🎲 随机账号生成:自动生成符合要求的测试账号,方便多用户测试<br>
• 🤖 智能注册登录:自动检测账号状态,不存在则自动注册<br>
• 📱 验证码注册:支持邮箱验证码注册新账号<br>
• 🔍 调试验证码:自动获取验证码,无需真实邮箱<br>
• 🔄 自动重试:用户名冲突时自动生成新用户名<br>
• 💾 本地存储自动保存Token下次访问无需重新获取<br>
• 📧 真实邮箱支持:检测到真实邮箱时使用真实验证码发送<br>
• 📡 **API调用监控**实时显示所有HTTP请求和响应方便调试<br>
• 🔍 详细日志:支持显示请求体、响应体和调用统计
</div>
<div style="margin-top: 15px; padding: 10px; background-color: #fff3e0; border-radius: 4px;">
<strong>🎯 多标签页测试说明:</strong><br>
• 每个标签页都有独立的标签页ID确保账号不冲突<br>
• 每次点击"一键测试"都会生成新的随机账号<br>
• 邮箱格式test[时间戳后6位][标签页ID][随机码]@[域名] (简洁格式)<br>
• 用户名格式user[时间戳后6位][标签页ID][随机码]<br>
• 密码自动生成,包含字母数字,符合安全要求<br>
• 支持同时打开多个标签页进行多用户测试<br>
• 每个标签页的Token独立存储互不干扰
</div>
<div style="margin-top: 15px; padding: 10px; background-color: #e3f2fd; border-radius: 4px;">
<strong>📡 API监控功能说明</strong><br>
• 🔍 实时监控自动捕获所有HTTP请求和响应<br>
• 📊 详细信息显示请求方法、URL、状态码、响应时间<br>
• 📋 请求体显示:可选择显示请求体内容,方便调试<br>
• 📥 响应体显示:可选择显示响应体内容<br>
• 📈 统计信息:实时统计请求总数、成功数、失败数<br>
• 🎨 颜色区分:请求、成功响应、错误响应用不同颜色标识<br>
• ⏱️ 性能分析:显示每个请求的耗时,便于性能优化
</div>
<div style="margin-top: 15px; padding: 10px; background-color: #e3f2fd; border-radius: 4px;">
<strong>🔗 相关链接:</strong>
<a href="/api-docs" target="_blank" style="margin-right: 15px; color: #1976d2; text-decoration: none; font-weight: bold;">📚 返回 API 文档</a>
<a href="/websocket/docs" target="_blank" style="margin-right: 15px; color: #1976d2; text-decoration: none;">📖 WebSocket API 文档</a>
<a href="/websocket-api/connection-info" target="_blank" style="color: #1976d2; text-decoration: none;">🔧 连接配置信息</a>
</div>
</div>
<div class="two-column">
<div class="container">
<h2>🔌 连接控制</h2>
<div id="connectionStatus" class="status disconnected">未连接</div>
<div class="form-group">
<label for="wsUrl">WebSocket 地址:</label>
<input type="text" id="wsUrl" value="wss://whaletownend.xinghangee.icu/game" />
</div>
<button id="connectBtn" onclick="toggleConnection()">连接</button>
<button onclick="quickTest()" style="background-color: #4caf50; margin-top: 10px;">🚀 一键测试 (自动获取Token + 连接 + 登录)</button>
<h3>🔐 用户认证</h3>
<div class="form-group">
<label>认证方式:</label>
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<button type="button" onclick="switchAuthMode('password')" id="passwordModeBtn" style="flex: 1; background-color: #1976d2;">密码登录</button>
<button type="button" onclick="switchAuthMode('code')" id="codeModeBtn" style="flex: 1; background-color: #666;">验证码登录</button>
</div>
</div>
<!-- 密码登录模式 -->
<div id="passwordAuthMode">
<div class="form-group">
<label for="testCredentials">测试账号:</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="email" id="testEmail" placeholder="邮箱" value="test@example.com" />
<input type="password" id="testPassword" placeholder="密码" value="Test123456" />
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;">
<input type="text" id="testUsername" placeholder="用户名 (可选)" />
<input type="text" id="testNickname" placeholder="昵称 (可选)" />
</div>
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button id="getTokenBtn" onclick="getTestToken()" style="flex: 2;">获取测试Token</button>
<button onclick="smartRegisterAndLogin()" style="flex: 2; background-color: #ff9800;">智能注册登录</button>
<button onclick="useDefaultAccount()" style="flex: 1; background-color: #4caf50;">默认账号</button>
<button onclick="applyRandomTestAccount()" style="flex: 1; background-color: #9c27b0;">随机账号</button>
</div>
<div style="display: flex; gap: 10px; margin-top: 5px;">
<button onclick="clearCurrentTabToken()" style="flex: 1; background-color: #f44336; font-size: 12px;">清空本标签页Token</button>
<button onclick="clearAllTabTokens()" style="flex: 1; background-color: #ff5722; font-size: 12px;">清空所有标签页Token</button>
</div>
</div>
</div>
<!-- 验证码登录模式 -->
<div id="codeAuthMode" style="display: none;">
<div class="form-group">
<label for="codeEmail">邮箱地址:</label>
<input type="email" id="codeEmail" placeholder="输入邮箱地址" value="test@example.com" />
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button onclick="sendVerificationCode()" id="sendCodeBtn" style="flex: 2;">发送验证码</button>
<button onclick="getDebugVerificationCode(document.getElementById('codeEmail').value)" style="flex: 1; background-color: #9c27b0;">获取验证码</button>
</div>
</div>
<div class="form-group">
<label for="verificationCode">验证码:</label>
<input type="text" id="verificationCode" placeholder="输入6位验证码" maxlength="6" />
<button onclick="loginWithCode()" id="codeLoginBtn" style="margin-top: 10px;" disabled>验证码登录</button>
</div>
<div class="form-group" style="border-top: 1px solid #ddd; padding-top: 15px; margin-top: 15px;">
<label>如果邮箱未注册,可以先注册:</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;">
<input type="text" id="codeUsername" placeholder="用户名 (可选)" />
<input type="text" id="codeNickname" placeholder="昵称 (可选)" />
</div>
<input type="password" id="codePassword" placeholder="设置密码" style="margin-top: 10px;" />
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button onclick="registerWithCode()" id="registerCodeBtn" style="flex: 2; background-color: #ff9800;">验证码注册</button>
<button onclick="sendEmailVerificationForRegister()" style="flex: 1; background-color: #9c27b0;">发送注册验证码</button>
</div>
</div>
</div>
<div class="form-group">
<label for="jwtToken">JWT Token:</label>
<textarea id="jwtToken" rows="3" placeholder="点击上方按钮自动获取或手动输入JWT Token"></textarea>
</div>
<button id="loginBtn" onclick="login()" disabled>登录</button>
</div>
<div class="container">
<h2>💬 消息发送</h2>
<div class="form-group">
<label for="messageType">消息类型:</label>
<select id="messageType">
<option value="chat">聊天消息</option>
<option value="position">位置更新</option>
</select>
</div>
<div id="chatFields">
<div class="form-group">
<label for="chatContent">消息内容:</label>
<input type="text" id="chatContent" placeholder="输入聊天消息" />
</div>
<div class="form-group">
<label for="chatScope">消息范围:</label>
<select id="chatScope">
<option value="local">本地 (当前地图)</option>
<option value="global">全局 (所有玩家)</option>
</select>
</div>
</div>
<div id="positionFields" style="display: none;">
<div class="form-group">
<label for="posX">X坐标:</label>
<input type="number" id="posX" value="150" />
</div>
<div class="form-group">
<label for="posY">Y坐标:</label>
<input type="number" id="posY" value="400" />
</div>
<div class="form-group">
<label for="mapId">地图ID:</label>
<select id="mapId">
<option value="whale_port">Whale Port (鲸鱼港)</option>
<option value="pumpkin_valley">Pumpkin Valley (南瓜谷)</option>
<option value="novice_village">Novice Village (新手村)</option>
</select>
</div>
</div>
<button id="sendBtn" onclick="sendMessage()" disabled>发送消息</button>
<div class="quick-actions">
<div class="quick-action" onclick="sendQuickMessage('Hello!')">快速发送: Hello!</div>
<div class="quick-action" onclick="sendQuickMessage('大家好!')">快速发送: 大家好!</div>
<div class="quick-action" onclick="sendQuickPosition()">发送位置更新</div>
<div class="quick-action" onclick="clearLog()">清空消息日志</div>
<div class="quick-action" onclick="clearApiLog()">清空API日志</div>
<div class="quick-action" onclick="showDebugInfo()">显示调试信息</div>
</div>
</div>
</div>
<!-- 日志区域 - 消息日志和API监控并排显示 -->
<div class="logs-section">
<div class="container">
<h2>📋 消息日志</h2>
<div id="messageLog" class="message-log"></div>
</div>
<div class="container">
<h2>📡 API 调用日志</h2>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<span style="font-size: 14px; color: #666;">实时监控前端API调用</span>
<div>
<button onclick="clearApiLog()" style="padding: 4px 8px; font-size: 12px; background-color: #ff9800;">清空日志</button>
<button onclick="toggleApiLogDetails()" id="toggleDetailsBtn" style="padding: 4px 8px; font-size: 12px; background-color: #9c27b0; margin-left: 5px;">显示详情</button>
</div>
</div>
<div id="apiLog" class="api-log"></div>
<div style="margin-top: 10px; font-size: 12px; color: #666;">
<div>📊 统计: <span id="apiStats">请求: 0, 成功: 0, 失败: 0</span></div>
<div style="margin-top: 5px;">
<label style="font-size: 12px;">
<input type="checkbox" id="autoScrollApi" checked> 自动滚动
</label>
<label style="font-size: 12px; margin-left: 15px;">
<input type="checkbox" id="showRequestBody" checked> 显示请求体
</label>
<label style="font-size: 12px; margin-left: 15px;">
<input type="checkbox" id="showResponseBody"> 显示响应体
</label>
</div>
</div>
</div>
</div>
<script>
let ws = null;
let isLoggedIn = false;
let currentAuthMode = 'password';
let generatedAccounts = new Set();
let apiCallCount = 0;
let apiSuccessCount = 0;
let apiErrorCount = 0;
let showApiDetails = false;
// API监控相关变量
const originalFetch = window.fetch;
// 重写fetch函数以监控API调用
window.fetch = function(...args) {
const [url, options = {}] = args;
const method = options.method || 'GET';
const startTime = Date.now();
// 记录请求
logApiCall('request', {
method,
url,
body: options.body,
headers: options.headers
});
// 调用原始fetch
return originalFetch.apply(this, args)
.then(response => {
const endTime = Date.now();
const duration = endTime - startTime;
// 记录响应
logApiCall('response', {
method,
url,
status: response.status,
statusText: response.statusText,
duration,
success: response.ok
}, response.clone());
return response;
})
.catch(error => {
const endTime = Date.now();
const duration = endTime - startTime;
// 记录错误
logApiCall('error', {
method,
url,
error: error.message,
duration,
success: false
});
throw error;
});
};
// 记录API调用
function logApiCall(type, data, response = null) {
const timestamp = new Date();
const apiLog = document.getElementById('apiLog');
const apiItem = document.createElement('div');
if (type === 'request') {
apiCallCount++;
apiItem.className = 'api-item api-request';
let content = \`
<div>
<span class="api-method">\${data.method}</span>
<span class="api-url">\${data.url}</span>
<span style="float: right; color: #666; font-size: 11px;">\${timestamp.toLocaleTimeString()}</span>
</div>
\`;
// 显示请求体
if (document.getElementById('showRequestBody')?.checked && data.body) {
let bodyContent = data.body;
try {
if (typeof bodyContent === 'string') {
bodyContent = JSON.stringify(JSON.parse(bodyContent), null, 2);
}
} catch (e) {
// 保持原始格式
}
content += \`<div class="api-body">📤 请求体: <pre>\${bodyContent}</pre></div>\`;
}
// 显示请求头(如果开启详情模式)
if (showApiDetails && data.headers) {
content += \`<div class="api-body">📋 请求头: <pre>\${JSON.stringify(data.headers, null, 2)}</pre></div>\`;
}
apiItem.innerHTML = content;
} else if (type === 'response') {
if (data.success) {
apiSuccessCount++;
apiItem.className = 'api-item api-response-success';
} else {
apiErrorCount++;
apiItem.className = 'api-item api-response-error';
}
let content = \`
<div>
<span class="api-method">\${data.method}</span>
<span class="api-url">\${data.url}</span>
<span class="api-status \${data.success ? 'success' : 'error'}">\${data.status} \${data.statusText}</span>
<span style="float: right; color: #666; font-size: 11px;">\${data.duration}ms</span>
</div>
\`;
// 显示响应体
if (document.getElementById('showResponseBody')?.checked && response) {
response.text().then(responseText => {
try {
const jsonResponse = JSON.parse(responseText);
const formattedResponse = JSON.stringify(jsonResponse, null, 2);
const responseDiv = apiItem.querySelector('.response-body') || document.createElement('div');
responseDiv.className = 'api-body response-body';
responseDiv.innerHTML = \`📥 响应体: <pre>\${formattedResponse}</pre>\`;
if (!apiItem.querySelector('.response-body')) {
apiItem.appendChild(responseDiv);
}
} catch (e) {
if (responseText) {
const responseDiv = apiItem.querySelector('.response-body') || document.createElement('div');
responseDiv.className = 'api-body response-body';
responseDiv.innerHTML = \`📥 响应体: <pre>\${responseText}</pre>\`;
if (!apiItem.querySelector('.response-body')) {
apiItem.appendChild(responseDiv);
}
}
}
}).catch(() => {
// 忽略响应体读取错误
});
}
apiItem.innerHTML = content;
} else if (type === 'error') {
apiErrorCount++;
apiItem.className = 'api-item api-response-error';
const content = \`
<div>
<span class="api-method">\${data.method}</span>
<span class="api-url">\${data.url}</span>
<span class="api-status error">ERROR</span>
<span style="float: right; color: #666; font-size: 11px;">\${data.duration}ms</span>
</div>
<div class="api-body">❌ 错误: \${data.error}</div>
\`;
apiItem.innerHTML = content;
}
apiLog.appendChild(apiItem);
// 自动滚动
if (document.getElementById('autoScrollApi')?.checked) {
apiLog.scrollTop = apiLog.scrollHeight;
}
// 更新统计
updateApiStats();
}
// 更新API统计
function updateApiStats() {
const statsEl = document.getElementById('apiStats');
if (statsEl) {
statsEl.textContent = \`请求: \${apiCallCount}, 成功: \${apiSuccessCount}, 失败: \${apiErrorCount}\`;
}
}
// 清空API日志
function clearApiLog() {
document.getElementById('apiLog').innerHTML = '';
apiCallCount = 0;
apiSuccessCount = 0;
apiErrorCount = 0;
updateApiStats();
addMessage('system', '🗑️ API调用日志已清空');
}
// 切换API日志详情显示
function toggleApiLogDetails() {
showApiDetails = !showApiDetails;
const btn = document.getElementById('toggleDetailsBtn');
btn.textContent = showApiDetails ? '隐藏详情' : '显示详情';
btn.style.backgroundColor = showApiDetails ? '#f44336' : '#9c27b0';
addMessage('system', showApiDetails ? '🔍 API详情模式已开启' : '🔍 API详情模式已关闭');
}
// 测试邮箱域名列表
const TEST_EMAIL_DOMAINS = [
'test.com', 'example.com', 'demo.org', 'sample.net', 'testmail.org'
];
// 检测邮箱是否为测试邮箱
function isTestEmail(email) {
if (!email || typeof email !== 'string') {
return true;
}
const emailLower = email.toLowerCase();
if (emailLower.includes('test_user_') || emailLower.startsWith('test')) {
return true;
}
const domain = emailLower.split('@')[1];
if (domain && TEST_EMAIL_DOMAINS.includes(domain)) {
return true;
}
const testPatterns = [
/test.*@/,
/demo.*@/,
/sample.*@/,
/@.*test\\./,
/@.*demo\\./,
/@.*sample\\./
];
for (const pattern of testPatterns) {
if (pattern.test(emailLower)) {
return true;
}
}
return false;
}
// 默认测试账号配置
const DEFAULT_TEST_ACCOUNT = {
email: 'websocket.test@example.com',
password: 'WebSocket123!',
username: 'websocket_test_user'
};
// 切换认证模式
function switchAuthMode(mode) {
currentAuthMode = mode;
const passwordMode = document.getElementById('passwordAuthMode');
const codeMode = document.getElementById('codeAuthMode');
const passwordBtn = document.getElementById('passwordModeBtn');
const codeBtn = document.getElementById('codeModeBtn');
if (mode === 'password') {
passwordMode.style.display = 'block';
codeMode.style.display = 'none';
passwordBtn.style.backgroundColor = '#1976d2';
codeBtn.style.backgroundColor = '#666';
} else {
passwordMode.style.display = 'none';
codeMode.style.display = 'block';
passwordBtn.style.backgroundColor = '#666';
codeBtn.style.backgroundColor = '#1976d2';
}
}
// 发送验证码
async function sendVerificationCode() {
const email = document.getElementById('codeEmail').value.trim();
const sendCodeBtn = document.getElementById('sendCodeBtn');
if (!email) {
addMessage('error', '❌ 请输入邮箱地址');
return;
}
sendCodeBtn.disabled = true;
sendCodeBtn.textContent = '发送中...';
addMessage('system', '🔄 正在发送验证码到: ' + email);
try {
const response = await fetch('/auth/send-login-verification-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifier: email
})
});
const result = await response.json();
if (response.ok) {
addMessage('system', '✅ 验证码已发送到邮箱');
document.getElementById('codeLoginBtn').disabled = false;
// 如果是测试模式,显示验证码
if (result.data && result.data.verification_code) {
addMessage('system', '🔧 测试模式验证码: ' + result.data.verification_code);
document.getElementById('verificationCode').value = result.data.verification_code;
} else {
// 尝试获取调试验证码
addMessage('system', '🔍 尝试获取验证码...');
await getDebugVerificationCode(email);
}
// 开始倒计时
startCountdown(sendCodeBtn, 60);
} else {
addMessage('error', '❌ 发送失败: ' + (result.message || '未知错误'));
sendCodeBtn.disabled = false;
sendCodeBtn.textContent = '发送验证码';
}
} catch (error) {
addMessage('error', '❌ 请求失败: ' + error.message);
sendCodeBtn.disabled = false;
sendCodeBtn.textContent = '发送验证码';
}
}
// 获取调试验证码
async function getDebugVerificationCode(email) {
try {
const response = await fetch('/auth/debug-verification-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email
})
});
const result = await response.json();
if (response.ok && result.data) {
addMessage('system', '🔧 调试信息: ' + JSON.stringify(result.data));
// 检查验证码是否存在且有效
if (result.data.exists && result.data.parsedData && result.data.parsedData.code) {
const code = result.data.parsedData.code;
addMessage('system', '🔧 调试模式验证码: ' + code);
document.getElementById('verificationCode').value = code;
} else if (result.data.rawData) {
// 尝试解析rawData
try {
const rawData = JSON.parse(result.data.rawData);
if (rawData.code) {
addMessage('system', '🔧 从rawData获取验证码: ' + rawData.code);
document.getElementById('verificationCode').value = rawData.code;
}
} catch (e) {
addMessage('system', '💡 无法解析rawData: ' + result.data.rawData);
}
} else {
addMessage('system', '💡 验证码不存在或已过期,请重新发送');
}
} else {
addMessage('system', '💡 无法获取验证码,请手动输入或使用真实邮箱');
}
} catch (error) {
addMessage('system', '💡 调试接口不可用,请手动输入验证码');
}
}
// 验证码登录
async function loginWithCode() {
const email = document.getElementById('codeEmail').value.trim();
const code = document.getElementById('verificationCode').value.trim();
const codeLoginBtn = document.getElementById('codeLoginBtn');
if (!email || !code) {
addMessage('error', '❌ 请输入邮箱和验证码');
return;
}
codeLoginBtn.disabled = true;
codeLoginBtn.textContent = '登录中...';
addMessage('system', '🔄 正在验证码登录...');
try {
const response = await fetch('/auth/verification-code-login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifier: email,
verification_code: code
})
});
const result = await response.json();
if (response.ok && result.data && result.data.access_token) {
document.getElementById('jwtToken').value = result.data.access_token;
addMessage('system', '✅ 验证码登录成功');
addMessage('system', '💡 现在可以点击"连接"按钮建立WebSocket连接');
} else {
addMessage('error', '❌ 验证码登录失败: ' + (result.message || '未知错误'));
// 如果是用户不存在,提示可以注册
if (result.message && result.message.includes('用户不存在')) {
addMessage('system', '💡 用户不存在,可以使用下方的"验证码注册"功能');
}
}
} catch (error) {
addMessage('error', '❌ 请求失败: ' + error.message);
} finally {
codeLoginBtn.disabled = false;
codeLoginBtn.textContent = '验证码登录';
}
}
// 验证码注册
async function registerWithCode() {
const email = document.getElementById('codeEmail').value.trim();
const code = document.getElementById('verificationCode').value.trim();
const password = document.getElementById('codePassword').value.trim();
let username = document.getElementById('codeUsername').value.trim();
let nickname = document.getElementById('codeNickname').value.trim();
if (!email || !code || !password) {
addMessage('error', '❌ 请输入邮箱、验证码和密码');
return;
}
// 自动生成用户名和昵称
if (!username) {
username = 'user_' + Date.now();
document.getElementById('codeUsername').value = username;
}
if (!nickname) {
nickname = username;
document.getElementById('codeNickname').value = nickname;
}
addMessage('system', '🔄 正在验证码注册...');
try {
// 直接使用验证码进行注册
const response = await fetch('/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
password: password,
username: username,
nickname: nickname,
email_verification_code: code
})
});
const result = await response.json();
if (response.ok) {
addMessage('system', '✅ 验证码注册成功!');
// 自动登录
await new Promise(resolve => setTimeout(resolve, 1000));
// 切换到密码模式并填入信息
document.getElementById('testEmail').value = email;
document.getElementById('testPassword').value = password;
const loginSuccess = await attemptLogin();
if (loginSuccess) {
addMessage('system', '🎉 验证码注册并登录成功!');
} else {
addMessage('system', '✅ 注册成功,请手动登录');
}
} else {
// 检查是否是用户名冲突
if (result.message && result.message.includes('用户名')) {
addMessage('system', '⚠️ 用户名冲突,请修改用户名后重试');
// 自动生成新用户名
const newUsername = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
document.getElementById('codeUsername').value = newUsername;
addMessage('system', '💡 已自动生成新用户名: ' + newUsername);
} else {
addMessage('error', '❌ 验证码注册失败: ' + (result.message || '未知错误'));
}
}
} catch (error) {
addMessage('error', '❌ 验证码注册请求失败: ' + error.message);
}
}
// 发送注册验证码
async function sendEmailVerificationForRegister() {
const email = document.getElementById('codeEmail').value.trim();
if (!email) {
addMessage('error', '❌ 请输入邮箱地址');
return;
}
addMessage('system', '🔄 正在发送注册验证码到: ' + email);
try {
const response = await fetch('/auth/send-email-verification', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email
})
});
const result = await response.json();
if (response.ok) {
addMessage('system', '✅ 注册验证码已发送');
// 如果是测试模式,显示验证码
if (result.data && result.data.verification_code) {
addMessage('system', '🔧 测试模式验证码: ' + result.data.verification_code);
document.getElementById('verificationCode').value = result.data.verification_code;
} else {
// 尝试获取调试验证码
addMessage('system', '🔍 尝试获取验证码...');
await getDebugVerificationCode(email);
}
} else {
addMessage('error', '❌ 发送失败: ' + (result.message || '未知错误'));
}
} catch (error) {
addMessage('error', '❌ 请求失败: ' + error.message);
}
}
// 倒计时函数
function startCountdown(button, seconds) {
let remaining = seconds;
const originalText = button.textContent;
const timer = setInterval(() => {
button.textContent = '重新发送 (' + remaining + 's)';
remaining--;
if (remaining < 0) {
clearInterval(timer);
button.disabled = false;
button.textContent = originalText;
}
}, 1000);
}
function generateRandomTestAccount() {
const timestamp = Date.now();
const randomId = Math.random().toString(36).substr(2, 6);
const tabId = window.tabId ? window.tabId.split('_').pop().substr(0, 4) : Math.random().toString(36).substr(2, 4);
const emailDomains = ['test.com', 'example.com', 'demo.org'];
const randomDomain = emailDomains[Math.floor(Math.random() * emailDomains.length)];
const emailPrefix = 'test' + timestamp.toString().substr(-6) + tabId + randomId;
const email = emailPrefix + '@' + randomDomain;
const username = 'user' + timestamp.toString().substr(-6) + tabId + randomId;
const nicknames = ['测试用户', '演示账号', '样例用户', 'TestUser', 'DemoAccount'];
const randomNickname = nicknames[Math.floor(Math.random() * nicknames.length)] + tabId;
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let password = 'Test123';
for (let i = 0; i < 4; i++) {
password += chars.charAt(Math.floor(Math.random() * chars.length));
}
const account = {
email: email,
password: password,
username: username,
nickname: randomNickname
};
generatedAccounts.add(account.email);
generatedAccounts.add(account.username);
return account;
}
// 使用默认测试账号
function useDefaultAccount() {
document.getElementById('testEmail').value = DEFAULT_TEST_ACCOUNT.email;
document.getElementById('testPassword').value = DEFAULT_TEST_ACCOUNT.password;
document.getElementById('testUsername').value = DEFAULT_TEST_ACCOUNT.username;
document.getElementById('testNickname').value = DEFAULT_TEST_ACCOUNT.username;
addMessage('system', '✅ 已设置默认测试账号: ' + DEFAULT_TEST_ACCOUNT.email);
}
// 应用随机测试账号到表单
function applyRandomTestAccount() {
const account = generateRandomTestAccount();
document.getElementById('testEmail').value = account.email;
document.getElementById('testPassword').value = account.password;
document.getElementById('testUsername').value = account.username;
document.getElementById('testNickname').value = account.nickname;
document.getElementById('jwtToken').value = '';
addMessage('system', '🎲 已生成随机测试账号:');
addMessage('system', '📧 邮箱: ' + account.email);
addMessage('system', '👤 用户名: ' + account.username);
addMessage('system', '🏷️ 昵称: ' + account.nickname);
addMessage('system', '🔑 密码: ' + account.password);
return account;
}
// 智能注册并登录 - 处理频率限制(改进真实邮箱支持)
async function smartRegisterAndLogin(recursionDepth = 0) {
// 防止无限递归
if (recursionDepth > 3) {
addMessage('error', '❌ 重试次数过多,请手动操作');
addMessage('system', '💡 建议:刷新页面或手动生成新账号');
return false;
}
const email = document.getElementById('testEmail').value.trim();
const password = document.getElementById('testPassword').value.trim();
let username = document.getElementById('testUsername').value.trim();
let nickname = document.getElementById('testNickname').value.trim();
if (!email || !password) {
addMessage('error', '❌ 请输入邮箱和密码');
return false;
}
// 如果没有填写用户名,自动生成
if (!username) {
username = 'user' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
document.getElementById('testUsername').value = username;
}
// 如果没有填写昵称,使用用户名
if (!nickname) {
nickname = username;
document.getElementById('testNickname').value = nickname;
}
if (recursionDepth === 0) {
addMessage('system', '🔄 开始智能注册登录流程...');
addMessage('system', '📋 使用账号信息:');
addMessage('system', ' 📧 邮箱: ' + email);
addMessage('system', ' 👤 用户名: ' + username);
addMessage('system', ' 🏷️ 昵称: ' + nickname);
} else {
addMessage('system', '🔄 重试注册登录 (第' + (recursionDepth + 1) + '次)...');
}
// 1. 先尝试登录
addMessage('system', '1⃣ 尝试登录现有账号...');
const loginSuccess = await attemptLogin();
if (loginSuccess) {
addMessage('system', '✅ 账号已存在,登录成功!');
return true;
}
// 2. 登录失败,尝试注册
addMessage('system', '2⃣ 账号不存在,开始注册新账号...');
const registerResult = await attemptRegister(email, password, username, nickname);
// 处理不同的注册结果
if (registerResult === true) {
// 注册成功,尝试登录
addMessage('system', '3⃣ 注册成功,正在登录...');
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒
const finalLoginSuccess = await attemptLogin();
if (finalLoginSuccess) {
addMessage('system', '🎉 智能注册登录完成!');
return true;
} else {
addMessage('error', '❌ 注册成功但登录失败,请手动登录');
return false;
}
} else if (registerResult === 'RATE_LIMITED') {
// 遇到频率限制,直接返回,不要重试
addMessage('system', '⏰ 遇到频率限制,停止自动重试');
addMessage('system', '💡 建议操作:');
addMessage('system', ' 1⃣ 等待1分钟后重新尝试');
addMessage('system', ' 2⃣ 切换到验证码模式手动操作');
addMessage('system', ' 3⃣ 使用现有的测试账号');
return 'RATE_LIMITED'; // 向上传递频率限制状态
} else if (registerResult === 'REAL_EMAIL_SENT') {
// 真实邮箱验证码已发送,需要手动处理
addMessage('system', '📬 真实邮箱验证码已发送!');
addMessage('system', '💡 由于使用了真实邮箱,需要手动完成注册:');
addMessage('system', ' 1⃣ 切换到"验证码登录"模式');
addMessage('system', ' 2⃣ 查收邮箱中的验证码');
addMessage('system', ' 3⃣ 使用"验证码注册"功能完成注册');
addMessage('system', ' 4⃣ 注册成功后可继续使用一键测试');
return 'REAL_EMAIL_SENT'; // 向上传递真实邮箱状态
} else {
// 其他注册失败,可能是账号冲突,尝试重新生成
if (recursionDepth < 2) { // 减少重试次数,避免触发频率限制
addMessage('system', '⚠️ 注册失败,可能是账号冲突,重新生成账号...');
applyRandomTestAccount();
// 添加更长的延迟避免触发频率限制
await new Promise(resolve => setTimeout(resolve, 2000));
return await smartRegisterAndLogin(recursionDepth + 1);
} else {
addMessage('error', '❌ 注册失败,请检查信息或手动操作');
addMessage('system', '💡 建议操作:');
addMessage('system', ' 1⃣ 点击"重新生成账号"按钮');
addMessage('system', ' 2⃣ 或切换到验证码模式手动注册');
addMessage('system', ' 3⃣ 或刷新页面重新开始');
return false;
}
}
}
// 尝试注册账号(添加真实邮箱支持)
async function attemptRegister(email, password, username, nickname, retryCount = 0) {
// 限制重试次数
if (retryCount > 2) {
addMessage('error', '❌ 注册重试次数过多,请手动操作');
return false;
}
try {
// 检测邮箱类型
const isRealEmailAddress = !isTestEmail(email);
addMessage('system', '📧 邮箱类型检测: ' + (isRealEmailAddress ? '真实邮箱' : '测试邮箱'));
// 1. 发送邮箱验证码
addMessage('system', '📧 发送邮箱验证码...');
const codeResponse = await fetch('/auth/send-email-verification', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email
})
});
const codeResult = await codeResponse.json();
let verificationCode = '';
addMessage('system', '📧 发送验证码响应: ' + JSON.stringify(codeResult));
if (codeResponse.ok) {
if (isRealEmailAddress) {
// 真实邮箱:提示用户查收邮件
addMessage('system', '📬 真实邮箱验证码已发送,请查收邮件');
addMessage('system', '💡 请手动输入收到的验证码,或切换到验证码模式操作');
// 对于真实邮箱,我们不能自动获取验证码,需要用户手动输入
// 这里返回特殊标识,让调用方知道需要手动处理
return 'REAL_EMAIL_SENT';
} else {
// 测试邮箱:尝试自动获取验证码
if (codeResult.data && codeResult.data.verification_code) {
verificationCode = codeResult.data.verification_code;
addMessage('system', '🔧 测试模式验证码: ' + verificationCode);
} else if (codeResult.data && codeResult.data.is_test_mode === false) {
// 如果不是测试模式,尝试获取调试验证码
addMessage('system', '🔍 尝试获取调试验证码...');
const debugCode = await getDebugEmailVerificationCode(email);
if (debugCode) {
verificationCode = debugCode;
} else {
addMessage('error', '❌ 无法获取验证码,请使用手动验证码注册功能');
return false;
}
} else {
// 如果都没有,提示用户手动操作
addMessage('error', '❌ 无法自动获取验证码,请切换到验证码模式手动操作');
return false;
}
}
} else {
// 检查是否是频率限制错误
if (codeResult.error_code === 'TOO_MANY_REQUESTS' || codeResult.message.includes('频繁')) {
addMessage('error', '❌ 发送验证码失败: ' + (codeResult.message || '未知错误'));
addMessage('system', '⏰ 检测到频率限制,建议等待或使用其他方式');
addMessage('system', '💡 解决方案:');
addMessage('system', ' 1⃣ 等待1分钟后重试');
addMessage('system', ' 2⃣ 切换到验证码模式手动操作');
addMessage('system', ' 3⃣ 使用现有账号登录');
return 'RATE_LIMITED'; // 返回特殊标识
} else {
addMessage('error', '❌ 发送验证码失败: ' + (codeResult.message || '未知错误'));
return false;
}
}
// 2. 使用验证码进行注册(只有测试邮箱才会执行到这里)
if (verificationCode) {
addMessage('system', '📝 开始注册,使用验证码: ' + verificationCode);
const response = await fetch('/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
password: password,
username: username,
nickname: nickname,
email_verification_code: verificationCode
})
});
const result = await response.json();
addMessage('system', '📝 注册响应: ' + JSON.stringify(result));
if (response.ok) {
addMessage('system', '✅ 账号注册成功');
return true;
} else {
// 显示详细的错误信息
addMessage('error', '❌ 注册失败: ' + (result.message || '未知错误'));
addMessage('system', '📋 注册请求详情:');
addMessage('system', ' 📧 邮箱: ' + email);
addMessage('system', ' 👤 用户名: ' + username);
addMessage('system', ' 🏷️ 昵称: ' + nickname);
addMessage('system', ' 🔄 重试次数: ' + retryCount);
// 检查是否是用户名冲突
if (result.message && (result.message.includes('用户名') || result.message.includes('username'))) {
addMessage('system', '⚠️ 用户名冲突,尝试生成新用户名...');
// 生成新的用户名(增加更多随机性)
const newUsername = 'user' + Date.now() + '_' + Math.random().toString(36).substr(2, 8) + '_retry' + retryCount;
document.getElementById('testUsername').value = newUsername;
// 添加延迟避免过快重试
await new Promise(resolve => setTimeout(resolve, 1000));
// 递归重试(带重试计数)
return await attemptRegister(email, password, newUsername, nickname, retryCount + 1);
}
// 检查是否是邮箱冲突
else if (result.message && (result.message.includes('邮箱') || result.message.includes('email'))) {
addMessage('system', '⚠️ 邮箱冲突,这不应该发生,因为我们生成了唯一邮箱');
addMessage('system', '💡 建议:刷新页面重新开始,或手动修改邮箱');
return false;
} else {
addMessage('error', '❌ 注册失败: ' + (result.message || '未知错误'));
// 如果是验证码错误,提示用户手动操作
if (result.message && result.message.includes('验证码')) {
addMessage('system', '💡 建议:切换到验证码登录模式,手动获取和输入验证码');
}
return false;
}
}
} else {
// 真实邮箱的情况,返回特殊标识
return 'REAL_EMAIL_SENT';
}
} catch (error) {
addMessage('error', '❌ 注册请求失败: ' + error.message);
return false;
}
}
// 获取邮箱验证码的调试信息
async function getDebugEmailVerificationCode(email) {
try {
// 等待一下,确保验证码已经生成
await new Promise(resolve => setTimeout(resolve, 1000));
const response = await fetch('/auth/debug-verification-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email
})
});
const result = await response.json();
if (response.ok && result.data) {
addMessage('system', '🔧 调试信息: ' + JSON.stringify(result.data));
// 检查验证码是否存在且有效
if (result.data.exists && result.data.parsedData && result.data.parsedData.code) {
const code = result.data.parsedData.code;
addMessage('system', '🔧 调试模式验证码: ' + code);
return code;
} else if (result.data.rawData) {
// 尝试解析rawData
try {
const rawData = JSON.parse(result.data.rawData);
if (rawData.code) {
addMessage('system', '🔧 从rawData获取验证码: ' + rawData.code);
return rawData.code;
}
} catch (e) {
addMessage('system', '💡 无法解析rawData: ' + result.data.rawData);
}
}
addMessage('system', '💡 验证码不存在或已过期');
return null;
} else {
addMessage('system', '💡 调试接口响应: ' + JSON.stringify(result));
return null;
}
} catch (error) {
addMessage('system', '💡 调试接口异常: ' + error.message);
return null;
}
}
// 尝试登录
async function attemptLogin() {
const email = document.getElementById('testEmail').value.trim();
const password = document.getElementById('testPassword').value.trim();
try {
const response = await fetch('/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
identifier: email,
password: password
})
});
const result = await response.json();
if (response.ok && result.data && result.data.access_token) {
document.getElementById('jwtToken').value = result.data.access_token;
addMessage('system', '✅ 登录成功Token已获取');
return true;
} else {
addMessage('system', '⚠️ 登录失败: ' + (result.message || '未知错误'));
return false;
}
} catch (error) {
addMessage('system', '⚠️ 登录请求失败: ' + error.message);
return false;
}
}
// 自动获取JWT Token的函数
async function getTestToken() {
const email = document.getElementById('testEmail').value.trim();
const password = document.getElementById('testPassword').value.trim();
const getTokenBtn = document.getElementById('getTokenBtn');
if (!email || !password) {
addMessage('error', '❌ 请输入邮箱和密码');
return;
}
getTokenBtn.disabled = true;
getTokenBtn.textContent = '获取中...';
const success = await attemptLogin();
getTokenBtn.disabled = false;
getTokenBtn.textContent = '获取测试Token';
if (!success) {
addMessage('system', '💡 提示: 请确保测试账号存在,或点击"智能注册登录"');
}
}
// 快速测试函数 - 一键连接并登录(修复频率限制检测,支持真实邮箱)
async function quickTest() {
addMessage('system', '🚀 开始一键测试流程...');
// 1. 智能获取Token
const tokenResult = await smartGetToken();
// 检查Token获取结果
if (tokenResult === 'RATE_LIMITED') {
// 频率限制,停止执行
addMessage('system', '⏰ 检测到频率限制,建议等待后重试');
addMessage('system', '💡 解决方案:');
addMessage('system', ' 1⃣ 等待1分钟后重新点击"一键测试"');
addMessage('system', ' 2⃣ 或者使用现有账号手动登录');
addMessage('system', ' 3⃣ 如需立即测试,可切换到验证码模式手动操作');
return; // 直接返回,不继续执行
} else if (tokenResult === 'REAL_EMAIL_SENT') {
// 真实邮箱验证码已发送,需要手动处理
addMessage('system', '📬 检测到真实邮箱,验证码已发送');
addMessage('system', '💡 请手动完成注册后再使用一键测试:');
addMessage('system', ' 1⃣ 切换到"验证码登录"模式');
addMessage('system', ' 2⃣ 查收邮箱中的验证码');
addMessage('system', ' 3⃣ 使用"验证码注册"功能完成注册');
addMessage('system', ' 4⃣ 注册成功后重新点击"一键测试"');
// 自动切换到验证码模式
switchAuthMode('code');
// 复制邮箱到验证码模式
const email = document.getElementById('testEmail').value;
if (email) {
document.getElementById('codeEmail').value = email;
}
return; // 直接返回,不继续执行
} else if (!tokenResult) {
// 其他类型的失败才切换到验证码模式
addMessage('system', '💡 自动获取Token失败尝试手动验证码方式...');
addMessage('system', '🔄 切换到验证码模式,请手动操作:');
addMessage('system', '1⃣ 切换到"验证码登录"标签');
addMessage('system', '2⃣ 点击"发送验证码"或"获取验证码"');
addMessage('system', '3⃣ 输入验证码后点击"验证码登录"或"验证码注册"');
// 自动切换到验证码模式
switchAuthMode('code');
// 复制邮箱到验证码模式
const email = document.getElementById('testEmail').value;
if (email) {
document.getElementById('codeEmail').value = email;
}
return; // 直接返回,不继续执行
}
// 2. Token获取成功建立连接
if (!ws || ws.readyState !== WebSocket.OPEN) {
addMessage('system', '🔌 建立WebSocket连接...');
connect();
// 等待连接建立
await new Promise(resolve => {
const checkConnection = () => {
if (ws && ws.readyState === WebSocket.OPEN) {
resolve();
} else if (ws && ws.readyState === WebSocket.CLOSED) {
addMessage('error', '❌ 连接失败');
resolve();
} else {
setTimeout(checkConnection, 100);
}
};
checkConnection();
});
}
// 3. 自动登录
if (ws && ws.readyState === WebSocket.OPEN) {
addMessage('system', '🔐 自动登录...');
setTimeout(() => {
login();
}, 1000);
}
}
// 智能获取Token - 自动检测和处理各种情况(改进频率限制处理)
async function smartGetToken() {
addMessage('system', '🔄 开始智能获取Token...');
// 1. 检查是否已有Token
const existingToken = document.getElementById('jwtToken').value.trim();
if (existingToken) {
addMessage('system', '✅ 检测到现有Token跳过获取');
return true;
}
// 2. 检查是否有输入的账号信息
const email = document.getElementById('testEmail').value.trim();
const password = document.getElementById('testPassword').value.trim();
// 3. 如果没有账号信息,生成随机测试账号
if (!email || !password || email === 'test@example.com') {
addMessage('system', '🎲 未检测到账号信息,生成随机测试账号...');
applyRandomTestAccount();
await new Promise(resolve => setTimeout(resolve, 500)); // 等待表单更新
}
// 4. 根据当前认证模式获取Token
if (currentAuthMode === 'password') {
const result = await smartPasswordLogin();
// 如果是频率限制,向上传递
if (result === 'RATE_LIMITED') {
return 'RATE_LIMITED';
}
return result;
} else {
addMessage('system', '💡 验证码模式需要手动发送验证码并登录');
return false;
}
}
// 智能密码登录(改进返回值处理)
async function smartPasswordLogin() {
const email = document.getElementById('testEmail').value.trim();
const password = document.getElementById('testPassword').value.trim();
// 1. 确保有账号信息此时应该已经通过smartGetToken生成了
if (!email || !password) {
addMessage('error', '❌ 缺少账号信息');
return false;
}
// 2. 尝试登录
const loginSuccess = await attemptLogin();
if (loginSuccess) {
return true;
}
// 3. 登录失败,尝试智能注册登录
addMessage('system', '🔧 登录失败,尝试智能注册...');
const registerResult = await smartRegisterAndLogin();
// 传递频率限制状态
if (registerResult === 'RATE_LIMITED') {
return 'RATE_LIMITED';
}
return registerResult;
}
// 切换消息类型时显示对应字段
document.getElementById('messageType').addEventListener('change', function() {
const chatFields = document.getElementById('chatFields');
const positionFields = document.getElementById('positionFields');
if (this.value === 'chat') {
chatFields.style.display = 'block';
positionFields.style.display = 'none';
} else {
chatFields.style.display = 'none';
positionFields.style.display = 'block';
}
});
function updateStatus(status, message) {
const statusEl = document.getElementById('connectionStatus');
statusEl.className = 'status ' + status;
statusEl.textContent = message;
}
function addMessage(type, content, timestamp = new Date()) {
const log = document.getElementById('messageLog');
if (!log) {
console.error('messageLog element not found');
return;
}
const messageEl = document.createElement('div');
messageEl.className = 'message-item message-' + type;
const timeStr = timestamp.toLocaleTimeString();
// 处理长内容但要小心避免破坏HTML
let processedContent = content;
if (typeof content === 'string') {
try {
// 先尝试格式化JSON
if (content.includes('{') || content.includes('[')) {
const jsonMatch = content.match(/(\{.*\}|\[.*\])/s);
if (jsonMatch) {
const jsonPart = JSON.parse(jsonMatch[1]);
const formattedJson = JSON.stringify(jsonPart, null, 2);
processedContent = content.replace(jsonMatch[1], formattedJson);
}
}
} catch (e) {
// 如果JSON解析失败保持原样
console.log('JSON parse failed, keeping original content');
}
}
// 安全地设置内容
try {
messageEl.innerHTML = '<strong>[' + timeStr + ']</strong> ' + processedContent;
} catch (e) {
// 如果innerHTML设置失败使用textContent
messageEl.textContent = '[' + timeStr + '] ' + content;
}
log.appendChild(messageEl);
log.scrollTop = log.scrollHeight;
}
function toggleConnection() {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close();
} else {
connect();
}
}
function connect() {
const url = document.getElementById('wsUrl').value;
updateStatus('connecting', '连接中...');
try {
ws = new WebSocket(url);
ws.onopen = function() {
updateStatus('connected', '已连接');
document.getElementById('connectBtn').textContent = '断开';
document.getElementById('loginBtn').disabled = false;
addMessage('system', '✅ WebSocket连接成功');
};
ws.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
addMessage('received', '📥 ' + JSON.stringify(data, null, 2));
if (data.t === 'login_success') {
isLoggedIn = true;
document.getElementById('sendBtn').disabled = false;
addMessage('system', '✅ 登录成功!用户: ' + data.username);
} else if (data.t === 'login_error') {
addMessage('error', '❌ 登录失败: ' + data.message);
} else if (data.t === 'chat_render') {
addMessage('system', '💬 收到消息: ' + data.from + ' 说: ' + data.txt);
}
} catch (e) {
addMessage('received', '📥 ' + event.data);
}
};
ws.onclose = function() {
updateStatus('disconnected', '未连接');
document.getElementById('connectBtn').textContent = '连接';
document.getElementById('loginBtn').disabled = true;
document.getElementById('sendBtn').disabled = true;
isLoggedIn = false;
addMessage('system', '🔌 WebSocket连接已关闭');
};
ws.onerror = function(error) {
addMessage('error', '❌ 连接错误: ' + error);
};
} catch (error) {
updateStatus('disconnected', '连接失败');
addMessage('error', '❌ 连接失败: ' + error.message);
}
}
function login() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
addMessage('error', '❌ 请先建立WebSocket连接');
return;
}
const token = document.getElementById('jwtToken').value.trim();
if (!token) {
addMessage('error', '❌ 请输入JWT Token');
return;
}
const message = {
type: 'login',
token: token
};
ws.send(JSON.stringify(message));
addMessage('sent', '📤 ' + JSON.stringify(message, null, 2));
}
function sendMessage() {
if (!ws || ws.readyState !== WebSocket.OPEN) {
addMessage('error', '❌ 请先建立WebSocket连接');
return;
}
if (!isLoggedIn) {
addMessage('error', '❌ 请先登录');
return;
}
const messageType = document.getElementById('messageType').value;
let message;
if (messageType === 'chat') {
const content = document.getElementById('chatContent').value.trim();
if (!content) {
addMessage('error', '❌ 请输入消息内容');
return;
}
message = {
t: 'chat',
content: content,
scope: document.getElementById('chatScope').value
};
} else {
message = {
t: 'position',
x: parseInt(document.getElementById('posX').value),
y: parseInt(document.getElementById('posY').value),
mapId: document.getElementById('mapId').value
};
}
ws.send(JSON.stringify(message));
addMessage('sent', '📤 ' + JSON.stringify(message, null, 2));
if (messageType === 'chat') {
document.getElementById('chatContent').value = '';
}
}
function sendQuickMessage(content) {
if (!isLoggedIn) {
addMessage('error', '❌ 请先登录');
return;
}
document.getElementById('messageType').value = 'chat';
document.getElementById('chatContent').value = content;
document.getElementById('messageType').dispatchEvent(new Event('change'));
sendMessage();
}
function sendQuickPosition() {
if (!isLoggedIn) {
addMessage('error', '❌ 请先登录');
return;
}
document.getElementById('messageType').value = 'position';
document.getElementById('messageType').dispatchEvent(new Event('change'));
sendMessage();
}
function clearLog() {
document.getElementById('messageLog').innerHTML = '';
}
function showDebugInfo() {
addMessage('system', '🔍 调试信息:');
addMessage('system', ' 🆔 标签页ID: ' + (window.tabId || '未设置'));
addMessage('system', ' 📊 已生成账号数: ' + generatedAccounts.size);
addMessage('system', ' 🔌 WebSocket状态: ' + (ws ? ws.readyState : '未连接'));
addMessage('system', ' 🔐 登录状态: ' + (isLoggedIn ? '已登录' : '未登录'));
addMessage('system', ' 🎛️ 认证模式: ' + currentAuthMode);
addMessage('system', ' 📡 API调用统计: 请求' + apiCallCount + '次, 成功' + apiSuccessCount + '次, 失败' + apiErrorCount + '次');
addMessage('system', ' 🔍 API详情模式: ' + (showApiDetails ? '开启' : '关闭'));
}
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
generatedAccounts.clear();
const timestamp = Date.now();
const microTime = Math.floor(performance.now() * 1000);
const randomPart = Math.random().toString(36).substr(2, 8);
const tabId = 'tab_' + timestamp + '_' + microTime + '_' + randomPart;
window.tabId = tabId;
addMessage('system', '🎮 WebSocket测试工具已就绪 (标签页: ' + tabId + ')');
addMessage('system', '💡 快速开始: 点击"🚀 一键测试"按钮自动完成所有步骤');
addMessage('system', '📋 手动步骤: 1⃣获取Token → 2⃣连接 → 3⃣登录 → 4⃣发送消息');
addMessage('system', '🔧 支持密码登录和验证码登录两种方式');
addMessage('system', '🎲 一键测试会自动生成随机测试账号,方便多用户测试');
addMessage('system', '📡 新功能: API调用监控已启用可实时查看所有HTTP请求');
addMessage('system', '🔍 API日志支持显示请求体、响应体和详细统计信息');
// 检查URL参数看是否从API文档跳转过来
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('from') === 'api-docs') {
addMessage('system', '👋 欢迎从API文档跳转过来建议使用"一键测试"快速体验');
}
// 检查是否有存储的Token使用标签页特定的key
const storedToken = localStorage.getItem('websocket_test_token_' + tabId);
if (storedToken) {
document.getElementById('jwtToken').value = storedToken;
addMessage('system', '✅ 检测到本标签页存储的Token');
} else {
// 检查是否有通用的Token向后兼容
const generalToken = localStorage.getItem('websocket_test_token');
if (generalToken) {
addMessage('system', '💡 检测到通用Token建议重新生成专用Token');
}
}
});
// 保存Token到本地存储使用标签页特定的key
function saveTokenToStorage() {
const token = document.getElementById('jwtToken').value.trim();
if (token && window.tabId) {
localStorage.setItem('websocket_test_token_' + window.tabId, token);
addMessage('system', '💾 Token已保存到本标签页存储');
}
}
// 清空当前标签页的Token
function clearCurrentTabToken() {
if (window.tabId) {
localStorage.removeItem('websocket_test_token_' + window.tabId);
document.getElementById('jwtToken').value = '';
addMessage('system', '🗑️ 已清空当前标签页的Token');
}
}
// 清空所有标签页的Token
function clearAllTabTokens() {
// 清空所有以websocket_test_token开头的localStorage项
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('websocket_test_token')) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
document.getElementById('jwtToken').value = '';
addMessage('system', '🗑️ 已清空所有标签页的Token (' + keysToRemove.length + '个)');
}
// 清空本地生成的账号记录
function clearGeneratedAccounts() {
const oldSize = generatedAccounts.size;
generatedAccounts.clear();
addMessage('system', '🗑️ 已清空本地账号记录 (' + oldSize + '个)');
}
// 监听Token输入框变化
document.addEventListener('DOMContentLoaded', function() {
const tokenInput = document.getElementById('jwtToken');
if (tokenInput) {
tokenInput.addEventListener('input', saveTokenToStorage);
}
});
// 页面卸载时清理资源
window.addEventListener('beforeunload', function() {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close();
}
});
// 添加一个全局错误处理函数
window.addEventListener('error', function(event) {
addMessage('error', '❌ 页面错误: ' + event.error.message);
});
</script>
</body>
</html>
`;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.send(html);
}
}