Appearance
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>