Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ghChat开发历程 #60

Open
aermin opened this issue Feb 25, 2019 · 0 comments
Open

ghChat开发历程 #60

aermin opened this issue Feb 25, 2019 · 0 comments

Comments

@aermin
Copy link
Owner

aermin commented Feb 25, 2019

本文用来写一些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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant