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

Easter79
• 阅读 656

😛 闲暇时间想做一个聊天室复盘一下这些年学习到的技术,于是在2020年6月24号就开始了 Genal 聊天室的开发之旅。
😈 项目采用全 typescript 开发,这是为了以后的功能迭代打基础。当然,我本身也是很喜欢 typescript 的。

项目界面

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

功能介绍


  • 更改用户名/头像上传

  • 群聊/私聊

  • 创建群/加入群聊/模糊搜索群

  • 添加好友/模糊搜索好友

  • 表情包

  • 消息分页

技术概览

  • 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 私聊消息表

其中中间表用于建立对于群/好友与用户之间的联系。下面是我画的思维导图,相信大家看完就能理解其中的奥妙啦。
vue+ts打造一个酷炫的星空聊天室

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 {    let userRoom = client.handshake.query.userId;    // 连接默认加入public房间    client.join('public');    // 用户独有消息房间 根据userId    if(userRoom) {      client.join(userRoom);    }    return '连接成功'  }}

  2. 封装全局的中间件,方便在开发过程中调试。

    // middleware.jsexport function logger(req, res, next) {  const { method, path } = req;  console.log(${method} ${path});  next();};// main.js 使用全局中间件app.use(logger)

  3. nestjs 的静态资源配置

    // main.js配置静态资源app.useStaticAssets(join(__dirname, '../public/', 'static'), {  prefix: '/static/', });

  4. nestjs 自定义异常过滤器

    // http-exception.filter.ts@Catch(HttpException)export class HttpExceptionFilter implements ExceptionFilter {  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.tsconst 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.vueimport { 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:https://github.com/genaller/genal-chat


近期

Node在大前端中的应用场景分析

10个Vue开发技巧(下)

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

若此文有用,何不素质三连❤️

本文分享自微信公众号 - Vue中文社区(vue_fe)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
Jacquelyn38 Jacquelyn38
1年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。 1、使用解构获取json数据let jsonData   id: 1, status: "OK", data: ['a', 'b'] ; let  id, status, data: number   jsonData; console.log(id, status, number )
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录 问题 用navicat导入数据时,报错: 原因这是因为当前的MySQL不支持datetime为0的情况。 解决修改sql\mode: sql\mode:SQL Mode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。 全局s
Stella981 Stella981
11个月前
KVM调整cpu和内存
一.修改kvm虚拟机的配置 1、virsh edit centos7 找到“memory”和“vcpu”标签,将 <name>centos7</name> <uuid>2220a6d1-a36a-4fbb-8523-e078b3dfe795</uuid>
Easter79 Easter79
11个月前
Twitter的分布式自增ID算法snowflake (Java版)
概述 == 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。 有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。 而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
11个月前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表: **时辰** **时间** **24时制** 子时 深夜 11:00 - 凌晨 01:00 23:00 - 01 :00 丑时 上午 01:00 - 上午 03:00 01:00 - 03 :00 寅时 上午 03:00 - 上午 0
Wesley13 Wesley13
11个月前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序 select * from table_name order id desc; 2.按照指定(多个)字段排序 select * from table_name order id desc,status desc; 3.按照指定字段和规则排序 selec
Stella981 Stella981
11个月前
Angular material mat
Icon Icon Name mat-icon code _add\_comment_ add comment icon <mat-icon> add\_comment</mat-icon> _attach\_file_ attach file icon <mat-icon> attach\_file</mat-icon> _attach\
Wesley13 Wesley13
11个月前
PHP中的NOW()函数
是否有一个PHP函数以与MySQL函数`NOW()`相同的格式返回日期和时间? 我知道如何使用`date()`做到这一点,但是我问是否有一个仅用于此的函数。 例如,返回: 2009-12-01 00:00:00 * * * ### #1楼 使用此功能: function getDatetimeNow() {
Wesley13 Wesley13
11个月前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
#### 背景描述 # Time: 2019-01-24T00:08:14.705724+08:00 # User@Host: **[**] @ [**] Id: ** # Schema: sentrymeta Last_errno: 0 Killed: 0 # Query_time: 0.315758 Lock_
helloworld_34035044 helloworld_34035044
2个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。 uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid() 或 uuid(sep)参数说明:sep 布尔值,生成的uuid中是否包含分隔符'',缺省为