Skip to content

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返回的消息以及其他内容了

image.png

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>

Released under the MIT License.