Skip to content

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', '加入失败');
}

然后我们查看一下我们推送的效果 image.png

ok 到这里我们的群聊以及对应的聊天基础就搭建好了,接下来只需要针对性的做出优化即可了

Released under the MIT License.