Appearance
NestJS-聊天数据库
1、数据关系表搭建
接下来我们将群组以及聊天部分结合进入我们的数据库之中,达到数据的存储以及查询使用,从而在nestJS之中实现当前用户登录以后可以打开自己加入的群组 并且点击对应群组进行聊天
👉建立用户群组对应实体groupuser.entity.ts
接下来我们就利用多对多的关系实现我们的群组聊天的结构搭建
javascript
import { Entity, PrimaryGeneratedColumn, Column,CreateDateColumn, UpdateDateColumn,OneToMany, ManyToOne} from 'typeorm';
import { format } from "date-fns";
import { SysUser } from '@/modules/user/user.entity';
import { sysGroup } from '@/modules/group/group.entity';
@Entity('sys_user_group')
export class SysUserGroup {
@PrimaryGeneratedColumn({ name: 'sys_user_group_id', comment: '主键ID' })
sysUserGroupId: number;
@ManyToOne(() => SysUser, sysUser => sysUser.sysUserGroups)
sysUser: SysUser;
@ManyToOne(() => sysGroup, sysGroup => sysGroup.sysUserGroups)
sysGroup: sysGroup;
@Column({
type: "timestamp",
default: () => "CURRENT_TIMESTAMP", // 数据库自动设置创建时间
transformer: {
to: (value: Date) => value, // 写入数据库时保持 Date 类型
from: (value: Date) =>
value ? format(new Date(value), "yyyy-MM-dd HH:mm:ss") : null, // 读取时转为字符串
},
comment: '群创建时间',
name: 'create_time',
})
createTime: Date; // 类型保持为 Date
@Column({
type: "timestamp",
default: () => "CURRENT_TIMESTAMP", // 默认值
onUpdate: "CURRENT_TIMESTAMP", // 关键配置,确保更新时间字段自动更新
transformer: {
to: (value: Date) => value,
from: (value: Date) =>
value ? format(new Date(value), "yyyy-MM-dd HH:mm:ss") : null,
},
comment: '群更新时间',
name: 'update_time',
})
updateTime: Date; // 类型保持为 Date
}
👉用户实体添加群聊字段user.entity.ts
javascript
import { SysGruop } from '@/modules/group/group.entity';
@ManyToMany(() => SysGruop, (group) => group.users)
groups: SysGruop[];
👉group.entity.ts添加多对多字段
javascript
// 群聊
import { ManyToMany } from 'typeorm';
import {User} from '@/modules/user/user.entity'
@ManyToMany(() => User, (user) => user.groups)
users: User[];
👉groupuser.entity.ts关系表之中引入
接下来利用关系表管理用户和群组之间的联系
javascript
// 群聊
import { ManyToOne,ManyToMany, JoinColumn } from 'typeorm';
import {User} from '@/modules/user/user.entity'
import { SysGruop } from '@/modules/group/group.entity';
@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
user: User;
@ManyToOne(() => SysGruop)
@JoinColumn({ name: 'group_id' })
group: SysGruop;
到这里我们的实体关系数据以及对应的数据表就搭建好le
2、服务层管理群聊
在网关接口中管理我们的群聊
👉 chat.module.ts引入模块
引入模块GroupModule
导入模块GroupModule
javascript
import { GroupModule } from '@/modules/group/group.module';
@Module({
imports: [TypeOrmModule.forFeature([SysChat]),GroupModule],
})
👉 group.service.ts加入群聊
用户加入群聊的时候验证用户和群聊都存在建立对应关系
javascript
async joinGroup(userId, groupId){
const user = await this.userRepository.findOneBy({ userId: userId });
const group = await this.groupRepository.findOneBy({ groupId: groupId });
if (user && group) {
const findresult = await this.groupUserRepository.findOne({
where: {
user: { userId: userId }, // user_id
group: { groupId: groupId }, // group_id
},
});
if (findresult) {
return { code: 200, message: '用户已加入群聊' };
} else {
const groupUser = new GroupUser();
// console.log(groupUser,'groupUser');
groupUser.user = user;
groupUser.group = group;
const resGroupuser= await this.groupUserRepository.save(groupUser);
if(resGroupuser){
return {
code: 200,
message: '加入成功',
};
}else{
return {
code: 500,
message: '加入失败',
}
}
}
}else{
return {
code: 500,
message: '用户或群不存在!',
}
}
}
👉chat.gateway.ts 加入群聊
添加加入群聊时候的逻辑
javascript
// 加入聊天室
@SubscribeMessage('joinRoom')
async handleJoinRoom(client: Socket,joinData){
console.log('用户', client.id, '想要加入聊天室', joinData);
// console.log('joinData', joinData);
const {userId, groupId} = joinData;
// 查找群聊
// 加入群聊
const resGroup = await this.groupService.joinGroup(userId, groupId);// console.log('resGroup',resGroup);
if(resGroup?.code === 200){
client.join(groupId); // 将用户加入到群聊房间 groupId作为房间名称
this.users[userId] = client.id;// 记录连接的用户
// 向房间内所有人广播消息
this.server.to(groupId).emit('message', `${client.id} 加入房间 ${groupId}`);
// 每五秒发送心跳保持连接
setInterval(() => {
client.emit(groupId,'heartbeat','保持连接');
}, 5000);
}else{
client.emit('error_message', '加入失败');
}
}
👉group.service.ts 退出群聊
javascript
async leaveGroup(userId, groupId){
const groupUser = await this.groupUserRepository.findOne({
where: { user: { userId: userId }, group: { groupId: groupId } },
});
if (groupUser) {
const resGroupuser=await this.groupUserRepository.remove(groupUser);
if(resGroupuser){
return {
code: 200,
message: '加入成功',
};
}else{
return {
code: 500,
message: '加入失败',
}
}
}
}
👉chat.gateway.ts 退出群聊
写好的模块引入然后我们完善一下
javascript
// 离开聊天室
@SubscribeMessage('leaveRoom')
async handleLeaveRoom(client: Socket, { groupId, message,userId }){
if (groupId&&userId) {
const resGroup = await this.groupService.leaveGroup(userId, groupId);
if(resGroup?.code === 200){
client.leave(groupId); // 从房间中移除用户
// 向房间内所有人广播消息
this.server.to(groupId).emit('message', `${client.id} 离开房间 ${groupId}`);
}else{
client.emit('error_message', '离开失败');
}
} else {
client.emit('error', `用户id:${userId},群组id:${groupId},消息:${message}`);
}
}
测试一下,我们加入群聊的时候,用户可以接受到信息,退出群聊,用户停止接受信息
3、完善群聊
接下来我们就在我们的聊天之中进行完善
先来写发送消息,发送消息的时候带群组ID,消息类型,以及消息发送人等几个关键参数
👉chat.gateway.ts发送消息
javascript
//在前端发送信息以后
socket.emit('sendGruopMessage',
{
groupId: groupId.value,
message: message.value,
userId: userStore.userId,
})
//在服务端接收
// 发送消息到聊天室
@SubscribeMessage('sendGruopMessage')
handleMessage(client: Socket, { groupId, message,userId }){
console.log('发送的消息', groupId, message,userId);
}
这个时候我们输出信息可以看到
javascript
发送的消息 2 我是发送的消息 64
👉chat.service.ts新增消息结合数据库
javascript
async add(createChatDto){
// 通用添加
const resdata = await addApi(this.chatRepository, createChatDto);
return resdata;
}
👉chat.gateway.ts发送消息逻辑写一下
javascript
// 发送消息到聊天室
@SubscribeMessage('sendGruopMessage')
async handleMessage(client: Socket, { groupId, message,userId }){
console.log('发送的消息', groupId, message,userId);
let addChatData = {
senderUserId:userId,
receiverUserId:groupId,
chatType: 'group',// 'group' 'single'
content: message,
messageType: 'text', // 'image', 'audio', 'video', 'file'
status: 'sent', // 'sent', 'delivered', 'read'
attachments: null, // 如果有附件,则可以传递 JSON 对象
groupId: groupId, // 如果是单聊,则为 null
groupName: null, // 如果是单聊,则为 null
isSystem: false,
isDeleted: false,
// createTime: new Date(),
// updateTime: new Date(),
senderUserAvatar: 'https://example.com/avatar.jpg', // 如果有头像 URL
senderUserName: 'John Doe', // 用户昵称
}
// console.log('新增聊天', addChatData);
const res = await this.chatService.add(addChatData);
// console.log('新增聊天反馈信息', res);
if(res?.code === 200){
// 向房间内所有人广播消息
this.server.to(groupId).emit('message',addChatData);
}else{
client.emit('error_message', '网络异常');
}
}
👉结果
服务端日志我们查看一下如下图所示:
javascript
//成功时候的数据如下
{
senderUserId: 64,
receiverUserId: 2,
chatType: 'group',
content: '我是发送的消息',
messageType: 'text',
status: 'sent',
attachments: null,
groupId: 2,
groupName: null,
isSystem: false,
isDeleted: false,
senderUserAvatar: 'https://example.com/avatar.jpg',
senderUserName: 'John Doe'
}
// 成功以后的提示如下
新增聊天反馈信息 { message: '添加成功!', code: 200 }
👉用户连接时推送最新的群聊信息
当用户加入房间的时候给用户推送当前房间最新的历史信息,这部分我们也是利用ws来进行推送,只需要推送一下我们历史表中(群组ID为当前群ID,消息类型为群组)的消息即可
前端接受信息
javascript
// 监听保持连接的消息
socket.on('chatmsglist', (msg) => {
console.log(message);
chatmsglist.value=msg;
// chatmsgList.value.push(msg);
});
👉chat.gateway.ts加入群聊以后推送信息
后端我们拿这个chatmsglist给用户推送信息
javascript
// 加入群聊成功后,发送当前群聊的所有历史消息
let queryParams={
groupId,
page:1,
pageSize:10
};
const reschatMessages = await this.chatService.findAll(queryParams);
if(reschatMessages?.code === 200){
this.server.to(groupId).emit('sendChatMessages',reschatMessages?.data);
}else{
client.emit('error_message', '加入失败');
}
然后我们查看一下我们推送的效果
ok 到这里我们的群聊以及对应的聊天基础就搭建好了,接下来只需要针对性的做出优化即可了