基于nodejs+json+websocket+html的聊天应用

ops/2025/2/1 4:03:53/

实现

html

<html lang="zh-CN"><head><meta charset="UTF-8"><title>Instant Messaging</title><!-- 引入Bootstrap CSS --><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/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/bootstrap@5.3.0/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.html" title=json>json');
const chatLogFilePath = path.join(__dirname, 'chatlog.html" title=json>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');

http://www.ppmy.cn/ops/154651.html

相关文章

【BQ3568HM开发板】如何在OpenHarmony上通过校园网的上网认证

引言 前面已经对BQ3568HM开发板进行了初步测试&#xff0c;后面我要实现MQTT的工作&#xff0c;但是遇到一个问题&#xff0c;就是开发板无法通过校园网的认证操作。未认证的话会&#xff0c;学校使用的深澜软件系统会屏蔽所有除了认证用的流量。好在我们学校使用的认证系统和…

性能测试丨分布式性能监控系统 SkyWalking

软件测试领域&#xff0c;分布式系统的复杂性不断增加&#xff0c;如何保证应用程序的高可用性与高性能&#xff0c;这是每一个软件测试工程师所面临的重大挑战。幸运的是&#xff0c;现在有了一些强大的工具来帮助我们应对这些挑战&#xff0c;其中之一便是Apache SkyWalking。…

Kafka运维宝典 (四)- Kafka 常用命令介绍

Kafka运维宝典 &#xff08;四&#xff09;- Kafka 常用命令介绍 文章目录 Kafka运维宝典 &#xff08;四&#xff09;- Kafka 常用命令介绍1. Kafka Broker 管理相关命令1.1 查看 Kafka Broker 信息1.2 查看 Kafka Broker API 版本 2. Kafka 主题管理相关命令2.1 查看所有主题…

力扣hot100--2

文章目录 力扣hot100-矩阵题目&#xff1a;矩阵置零题解 题目&#xff1a;螺旋矩阵题解 题目&#xff1a;旋转图像题解 力扣hot100-矩阵 题目&#xff1a;矩阵置零 原题链接&#xff1a;矩阵置零 题解 方法&#xff1a;通过先标记需要置为 0 的位置&#xff0c;再进行修改…

20250124 Flink 增量聚合 vs 全量聚合

1. 增量聚合 vs 全量聚合 (1) 增量聚合&#xff08;ReduceFunction / AggregateFunction&#xff09; 工作方式&#xff1a; 逐步计算&#xff1a;每一条数据到达窗口时&#xff0c;立即与当前聚合结果结合&#xff0c;生成新的中间结果。 仅保存中间状态&#xff1a;内存中只…

活动回顾和预告|微软开发者社区 Code Without Barriers 上海站首场活动成功举办!

Code Without Barriers 上海活动回顾 Code Without Barriers&#xff1a;AI & DATA 深入探索人工智能与数据如何变革行业 2025年1月16日&#xff0c;微软开发者社区 Code Without Barriers &#xff08;CWB&#xff09;携手 She Rewires 她原力在大中华区的首场活动“AI &…

深入解析 C++17 中的 std::not_fn

文章目录 1. std::not_fn 的定义与目的2. 基本用法2.1 基本示例2.2 使用 Lambda 表达式2.3 与其他函数适配器的比较3. 在标准库中的应用3.1 结合标准库算法使用3.1.1 std::find_if 中的应用3.1.2 std::remove_if 中的应用3.1.3 其他标准库算法中的应用4. 高级技巧与最佳实践4.1…

pytorch实现半监督学习

半监督学习&#xff08;Semi-Supervised Learning&#xff0c;SSL&#xff09;结合了有监督学习和无监督学习的特点&#xff0c;通常用于部分数据有标签、部分数据无标签的场景。其主要步骤如下&#xff1a; 1. 数据准备 有标签数据&#xff08;Labeled Data&#xff09;&…