上节学了 Nest 里如何创建 WebSocket 服务,这节我们实现下群聊功能。
微信我们可以在不同的群聊里聊天:
如何实现这种功能呢?
这就要用到 socket.io 的 room 功能了。
socket.io 支持加入房间:
socket.join('room666')
可以向对应房间发消息:
serveer.to("room666").emit("新成员加入了群聊")
这样就实现了群聊功能。
我们来写一下:
nest new group-chat-room
进入项目,安装 websocket 的包:
npm i --save @nestjs/websockets @nestjs/platform-socket.io socket.io
然后创建个 websocket 模块:
nest g resource chatroom
注意,选择生成 WebSockets 类型的代码。
这样,基于 websocket 的 crud 代码就生成了:
这些我们上节写过。
在 main.ts 里支持下 pages 这个静态目录的访问:
import { NestApplication, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets('pages');
await app.listen(3000);
}
bootstrap();
创建 pages/index.html
<html>
<head>
<script src="https://cdn.socket.io/4.3.2/socket.io.min.js"></script>
<script>
const socket = io('http://localhost:3000');
socket.on('connect', function() {
console.log('Connected');
socket.emit('findAllChatroom', function(data) {
console.log('allChatroom', data);
});
});
socket.on('disconnect', function() {
console.log('Disconnected');
});
</script>
</head>
<body></body>
</html>
把服务跑起来:
npm run start:dev
浏览器访问下:
打印了返回的消息。
然后我们实现下房间的功能:
import { MessageBody,SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway()
export class ChatroomGateway {
@WebSocketServer() server: Server;
@SubscribeMessage('joinRoom')
joinRoom(client: Socket, room: string): void {
console.log(room);
client.join(room);
this.server.to(room).emit('message', `新用户加入了 ${room} 房间`);
}
@SubscribeMessage('sendMessage')
sendMessage(client: Socket, payload: any): void {
console.log(payload);
this.server.to(payload.room).emit('message', payload.message);
}
}
添加一个 joinRoom 的路由,它接收 room 参数,把 client 加入对应房间。
然后给这个房间发送一个欢迎消息。
然后加一个 sendMessage 的路由,接收房间和消息,可以给对应 room 发送消息。
之前我们都是这样取消息:
这两种是等价写法:
然后我们在客户端也加入 room 功能:
<html>
<head>
<script src="https://cdn.socket.io/4.3.2/socket.io.min.js"></script>
<script>
const roomName = prompt('输入群聊名');
if(roomName) {
const socket = io('http://localhost:3000');
socket.on('connect', function() {
console.log('Connected');
socket.emit('joinRoom', roomName);
socket.on('message', (message) => {
console.log('收到来自房间的消息:', message);
});
socket.emit('sendMessage', { room: roomName, message: 'Hello, everyone!' });
});
socket.on('disconnect', function() {
console.log('Disconnected');
});
} else {
alert('请输入群聊名');
}
</script>
</head>
<body></body>
</html>
进入页面首先输入群聊名,然后加入对应房间,并发一个消息。
测试下:
打开页面,进入 aaa 房间,发送了一条消息:
再打开一个页面,进入 aaa 房间:
这时候之前那个房间就有 2 条消息了:
再打开一个页面,进入 bbb 房间,这时候之前的 aaa 房间并没有收到消息:
这样,群聊房间功能就实现了。
我们再完善一下:
首先 payload 都传入 room 和 nickName。
import { MessageBody,SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway()
export class ChatroomGateway {
@WebSocketServer() server: Server;
@SubscribeMessage('joinRoom')
joinRoom(client: Socket, payload: any): void {
console.log(payload.roomName);
client.join(payload.roomName);
this.server.to(payload.roomName).emit('message', {
nickName: payload.nickName,
message: `${payload.nickName} 加入了 ${payload.roomName} 房间`
});
}
@SubscribeMessage('sendMessage')
sendMessage(@MessageBody() payload: any): void {
console.log(payload);
this.server.to(payload.room).emit('message', { nickName: payload.nickName, message: payload.message});
}
}
然后改下 client:
<html>
<head>
<script src="https://cdn.socket.io/4.3.2/socket.io.min.js"></script>
</head>
<body>
<div id="messageBox">
</div>
<input id="messageInput"/>
<button id="sendMessage">发送</button>
<script>
const messageBox = document.getElementById('messageBox');
const messageInput = document.getElementById('messageInput');
const sendMessage = document.getElementById('sendMessage');
const roomName = prompt('输入群聊名');
const nickName = prompt('输入昵称');
if(roomName && nickName) {
const socket = io('http://localhost:3000');
socket.on('connect', function() {
console.log('Connected');
socket.emit('joinRoom', { roomName, nickName});
socket.on('message', (payload) => {
console.log('收到来自房间的消息:', payload);
const item = document.createElement('div');
item.className = 'message'
item.textContent = payload.nickName + ': ' + payload.message;
messageBox.appendChild(item);
});
});
sendMessage.onclick = function() {
socket.emit('sendMessage', { room: roomName, nickName, message: messageInput.value });
}
socket.on('disconnect', function() {
console.log('Disconnected');
});
}
</script>
</body>
</html>
进入页面输入群聊名和昵称。
加上 messageBox 用于显示消息。
在输入框输入内容,点击的时候发送消息。
测试下:
打开一个页面发消息:
再打开一个页面:
可以看到,另一个页面也收到消息了,因为这俩在一个房间。
我们进入其他房间发消息试试:
这时候另外两个页面就没收到消息了:
因为在不同房间。
代码在小册仓库。
总结
这节我们实现了群聊功能。
主要是基于 socket.io 的 room 实现的,可以把 client socket 加入某个 room,然后向这个 room 发消息。
这样,发消息的时候带上昵称、群聊名等内容,就可以往指定群聊发消息了。
更完善的聊天室,会带上 userId、groupId 等,然后可以根据这俩 id 查询更详细的信息,但只是消息格式更复杂一些,原理都是 room。