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)的账户密码进行校验
=>
当页面刷新或者关掉页面后重开时,如何让未读消息提示仍存在且准确地显示?
今天完成了件挺开心的事情,自己构想设计实现了一套保持且准确更新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压缩后
首次加载6~7s => 2.3s左右
路由按需加载
引用@loadable/component 这个库
简单使用: loadable(() => import('../containers/SettingPage')
使用示例
<Route path="/setting" exact component={loadable(() => import('../containers/SettingPage'))} />
首次加载(聊天主页面)2.3s左右 => 1.3s左右
可以看到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
引进eslint
npm install eslint --save-dev
./node_modules/.bin/eslint --init
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证书
- SSL证书更新
[续费购买证书]https://help.aliyun.com/document_detail/28544.html?spm=5176.2020520163.0.0.296f56a7A543n4
将续费购买的证书更新(即重新安装)到您的服务器中替换即将过期的证书 => Nginx/Tengine服务器安装SSL证书
最后要重启Nginx service nginx restart
且重启服务器中的项目
然后这里检查ssl到期时间
- 配置七牛云ssl
查看证书文件内容的网站 :https://myssl.com/cert_decode.html
未完。。。
如何写一个高阶组件
例子: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
fix issue: Error during WebSocket handshake: Unexpected response code: 400
如何把你的应用改造成PWA?
更新:
直接用为pwa打造的一个库workbox
详解:
GitAds广告