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>
相关推荐
Cao_Ron6 分钟前
用 SVG image symbol 画 ECharts graph 圆形节点
前端
hxy060111 分钟前
React-Call让你的React组件像异步函数一样调用
前端·react.js
Hilaku12 分钟前
为什么 AI 写前端时,总是优先选择 React,而不是 Vue?
前端·javascript·程序员
京东云开发者14 分钟前
GPT-5.6、ChatGPT Work 与 Codex 更新食用指南
前端
Cao_Ron25 分钟前
用 ECharts graph 画出卡片式矩形节点
前端
skiyee33 分钟前
换个底座的 Wot Starter 居然让 AI 更懂项目!
前端·ai编程
anyup40 分钟前
这一套绝了!AI 大模型写故事,星云 SDK 驱动 3D 数字人实时演绎
前端·人工智能·aigc
神奇小汤圆1 小时前
2026 Java岗面试题库(已收录GitHub),覆盖所有考点
后端
耀耀切克闹灬1 小时前
短链跳转
前端
孟陬1 小时前
高质量全方位的 AI 工具 sse-stuntman — Mock SSE 接口
前端·node.js·openai