aermin / blog

📝 My blog / notes

Home Page:https://www.aermin.top/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ghChat开发历程

aermin opened this issue · comments

本文用来写一些ghChat开发过程中的流水账,不断更新文字。 可以先star 收藏哦

私聊群聊重构与设计

始化时请求聊天列表所有聊天对象的聊天记录(后期限制为每个聊天项只初始化20条最新聊天内容已实现,避免初始化数据量过大,时间过长),接着根据点击列表导致chatId(取自url params)的改变,重新渲染新的聊天内容。以前vue-chat的实现方式是点击进入每个聊天页面都会发1至多次请求然后渲染页面,性能较差。现在直接在后端整合好一次性用websocket发过来,减少请求次数且websocket在此情况性能更优一些。

Auth 系统设计

之前的Auth系统是login或者register的时候通过用户输入的userName来判断,后来支持了github auth 登录,造成有可能出现多个相同的userName,这样之前的userName Auth 登录会有bug,所以目前采用的解决方案是将userName Auth 和 Github Auth 分离开,userName Auth登录的时候将查找非github用户(github_id IS NULL)的账户密码进行校验

=>

image

当页面刷新或者关掉页面后重开时,如何让未读消息提示仍存在且准确地显示?

今天完成了件挺开心的事情,自己构想设计实现了一套保持且准确更新ghChat未读消息的系统。 一开始是最笨的想法,在数据库存unread字段,每次unread需要更新都去改动数据库,可需要更新unread的场景太多了,造成频繁操作数据库,性能特差。以下是新的系统设计的思路+代码。

思路:

  • 用localstorage 替代redux对消息列表数据的储存,解决了页面刷新或重开存在redux内存中的数据丢失的问题,未读信息得以再次展示。

  • 有个场景是用户关掉网页好久才重开页面,这时显示localstorage的未读信息数目是不准的,可能在这段时间群聊或私聊信息更新了很多条,也是属于该用户未读的,解决办法是拿本地localstorage存的消息列表存的都是当时最新的一条信息,拿这些信息的时间给后端,后端据此查询数据库看多出了多少条,加上即可。

核心代码:

前端

// 每次redux更新消息列表数据时,也同时更新localstorage
const getHomePageListReducer = (previousState = [], action) => {
  switch (action.type) {
    case SET_HOME_PAGE_LIST:
    case UPDATE_HOME_PAGE_LIST:
    case CLEAR_UNREAD:
    case DELETE_CHAT_FROM_LIST:
    case SHOW_CALL_ME_TIP:
      localStorage.setItem('homePageList', JSON.stringify(action.data));
      return [...action.data];
    default:
      return previousState;
  }
};
//页面也优先用localStorage的数据展示
const mapStateToProps = state => ({
  homePageList: JSON.parse(localStorage.getItem('homePageList')) || state.homePageListState,
});

后端

    for (const item of homePageList) {
      if (clientHomePageList) {  // 如果客户端传了localstorage的列表信息数据过来,
        const goal = clientHomePageList.find(e => (e.user_id ? e.user_id === item.user_id : e.to_group_id === item.to_group_id));
        const sortTime = goal.time;  // 就根据数据中的时间查询数据库
        const res = item.user_id ? await privateChatModel.getUnreadCount({ sortTime, from_user: user_id, to_user: item.user_id })
          : await groupChatModel.getUnreadCount({ sortTime, to_group_id: item.to_group_id });
        item.unread = goal.unread + JSON.parse(JSON.stringify(res))[0].unread; // 客户端未读数目+数据库查询到新增的未读数据 = 当前用户的总未读数目
      }
    }

msyql

//群聊
SELECT count(time) as unread FROM group_msg as p where p.time > ? and p.to_group_id = ?;
// 私聊
SELECT count(time) as unread FROM private_msg AS p WHERE p.time > ? and ((p.from_user = ? and p.to_user= ?) or (p.from_user = ? and p.to_user=?));

完整实现代码

如何保持webSocket断开重连,而且只有一条处于连接状态

之前的方式是在前端做断开重连,最后发现能(艰难)做到断开重连没错,可是webSocket会有多条处于连接状态,浪费server资源。原因是尝试电脑休眠,前端的socket.reconnect 偶尔会不执行,而我们需要在这个监听事件里做更新socket id,取断开这段时间的消息,重新加入各种room的操作,所以只能hardcore地在socket.disconnect 做这些事情,顺便手动重连webSocket, 避免socket.io的reconnect事件失败,可是这样又带来webSocket有时候没关掉就开始了一条新的连接,造成虽能保持webSocket不断,但很条并存活跃的情况。

现在是在后端的connection事件来做断开重连及后续的事情,毕竟前端不管你怎么断,只要你重连了我server就触发connection事件。这边可能会问,上面不是说前端的socket.reconnect 偶尔会不执行吗?没错,这个事件是可能没执行,可是发现就算没执行,也有一条活跃的webSocket,不管是库的原因还是啥,反正你存在一条活跃的webSocket那我后端跟你保持这条webSocket的通信就好了。

相关重构代码

如何做性能优化

开启gzip

由于服务器(学生机10块/月)垃圾,带宽小(1M),所以看来是超过一百k就贼慢

开gzip压缩前
15271550934697_ pic_hd

开gzip压缩后

15811550979732_ pic_hd

首次加载6~7s => 2.3s左右

相关代码

路由按需加载

引用@loadable/component 这个库

简单使用: loadable(() => import('../containers/SettingPage')

使用示例

      <Route path="/setting" exact component={loadable(() => import('../containers/SettingPage'))} />

首次加载(聊天主页面)2.3s左右 => 1.3s左右

按需加载前(无缓存):
image

按需加载后(无缓存):
image

可以看到mainView 和 rightView里面的component按需加载,拆包后,由于加载的文件体积大幅较小,所以速度快了很多

build文件分包

SplitChunksPlugin --webpack4 doc
webpack4使用分包vendor的方式 --stackoverflow
common-chunk-and-vendor-chunk -- webpack examples
我的pr

webpack4后打包生产的包超过244k(具体数字忘了...)终端会有提示,现在ghChat每个build文件都是小于这个数的,所以页面加载蛮快的。
用了webpack4对vendor-chunk的处理方式,对比webpack3在entry加vendor的方式,对比如下图,图中文件按size从大往下排,可以看到最后实际生产请求使用的最大资源文件中,webpack3的是168k,webpack4的是131k

image
image

引进eslint

elint官网文档

npm install eslint --save-dev

./node_modules/.bin/eslint --init

image

1.使用流行的风格指南,直接用别人配置好的eslint(如airbnb,建议使用)
2.会让你回答形式地配置你的eslint
3.检查js文件

项目中的eslint是基于airbng 的 eslint-config-airbnb
上再做点配置

当遇到npm包下载不全怎么办

试试npm install --only=dev

Error: Cannot find module 'webpack'

如何给你的网站配置ssl证书,以及怎么配置七牛云ssl(上传自己的证书)

  • Nginx/Tengine服务器安装SSL证书

在阿里云那边申请免费的ssl证书,下载下来,在服务器上的/etc/nginx目录下建一个cer文件夹,用

scp -P 22 /Users/xxx/xxx/xxx/xxx.key root@${服务器ip}:/etc/nginx/cert  

把文件都放在这个cert文件夹

按照阿里云文档操作=> Nginx/Tengine服务器安装SSL证书

Nginx代理centos端口

  • SSL证书更新

[续费购买证书]https://help.aliyun.com/document_detail/28544.html?spm=5176.2020520163.0.0.296f56a7A543n4

将续费购买的证书更新(即重新安装)到您的服务器中替换即将过期的证书 => Nginx/Tengine服务器安装SSL证书

最后要重启Nginx service nginx restart
且重启服务器中的项目
然后这里检查ssl到期时间
7641581416614_ pic_hd

  • 配置七牛云ssl

查看证书文件内容的网站 :https://myssl.com/cert_decode.html

然后把内容复制粘贴在七牛云那边
image

未完。。。

如何写一个高阶组件

例子:ghChat的modal组件
详解: TODO

代码部署的简单方案

因为是开源的,所以项目放github,然后clone到本地开发,在服务器那边也clone一份,用来打包部署。所以整个流程: 本地开发->push github origin -> pull 到服务器上->npm run build

用环境变量来区分开发环境和生产环境,从而执行不同的代码
比如部署ghChat这个项目到服务器上,如果你是centos的
执行 vim ~/.bash_profile
然后 export NODE_ENV=production 把这句加进去,这样,你代码能切换到生产版本的

以上是一开始用的落后方式....

在服务器中创建git服务器,这样能本地打包好直接push到服务器中的项目

用webpack根据不同环境做打包,比如部署到生产环境,打包前端代码在ghchat/ 目录下执行 npm run build:prod 打包出build文件夹,打包后端代码在ghchat/server 目录下执行 npm run build:prod 打包出dist文件夹,分别把这两个文件夹丢到你的服务器上,然后把dist/index.js文件跑起来(追求简单快速可以把ghChat/package.json 文件一并拷贝过来放一块,然后执行 npm run start:prod )

设计滚动条置地时机

交流群有个群友反应收到群信息没有置底,其实是刚被我删掉这个feature了,因为我发现当我上滑看群消息的时候,有人发信息过来我就被置底了,很不友好,结果我就去掉收到信息置底了(没产品思维真可怕😂),感觉群友建议,然后我去看了qq和微信桌面端的实现,感觉qq的实现更人性化:

当滚动条不在底部的时候,有人发信息不置地,当滚动条在底部的时候,有人发信息就置地,当然,自己发信息肯定置底。

实现代码

Error during WebSocket handshake: Unexpected response code: 400

image

fix issue: Error during WebSocket handshake: Unexpected response code: 400

如何把你的应用改造成PWA?

官方中文文档
ghChat 相关代码

更新:
直接用为pwa打造的一个库workbox

ghChat 相关代码

详解:

GitAds广告

GitAds