实现
html
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Instant Messaging</title>
<!-- 引入Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
}
#chat {
border: 1px solid #ccc;
height: calc(100% - 120px);
overflow-y: scroll;
padding: 10px;
}
.message {
margin-bottom: 10px;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
}
.message-content {
background-color: #e1ffc7;
padding: 10px;
border-radius: 15px;
max-width: 60%;
word-wrap: break-word;
}
.message-content.other {
background-color: #c7e1ff;
}
.message.self .message-content {
margin-left: auto;
}
.message-time {
font-size: 16px;
}
.message {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 10px;
}
.message-sender {
font-size: 14px;
margin: 0 0 5px 0;
color: #999;
}
.message-content {
background-color: #e1ffc7;
padding: 10px;
border-radius: 15px;
max-width: 60%;
word-wrap: break-word;
display: flex;
flex-direction: column;
}
.message-content.other {
background-color: #c7e1ff;
align-self: flex-start;
}
.self {
display: flex;
flex-direction: row-reverse;
}
#container-fluid {
width: 88%;
}
/* #auth-buttons {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
} */
/* 确保body和html占满整个视口,并移除默认边距 */
body,
html {
height: 100%;
margin: 0;
}
/* 使用Flexbox布局使#auth-buttons居中 */
#auth-buttons {
position: absolute;
display: flex;
flex-direction: column;
/* 垂直排列子元素 */
align-items: center;
/* 水平居中对齐 */
justify-content: center;
/* 垂直居中对齐 */
width: 480px;
top: 25%;
left: 50%;
margin-left: -240px;
/* left: -50%; */
/* 宽度设置为100% */
}
/* 可选:为输入框和按钮添加一些间距 */
#wrapper {
max-width: 400px;
/* 设置最大宽度 */
width: 100%;
/* 宽度设置为100% */
}
.w-100 {
width: 100px;
}
</style>
</head>
<body>
<!-- 登录注册表单 -->
<div id="auth-buttons">
<div class="w-100 mb-4">
<input id="account-input" class="form-control mb-2" type="text" placeholder="账户">
<input id="password-input" class="form-control" type="password" placeholder="密码">
</div>
<div class="w-100">
<button id="login-btn" class="btn btn-primary mr-2 w-100 mb-2">登录</button>
<button id="register-btn" class="btn btn-secondary w-100">注册</button>
</div>
</div>
<div id="container-fluid" style="height: 80%;display: none;">
<div class="row" style="height: 80%;">
<div class="col" id="chat"></div>
</div>
<div>连接状态:<button type="button" class="btn btn-success btn-sm">Success</button></div>
<div class="col-auto" style="padding: 10px;">
<input id="message" class="form-control" type="" placeholder="输入消息..." />
<button id="sendBtn" class="btn btn-primary mt-2">发送</button>
</div>
</div>
<!-- 添加消息提示容器 -->
<div id="message-alert" class="alert alert-danger d-none" role="alert">
<!-- 错误信息将插入到这里 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 获取登录和注册按钮
const loginBtn = document.getElementById('login-btn');
const registerBtn = document.getElementById('register-btn');
const authButtons = document.getElementById('auth-buttons');
const chatContainer = document.getElementById('container-fluid');
const accountInput = document.getElementById('account-input');
const passwordInput = document.getElementById('password-input');
const messageAlert = document.getElementById('message-alert');
changeContentShow = () => {
console.log(authButtons.style.display)
authButtons.style.display = 'none';
chatContainer.style.display = 'block';
}
// 登录按钮点击事件
loginBtn.onclick = () => {
const account = accountInput.value.trim();
const password = passwordInput.value;
if (account === '' || password === '') {
// 使用Bootstrap的消息提示显示错误信息
messageAlert.textContent = '账户和密码不能为空';
messageAlert.classList.remove('d-none'); // 显示消息提示
return;
}
messageAlert.style.display = 'none'
ws.send(JSON.stringify({ type: 'login', username: account, password }));
// 登录成功后隐藏登录按钮,显示聊天界面
changeContentShow()
};
const setTimer = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
// 注册按钮点击事件
registerBtn.onclick = () => {
// 执行注册逻辑
const account = accountInput.value.trim();
const password = passwordInput.value;
if (account === '' || password === '') {
// 使用Bootstrap的消息提示显示错误信息
messageAlert.textContent = '账户和密码不能为空';
messageAlert.classList.remove('d-none'); // 显示消息提示
return;
}
messageAlert.style.display = 'none'
ws.send(JSON.stringify({ type: 'register', username: account, password }));
// ...
// 注册成功后隐藏注册按钮,显示聊天界面
changeContentShow()
};
// ...(保留原有的WebSocket连接和其他代码)...
const ws = new WebSocket('ws://localhost:8082');
const chat = document.getElementById('chat');
const messageInput = document.getElementById('message');
const sendBtn = document.getElementById('sendBtn');
// 监听服务器发送的消息
ws.onmessage = async (event) => {
console.log(event.data);
const data = JSON.parse(event.data);
if (data.type === 'login' && data.code === 200) {
localStorage.setItem('userInfo', JSON.stringify(data.clients));
changeContentShow()
return;
}
if (data.type === 'error') {
alert(data.message);
location.reload();
} else {
if (Array.isArray(data)) {
data.forEach(m => {
addMessage(m, true);
});
} else {
addMessage(data, true);
}
}
};
// 发送消息
sendBtn.onclick = () => {
const msg = messageInput.value.trim();
if (msg) {
ws.send(JSON.stringify({ type: 'message', message: msg }));
addMessage({ message: msg }, false); // 自己发送的消息
messageInput.value = '';
}
};
// 允许按 Enter 键发送消息
messageInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter') {
sendBtn.click();
}
});
function addMessage(message, isOther, senderName = '') {
const messageContainer = document.createElement('div');
messageContainer.className = `message ${isOther ? 'other' : 'self'}`;
// 添加头像
const avatar = document.createElement('img');
avatar.src = isOther ? './public/images/downloaded-image3.jpg' : './public/images/downloaded-image6.jpg'; // 替换为实际头像路径或默认头像
console.log(localStorage.getItem('userInfo'))
const username = JSON.parse(localStorage.getItem('userInfo'))?.username || '我';
avatar.alt = senderName || (isOther ? message.sender : username);
avatar.className = 'avatar';
// 添加网名
const name = document.createElement('div');
name.className = 'message-sender';
name.textContent = senderName || (isOther ? message.sender : username);
// 添加消息内容
const messageContent = document.createElement('div');
if (isOther) {
messageContent.className = `message-content other`;
} else {
messageContent.className = `message-content`;
}
messageContent.textContent = message.message;
// 组合元素
messageContainer.appendChild(avatar);
messageContainer.appendChild(name);
messageContainer.appendChild(messageContent);
chat.appendChild(messageContainer);
chat.scrollTop = chat.scrollHeight;
}
if (localStorage.getItem('userInfo')) {
// const { username, password } = JSON.parse(localStorage.getItem('userInfo'));
// ws.send(JSON.stringify({ type: 'login', username, password }));
changeContentShow()
}
</script>
</body>
</html>
nodejs
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');
const wss = new WebSocket.Server({ port: 8082 });
const usersFilePath = path.join(__dirname, 'users.json');
const chatLogFilePath = path.join(__dirname, 'chatlog.json');
let clientNameCounter = 1;
const clients = new Map();
// 加载用户数据
let users = [];
if (fs.existsSync(usersFilePath)) {
const data = fs.readFileSync(usersFilePath, 'utf-8');
users = JSON.parse(data);
}
// 当有客户端连接时触发
wss.on('connection', (ws) => {
console.log('新客户端已连接');
// 提示客户端输入用户名和密码
// ws.send(JSON.stringify({ type: 'register' }));
// 监听客户端的响应
ws.on('message', (message) => {
console.log(`收到消息: ${message}`);
const data = JSON.parse(message) || {};
if (data.type === 'register') {
// 注册新用户
if (users.find(user => user.username === data.username)) {
ws.send(JSON.stringify({ type: 'error', message: '用户名已存在' }));
} else {
users.push({ username: data.username, password: data.password });
fs.writeFileSync(usersFilePath, JSON.stringify(users, null, 2));
const clientName = data.username;
clients.set(ws, clientName);
console.log(`客户端 ${clientName} 注册成功,连接数: `, wss.clients.size);
}
} else if (data.type === 'login') {
// 验证用户
const user = users.find(user => user.username === data.username && user.password === data.password);
if (user) {
const clientName = user.username;
clients.set(ws, clientName);
console.log(`客户端 ${clientName} 登录成功,连接数: `, wss.clients.size);
// 读取并发送历史聊天记录给新连接的客户端
let messages = [];
if (fs.existsSync(chatLogFilePath)) {
const data = fs.readFileSync(chatLogFilePath, 'utf-8');
messages = JSON.parse(data);
}
ws.send(JSON.stringify({ type: 'login', code: 200, message: '登录成功', clients: user }));
ws.send(JSON.stringify(messages));
} else {
ws.send(JSON.stringify({ type: 'error', message: '用户名或密码错误' }));
}
} else if (data.type === 'message') {
// 处理客户端发送的消息
// console.log(`收到消息: ${data.message}`);
const msg = data.message;
// console.log(clients)
// 读取现有消息
let messages = [];
if (fs.existsSync(chatLogFilePath)) {
const data = fs.readFileSync(chatLogFilePath, 'utf-8');
messages = JSON.parse(data);
}
// 添加新消息
messages.push({
sender: clients.get(ws),
message: msg,
time: new Date().toISOString()
});
// 写回文件
fs.writeFileSync(chatLogFilePath, JSON.stringify(messages, null, 2));
// 广播消息给所有连接的客户端,除了发送者
const msgJson = JSON.stringify({ sender: clients.get(ws), message: msg, time: new Date().toISOString() });
console.log(`广播消息: ${msgJson}`);
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(msgJson);
}
});
}
});
// 处理客户端断开连接
ws.on('close', () => {
console.log(`客户端 ${clients.get(ws)} 已断开`);
clients.delete(ws);
});
});
console.log('WebSocket服务器已启动,监听端口8082');