NestJS-websockets和socket.io

NestJS-websockets和socket.io

1、安装

javascript 复制代码
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io

2、创建一个聊天模块

javascript 复制代码
import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';

@Module({
  providers: [ChatGateway],
})
export class ChatModule {}

## 1、注意部分
`需要注意:这部分模块已经自动加入到我们的根模块之中`,没有的话我们需要手动导入

```javascript
import { ChatModule } from './modules/chat/chat.module'; // 导入聊天模块
@Module({
  imports: [
    TypeOrmModule.forRoot(typeOrmConfig),  // 数据库
    ChatModule, //聊天模块
  ],
})
export class AppModule {}

2、依赖安装

这部分我们使用的依赖主要如下

javascript 复制代码
npm install @nestjs/websockets 
npm install @nestjs/platform-socket.io 
npm install socket.io

yarn add @nestjs/websockets 
yarn add @nestjs/platform-socket.io 
yarn add socket.io


// 几个模块依赖的作用
@nestjs/websockets依赖
WebSocket 支持模块,帮助在 NestJS中实现WebSocket通信

@nestjs/platform-socket.io
NestJS 对 Socket.IO 的支持包,实现实时的双向通信

socket.io
实际的 WebSocket 连接和消息传输都是通过 socket.io 来实现的

3、WebSocket 网关模块

网关是处理客户端连接、消息发送和接收的核心。我们将创建一个简单的 ChatGateway

这里我们主要实现的功能就是用户可以发送和接受websocket消息就算初步连接成功

👉 chat.gateway.ts

这部分我们先写一个最简单的ws部分提供测试,这里大致就是提供用户的ws添加

javascript 复制代码
// src/modules/chat/chat.gateway.ts
import { WebSocketGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway(3000, { cors: true }) // 设置 WebSocket 服务器监听端口
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer() server: Server;

  // 存储连接的用户
  private users: Set<Socket> = new Set();

  // 当有客户端连接时触发
  handleConnection(client: Socket) {
    console.log('WebSocket 服务器已连接!', client.id);
    this.users.add(client); // 添加用户到集合中
  }

  // 当客户端断开连接时触发
  handleDisconnect(client: Socket) {
    this.users.delete(client); // 从集合中移除用户
    console.log('Client disconnected:', client.id);
  }

  // 监听前端发来的消息
  @SubscribeMessage('message')
  handleMessage(client: Socket, payload: string): void {
    console.log('WebSocket 服务器收到消息:', payload);
    // 广播消息给所有连接的客户端
    this.server.emit('message', payload);

    // 广播消息给所有连接的用户
    this.users.forEach(user => {
      if (user !== client) {
        user.emit('message', payload);
      }
    });
    // return { event: 'message', data: payload };
  }
}

👉允许跨域

javascript 复制代码
 cors: {
    origin: '*',  // 允许所有域名访问
    methods: ['GET', 'POST'],  // 允许的 HTTP 方法
    allowedHeaders: ['Content-Type'],  // 允许的请求头
    credentials: true,  // 是否允许发送凭证(如 Cookies)
  },

👉 chat.module.ts

在模块之中引入websocketio的网关层

javascript 复制代码
import { ChatGateway } from './chat.gateway';

@Module({
  imports: [TypeOrmModule.forFeature([SysChat])],  // 导入实体模块
  controllers: [ChatController],// 注册控制器
  providers: [ChatService, ChatGateway],
})
export class ChatModule {}

这个时候前台已经可以正常访问和接受我们的websocket推送的信息了

👉页面测试

写一个单页面进行测试

javascript 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebSocket Chat</title>
  <script src="https://cdn.socket.io/4.3.2/socket.io.min.js"></script>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
    }
    #messageBox {
      width: 100%;
      height: 100px;
      margin-bottom: 10px;
    }
    #sendBtn {
      padding: 10px 15px;
      font-size: 16px;
      cursor: pointer;
    }
    #messages {
      margin-top: 20px;
      max-width: 100%;
      max-height: 200px;
      overflow-y: scroll;
    }
    .message {
      padding: 5px;
      border-bottom: 1px solid #ccc;
    }
  </style>
</head>
<body>

  <h1>WebSocket Chat</h1>
  <textarea id="messageBox" placeholder="Type your message here..."></textarea><br>
  <button id="sendBtn">Send Message</button>

  <div id="messages"></div>

  <script>
    const socket = io('http://localhost:3001'); // 连接到 WebSocket 服务器(端口 8888)

    // 监听服务器推送的消息
    socket.on('message', (message) => {
      console.log('Received message:', message);
      const messagesDiv = document.getElementById('messages');
      const messageElement = document.createElement('div');
      messageElement.classList.add('message');
      messageElement.textContent = message;
      messagesDiv.appendChild(messageElement);
    });

    // 发送消息到服务器
    const sendMessage = (message) => {
      socket.emit('message', message);
    };

    // 发送按钮点击事件
    document.getElementById('sendBtn').addEventListener('click', () => {
      const message = document.getElementById('messageBox').value;
      sendMessage(message);
      document.getElementById('messageBox').value = '';  // 清空输入框
    });
  </script>
</body>
</html>

可以看到我们已经可以拿到我们ws返回的消息以及其他内容了

4、群组实时聊天

接下来我们利用WebSocket 建立不同的群组,并且用户可以加入不同的群组之中进行聊天

👉创建一个假的群组房间

javascript 复制代码
 private rooms: Room[] = [
    { name: 'General', users: [] },
    { name: 'Sports', users: [] },
    { name: 'Tech', users: [] },
  ];

👉模拟用户加入群组

javascript 复制代码
// 加入聊天室
  @SubscribeMessage('joinRoom')
  handleJoinRoom(client: Socket, roomName: string): void {
    const room = this.rooms.find(r => r.name === roomName);
    if (room && !room.users.includes(client.id)) {
      room.users.push(client.id);
      client.join(roomName);
      this.server.to(roomName).emit('message', `${client.id} has joined the room: ${roomName}`);
    } else {
      client.emit('error', `Room ${roomName} not found or you're already in it.`);
    }
  }

👉模拟用户退出群组

javascript 复制代码
  // 离开聊天室
  @SubscribeMessage('leaveRoom')
  handleLeaveRoom(client: Socket, roomName: string): void {
    const room = this.rooms.find(r => r.name === roomName);
    if (room && room.users.includes(client.id)) {
      room.users = room.users.filter(user => user !== client.id);
      client.leave(roomName);
      this.server.to(roomName).emit('message', `${client.id} has left the room: ${roomName}`);
    } else {
      client.emit('error', `You're not in the room: ${roomName}`);
    }
  }

👉发送消息到聊天室

javascript 复制代码
 // 发送消息到聊天室
  @SubscribeMessage('sendMessage')
  handleMessage(client: Socket, { roomName, message }: { roomName: string, message: string }): void {
    const room = this.rooms.find(r => r.name === roomName);
    if (room) {
      this.server.to(roomName).emit('message', `${client.id}: ${message}`);
    } else {
      client.emit('error', `Room ${roomName} not found.`);
    }
  }

👉页面测试

新建一个页面实现我们对于socket的使用

javascript 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebSocket Chat</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
    }
    #messages {
      border: 1px solid #ccc;
      padding: 10px;
      height: 300px;
      overflow-y: scroll;
    }
    #input {
      width: 300px;
    }
  </style>
</head>
<body>

  <h1>WebSocket Chat</h1>

  <div>
    <input type="text" id="roomName" placeholder="Enter room name" />
    <button id="joinRoomBtn">Join Room</button>
    <button id="createRoomBtn">Create Room</button>
    <button id="leaveRoomBtn">Leave Room</button>
  </div>

  <div>
    <input type="text" id="message" placeholder="Type a message" />
    <button id="sendMessageBtn">Send Message</button>
  </div>

  <div id="messages"></div>

  <script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.5.1/dist/socket.io.min.js"></script>
  <script>
    const socket = io('http://localhost:3000'); // 连接到 WebSocket 服务器
    const messagesDiv = document.getElementById('messages');
    const joinRoomBtn = document.getElementById('joinRoomBtn');
    const leaveRoomBtn = document.getElementById('leaveRoomBtn');
    const sendMessageBtn = document.getElementById('sendMessageBtn');
    const roomNameInput = document.getElementById('roomName');
    const messageInput = document.getElementById('message');

    // 当服务器发送消息时,显示在消息框中
    socket.on('message', function (data) {
      const p = document.createElement('p');
      p.textContent = data;
      messagesDiv.appendChild(p);
    });

    socket.on('error', function (data) {
      const p = document.createElement('p');
      p.style.color = 'red';
      p.textContent = data;
      messagesDiv.appendChild(p);
    });

    // 加入聊天室
    joinRoomBtn.addEventListener('click', function () {
      const roomName = roomNameInput.value;
      if (roomName) {
        socket.emit('joinRoom', roomName);
      } else {
        alert('Please enter a room name');
      }
    });

    // 退出聊天室
    leaveRoomBtn.addEventListener('click', function () {
      const roomName = roomNameInput.value;
      if (roomName) {
        socket.emit('leaveRoom', roomName);
      } else {
        alert('Please enter a room name');
      }
    });

    // 发送消息到聊天室
    sendMessageBtn.addEventListener('click', function () {
      const roomName = roomNameInput.value;
      const message = messageInput.value;
      if (roomName && message) {
        socket.emit('sendMessage', { roomName, message });
        messageInput.value = ''; // 清空消息输入框
      } else {
        alert('Please enter both room name and message');
      }
    });

    // 创建聊天室(通过加入聊天室实现,后端没有创建聊天室接口)
    document.getElementById('createRoomBtn').addEventListener('click', function () {
      const roomName = roomNameInput.value;
      if (roomName) {
        socket.emit('joinRoom', roomName); // 直接加入房间,相当于创建
      } else {
        alert('Please enter a room name');
      }
    });

    // 自动滚动到最底部
    messagesDiv.scrollTop = messagesDiv.scrollHeight;
  </script>
</body>
</html>
相关推荐
晚霞的不甘6 分钟前
Flutter for OpenHarmony天气卡片应用:用枚举与动画打造沉浸式多城市天气浏览体验
前端·flutter·云原生·前端框架
苏三说技术19 分钟前
xxl-job 和 elastic-job,哪个更好?
后端
xkxnq20 分钟前
第五阶段:Vue3核心深度深挖(第74天)(Vue3计算属性进阶)
前端·javascript·vue.js
三小河27 分钟前
Agent Skill与Rules的区别——以Cursor为例
前端·javascript·后端
Hilaku34 分钟前
不要在简历上写精通 Vue3?来自面试官的真实劝退
前端·javascript·vue.js
三小河40 分钟前
前端视角详解 Agent Skill
前端·javascript·后端
牛奔1 小时前
Go 是如何做抢占式调度的?
开发语言·后端·golang
Aniugel1 小时前
单点登录(SSO)系统
前端
颜酱1 小时前
二叉树遍历思维实战
javascript·后端·算法
鹏多多1 小时前
移动端H5项目,还需要react-fastclick解决300ms点击延迟吗?
前端·javascript·react.js