NestJS + socket.io 开发聊天室
第一步:安装依赖
shell
pnpm add @nestjs/platform-socket.io @nestjs/platform-ws @nestjs/websockets socket.io
pnpm add @types/socket.io -D第二步:核心逻辑
src/chat/chat.gateway.ts
js
// chat.gateway.ts
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
OnGatewayConnection,
OnGatewayDisconnect,
ConnectedSocket,
MessageBody,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
// 定义用户接口
interface User {
id: string;
username: string;
socketId: string;
}
@WebSocketGateway({
cors: {
origin: '*',
credentials: true,
},
namespace: 'chat',
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
// 添加 users 属性
private users: Map<string, User> = new Map();
// 监听客户端连接
handleConnection(client: Socket) {
console.log('\n========== 新客户端连接 ==========');
console.log('客户端 Socket ID:', client.id);
console.log('客户端 IP:', client.handshake.address);
console.log('客户端 Headers:', client.handshake.headers);
console.log('客户端 Query 参数:', client.handshake.query);
console.log('传输方式:', client.conn.transport);
console.log('===================================\n');
}
// 监听客户端断开连接
handleDisconnect(client: Socket) {
console.log('\n========== 客户端断开连接 ==========');
console.log('客户端 Socket ID:', client.id);
console.log('断开时间:', new Date().toLocaleString());
console.log('===================================\n');
// 移除断开连接的用户
const user = Array.from(this.users.values()).find(
(u) => u.socketId === client.id,
);
if (user) {
this.users.delete(user.id);
// 广播用户离开消息
this.server.emit('usersUpdate', Array.from(this.users.values()));
}
}
// 用户登录 - 打印详细客户端信息
@SubscribeMessage('login')
handleLogin(
@ConnectedSocket() client: Socket,
@MessageBody() data: { username: string },
) {
console.log('\n========== 用户登录 ==========');
console.log('客户端信息:');
console.log(' - Socket ID:', client.id);
console.log(' - IP地址:', client.handshake.address);
console.log(' - 连接的房间:', this.getClientRooms(client));
console.log(' - 是否已认证:', client.handshake.auth);
console.log('用户数据:');
console.log(' - 用户名:', data.username);
console.log(' - 完整数据:', JSON.stringify(data, null, 2));
console.log('===============================\n');
// 可以打印更多客户端信息
this.printClientDetails(client);
// 保存用户信息
const user: User = {
id: Date.now().toString(),
username: data.username,
socketId: client.id,
};
this.users.set(user.id, user);
// 广播用户加入
this.server.emit('usersUpdate', Array.from(this.users.values()));
// 业务逻辑...
return { success: true, user: { id: client.id, username: data.username } };
}
// 发送消息 - 打印消息内容和客户端信息
@SubscribeMessage('message')
async handleMessage(
@ConnectedSocket() client: Socket,
@MessageBody() data: { username: string; content: string },
) {
const realOnlineCount = await this.getOnlineUsersCount();
console.log('\n========== 收到新消息 ==========');
console.log('发送者:');
console.log(' - Socket ID:', client.id);
console.log(' - 用户名:', data.username);
console.log('消息内容:');
console.log(' - 文本:', data.content);
console.log(' - 长度:', data.content.length);
console.log(' - 时间:', new Date().toLocaleString());
console.log('在线用户数:', realOnlineCount);
console.log('================================\n');
// 广播消息...
this.server.emit('message', {
id: Date.now().toString(),
username: data.username,
content: data.content,
timestamp: new Date(),
});
}
// 私聊消息 - 打印详细信息
@SubscribeMessage('privateMessage')
handlePrivateMessage(
@ConnectedSocket() client: Socket,
@MessageBody()
data: { toUserId: string; fromUsername: string; content: string },
) {
console.log('\n========== 私聊消息 ==========');
console.log('发送方信息:');
console.log(' - Socket ID:', client.id);
console.log(' - 用户名:', data.fromUsername);
console.log('接收方信息:');
console.log(' - 用户ID:', data.toUserId);
console.log('消息内容:');
console.log(' - 文本:', data.content);
console.log('===============================\n');
// 查找目标用户
const targetUser = this.users.get(data.toUserId);
if (targetUser) {
const privateMessage = {
id: Date.now().toString(),
username: data.fromUsername,
content: `[私聊] ${data.content}`,
timestamp: new Date(),
type: 'private',
};
// 发送给目标用户
this.server
.to(targetUser.socketId)
.emit('privateMessage', privateMessage);
// 发送给发送方确认
client.emit('privateMessage', {
...privateMessage,
content: `[私发给 ${targetUser.username}] ${data.content}`,
});
}
}
// 获取在线用户 - 打印请求信息
@SubscribeMessage('getUsers')
handleGetUsers(@ConnectedSocket() client: Socket) {
console.log(
`\n[${new Date().toLocaleTimeString()}] 客户端 ${client.id} 请求在线用户列表`,
);
client.emit('usersUpdate', Array.from(this.users.values()));
}
// 辅助方法:获取客户端房间列表(类型安全)
private getClientRooms(client: Socket): string[] {
// 方法1:使用 Set 迭代器
const rooms: string[] = [];
if (client.rooms) {
// 将 Set 转换为数组
if (typeof client.rooms.forEach === 'function') {
client.rooms.forEach((room: string) => {
rooms.push(room);
});
}
// 或者使用扩展运算符(需要配置 tsconfig)
// return [...client.rooms];
}
return rooms;
}
// 辅助方法:获取在线用户数量
private async getOnlineUsersCount(): Promise<number> {
try {
// 方法1:获取所有连接的 sockets
const sockets = await this.server.fetchSockets();
return sockets.length;
} catch (error) {
console.error('获取在线用户数失败:', error);
return 0;
}
}
// 辅助方法:打印客户端详细信息(修复 rooms 类型)
private printClientDetails(client: Socket) {
console.log('\n========== 客户端详细信息 ==========');
console.log('1. 基础信息:');
console.log(` - ID: ${client.id}`);
console.log(` - 是否连接: ${client.connected}`);
console.log(` - 是否断开: ${client.disconnected}`);
console.log('\n2. 握手信息:');
console.log(` - 地址: ${client.handshake.address}`);
console.log(` - URL: ${client.handshake.url}`);
console.log(
` - 协议: ${client.handshake.headers['sec-websocket-version'] || 'HTTP'}`,
);
console.log('\n3. Headers:');
Object.entries(client.handshake.headers).forEach(([key, value]) => {
console.log(` - ${key}: ${value as string}`);
});
console.log('\n4. Query 参数:');
Object.entries(client.handshake.query).forEach(([key, value]) => {
console.log(` - ${key}: ${value as string}`);
});
console.log('\n5. Auth 数据:');
console.log(` - ${JSON.stringify(client.handshake.auth, null, 2)}`);
console.log('\n6. 房间信息:');
// 安全地处理 rooms
const roomsList = this.getClientRooms(client);
console.log(` - 房间数量: ${roomsList.length}`);
console.log(` - 房间列表: ${roomsList.join(', ') || '无'}`);
// 额外显示房间详细信息
if (roomsList.length > 0) {
console.log(' - 详细房间信息:');
roomsList.forEach((room, index) => {
console.log(` ${index + 1}. ${room}`);
});
}
console.log('\n7. 数据存储:');
console.log(` - Data: ${JSON.stringify(client.data, null, 2)}`);
console.log('=====================================\n');
}
}src/app.module.ts
第三步:注册
js
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ChatGateway } from './chat/chat.gateway';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService, ChatGateway],
})
export class AppModule {}第四步:测试
shell
npm run start:dev