|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-CN">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>二手交易应用 - 实时聊天测试</title>
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
|
background-color: #f5f5f5;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
max-width: 1200px;
|
|
|
margin: 0 auto;
|
|
|
padding: 20px;
|
|
|
}
|
|
|
|
|
|
h1 {
|
|
|
text-align: center;
|
|
|
margin-bottom: 30px;
|
|
|
color: #2c3e50;
|
|
|
}
|
|
|
|
|
|
.login-panel {
|
|
|
background: white;
|
|
|
padding: 30px;
|
|
|
border-radius: 10px;
|
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
|
margin-bottom: 30px;
|
|
|
max-width: 500px;
|
|
|
margin-left: auto;
|
|
|
margin-right: auto;
|
|
|
}
|
|
|
|
|
|
.form-group {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
label {
|
|
|
display: block;
|
|
|
margin-bottom: 8px;
|
|
|
font-weight: 500;
|
|
|
color: #555;
|
|
|
}
|
|
|
|
|
|
input {
|
|
|
width: 100%;
|
|
|
padding: 10px 15px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 5px;
|
|
|
font-size: 16px;
|
|
|
}
|
|
|
|
|
|
input:focus {
|
|
|
outline: none;
|
|
|
border-color: #3498db;
|
|
|
}
|
|
|
|
|
|
.btn {
|
|
|
background-color: #3498db;
|
|
|
color: white;
|
|
|
border: none;
|
|
|
padding: 12px 20px;
|
|
|
border-radius: 5px;
|
|
|
font-size: 16px;
|
|
|
cursor: pointer;
|
|
|
transition: background-color 0.3s;
|
|
|
}
|
|
|
|
|
|
.btn:hover {
|
|
|
background-color: #2980b9;
|
|
|
}
|
|
|
|
|
|
.btn:disabled {
|
|
|
background-color: #bdc3c7;
|
|
|
cursor: not-allowed;
|
|
|
}
|
|
|
|
|
|
.chat-container {
|
|
|
display: none;
|
|
|
gap: 20px;
|
|
|
margin-bottom: 30px;
|
|
|
}
|
|
|
|
|
|
.chat-window {
|
|
|
background: white;
|
|
|
border-radius: 10px;
|
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
|
overflow: hidden;
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
height: 600px;
|
|
|
}
|
|
|
|
|
|
.chat-header {
|
|
|
background-color: #3498db;
|
|
|
color: white;
|
|
|
padding: 15px 20px;
|
|
|
font-weight: 600;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.connection-status {
|
|
|
font-size: 12px;
|
|
|
padding: 4px 8px;
|
|
|
border-radius: 12px;
|
|
|
background-color: rgba(255,255,255,0.2);
|
|
|
}
|
|
|
|
|
|
.status-connected {
|
|
|
background-color: #2ecc71;
|
|
|
}
|
|
|
|
|
|
.status-disconnected {
|
|
|
background-color: #e74c3c;
|
|
|
}
|
|
|
|
|
|
.message-area {
|
|
|
flex: 1;
|
|
|
padding: 20px;
|
|
|
overflow-y: auto;
|
|
|
background-color: #f9f9f9;
|
|
|
}
|
|
|
|
|
|
.message {
|
|
|
margin-bottom: 15px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.message-info {
|
|
|
font-size: 12px;
|
|
|
color: #777;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.message-content {
|
|
|
max-width: 70%;
|
|
|
padding: 10px 15px;
|
|
|
border-radius: 8px;
|
|
|
word-wrap: break-word;
|
|
|
}
|
|
|
|
|
|
.sent .message-content {
|
|
|
background-color: #3498db;
|
|
|
color: white;
|
|
|
align-self: flex-end;
|
|
|
border-bottom-right-radius: 2px;
|
|
|
}
|
|
|
|
|
|
.received .message-content {
|
|
|
background-color: #e0e0e0;
|
|
|
color: #333;
|
|
|
align-self: flex-start;
|
|
|
border-bottom-left-radius: 2px;
|
|
|
}
|
|
|
|
|
|
.system .message-content {
|
|
|
background-color: #f39c12;
|
|
|
color: white;
|
|
|
align-self: center;
|
|
|
font-style: italic;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.input-area {
|
|
|
padding: 15px;
|
|
|
background-color: white;
|
|
|
border-top: 1px solid #eee;
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.message-input {
|
|
|
flex: 1;
|
|
|
padding: 10px 15px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 20px;
|
|
|
font-size: 16px;
|
|
|
}
|
|
|
|
|
|
.send-btn {
|
|
|
background-color: #3498db;
|
|
|
color: white;
|
|
|
border: none;
|
|
|
padding: 10px 20px;
|
|
|
border-radius: 20px;
|
|
|
cursor: pointer;
|
|
|
transition: background-color 0.3s;
|
|
|
}
|
|
|
|
|
|
.send-btn:hover {
|
|
|
background-color: #2980b9;
|
|
|
}
|
|
|
|
|
|
.send-btn:disabled {
|
|
|
background-color: #bdc3c7;
|
|
|
cursor: not-allowed;
|
|
|
}
|
|
|
|
|
|
.info-box {
|
|
|
background-color: #e8f4f8;
|
|
|
border-left: 4px solid #3498db;
|
|
|
padding: 15px;
|
|
|
margin-bottom: 20px;
|
|
|
border-radius: 5px;
|
|
|
}
|
|
|
|
|
|
.account-info {
|
|
|
background-color: #e8f8f5;
|
|
|
border: 1px solid #2ecc71;
|
|
|
padding: 15px;
|
|
|
border-radius: 5px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.account-info h3 {
|
|
|
color: #27ae60;
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
.account-info code {
|
|
|
background-color: #f0f0f0;
|
|
|
padding: 2px 5px;
|
|
|
border-radius: 3px;
|
|
|
font-family: monospace;
|
|
|
}
|
|
|
|
|
|
.instructions {
|
|
|
background-color: #fff9c4;
|
|
|
padding: 20px;
|
|
|
border-radius: 10px;
|
|
|
margin-bottom: 30px;
|
|
|
}
|
|
|
|
|
|
.instructions h3 {
|
|
|
color: #f57c00;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.instructions ol {
|
|
|
margin-left: 20px;
|
|
|
}
|
|
|
|
|
|
.instructions li {
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="container">
|
|
|
<h1>二手交易应用 - 实时聊天测试</h1>
|
|
|
|
|
|
<div class="instructions">
|
|
|
<h3>使用说明</h3>
|
|
|
<ol>
|
|
|
<li>请确保后端服务已在 <code>http://localhost:8080</code> 运行</li>
|
|
|
<li>使用提供的模拟账号登录系统</li>
|
|
|
<li>系统会自动建立WebSocket连接</li>
|
|
|
<li>可以在两个聊天窗口之间互相发送消息进行测试</li>
|
|
|
</ol>
|
|
|
</div>
|
|
|
|
|
|
<div class="account-info">
|
|
|
<h3>模拟测试账号</h3>
|
|
|
<p>账号1:手机号 <code>13800138001</code>, 密码 <code>password1</code></p>
|
|
|
<p>账号2:手机号 <code>13800138002</code>, 密码 <code>password2</code></p>
|
|
|
</div>
|
|
|
|
|
|
<!-- 登录面板 -->
|
|
|
<div class="login-panel">
|
|
|
<h2 style="margin-bottom: 20px; text-align: center;">用户登录</h2>
|
|
|
<div class="form-group">
|
|
|
<label for="phone">手机号:</label>
|
|
|
<input type="text" id="phone" placeholder="请输入手机号" value="13800138001">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label for="password">密码:</label>
|
|
|
<input type="password" id="password" placeholder="请输入密码" value="password1">
|
|
|
</div>
|
|
|
<button class="btn" id="login-btn">登录</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- 聊天窗口容器 -->
|
|
|
<div class="chat-container" id="chat-container">
|
|
|
<!-- 用户1的聊天窗口 -->
|
|
|
<div class="chat-window">
|
|
|
<div class="chat-header">
|
|
|
<span>用户1的聊天窗口</span>
|
|
|
<span class="connection-status" id="status-user1">断开</span>
|
|
|
</div>
|
|
|
<div class="message-area" id="messages-user1"></div>
|
|
|
<div class="input-area">
|
|
|
<input type="text" class="message-input" id="input-user1" placeholder="输入消息...">
|
|
|
<button class="send-btn" id="send-btn1">发送</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 用户2的聊天窗口 -->
|
|
|
<div class="chat-window">
|
|
|
<div class="chat-header">
|
|
|
<span>用户2的聊天窗口</span>
|
|
|
<span class="connection-status" id="status-user2">断开</span>
|
|
|
</div>
|
|
|
<div class="message-area" id="messages-user2"></div>
|
|
|
<div class="input-area">
|
|
|
<input type="text" class="message-input" id="input-user2" placeholder="输入消息...">
|
|
|
<button class="send-btn" id="send-btn2">发送</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="info-box" id="info-box" style="display: none;">
|
|
|
<h3>系统信息</h3>
|
|
|
<p id="system-info">正在连接...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
// 全局变量
|
|
|
let currentUser = null;
|
|
|
let ws1 = null; // 用户1的WebSocket连接
|
|
|
let ws2 = null; // 用户2的WebSocket连接
|
|
|
const API_BASE_URL = 'http://localhost:8080';
|
|
|
|
|
|
// 初始化DOM元素
|
|
|
const loginBtn = document.getElementById('login-btn');
|
|
|
const phoneInput = document.getElementById('phone');
|
|
|
const passwordInput = document.getElementById('password');
|
|
|
const chatContainer = document.getElementById('chat-container');
|
|
|
const infoBox = document.getElementById('info-box');
|
|
|
const systemInfo = document.getElementById('system-info');
|
|
|
const statusUser1 = document.getElementById('status-user1');
|
|
|
const statusUser2 = document.getElementById('status-user2');
|
|
|
const messagesUser1 = document.getElementById('messages-user1');
|
|
|
const messagesUser2 = document.getElementById('messages-user2');
|
|
|
const inputUser1 = document.getElementById('input-user1');
|
|
|
const inputUser2 = document.getElementById('input-user2');
|
|
|
const sendBtn1 = document.getElementById('send-btn1');
|
|
|
const sendBtn2 = document.getElementById('send-btn2');
|
|
|
|
|
|
// 登录按钮事件
|
|
|
loginBtn.addEventListener('click', login);
|
|
|
|
|
|
// 发送按钮事件
|
|
|
sendBtn1.addEventListener('click', () => sendMessage(1));
|
|
|
sendBtn2.addEventListener('click', () => sendMessage(2));
|
|
|
|
|
|
// 回车键发送消息
|
|
|
inputUser1.addEventListener('keypress', (e) => {
|
|
|
if (e.key === 'Enter') sendMessage(1);
|
|
|
});
|
|
|
|
|
|
inputUser2.addEventListener('keypress', (e) => {
|
|
|
if (e.key === 'Enter') sendMessage(2);
|
|
|
});
|
|
|
|
|
|
// 用户登录函数
|
|
|
async function login() {
|
|
|
const phone = phoneInput.value.trim();
|
|
|
const password = passwordInput.value.trim();
|
|
|
|
|
|
if (!phone || !password) {
|
|
|
alert('请输入手机号和密码');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// 禁用登录按钮
|
|
|
loginBtn.disabled = true;
|
|
|
loginBtn.textContent = '登录中...';
|
|
|
|
|
|
// 调用登录API
|
|
|
const response = await fetch(`${API_BASE_URL}/api/auth/login`, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Content-Type': 'application/json'
|
|
|
},
|
|
|
body: JSON.stringify({ phone, password })
|
|
|
});
|
|
|
|
|
|
if (!response.ok) {
|
|
|
throw new Error('登录失败');
|
|
|
}
|
|
|
|
|
|
const data = await response.json();
|
|
|
currentUser = data;
|
|
|
|
|
|
// 显示系统信息
|
|
|
infoBox.style.display = 'block';
|
|
|
systemInfo.textContent = `登录成功!用户ID: ${data.userId}, 用户名: ${data.username}`;
|
|
|
|
|
|
// 显示聊天容器
|
|
|
chatContainer.style.display = 'flex';
|
|
|
|
|
|
// 自动建立两个用户的WebSocket连接(仅用于测试)
|
|
|
connectWebSocket();
|
|
|
|
|
|
} catch (error) {
|
|
|
alert('登录失败:' + error.message);
|
|
|
console.error('登录错误:', error);
|
|
|
} finally {
|
|
|
// 恢复登录按钮
|
|
|
loginBtn.disabled = false;
|
|
|
loginBtn.textContent = '登录';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 建立WebSocket连接
|
|
|
function connectWebSocket() {
|
|
|
// 连接用户1的WebSocket
|
|
|
ws1 = new WebSocket(`ws://localhost:8080/ws/chat?userId=1&username=用户1`);
|
|
|
setupWebSocketEvents(ws1, 1);
|
|
|
|
|
|
// 连接用户2的WebSocket
|
|
|
ws2 = new WebSocket(`ws://localhost:8080/ws/chat?userId=2&username=用户2`);
|
|
|
setupWebSocketEvents(ws2, 2);
|
|
|
}
|
|
|
|
|
|
// 设置WebSocket事件处理
|
|
|
function setupWebSocketEvents(ws, userId) {
|
|
|
// 连接成功
|
|
|
ws.onopen = () => {
|
|
|
console.log(`用户${userId} WebSocket连接已建立`);
|
|
|
updateConnectionStatus(userId, true);
|
|
|
addSystemMessage(userId, 'WebSocket连接已建立');
|
|
|
};
|
|
|
|
|
|
// 接收消息
|
|
|
ws.onmessage = (event) => {
|
|
|
try {
|
|
|
const message = JSON.parse(event.data);
|
|
|
console.log(`用户${userId} 收到消息:`, message);
|
|
|
|
|
|
// 处理不同类型的消息
|
|
|
if (message.type === 'CHAT') {
|
|
|
// 显示在对应窗口
|
|
|
const targetUserId = message.sender.id === userId ? userId :
|
|
|
message.sender.id === 1 ? 2 : 1;
|
|
|
|
|
|
addMessage(targetUserId, {
|
|
|
id: message.id,
|
|
|
content: message.content,
|
|
|
sender: message.sender,
|
|
|
receiver: message.receiver,
|
|
|
createdAt: message.createdAt,
|
|
|
sent: message.sender.id === userId
|
|
|
});
|
|
|
} else if (message.type === 'SYSTEM') {
|
|
|
// 系统消息显示在所有窗口
|
|
|
addSystemMessage(1, message.content);
|
|
|
addSystemMessage(2, message.content);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('解析消息错误:', error);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 连接关闭
|
|
|
ws.onclose = () => {
|
|
|
console.log(`用户${userId} WebSocket连接已关闭`);
|
|
|
updateConnectionStatus(userId, false);
|
|
|
addSystemMessage(userId, 'WebSocket连接已关闭');
|
|
|
|
|
|
// 尝试重连
|
|
|
setTimeout(() => {
|
|
|
console.log(`尝试重新连接用户${userId}...`);
|
|
|
connectWebSocket();
|
|
|
}, 5000);
|
|
|
};
|
|
|
|
|
|
// 连接错误
|
|
|
ws.onerror = (error) => {
|
|
|
console.error(`用户${userId} WebSocket错误:`, error);
|
|
|
updateConnectionStatus(userId, false);
|
|
|
addSystemMessage(userId, `WebSocket连接错误: ${error.message || '未知错误'}`);
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// 更新连接状态显示
|
|
|
function updateConnectionStatus(userId, connected) {
|
|
|
const statusElement = userId === 1 ? statusUser1 : statusUser2;
|
|
|
statusElement.textContent = connected ? '已连接' : '断开';
|
|
|
statusElement.className = connected ?
|
|
|
'connection-status status-connected' :
|
|
|
'connection-status status-disconnected';
|
|
|
}
|
|
|
|
|
|
// 发送消息
|
|
|
function sendMessage(fromUserId) {
|
|
|
const inputElement = fromUserId === 1 ? inputUser1 : inputUser2;
|
|
|
const content = inputElement.value.trim();
|
|
|
const ws = fromUserId === 1 ? ws1 : ws2;
|
|
|
const toUserId = fromUserId === 1 ? 2 : 1;
|
|
|
|
|
|
if (!content || !ws || ws.readyState !== WebSocket.OPEN) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 构建消息对象
|
|
|
const message = {
|
|
|
type: 'CHAT',
|
|
|
content: content,
|
|
|
receiverId: toUserId,
|
|
|
senderId: fromUserId
|
|
|
};
|
|
|
|
|
|
// 发送消息
|
|
|
ws.send(JSON.stringify(message));
|
|
|
|
|
|
// 清空输入框
|
|
|
inputElement.value = '';
|
|
|
|
|
|
// 在发送者窗口显示消息
|
|
|
addMessage(fromUserId, {
|
|
|
content: content,
|
|
|
sender: { id: fromUserId, username: `用户${fromUserId}` },
|
|
|
receiver: { id: toUserId, username: `用户${toUserId}` },
|
|
|
createdAt: new Date().toISOString(),
|
|
|
sent: true
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 添加消息到指定用户的聊天窗口
|
|
|
function addMessage(userId, message) {
|
|
|
const messagesContainer = userId === 1 ? messagesUser1 : messagesUser2;
|
|
|
const messageType = message.sent ? 'sent' : 'received';
|
|
|
|
|
|
// 格式化时间
|
|
|
const date = new Date(message.createdAt);
|
|
|
const timeStr = date.toLocaleTimeString('zh-CN', {
|
|
|
hour: '2-digit',
|
|
|
minute: '2-digit'
|
|
|
});
|
|
|
|
|
|
// 创建消息元素
|
|
|
const messageElement = document.createElement('div');
|
|
|
messageElement.className = `message ${messageType}`;
|
|
|
|
|
|
// 设置消息内容
|
|
|
messageElement.innerHTML = `
|
|
|
<div class="message-info">
|
|
|
${message.sender.username} · ${timeStr}
|
|
|
</div>
|
|
|
<div class="message-content">
|
|
|
${escapeHtml(message.content)}
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
// 添加到消息容器
|
|
|
messagesContainer.appendChild(messageElement);
|
|
|
|
|
|
// 滚动到底部
|
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
|
}
|
|
|
|
|
|
// 添加系统消息
|
|
|
function addSystemMessage(userId, content) {
|
|
|
const messagesContainer = userId === 1 ? messagesUser1 : messagesUser2;
|
|
|
const date = new Date();
|
|
|
const timeStr = date.toLocaleTimeString('zh-CN', {
|
|
|
hour: '2-digit',
|
|
|
minute: '2-digit'
|
|
|
});
|
|
|
|
|
|
const messageElement = document.createElement('div');
|
|
|
messageElement.className = 'message system';
|
|
|
messageElement.innerHTML = `
|
|
|
<div class="message-info">系统消息 · ${timeStr}</div>
|
|
|
<div class="message-content">${escapeHtml(content)}</div>
|
|
|
`;
|
|
|
|
|
|
messagesContainer.appendChild(messageElement);
|
|
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
|
}
|
|
|
|
|
|
// HTML转义函数
|
|
|
function escapeHtml(text) {
|
|
|
const div = document.createElement('div');
|
|
|
div.textContent = text;
|
|
|
return div.innerHTML;
|
|
|
}
|
|
|
|
|
|
// 页面加载完成后检查WebSocket支持
|
|
|
window.addEventListener('load', () => {
|
|
|
if (!window.WebSocket) {
|
|
|
alert('您的浏览器不支持WebSocket,无法使用实时聊天功能!');
|
|
|
}
|
|
|
});
|
|
|
</script>
|
|
|
</body>
|
|
|
</html> |