genaller [github]
阅读 241
nestjs+vue+ts打造一个酷炫的星空聊天室

简介

😛 闲暇时间想做一个聊天室来巩固前端技能,于是在2020年6月24号就开始了阿童木聊天室的开发之旅。

😈 项目采用全 typescript 开发,这是为了以后的功能迭代打基础。当然,我本身也是很喜欢 typescript 的。

项目界面

图片

功能介绍

  • 更改用户名/头像上传
  • 群聊/私聊
  • 创建群/加入群聊/模糊搜索群
  • 添加好友/模糊搜索好友
  • 表情包
  • 消息分页

技术概览

  • Typescript:JavaScript 的一个超集,它最大的优势是提供了类型系统和提高了代码的可读性和可维护性。
  • Vue2.6.x:前端渐进式框架。
  • Socket/io:实现实时通信,websocket 第三方库。
  • Vuex:专为 Vue.js 应用程序开发的状态管理模式。
  • Nestjs:是一个用于构建高效、可扩展的 Node.js 服务端应用框架,基于 TypeScript 编写并且结合了 OOP1、FP2、FRP3 的相关理念。
  • Typeorm: 支持最新的 JavaScript 特性并提供额外的特性以帮助你开发任何使用数据库的应用程序。
  • ES6+:采用 ES6+ 语法,箭头函数、async/await 等等语法很好用。
  • SASS(SCSS):用 SCSS 做 CSS 预处理语言,可以使用最高效的方式,以少量的代码创建复杂的设计。

数据库表结构设计

数据库使用了六张表,分别是

  • user 用户表
  • group 群表
  • user_group 用户_群中间表
  • group_message 群消息表
  • user_friend 用户_好友中间表
  • friend_message 私聊消息表

其中中间表用于建立对于群/好友与用户之间的联系。下面是我画的思维导图,相信大家看完就能理解其中的奥妙啦。

图片

WebSocket的建立逻辑

用户房间的建立

每个用户进入聊天室都会自动加入名为 public 的 WebSocket 房间和以用户 id 为命名的 WebSocket 房间,其中建立用户房间是为了方便系统针对用户单独广播事件。如果不了解房间的概念,可以认为只有房间内的人才能接收到房间内的广播,更多信息请移步 socket.io 官网。

群聊房间的建立

以 groupId 作为 WebSocket 房间的名字,每次有新用户加入群都会在群房间内广播用户进群事件并附带上新用户的详细信息,然后其他用户会存储新用户的信息。当新用户发消息的时候,其他用户收到消息后可以通过消息的userId找到对应用户的详细信息。这样能保证消息发出后其他用户能够快速知道消息的主人.

私聊房间的建立

每当发起一个添加好友的请求,就会把用户的 userId 和好友的 userId 拼接成的字符串作为 WebSocket 的房间名,从而建立私聊房间。

后端架构

后端使用了 nestjs 这个近几年发展迅猛的 node.js 框架。nestjs 的优势有很多, 我只列举出以下几点:

  1. 基于 TypeScript 构建,同时兼容普通的 ES6。
  2. nestjs 的依赖注入以及模块化的思想,使得代码结构清晰,便于维护。
  3. nestjs 的 @nestjs/websockets 包封装好了对于 WebSocket 事件的处理,对于开发聊天室有优势。

下面是一些后端的逻辑代码。

  1. 使用 nestjs 建立 WebSocket 连接
    // chat.gateway.ts
    @WebSocketGateway()
    export class ChatGateway {
    // socket连接钩子
    async handleConnection(client: Socket): Promise<string> {
     let userRoom = client.handshake.query.userId;
     // 连接默认加入public房间
     client.join('public');
     // 用户独有消息房间 根据userId
     if(userRoom) {
       client.join(userRoom);
     }
     return '连接成功'
    }
    }
  2. 封装全局的中间件,方便在开发过程中调试。
    // middleware.js
    export function logger(req, res, next) {
    const { method, path } = req;
    console.log(`${method} ${path}`);
    next();
    };
    

// main.js
使用全局中间件
app.use(logger)

3. nestjs 的静态资源配置
```js
// main.js
配置静态资源
app.useStaticAssets(join(__dirname, '../public/', 'static'), {
  prefix: '/static/', 
});
  1. nestjs 自定义异常过滤器
    // http-exception.filter.ts
    @Catch(HttpException)
    export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
    catch(exception: HttpException, host: ArgumentsHost) {
     const ctx = host.switchToHttp();
     const response = ctx.getResponse();
     const request = ctx.getRequest();
     const status = exception.getStatus();
     const exceptionRes: any = exception.getResponse();
     const {
       error,
       message,
     } = exceptionRes;
     // 以下格式将在请求错误时返回给前端
     response.status(status).json({
       status,
       timestamp: new Date().toISOString(),
       path: request.url,
       error,
       message,
     });
    }
    }

前端架构

页面初始化

初始化会调起 WebSocket 连接函数,然后拿到用户所有的群信息和所有的好友信息,再通过建立 WebSocket 通信的规则加入到对应的房间,然后使用 vuex 派发最新的数据。

数据处理

群的数据类型

// 群组
interface Group {
  groupId: string;
  userId: string; // 群主id
  groupName: string;
  notice: string;
  messages: GroupMessage[];
  createTime: number;
}

好友的数据类型

// 好友
interface Friend {
  userId: string;
  username: string;
  avatar: string;
  role?: string;
  tag?: string;
  messages: FriendMessage[];
  createTime: number;
}

我曾经用对象数组 [ friend1 , friend2 ... ] 这样的结构去管理所有的 群/好友 数据,但是当数据量很大的时候,查询和更新 群/好友 数据会变得很消耗性能。每次好友名字变了或者头像变了就得遍历查找一遍数组才能更新相应信息。

后来我用对象的结构,优化了聊天室的代码。我使用一个对象 gather 来管理 群/好友 的信息, gather 的键为 groupId/userId ,值为对应的 群/好友 的数据,结构如下

gather = {
 'userId': {
   userId: 'userId'
   username: 'xxx'
   messages: [];
   ...
 }
}

每个群和用户都有独一无二的 id,所以无需担心重复。使用这样的结构后,更新数据便非常的轻松,只需要拿到需要更新的id,然后直接覆盖 gather.id 对应的值就行了

vuex

聊天室涉及到数据的即时更新和各个 vue 组件的数据同步,处理这样的业务场景是 vuex 的强项。 我把建立 WebSocket 连接的函数写在了 vuex 的 action 中,在用户登录成功后调起连接函数,下面是精简后的代码。

// actions.ts
const actions: ActionTree<ChatState, RootState> = {
  // 初始化WebSocket
  async connectSocket({commit, state, dispatch, rootState}, callback) {
    // WebSocket连接建立
    socket.on('connect', async () => {
      // 订阅群消息时间
      socket.on('groupMessage', (res: any) => {
        console.log('on groupMessage', res)
        if (!res.code) {
          // 对群消息进行处理
          commit(ADD_GROUP_MESSAGE, res.data)
        }
      })
    }
  }

不得不说 vuex-class 这个库帮了我很大的忙,它是 vuex 结合 typescript 开发的很好的粘合剂。
使用了 vuex-class ,那么在 vue 组件中调用 vuex 的方法只需要这么写:

// GenalChat.vue
import { namespace } from 'vuex-class'
const appModule = namespace('app')
export default class GenalChat extends Vue {
  @appModule.Getter('user') user: User;
  @appModule.Action('login') login: Function;
}

总结

  目前聊天室已经具备完整的聊天功能,同时,我今后也会陆续开发更多酷炫的功能,喜欢的朋友给个 star 鼓励一下我吧!

项目地址

github: genal-chat

关注下面的标签,发现更多相似文章
评论
相关推荐
f'g'h'g'h'f'g'h'g'f'h

发布告白夫妇...

nestjs+vue+ts打造一个酷炫的星空聊天室

简介😛 闲暇时间想做一个聊天室来巩固前端技能,于是在2020年6月24号就开始了阿童木聊天室的开发之旅。😈 项目采用全 typescript 开发,这是为了以后的功能迭代打基础。当然,我本身也是很...

好几个放假

东方红格当然多层次发鬼地方...

《快学 Go 语言》第 16 课 —— 包管理 GOPATH 和 Vendor

来源: 知乎 原文: 《快学 Go 语言》第 16 课 —— 包管理 GOPATH 和 Vendor 到目前位置我们一直在编写单文件代码,只有一个 main.go 文件。本节我们要开始朝完整的项目结构...

dddd

...

《快学 Go 语言》第 15 课 —— 反射

来源: 知乎 原文: 《快学 Go 语言》第 15 课 —— 反射 反射是 Go 语言学习的一个难点,但也是非常重要的一个知识点。反射是洞悉 Go 语言类型系统设计的法宝,Go 语言的 ORM 库离不...

hello

*《快学 Go 语言》第 14 课 —— 魔术变性指针来源: 知乎 原文: 《快学 Go 语言》第 14 课 —— 魔术变性指针 本节我们要学习一些 Go 语言的魔法功能,通过内置的 unsafe 包...

《快学 Go 语言》第 14 课 —— 魔术变性指针

来源: 知乎 原文: 《快学 Go 语言》第 14 课 —— 魔术变性指针 本节我们要学习一些 Go 语言的魔法功能,通过内置的 unsafe 包提供的功能,直接操纵指定内存地址的内存。有了 unsa...

《快学 Go 语言》第 13 课 —— 并发与安全

来源: 知乎 原文: 《快学 Go 语言》第 13 课 —— 并发与安全 上一节我们提到并发编程不同的协程共享数据的方式除了通道之外还有就是共享变量。虽然 Go 语言官方推荐使用通道的方式来共享数据,...

《快学 Go 语言》第 12 课 —— 通道

来源: 知乎 原文: 《快学 Go 语言》第 12 课 —— 通道 不同的并行协程之间交流的方式有两种,一种是通过共享变量,另一种是通过队列。Go 语言鼓励使用队列的形式来交流,它单独为协程之间的队列...

《快学 Go 语言》第 11 课 —— 千军万马跑协程

来源: 知乎 原文: 《快学 Go 语言》第 11 课 —— 千军万马跑协程 协程和通道是 Go 语言作为并发编程语言最为重要的特色之一,初学者可以完全将协程理解为线程,但是用起来比线程更加简单,占用...

测试发布

测试文章发布...

《快学 Go 语言》第 10 课 —— 错误与异常

来源: 知乎 原文: 《快学 Go 语言》第 10 课 —— 错误与异常 Go 语言的异常处理语法绝对是独树一帜,在我见过的诸多高级语言中,Go 语言的错误处理形式就是一朵奇葩。一方面它鼓励你使用 C...

《快学 Go 语言》第 9 课 —— 接口

来源: 知乎 原文: 《快学 Go 语言》第 9 课 —— 接口 接口是一个对象的对外能力的展现,我们使用一个对象时,往往不需要知道一个对象的内部复杂实现,通过它暴露出来的接口,就知道了这个对象具备哪...

《快学 Go 语言》第 8 课 —— 结构体

来源: 知乎 原文: 《快学 Go 语言》第 8 课 —— 结构体 本节我们要开讲 Go 语言在数据结构上最重要的概念 —— 结构体。如果说 Go 语言的基础类型是原子,那么结构体就是分子。分子是原子...

《快学 Go 语言》第 7 课 —— 字符串

来源: 知乎 原文: 《快学 Go 语言》第 7 课 —— 字符串 字符串通常有两种设计,一种是「字符」串,一种是「字节」串。「字符」串中的每个字都是定长的,而「字节」串中每个字是不定长的。Go 语言...

《快学 Go 语言》第 6 课 —— 字典

来源: 知乎 原文: 《快学 Go 语言》第 6 课 —— 字典 字典在数学上的词汇是映射,将一个集合中的所有元素关联到另一个集合中的部分或全部元素,并且只能是一一映射或者多对一映射。&lt;img ...

《快学 Go 语言》第 5 课 —— 灵活的切片

来源: 知乎 原文: 《快学 Go 语言》第 5 课 —— 灵活的切片 切片无疑是 Go 语言中最重要的数据结构,也是最有趣的数据结构,它的英文词汇叫 slice。所有的 Go 语言开发者都津津乐道地...

《快学 Go 语言》第 4 课 —— 低调的数组

来源: 知乎 原文: 《快学 Go 语言》第 4 课 —— 低调的数组 Go 语言里面的数组其实很不常用,这是因为数组是定长的静态的,一旦定义好长度就无法更改,而且不同长度的数组属于不同的类型,之间不...

《快学 Go 语言》第 3 课 —— 分支与循环

来源: 知乎 原文: 《快学 Go 语言》第 3 课 —— 分支与循环 程序 = 数据结构 + 算法上面这个等式每一个初学编程的同学都从老师那里听说过。它并不是什么严格的数据公式,它只是对一般程序的简...