

IM跨平台技术学习(七):得物基于Electron开发客服IM桌面端的技术实践
source link: http://www.52im.net/thread-4159-1-1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文由得物技术团队Uni分享,即时通讯网收录时有内容修订和排版优化。
本文要分享的是得物技术团队基于Electron开发客服IM桌面端的技术实践过程,内容包括桌面技术选型、Electron的基础概念、具体的实施技术方案、遇到的棘手问题等。
Electron社区虽然很活跃,但是不一样的场景遇到的技术问题,几乎找不到对应的解决方案,我们很多都是在探索过程中不断的去完善,希望本文能带给你一些启发。

二、系列文章
本文是系列文章中的第7篇,本系列总目录如下:
三、业务背景
随着公司业务的快速发展,商家客服也纳入了我们的服务范围,商家客服工作台的定位是通过工具和数据服务商家,一站式解决用户购买咨询诉求。通过工具和运营策略协助商家提升服务品质,让品牌商家有动力运营好潜在的客户,从而达到提升用户服务的目标。
已有web端聊天系统的前提下,商家客服为什么要迁移桌面应用?
首先我们收到部分商家客服反馈:

用户是上帝,我们是很重视用户的反馈的,所以首先我们想的是如何在web端解决这些问题,下面我们逐一分析下以上问题我们能不能在网页端解决呢?
1)针对客服A同学问题:大多数客服职场的台式机是不会安装音频设备,如果人家没音频,没外音,我们能强迫他买个播放器吗,那肯定是不能.,如果是自营客服还有点处理方案,真需要,公司可以统一采购,但是ToC的显然不能强制做什么事情,所以此问题无解.
2)针对客服B同学问题:这句话怎么理解呢?是这样的,在一屏既有web浏览器,又有其他应用如飞书之类的PC应用叠着放,web notification通知由于是浏览器级别的,提醒不到最上层,浏览器的提醒确实有点弱(看不到提醒会影响到客服的首响,首响会影响客服的绩效,咱公司对于用户的服务效率是比较严格的),所以此问题无解。
3)针对客服C同学问题:em。。。好的,您说的对,web网页给商家客服的感觉就是我们平台有点赶不上形势。
基于上面的一些场景,想必大家已经对为何做桌面应用有个初步的了解。下面以一张图来看下Web应用跟桌面应用的区别。

四、技术选型
为什么会选择Electron而不是其他应用开发框架?
4.1、Electron架构简介

Electron的构成主要是上面的3个大模块,每个模块各司其职,让Electron有了桌面应用的能力。
逐个理解Electron的3个大模块:
- 1)Chromium:可以为Electron提供UI渲染能力,再加上Chromium本身就是跨平台的,所以也不用考虑代码的兼容性(只要有Chromium,就能用JavaScript,HTML,CSS这些前端开发工程师所熟知的三剑客来开发页面);
- 2)Node.js:Chromium并不具备原生GUI操作的能力,所以Node.js正好补足了这个能力(能够调用操作系统的底层 API,比如path、fs、crypto 这些模块,甚至能集成C++);
- 3)Native APIs:Native API让Electron有了跨平台和桌面端的原生能力(比如说它有统一的原生界面,窗口、托盘这些)。
4.2、Electron与其他框架的区别
下面是Electron与Native、QT、NW应用的对比图:

如上图所示:
- 1)Native(C++/C#/Objective-C)不管从原生体验、包的体积、性能方面来说都是最佳的选择,但是开发门槛高、迭代速度慢;
- 2)QT是基于C++的跨平台开发框架,跨平台应用十分广泛(Mac、Windows、ios、Android、Linux、嵌入式),众所周知的WPS就是用QT开发的。性能很好,甚至于可以媲美原生的体验,但是整体门槛还是比较高的;
- 3)Web技术的代表Electron 和 NW.js ,相比之前选择Electron,Electron有非常活跃的社区(有102k star),有Atom、vscode这样的大型应用都是基于Electron开发的,性能相比于natvie是肯定要差一些,但综合来看,Electron是作为开发桌面应用的目前首选;
- 4)值得一提的是Flutter desktop,从渲染原理看flutter是skia自绘性能优于Electron,但问题还是稳定性和生态。Electron由于是nodejs+chromium,前端的生态可以直接用,所以Flutter desktop就不纳入考虑范围的,但值得关注。
除了以上这些,最主要的一点:Electron能快速交付,业务层复用web系统的代码,只需要关注主进程就好了,并且也能满足业务需要的系统级别的一些功能。

五、技术实现
5.1、项目架构

首先介绍下Electron框架里面两个重要的概念主进程和渲染进程。
- 1)主进程:主要负责创建和管理BrowserWindow实例以及应用程序事件。它可以执行注册全局快捷方式,创建系统菜单和对话框,响应自动更新事件等操作(主进程以及所有Node.js模块中都提供了一部分Electron API);
- 2)渲染进程:渲染过程负责运行应用程序的用户界面,渲染进程中提供了所有DOM API、Node.js API和Electron API的子集。

如上面截图:打开Electron项目之后会有多个进程,一个项目有且只有一个主进程,创建窗口等有关系统事件写在主进程中进行,但是渲染进程可能有多个。
那为什么会有多个渲染进程呢?
Electron应用是Chromium内核,所以多进程的架构也来源于Chromium,Chromium会单独运行每个标签,任何一个标签页崩溃了都不会影响到其他标签。因此,每个进程在自己的空间中运行,由操作系统调度。如果某个进程触发了无限循环,也不会导致整个应用down掉。
在上文说到两个字“迁移”:是的,我们在开发桌面应用之前有非常成熟的web端商家客服聊天系统了,所以我们在开发桌面应用的时候大多数时候是关心主进程的,渲染进程并不太需要关心。
5.2、主进程功能模块
5.2.1通信模块
主要是调用Electron框架本身的API以及通用方法的封装:

5.2.1.1)主进程到渲染进程的通信:

5.2.1.2)渲染进程到主进程的通信:
有两种方案。
方案一:是在主进程开启了nodeIntegration: true之后在渲染进程里面可以使用window.require('Electron')来引入写通信相关代码:
方案二:是需要在主进程编写preload.js(在初始化窗口的时候引入):

5.2.1.3)通信的同步和异步问题:
异步:渲染进程->发送->主进程

同步:渲染进程->发送->主进程

5.2.2菜单模块
主要是调用Electron框架本身的API,满足快速扩展菜单功能以及自定义菜单功能:

但是商家客服项目没用到原生菜单,而是用了自定义的菜单。
没有使用原生菜单的主要原因是:
- 1)原生菜单样式较死板,不能调整;
- 2)自定义编写菜单能有各种自己想要的功能,可控制其展示。
5.2.3 托盘模块
托盘属于系统级的操作,所以是在主进程中设置的。在开始之前需要注意的地方,设置托盘必须在程序ready之后。
实现较简单,代码如下:

5.2.4 异常处理模块
主要调用Electron框架本身API,结合Node.js API,检测系统异常后自动刷新并上报,渲染进程的异常已经使用sls&arms处理,主进程的异常主要是通过Electron的crashReporter API来记录日志。

上面提交参数有两个需要注意的点:
- 1)submitURL 是以post方式上传;
- 2)extra 一个你可以定义的对象,附带在崩溃报告上一起发送(只有字符串属性可以被正确发送,不支持嵌套对象)。
5.3、渲染进程功能模块
渲染进程的代码大部分跟商家客服IM web端一致,很多只是迁移即可。
5.3.1登录改造
登录信息本地化,在登录成功的时候,把账号信息缓存,下次打开应用的时候客服无需再重新输入,直接从缓存获取即可。
逻辑如下图所示:

5.3.2静态资源
传统Web应用,将项目代码部署服务器,项目运行时,访问的是服务器静态资源,现在版本发布流程,走的是cdn资源,总而言之都是通过网络获取。

Electron提供将静态资源打包到安装程序,在安装时,将项目文件同步安装到用户电脑,使其具备访问本地文件,减少了请求占用资源。一定程度上也能改善因网速问题导致的静态资源不能实时获取,页面白屏问题。

5.3.3 数据存储
Electron应用里面的数据存储是通过Electron-store第三方库来实现的。
实现比较简单,如下:

5.3.4 渲染进程打包
这块为什么要单拎出来讲渲染进程打包呢,是因为web项目迁移变成应用渲染进程的时候不能像web应用一样直接打包,需要调整请求API代码,API前缀需要区分本地调试和应用环境。
代码如下:

使用Electron, 将项目打包成离线应用。使用file协议,在本地读取静态资源。但是ajax请求如果用相对路径,打包之后,会直接找到根目录。如下截图。

所以打包的时候需要给ajax提供完整的url路径。
六、技术挑战
6.1、概述
我们在从0到1搭建商家客服IM桌面端的过程中,遇到了很多的问题。
原因是Electron社区虽然很活跃,但是不一样场景遇到的问题,几乎找不到对应的解决方案,所以很多都是在探索过程中不断的去完善。
这里主要围绕发布构建流程和安全性来讲下,我们是怎么解决的。
6.2、安全性问题
Electron客户端的安全问题也是非常重要的,那都遇到了哪些安全问题以及我们又是如何解决的呢?
具体如下:
- 1)渲染进程XSS:Electron实现的桌面端软件渲染层的原理实际是通过chrome内核渲染的,同样存在XSS注入的风险(举个例子:在html页面中可以执行命令:<img/src=x οnerrοr="require('child_process').exec('gnome-calculator')"> ,就可以打开当前操作系统的计算器。接入了公司统一的XSS治理方案,该问题即得到解决);
- 2)用户认证信息泄漏问题:商家客服桌面端登录调用商家的授权接口,APP网关有校验,可以确保登陆没有问题;
- 3)本地缓存明文读取问题:本地数据泄漏(例如:indexDB、localStorage、sessionStorage等),我们主要用加密和解密算法对本地缓存信息进行处理。
没有绝对的安全,我们能做的就是尽可能的提高安全门槛,过程中我们也积极同公司的安全部门进行沟通,让他们排查桌面端发布之后的安全漏洞,最终验证都是满足安全标准,符合发布的条件。
6.3、发布构建流程
应用发布涉及到渲染进程和主进程,渲染进程主要是负责给主进程提供渲染包,主进程使用Electron-builder库来打包部署所发布的包。

前面已经说过:Electron的好处是可以无缝集成web端的业务逻辑代码,这里上图左边红色的是web端构建出的产物,我们会把这部分构建产物同步到主进程的app/render目录下(即渲染进程目录),这样在打包应用包的时候,就能集成渲染进程的业务逻辑,而不需要维护两份web端的代码。
此方案还存在不少的缺陷:由于生产构建环境需要window环境,所以暂时不支持在远程打包,目前都是在本地window机器上打出完整的包之后再上传到CDN,商家客服通过加载CDN的更新包来替换本地安装文件,实现软件的本地安装。
6.4、应用更新问题
应用开发离不开“更新”这个话题,比如飞书应用会时不时弹出一个更新窗口,让你选择是否更新。我们的商家客服IM在推广桌面应用之后,也存在更新这个问题。
在业务快速发展的同时,如何将业务需求更好的同步给商家使用,这是商家客服桌面应用面临的最大的挑战。
6.4.1全量更新:手动下载安装
这是最基础的更新模式,主要思路是在打开app(其他时候也行,我们业务主要是打开app的时候)的时候访问远程的json文件,文件内容包含版本号,更新内容等等最新版本的信息,拿到远程版本号会跟本地应用版本号做比较,如果版本号不一致,就询问用户是否更新,需要更新的话会下载到本地,用户手动点击安装即可。
这个更新方式不推荐使用,如果你的应用一年更新一次,ok,是可以这么做的。

6.4.2增量更新
在网速快的情况下,全量更新跟增量更新几乎是没有区别的。但是网速慢的情况下它俩之间的差距会被放大,用户体验不是很好。
我们不能想当然的以为所有用户网速都很好,这是不现实的,所以不管是PC应用还是移动端应用,大多数情况下是需要做增量更新。
下面表格是网速不一样情况下的下载耗时对比:

6.4.3增量更新方案1:electron-updater
现在就开始介绍我们在商家客服IM应用(windows应用)中是怎么实现增量更新功能的。
更新在大的分类上区分全量以及增量更新,在每个小分类里面也区分强更新、弱更新(业务上的区分,底层实现没区别)。
简单来说:
- 1)强更新指的是用户必须更新,不更新将无法使用系统功能;
- 2)弱更新指的是用户想要的时候再去触发应用的更新,完全由用户自主选择。

更新流程:

其中electron-updater作用于“更新应用”这个节点,主要是依赖新旧版本blockmap文件的对比来实现增量更新。
下图为electron-builder打包出来的release包,每次打包都会有对应的blockmap文件。

electron-updater更新实现主要流程分两大步。
生产的blockmap文件:
- 1)使用7z压缩安装包;
- 2)读取安装包的header;
- 3)计算出每个file的offset和end得到相应的hash生产blockmap。
使用blockmap文件:
- 1)下载云上的blockmap文件跟本地blockmap文件对比(从上面截图可以看出blockmap文件很小,所以下载并不会对应用性能产生影响);
- 2)使用range,request(范围请求)请求更新内容的部分。
6.4.4增量更新方案2:文件替换
还有一种增量更新方式就是文件替换,只更新需要更新的模块。
这种方式只更新需要渲染进程的资源,大部分情况下主进程的资源不用更改,所以下载的资源会比较小,更新较快。因为是在线热更新,更新完成后不用重新启动软件,只需要刷新页面重新加载资源即可。
其实之前我觉的这样的思路挺好的,看下面的流程图也是可以实现的,也很符合商家客服桌面应用产品需求。

可是后来发现其实忽略了以下两个点:
- 1)替换用户本地文件这个本身有权限问题(比如windows用户安装到了C盘,写入文件是有管理员权限限制的);
- 2)文件被占用问题(众所周知,当文件夹中存在正在被占用的文件时,删除会失败)。
所以在覆盖原文件同时需要退出应用避免占用,所以这个方式也不是很可靠。
七、遇到的问题
我们在基于Electron开发客服IM桌面端的过程无疑遇到了很多问题,我拣主要的几个问题分享一下。
问题一:Electron 的硬件加速功能,在 win7 或者 Linux 系统上,容易出现黑屏或者卡死:
解决方案:判断是不是win7及以下系统,如果是app.disableHardwareAcceleration(),禁用当前应用程序的硬件加速。
问题二:“Uncaught ReferenceError: require is not defined”:
这个报错是试图在渲染进程使用node的时候出现的,不是不能用,只要开启 主进程的nodeIntegration: true就好了, 但不建议(有安全问题)。
解决方案:

问题三:“Note:you may have one or two (large) stale temporary file(s) left in your temporary directory (Generally this only happens on Windows 9x)”:
这个是打包了半天都打不出来一个完整的包的情况下出现的。
解决方案:当时是因为我没删除原来的包导致我放打包文件的C盘满了。所以删除一些缓存就好了,nsis打包大概率都是跟磁盘有关。
问题四:下载npm包特别慢:
解决方案:yarn安装、Electron相关的包优先使用淘宝镜像安装、使用公司镜像安装公司内部包。
八、本文小结
一路开发下来,感慨很多。
作为公司第一个Electron应用,不管是在开发上,打包上,或者说在部署上,都遇到了一些挑战。
在网上也没有比较详细的文档,外面做的好的也不会把详细方案分享出来。但是即使遇到了这些问题,也不能否认Electron是目前最适配于我们业务目标以及适配于开发资源的一个框架。
目前我们已有线上稳定版本,也逐步在推广到全部商家客服。
接下来需要完善的开发流程,克服的技术难点有很多,商家客服工作台应用也会越来越完善。
九、参考资料
[1] Electron官方开发者手册
[2] 快速了解新一代跨平台桌面技术——Electron
[3] Electron初体验(快速开始、跨进程通信、打包、踩坑等)
[4] Electron 基础入门 简单明了,看完啥都懂了
[5] vivo的Electron技术栈选型、全方位实践总结
[6] 融云基于Electron的IM跨平台SDK改造实践总结
[7] 闲鱼IM基于Flutter的移动端跨端改造实践
[8] 网易云信基于Electron的IM消息全文检索技术实践
Recommend
-
10
-
16
Electron初探:基于Web的跨平台桌面应用开发 Posted on 2021-11-15...
-
7
IM跨平台技术学习(二):Electron初体验(快速开始、跨进程通信、打包、踩坑等)
-
5
本文由vivo...
-
7
本文由蘑菇...
-
4
本文由融云...
-
7
本文作者网...
-
7
本文由QQ技...
-
6
IM跨平台技术学习(九):全面解密新QQ桌面版的Electron内存优化实践-IM开发/专项技术区 - 即时通讯开发者社区! 想开发IM:买成品怕坑?租第3方怕贵...
-
13
本文由21CTO万能的大雄分享,本文有修订和改动。在当今快速发展的技术环境中,对跨平台桌面应用程序的需求正在不断激增。开发人员面临着选择正确框架之挑战,以便可以高效构建可在 Windows、macOS 和 Linux 上无缝运行的应用...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK