当前位置: 首页 > article >正文

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

实现

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.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');

http://www.kler.cn/a/522381.html

相关文章:

  • 二叉树介绍
  • 大数据Hadoop入门3
  • FileReader使用
  • Synology 群辉NAS安装(7)lsof指令和synogear
  • 国内不支持个人App开发者的应用商店/平台—耻辱柱
  • Linux——网络(udp)
  • 【unity游戏开发之InputSystem——05】PlayerInput组件的介绍(基于unity6开发介绍)
  • android studio生成jsk
  • 通过visual studio生成与查看汇编代码
  • Spring Web MVC 入门
  • 视频行为分析系统,可做安全行为检测,比如周界入侵,打架
  • LeetCode热题100(七)—— 3.无重复字符的最长子串
  • OpenAI的真正对手?DeepSeek-R1如何用强化学习重构LLM能力边界——DeepSeek-R1论文精读
  • Ubuntu Server连接wifi
  • CSS-in-JS详解
  • 【C++数论】880. 索引处的解码字符串|2010
  • 一个小小的个人博客系统
  • GraphQL 教程
  • c语言网 1130数字母
  • DDD 分层架构实战指南:从项目结构到落地挑战
  • DeepSeek R1:推理模型新纪元与价格战
  • 【2025最新计算机毕业设计】基于SSM房屋租赁平台【提供源码+答辩PPT+文档+项目部署】(高质量源码,可定制,提供文档,免费部署到本地)
  • 解除阿里云盘压缩包分享限制的最新工具(2025年更新)
  • Node相关配置迁移
  • Node.js下载安装及环境配置
  • 使用脚本执行地理处理工具