482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			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 测试工具</title>
 | ||
|     <style>
 | ||
|         * {
 | ||
|             box-sizing: border-box;
 | ||
|             margin: 0;
 | ||
|             padding: 0;
 | ||
|             font-family: 'Arial', 'Microsoft YaHei', sans-serif;
 | ||
|         }
 | ||
| 
 | ||
|         body {
 | ||
|             max-width: 1200px;
 | ||
|             margin: 20px auto;
 | ||
|             padding: 0 20px;
 | ||
|             background-color: #f5f7fa;
 | ||
|         }
 | ||
| 
 | ||
|         .container {
 | ||
|             background: white;
 | ||
|             border-radius: 8px;
 | ||
|             box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
 | ||
|             padding: 25px;
 | ||
|             margin-bottom: 20px;
 | ||
|         }
 | ||
| 
 | ||
|         h1 {
 | ||
|             color: #2c3e50;
 | ||
|             margin-bottom: 20px;
 | ||
|             font-size: 24px;
 | ||
|             border-bottom: 2px solid #3498db;
 | ||
|             padding-bottom: 10px;
 | ||
|         }
 | ||
| 
 | ||
|         .status-bar {
 | ||
|             display: flex;
 | ||
|             align-items: center;
 | ||
|             gap: 15px;
 | ||
|             margin-bottom: 20px;
 | ||
|             padding: 12px 15px;
 | ||
|             background-color: #f8f9fa;
 | ||
|             border-radius: 6px;
 | ||
|         }
 | ||
| 
 | ||
|         .status-label {
 | ||
|             font-weight: bold;
 | ||
|             color: #495057;
 | ||
|         }
 | ||
| 
 | ||
|         .status-value {
 | ||
|             padding: 4px 10px;
 | ||
|             border-radius: 4px;
 | ||
|             font-weight: bold;
 | ||
|         }
 | ||
| 
 | ||
|         .status-connected {
 | ||
|             background-color: #d4edda;
 | ||
|             color: #155724;
 | ||
|         }
 | ||
| 
 | ||
|         .status-disconnected {
 | ||
|             background-color: #f8d7da;
 | ||
|             color: #721c24;
 | ||
|         }
 | ||
| 
 | ||
|         .status-connecting {
 | ||
|             background-color: #fff3cd;
 | ||
|             color: #856404;
 | ||
|         }
 | ||
| 
 | ||
|         .btn {
 | ||
|             padding: 8px 16px;
 | ||
|             border: none;
 | ||
|             border-radius: 4px;
 | ||
|             cursor: pointer;
 | ||
|             font-size: 14px;
 | ||
|             font-weight: 500;
 | ||
|             transition: background-color 0.2s;
 | ||
|         }
 | ||
| 
 | ||
|         .btn-primary {
 | ||
|             background-color: #3498db;
 | ||
|             color: white;
 | ||
|         }
 | ||
| 
 | ||
|         .btn-primary:hover {
 | ||
|             background-color: #2980b9;
 | ||
|         }
 | ||
| 
 | ||
|         .btn-danger {
 | ||
|             background-color: #e74c3c;
 | ||
|             color: white;
 | ||
|         }
 | ||
| 
 | ||
|         .btn-danger:hover {
 | ||
|             background-color: #c0392b;
 | ||
|         }
 | ||
| 
 | ||
|         .btn-success {
 | ||
|             background-color: #2ecc71;
 | ||
|             color: white;
 | ||
|         }
 | ||
| 
 | ||
|         .btn-success:hover {
 | ||
|             background-color: #27ae60;
 | ||
|         }
 | ||
| 
 | ||
|         .control-group {
 | ||
|             display: flex;
 | ||
|             gap: 15px;
 | ||
|             margin-bottom: 20px;
 | ||
|             align-items: center;
 | ||
|         }
 | ||
| 
 | ||
|         .input-group {
 | ||
|             display: flex;
 | ||
|             gap: 10px;
 | ||
|             align-items: center;
 | ||
|         }
 | ||
| 
 | ||
|         .input-group label {
 | ||
|             color: #495057;
 | ||
|             font-weight: 500;
 | ||
|         }
 | ||
| 
 | ||
|         .input-group input, .input-group select {
 | ||
|             padding: 8px 12px;
 | ||
|             border: 1px solid #ced4da;
 | ||
|             border-radius: 4px;
 | ||
|             font-size: 14px;
 | ||
|         }
 | ||
| 
 | ||
|         .message-area {
 | ||
|             margin-top: 20px;
 | ||
|         }
 | ||
| 
 | ||
|         .message-input {
 | ||
|             width: 100%;
 | ||
|             height: 100px;
 | ||
|             padding: 12px;
 | ||
|             border: 1px solid #ced4da;
 | ||
|             border-radius: 6px;
 | ||
|             resize: none;
 | ||
|             font-size: 14px;
 | ||
|             margin-bottom: 10px;
 | ||
|         }
 | ||
| 
 | ||
|         .log-area {
 | ||
|             width: 100%;
 | ||
|             height: 300px;
 | ||
|             padding: 15px;
 | ||
|             border: 1px solid #ced4da;
 | ||
|             border-radius: 6px;
 | ||
|             background-color: #f8f9fa;
 | ||
|             overflow-y: auto;
 | ||
|             font-size: 14px;
 | ||
|             line-height: 1.6;
 | ||
|         }
 | ||
| 
 | ||
|         .log-item {
 | ||
|             margin-bottom: 8px;
 | ||
|             padding-bottom: 8px;
 | ||
|             border-bottom: 1px dashed #e9ecef;
 | ||
|         }
 | ||
| 
 | ||
|         .log-time {
 | ||
|             color: #6c757d;
 | ||
|             font-size: 12px;
 | ||
|             margin-right: 10px;
 | ||
|         }
 | ||
| 
 | ||
|         .log-send {
 | ||
|             color: #2980b9;
 | ||
|         }
 | ||
| 
 | ||
|         .log-receive {
 | ||
|             color: #27ae60;
 | ||
|         }
 | ||
| 
 | ||
|         .log-status {
 | ||
|             color: #856404;
 | ||
|         }
 | ||
| 
 | ||
|         .log-error {
 | ||
|             color: #e74c3c;
 | ||
|         }
 | ||
|     </style>
 | ||
| </head>
 | ||
| <body>
 | ||
| <div class="container">
 | ||
|     <h1>WebSocket 测试工具</h1>
 | ||
| 
 | ||
|     <!-- 连接状态区 -->
 | ||
|     <div class="status-bar">
 | ||
|         <div class="status-label">连接状态:</div>
 | ||
|         <div id="connectionStatus" class="status-value status-disconnected">未连接</div>
 | ||
|         <div class="status-label">服务地址:</div>
 | ||
|         <div id="wsUrl" class="status-value">ws://192.168.110.25:8000/ws</div>
 | ||
|         <div class="status-label">连接时间:</div>
 | ||
|         <div id="connectTime" class="status-value">-</div>
 | ||
|     </div>
 | ||
| 
 | ||
|     <!-- 控制按钮区 -->
 | ||
|     <div class="control-group">
 | ||
|         <button id="connectBtn" class="btn btn-primary">建立连接</button>
 | ||
|         <button id="disconnectBtn" class="btn btn-danger" disabled>断开连接</button>
 | ||
| 
 | ||
|         <!-- 心跳控制 -->
 | ||
|         <div class="input-group">
 | ||
|             <label>自动心跳:</label>
 | ||
|             <select id="autoHeartbeat">
 | ||
|                 <option value="on">开启</option>
 | ||
|                 <option value="off">关闭</option>
 | ||
|             </select>
 | ||
|             <label>间隔(秒):</label>
 | ||
|             <input type="number" id="heartbeatInterval" value="30" min="10" max="120" style="width: 80px;">
 | ||
|             <button id="sendHeartbeatBtn" class="btn btn-success">手动发送心跳</button>
 | ||
|         </div>
 | ||
|     </div>
 | ||
| 
 | ||
|     <!-- 自定义消息发送区 -->
 | ||
|     <div class="message-area">
 | ||
|         <h3>发送自定义消息</h3>
 | ||
|         <textarea id="messageInput" class="message-input"
 | ||
|                   placeholder='示例:{"type":"test","content":"Hello WebSocket"}'>{"type":"test","content":"Hello WebSocket"}</textarea>
 | ||
|         <button id="sendMessageBtn" class="btn btn-primary" disabled>发送消息</button>
 | ||
|     </div>
 | ||
| 
 | ||
|     <!-- 日志显示区 -->
 | ||
|     <div class="message-area">
 | ||
|         <h3>消息日志</h3>
 | ||
|         <div id="logContainer" class="log-area">
 | ||
|             <div class="log-item"><span class="log-time">[加载完成]</span> 请点击「建立连接」开始测试</div>
 | ||
|         </div>
 | ||
|         <button id="clearLogBtn" class="btn btn-primary" style="margin-top: 10px;">清空日志</button>
 | ||
|     </div>
 | ||
| </div>
 | ||
| 
 | ||
| <script>
 | ||
|     // 全局变量
 | ||
|     let ws = null;
 | ||
|     let heartbeatTimer = null;
 | ||
|     const wsUrl = "ws://192.168.110.25:8000/ws";
 | ||
| 
 | ||
|     // DOM 元素
 | ||
|     const connectionStatus = document.getElementById('connectionStatus');
 | ||
|     const connectTime = document.getElementById('connectTime');
 | ||
|     const connectBtn = document.getElementById('connectBtn');
 | ||
|     const disconnectBtn = document.getElementById('disconnectBtn');
 | ||
|     const sendMessageBtn = document.getElementById('sendMessageBtn');
 | ||
|     const sendHeartbeatBtn = document.getElementById('sendHeartbeatBtn');
 | ||
|     const autoHeartbeat = document.getElementById('autoHeartbeat');
 | ||
|     const heartbeatInterval = document.getElementById('heartbeatInterval');
 | ||
|     const messageInput = document.getElementById('messageInput');
 | ||
|     const logContainer = document.getElementById('logContainer');
 | ||
|     const clearLogBtn = document.getElementById('clearLogBtn');
 | ||
| 
 | ||
|     // 工具函数:添加日志
 | ||
|     function addLog(content, type = 'status') {
 | ||
|         const now = new Date().toLocaleString('zh-CN', {
 | ||
|             year: 'numeric', month: '2-digit', day: '2-digit',
 | ||
|             hour: '2-digit', minute: '2-digit', second: '2-digit'
 | ||
|         });
 | ||
|         const logItem = document.createElement('div');
 | ||
|         logItem.className = 'log-item';
 | ||
| 
 | ||
|         let logClass = '';
 | ||
|         switch (type) {
 | ||
|             case 'send':
 | ||
|                 logClass = 'log-send';
 | ||
|                 break;
 | ||
|             case 'receive':
 | ||
|                 logClass = 'log-receive';
 | ||
|                 break;
 | ||
|             case 'error':
 | ||
|                 logClass = 'log-error';
 | ||
|                 break;
 | ||
|             default:
 | ||
|                 logClass = 'log-status';
 | ||
|         }
 | ||
| 
 | ||
|         logItem.innerHTML = `<span class="log-time">[${now}]</span> <span class="${logClass}">${content}</span>`;
 | ||
|         logContainer.appendChild(logItem);
 | ||
|         // 滚动到最新日志
 | ||
|         logContainer.scrollTop = logContainer.scrollHeight;
 | ||
|     }
 | ||
| 
 | ||
|     // 工具函数:格式化JSON(便于日志显示)
 | ||
|     function formatJson(jsonStr) {
 | ||
|         try {
 | ||
|             const obj = JSON.parse(jsonStr);
 | ||
|             return JSON.stringify(obj, null, 2);
 | ||
|         } catch (e) {
 | ||
|             return jsonStr; // 非JSON格式直接返回
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 建立WebSocket连接
 | ||
|     function connectWebSocket() {
 | ||
|         if (ws) {
 | ||
|             addLog('已存在连接,无需重复建立', 'error');
 | ||
|             return;
 | ||
|         }
 | ||
| 
 | ||
|         try {
 | ||
|             ws = new WebSocket(wsUrl);
 | ||
| 
 | ||
|             // 连接成功
 | ||
|             ws.onopen = function () {
 | ||
|                 connectionStatus.className = 'status-value status-connected';
 | ||
|                 connectionStatus.textContent = '已连接';
 | ||
|                 const now = new Date().toLocaleString('zh-CN');
 | ||
|                 connectTime.textContent = now;
 | ||
|                 addLog(`连接成功!服务地址:${wsUrl}`, 'status');
 | ||
| 
 | ||
|                 // 更新按钮状态
 | ||
|                 connectBtn.disabled = true;
 | ||
|                 disconnectBtn.disabled = false;
 | ||
|                 sendMessageBtn.disabled = false;
 | ||
| 
 | ||
|                 // 开启自动心跳(默认开启)
 | ||
|                 if (autoHeartbeat.value === 'on') {
 | ||
|                     startAutoHeartbeat();
 | ||
|                 }
 | ||
|             };
 | ||
| 
 | ||
|             // 接收消息
 | ||
|             ws.onmessage = function (event) {
 | ||
|                 const message = event.data;
 | ||
|                 addLog(`收到消息:\n${formatJson(message)}`, 'receive');
 | ||
|             };
 | ||
| 
 | ||
|             // 连接关闭
 | ||
|             ws.onclose = function (event) {
 | ||
|                 connectionStatus.className = 'status-value status-disconnected';
 | ||
|                 connectionStatus.textContent = '已断开';
 | ||
|                 addLog(`连接断开!代码:${event.code},原因:${event.reason || '未知'}`, 'status');
 | ||
| 
 | ||
|                 // 清除自动心跳
 | ||
|                 stopAutoHeartbeat();
 | ||
| 
 | ||
|                 // 更新按钮状态
 | ||
|                 connectBtn.disabled = false;
 | ||
|                 disconnectBtn.disabled = true;
 | ||
|                 sendMessageBtn.disabled = true;
 | ||
| 
 | ||
|                 // 重置WebSocket对象
 | ||
|                 ws = null;
 | ||
|             };
 | ||
| 
 | ||
|             // 连接错误
 | ||
|             ws.onerror = function (error) {
 | ||
|                 addLog(`连接错误:${error.message || '未知错误'}`, 'error');
 | ||
|             };
 | ||
| 
 | ||
|         } catch (e) {
 | ||
|             addLog(`建立连接失败:${e.message}`, 'error');
 | ||
|             ws = null;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 断开WebSocket连接
 | ||
|     function disconnectWebSocket() {
 | ||
|         if (!ws) {
 | ||
|             addLog('当前无连接,无需断开', 'error');
 | ||
|             return;
 | ||
|         }
 | ||
| 
 | ||
|         ws.close(1000, '手动断开连接');
 | ||
|     }
 | ||
| 
 | ||
|     // 发送心跳消息(符合约定格式:{"timestamp":xxxxx, "type":"heartbeat"})
 | ||
|     function sendHeartbeat() {
 | ||
|         if (!ws || ws.readyState !== WebSocket.OPEN) {
 | ||
|             addLog('发送心跳失败:当前无有效连接', 'error');
 | ||
|             return;
 | ||
|         }
 | ||
| 
 | ||
|         const heartbeatMsg = {
 | ||
|             timestamp: Date.now(), // 当前毫秒时间戳
 | ||
|             type: "heartbeat"
 | ||
|         };
 | ||
|         const msgStr = JSON.stringify(heartbeatMsg);
 | ||
| 
 | ||
|         ws.send(msgStr);
 | ||
|         addLog(`发送心跳:\n${formatJson(msgStr)}`, 'send');
 | ||
|     }
 | ||
| 
 | ||
|     // 开启自动心跳
 | ||
|     function startAutoHeartbeat() {
 | ||
|         // 先停止已有定时器
 | ||
|         stopAutoHeartbeat();
 | ||
| 
 | ||
|         const interval = parseInt(heartbeatInterval.value) * 1000;
 | ||
|         if (isNaN(interval) || interval < 10000) {
 | ||
|             addLog('自动心跳间隔无效,已重置为30秒', 'error');
 | ||
|             heartbeatInterval.value = 30;
 | ||
|             return startAutoHeartbeat();
 | ||
|         }
 | ||
| 
 | ||
|         addLog(`开启自动心跳,间隔:${heartbeatInterval.value}秒`, 'status');
 | ||
|         heartbeatTimer = setInterval(sendHeartbeat, interval);
 | ||
|     }
 | ||
| 
 | ||
|     // 停止自动心跳
 | ||
|     function stopAutoHeartbeat() {
 | ||
|         if (heartbeatTimer) {
 | ||
|             clearInterval(heartbeatTimer);
 | ||
|             heartbeatTimer = null;
 | ||
|             addLog('已停止自动心跳', 'status');
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 发送自定义消息
 | ||
|     function sendCustomMessage() {
 | ||
|         if (!ws || ws.readyState !== WebSocket.OPEN) {
 | ||
|             addLog('发送消息失败:当前无有效连接', 'error');
 | ||
|             return;
 | ||
|         }
 | ||
| 
 | ||
|         const msgStr = messageInput.value.trim();
 | ||
|         if (!msgStr) {
 | ||
|             addLog('发送消息失败:消息内容不能为空', 'error');
 | ||
|             return;
 | ||
|         }
 | ||
| 
 | ||
|         try {
 | ||
|             // 验证JSON格式(可选,仅提示不强制)
 | ||
|             JSON.parse(msgStr);
 | ||
|             ws.send(msgStr);
 | ||
|             addLog(`发送自定义消息:\n${formatJson(msgStr)}`, 'send');
 | ||
|         } catch (e) {
 | ||
|             addLog(`JSON格式错误:${e.message},仍尝试发送原始内容`, 'error');
 | ||
|             ws.send(msgStr);
 | ||
|             addLog(`发送自定义消息(非JSON):\n${msgStr}`, 'send');
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     // 绑定按钮事件
 | ||
|     connectBtn.addEventListener('click', connectWebSocket);
 | ||
|     disconnectBtn.addEventListener('click', disconnectWebSocket);
 | ||
|     sendMessageBtn.addEventListener('click', sendCustomMessage);
 | ||
|     sendHeartbeatBtn.addEventListener('click', sendHeartbeat);
 | ||
|     clearLogBtn.addEventListener('click', () => {
 | ||
|         logContainer.innerHTML = '<div class="log-item"><span class="log-time">[日志已清空]</span> 请继续操作...</div>';
 | ||
|     });
 | ||
| 
 | ||
|     // 自动心跳开关变更事件
 | ||
|     autoHeartbeat.addEventListener('change', function () {
 | ||
|         if (ws && ws.readyState === WebSocket.OPEN) {
 | ||
|             if (this.value === 'on') {
 | ||
|                 startAutoHeartbeat();
 | ||
|             } else {
 | ||
|                 stopAutoHeartbeat();
 | ||
|             }
 | ||
|         } else {
 | ||
|             addLog('需先建立有效连接才能控制自动心跳', 'error');
 | ||
|             // 重置选择
 | ||
|             this.value = 'off';
 | ||
|         }
 | ||
|     });
 | ||
| 
 | ||
|     // 心跳间隔变更事件(实时生效)
 | ||
|     heartbeatInterval.addEventListener('change', function () {
 | ||
|         if (autoHeartbeat.value === 'on' && ws && ws.readyState === WebSocket.OPEN) {
 | ||
|             startAutoHeartbeat();
 | ||
|         }
 | ||
|     });
 | ||
| 
 | ||
|     // 快捷键支持(Ctrl+Enter发送消息)
 | ||
|     messageInput.addEventListener('keydown', function (e) {
 | ||
|         if (e.ctrlKey && e.key === 'Enter') {
 | ||
|             sendCustomMessage();
 | ||
|             e.preventDefault();
 | ||
|         }
 | ||
|     });
 | ||
| </script>
 | ||
| </body>
 | ||
| </html> | 
