websocket:集成通知测试功能到WebSocket测试页面

- 添加通知模式切换功能,支持聊天和通知两种测试模式
- 实现通知WebSocket连接和用户认证
- 添加通知发送界面,支持API和WebSocket两种发送方式
- 集成通知管理功能,支持列表查看和已读标记
- 修复HTML结构,确保通知模式与聊天模式平级显示
- 更新页面标题和功能描述
This commit is contained in:
moyin
2026-01-10 21:54:17 +08:00
parent c5a04b01a1
commit 28bea2f001

View File

@@ -19,9 +19,9 @@ export class WebSocketTestController {
@Get()
@ApiOperation({
summary: '🔌 WebSocket 测试页面 - 一键测试工具 + API监控',
summary: '🔌 WebSocket 测试页面 + 通知系统 - 一键测试工具 + API监控',
description: `
**🚀 功能强大的WebSocket测试工具**
**🚀 功能强大的WebSocket测试工具 + 通知系统**
提供完整的WebSocket测试功能
- ✅ 自动获取JWT Token
@@ -30,7 +30,16 @@ export class WebSocketTestController {
- ✅ 聊天消息发送测试
- ✅ 位置更新测试
- ✅ 实时消息日志
- 📡 **新增:API调用监控** - 实时显示所有HTTP请求
- 📡 **API调用监控** - 实时显示所有HTTP请求
- 🔔 **通知系统测试** - 完整的通知功能测试
**新增通知系统功能**
- 🔔 实时通知WebSocket连接
- 📢 通知发送和接收测试
- 📋 通知列表管理
- 🔢 未读通知统计
- 🎯 支持系统、用户、广播通知
- ⏰ 定时通知功能
**使用方法**
1. 点击下方"Execute"按钮
@@ -58,7 +67,7 @@ export class WebSocketTestController {
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 测试工具 + API监控 - Pixel Game Server</title>
<title>WebSocket 测试工具 + 通知系统 - Pixel Game Server</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -73,6 +82,7 @@ export class WebSocketTestController {
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
min-height: 200px; /* 确保容器有最小高度 */
}
.status {
padding: 10px;
@@ -234,6 +244,7 @@ export class WebSocketTestController {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
min-height: 400px; /* 确保两列布局有最小高度 */
}
.logs-section {
@@ -262,10 +273,27 @@ export class WebSocketTestController {
max-width: calc(100vw - 60px);
}
}
/* 通知模式特定样式 */
#noticeMode {
width: 100%;
min-height: 500px;
}
#noticeMode .container {
background: white;
border: 1px solid #ddd;
min-height: 300px;
}
#noticeMode h2, #noticeMode h3 {
color: #1976d2;
margin-top: 0;
}
</style>
</head>
<body>
<h1>🎮 Pixel Game Server - WebSocket 测试工具 + API监控</h1>
<h1>🎮 Pixel Game Server - WebSocket 测试工具 + 通知系统</h1>
<div class="info-panel">
<h3>📋 使用说明</h3>
@@ -286,7 +314,8 @@ export class WebSocketTestController {
• 💾 本地存储自动保存Token下次访问无需重新获取<br>
• 📧 真实邮箱支持:检测到真实邮箱时使用真实验证码发送<br>
• 📡 **API调用监控**实时显示所有HTTP请求和响应方便调试<br>
• 🔍 详细日志:支持显示请求体、响应体和调用统计
• 🔍 详细日志:支持显示请求体、响应体和调用统计<br>
• 🔔 **通知系统测试**完整的通知功能测试支持实时推送和API调用
</div>
<div style="margin-top: 15px; padding: 10px; background-color: #fff3e0; border-radius: 4px;">
@@ -319,6 +348,17 @@ export class WebSocketTestController {
</div>
</div>
<!-- 功能切换按钮 -->
<div class="container" style="text-align: center; margin-bottom: 20px;">
<h2>🎛️ 功能选择</h2>
<div style="display: flex; gap: 10px; justify-content: center;">
<button id="chatModeBtn" onclick="switchMode('chat')" style="flex: 1; max-width: 200px; background-color: #1976d2;">💬 聊天测试</button>
<button id="noticeModeBtn" onclick="switchMode('notice')" style="flex: 1; max-width: 200px; background-color: #666;">🔔 通知测试</button>
</div>
</div>
<!-- 聊天模式 -->
<div id="chatMode">
<div class="two-column">
<div class="container">
<h2>🔌 连接控制</h2>
@@ -458,18 +498,110 @@ export class WebSocketTestController {
</div>
</div>
</div>
</div>
<!-- 通知模式 -->
<div id="noticeMode" style="display: none; background-color: #f0f8ff; border: 2px solid red; padding: 20px;">
<h1 style="color: red; font-size: 24px;">🔔 通知测试模式</h1>
<p style="color: blue; font-size: 18px;">如果你能看到这个文字,说明通知模式正常显示了!</p>
<div class="two-column">
<div class="container">
<h2>🔔 通知系统控制</h2>
<div id="noticeConnectionStatus" class="status disconnected">未连接</div>
<div class="form-group">
<label for="noticeWsUrl">通知WebSocket地址:</label>
<input type="text" id="noticeWsUrl" value="ws://localhost:3000/ws/notice" />
</div>
<button id="noticeConnectBtn" onclick="toggleNoticeConnection()">连接通知系统</button>
<h3>🔐 用户认证</h3>
<div class="form-group">
<label for="noticeUserId">用户ID:</label>
<input type="number" id="noticeUserId" value="1" placeholder="输入用户ID" />
</div>
<div class="form-group">
<label for="noticeJwtToken">JWT Token:</label>
<textarea id="noticeJwtToken" rows="3" placeholder="输入JWT Token (可从聊天模式复制)"></textarea>
<button onclick="copyTokenFromChat()" style="margin-top: 5px; background-color: #4caf50;">从聊天模式复制Token</button>
</div>
<button onclick="authenticateNotice()">认证通知连接</button>
</div>
<div class="container">
<h2>📢 发送通知</h2>
<div class="form-group">
<label for="noticeTitle">通知标题:</label>
<input type="text" id="noticeTitle" placeholder="输入通知标题" />
</div>
<div class="form-group">
<label for="noticeContent">通知内容:</label>
<textarea id="noticeContent" rows="3" placeholder="输入通知内容"></textarea>
</div>
<div class="form-group">
<label for="noticeType">通知类型:</label>
<select id="noticeType">
<option value="system">系统通知</option>
<option value="user">用户通知</option>
<option value="broadcast">广播通知</option>
</select>
</div>
<div class="form-group">
<label for="targetUserId">目标用户ID (留空为广播):</label>
<input type="number" id="targetUserId" placeholder="目标用户ID" />
</div>
<div class="form-group">
<label for="scheduledTime">定时发送 (可选):</label>
<input type="datetime-local" id="scheduledTime" />
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<button onclick="sendNoticeViaAPI()" style="background-color: #4caf50;"><3E> 过通过API发送</button>
<button onclick="sendNoticeViaWS()" style="background-color: #ff9800;">🔌 通过WebSocket发送</button>
</div>
<div class="quick-actions" style="margin-top: 15px;">
<div class="quick-action" onclick="sendQuickNotice('系统维护', '系统将在今晚进行维护')">快速: 系统维护</div>
<div class="quick-action" onclick="sendQuickNotice('新功能上线', '我们上线了新的通知功能!')">快速: 新功能</div>
<div class="quick-action" onclick="sendQuickNotice('测试通知', '这是一条测试通知')">快速: 测试通知</div>
<div class="quick-action" onclick="loadNoticeList()"><3E> 刷取新通知列表</div>
<div class="quick-action" onclick="getUnreadCount()"><3E> 获取未读数日</div>
<div class="quick-action" onclick="clearNoticeLog()">🗑️ 清空通知日志</div>
</div>
<h3>📋 通知管理</h3>
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<button onclick="loadNoticeList()" style="flex: 1;">获取通知列表</button>
<button onclick="getUnreadCount()" style="flex: 1; background-color: #ff9800;">获取未读数量</button>
</div>
<div id="noticeList" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background-color: #fafafa;">
<div style="text-align: center; color: #666;">点击"获取通知列表"加载通知</div>
</div>
</div>
</div>
</div>
<!-- 日志区域 - 消息日志和API监控并排显示 -->
<div class="logs-section">
<div class="container">
<h2>📋 消息日志</h2>
<h2 id="logTitle">📋 消息日志</h2>
<div id="messageLog" class="message-log"></div>
</div>
<div class="container">
<h2>📡 API 调用日志</h2>
<h2 id="apiLogTitle">📡 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>
<span style="font-size: 14px; color: #666;" id="apiLogSubtitle">实时监控前端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>
@@ -502,6 +634,11 @@ export class WebSocketTestController {
let apiSuccessCount = 0;
let apiErrorCount = 0;
let showApiDetails = false;
let currentMode = 'chat'; // 当前模式chat 或 notice
// 通知系统相关变量
let noticeWs = null;
let noticeAuthenticated = false;
// API监控相关变量
const originalFetch = window.fetch;
@@ -1869,6 +2006,16 @@ export class WebSocketTestController {
addMessage('system', '🎲 一键测试会自动生成随机测试账号,方便多用户测试');
addMessage('system', '📡 新功能: API调用监控已启用可实时查看所有HTTP请求');
addMessage('system', '🔍 API日志支持显示请求体、响应体和详细统计信息');
addMessage('system', '🔔 新增: 通知系统测试功能,可切换到通知模式进行测试');
// 请求通知权限
if (Notification.permission === 'default') {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
addMessage('system', '✅ 浏览器通知权限已获取');
}
});
}
// 检查URL参数看是否从API文档跳转过来
const urlParams = new URLSearchParams(window.location.search);
@@ -1951,6 +2098,396 @@ export class WebSocketTestController {
window.addEventListener('error', function(event) {
addMessage('error', '❌ 页面错误: ' + event.error.message);
});
// ==================== 通知系统功能 ====================
// 切换功能模式
function switchMode(mode) {
console.log('switchMode called with:', mode);
currentMode = mode;
const chatMode = document.getElementById('chatMode');
const noticeMode = document.getElementById('noticeMode');
const chatBtn = document.getElementById('chatModeBtn');
const noticeBtn = document.getElementById('noticeModeBtn');
const logTitle = document.getElementById('logTitle');
const apiLogTitle = document.getElementById('apiLogTitle');
const apiLogSubtitle = document.getElementById('apiLogSubtitle');
console.log('Elements found:', {
chatMode: !!chatMode,
noticeMode: !!noticeMode,
chatBtn: !!chatBtn,
noticeBtn: !!noticeBtn
});
if (mode === 'chat') {
if (chatMode) chatMode.style.display = 'block';
if (noticeMode) noticeMode.style.display = 'none';
if (chatBtn) chatBtn.style.backgroundColor = '#1976d2';
if (noticeBtn) noticeBtn.style.backgroundColor = '#666';
if (logTitle) logTitle.textContent = '📋 消息日志';
if (apiLogTitle) apiLogTitle.textContent = '📡 API 调用日志';
if (apiLogSubtitle) apiLogSubtitle.textContent = '实时监控前端API调用';
addMessage('system', '🔄 已切换到聊天测试模式');
console.log('Switched to chat mode');
} else {
console.log('Switching to notice mode...');
if (chatMode) {
chatMode.style.display = 'none';
console.log('Chat mode hidden');
}
if (noticeMode) {
noticeMode.style.display = 'block';
console.log('Notice mode shown, display style:', noticeMode.style.display);
console.log('Notice mode computed style:', window.getComputedStyle(noticeMode).display);
}
if (chatBtn) chatBtn.style.backgroundColor = '#666';
if (noticeBtn) noticeBtn.style.backgroundColor = '#1976d2';
if (logTitle) logTitle.textContent = '🔔 通知日志';
if (apiLogTitle) apiLogTitle.textContent = '📡 通知API日志';
if (apiLogSubtitle) apiLogSubtitle.textContent = '实时监控通知API调用';
addMessage('system', '🔄 已切换到通知测试模式');
console.log('Notice mode switch completed');
// 自动复制Token
copyTokenFromChat();
}
}
// 从聊天模式复制Token
function copyTokenFromChat() {
const chatToken = document.getElementById('jwtToken').value.trim();
if (chatToken) {
document.getElementById('noticeJwtToken').value = chatToken;
addMessage('system', '✅ 已从聊天模式复制Token');
} else {
addMessage('system', '💡 聊天模式中没有Token请先在聊天模式获取Token');
}
}
// 切换通知WebSocket连接
function toggleNoticeConnection() {
if (noticeWs && noticeWs.readyState === WebSocket.OPEN) {
noticeWs.close();
} else {
connectNotice();
}
}
// 连接通知WebSocket
function connectNotice() {
const url = document.getElementById('noticeWsUrl').value;
updateNoticeStatus('connecting', '连接中...');
try {
noticeWs = new WebSocket(url);
noticeWs.onopen = function() {
updateNoticeStatus('connected', '已连接');
document.getElementById('noticeConnectBtn').textContent = '断开连接';
addMessage('system', '✅ 通知WebSocket连接成功');
};
noticeWs.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
addMessage('received', '🔔 收到通知: ' + JSON.stringify(data, null, 2));
if (data.type === 'notice') {
showNotificationPopup(data.data);
} else if (data.type === 'authenticated') {
noticeAuthenticated = true;
addMessage('system', '✅ 通知系统认证成功');
} else if (data.type === 'pong') {
addMessage('system', '🏓 收到pong响应');
}
} catch (e) {
addMessage('received', '🔔 ' + event.data);
}
};
noticeWs.onclose = function() {
updateNoticeStatus('disconnected', '未连接');
document.getElementById('noticeConnectBtn').textContent = '连接通知系统';
noticeAuthenticated = false;
addMessage('system', '🔌 通知WebSocket连接已关闭');
};
noticeWs.onerror = function(error) {
addMessage('error', '❌ 通知连接错误: ' + error);
};
} catch (error) {
updateNoticeStatus('disconnected', '连接失败');
addMessage('error', '❌ 通知连接失败: ' + error.message);
}
}
// 更新通知连接状态
function updateNoticeStatus(status, message) {
const statusEl = document.getElementById('noticeConnectionStatus');
statusEl.className = 'status ' + status;
statusEl.textContent = message;
}
// 认证通知连接
function authenticateNotice() {
if (!noticeWs || noticeWs.readyState !== WebSocket.OPEN) {
addMessage('error', '❌ 请先建立通知WebSocket连接');
return;
}
const userId = document.getElementById('noticeUserId').value.trim();
if (!userId) {
addMessage('error', '❌ 请输入用户ID');
return;
}
const message = {
event: 'authenticate',
data: { userId: parseInt(userId) }
};
noticeWs.send(JSON.stringify(message));
addMessage('sent', '📤 发送认证: ' + JSON.stringify(message, null, 2));
}
// 通过API发送通知
async function sendNoticeViaAPI() {
const title = document.getElementById('noticeTitle').value.trim();
const content = document.getElementById('noticeContent').value.trim();
const type = document.getElementById('noticeType').value;
const targetUserId = document.getElementById('targetUserId').value.trim();
const scheduledTime = document.getElementById('scheduledTime').value;
const token = document.getElementById('noticeJwtToken').value.trim();
if (!title || !content) {
addMessage('error', '❌ 请输入通知标题和内容');
return;
}
if (!token) {
addMessage('error', '❌ 请输入JWT Token');
return;
}
const payload = {
title,
content,
type,
userId: targetUserId ? parseInt(targetUserId) : undefined,
scheduledAt: scheduledTime || undefined
};
try {
let endpoint = '/api/notices';
if (type === 'system') {
endpoint = '/api/notices/system';
} else if (type === 'broadcast') {
endpoint = '/api/notices/broadcast';
}
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify(payload)
});
const result = await response.json();
if (response.ok) {
addMessage('system', '✅ 通知发送成功: ' + JSON.stringify(result, null, 2));
clearNoticeForm();
} else {
addMessage('error', '❌ 通知发送失败: ' + (result.message || '未知错误'));
}
} catch (error) {
addMessage('error', '❌ 请求失败: ' + error.message);
}
}
// 通过WebSocket发送通知 (这里只是演示实际通知应该通过API发送)
function sendNoticeViaWS() {
if (!noticeWs || noticeWs.readyState !== WebSocket.OPEN) {
addMessage('error', '❌ 请先建立通知WebSocket连接');
return;
}
if (!noticeAuthenticated) {
addMessage('error', '❌ 请先认证通知连接');
return;
}
const message = {
event: 'ping'
};
noticeWs.send(JSON.stringify(message));
addMessage('sent', '📤 发送ping: ' + JSON.stringify(message, null, 2));
}
// 发送快速通知
function sendQuickNotice(title, content) {
document.getElementById('noticeTitle').value = title;
document.getElementById('noticeContent').value = content;
document.getElementById('noticeType').value = 'system';
sendNoticeViaAPI();
}
// 加载通知列表
async function loadNoticeList() {
const token = document.getElementById('noticeJwtToken').value.trim();
if (!token) {
addMessage('error', '❌ 请输入JWT Token');
return;
}
try {
const response = await fetch('/api/notices', {
headers: {
'Authorization': 'Bearer ' + token
}
});
const notices = await response.json();
if (response.ok) {
displayNoticeList(notices);
addMessage('system', '✅ 通知列表加载成功,共 ' + notices.length + ' 条');
} else {
addMessage('error', '❌ 加载通知列表失败: ' + (notices.message || '未知错误'));
}
} catch (error) {
addMessage('error', '❌ 请求失败: ' + error.message);
}
}
// 获取未读通知数量
async function getUnreadCount() {
const token = document.getElementById('noticeJwtToken').value.trim();
if (!token) {
addMessage('error', '❌ 请输入JWT Token');
return;
}
try {
const response = await fetch('/api/notices/unread-count', {
headers: {
'Authorization': 'Bearer ' + token
}
});
const result = await response.json();
if (response.ok) {
addMessage('system', '📊 未读通知数量: ' + result.count);
} else {
addMessage('error', '❌ 获取未读数量失败: ' + (result.message || '未知错误'));
}
} catch (error) {
addMessage('error', '❌ 请求失败: ' + error.message);
}
}
// 显示通知列表
function displayNoticeList(notices) {
const listEl = document.getElementById('noticeList');
listEl.innerHTML = '';
if (notices.length === 0) {
listEl.innerHTML = '<div style="text-align: center; color: #666;">暂无通知</div>';
return;
}
notices.forEach(notice => {
const noticeEl = document.createElement('div');
noticeEl.style.cssText = 'border: 1px solid #ddd; padding: 8px; margin-bottom: 8px; border-radius: 4px; background: white;';
const statusColor = notice.status === 'read' ? '#666' : '#1976d2';
const statusText = notice.status === 'read' ? '已读' : '未读';
noticeEl.innerHTML = \`
<div style="font-weight: bold; margin-bottom: 4px;">\${notice.title}</div>
<div style="margin-bottom: 4px;">\${notice.content}</div>
<div style="font-size: 12px; color: #666;">
类型: \${notice.type} |
状态: <span style="color: \${statusColor};">\${statusText}</span> |
时间: \${new Date(notice.createdAt).toLocaleString()}
\${notice.status !== 'read' ?
\`<button onclick="markNoticeAsRead(\${notice.id})" style="margin-left: 10px; padding: 2px 6px; font-size: 11px; background-color: #4caf50; color: white; border: none; border-radius: 2px; cursor: pointer;">标记已读</button>\` :
''
}
</div>
\`;
listEl.appendChild(noticeEl);
});
}
// 标记通知为已读
async function markNoticeAsRead(noticeId) {
const token = document.getElementById('noticeJwtToken').value.trim();
if (!token) {
addMessage('error', '❌ 请输入JWT Token');
return;
}
try {
const response = await fetch(\`/api/notices/\${noticeId}/read\`, {
method: 'PATCH',
headers: {
'Authorization': 'Bearer ' + token
}
});
const result = await response.json();
if (response.ok) {
addMessage('system', '✅ 通知已标记为已读');
loadNoticeList(); // 刷新列表
} else {
addMessage('error', '❌ 标记失败: ' + (result.message || '未知错误'));
}
} catch (error) {
addMessage('error', '❌ 请求失败: ' + error.message);
}
}
// 显示通知弹窗
function showNotificationPopup(notice) {
// 使用浏览器通知API
if (Notification.permission === 'granted') {
new Notification(notice.title, {
body: notice.content,
icon: '/favicon.ico'
});
}
// 在页面上显示
addMessage('system', \`🔔 新通知: \${notice.title} - \${notice.content}\`);
}
// 清空通知表单
function clearNoticeForm() {
document.getElementById('noticeTitle').value = '';
document.getElementById('noticeContent').value = '';
document.getElementById('targetUserId').value = '';
document.getElementById('scheduledTime').value = '';
}
// 清空通知日志
function clearNoticeLog() {
if (currentMode === 'notice') {
clearLog();
addMessage('system', '🗑️ 通知日志已清空');
}
}
</script>
</body>
</html>