NestJS-聊天数据库

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 到这里我们的群聊以及对应的聊天基础就搭建好了,接下来只需要针对性的做出优化即可了

相关推荐
@大迁世界4 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路12 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
勇哥java实战分享14 分钟前
短信平台 Pro 版本 ,比开源版本更强大
后端
是一个Bug16 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213818 分钟前
React面向组件编程
开发语言·前端·javascript
学历真的很重要18 分钟前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
计算机毕设VX:Fegn089521 分钟前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
上进小菜猪37 分钟前
基于 YOLOv8 的智能杂草检测识别实战 [目标检测完整源码]
后端
持续升级打怪中39 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路43 分钟前
GDAL 实现矢量合并
前端