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

实现

html

javascript 复制代码
<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

javascript 复制代码
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');
相关推荐
小十十4 小时前
K8s+Nginx-ingress+Websocket基础知识理解
websocket·nginx·kubernetes
Spider_Man4 小时前
前端路由双雄传:Hash vs. History
前端·javascript·html
凤年徐5 小时前
解锁网页魔法:零基础HTML通关秘籍
前端·javascript·css·前端框架·html·web
小西↬17 小时前
vite+vue3+websocket处理音频流发送到后端
javascript·websocket·音视频
Vic1010118 小时前
Hutool 的完整 JSON 工具类示例
开发语言·json
生涯にわたる学び18 小时前
数据库02 网页html01 day44
数据库·html
剪刀石头布啊1 天前
iframe通信、跨标签通信的常见方案
前端·javascript·html
无羡仙1 天前
当点击链接不再刷新页面
前端·javascript·html
电商数据girl1 天前
如何利用API接口与网页爬虫协同进行电商平台商品数据采集?
大数据·开发语言·人工智能·python·django·json
典学长编程1 天前
前端开发(HTML,CSS,VUE,JS)从入门到精通!第二天(CSS)
前端·javascript·css·html