forked from datawhale/whale-town-end
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
1962 lines
88 KiB
TypeScript
1962 lines
88 KiB
TypeScript
/**
|
||
* 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);
|
||
}
|
||
} |