1. 概述
本文介绍electron应用的在线升级方案调研和开发。
2. 客户端升级方案探讨
如上图,通过三个方面讨论一下在线升级的几个概念
2.1. 带宽因素
带宽的大小,直接关系到安装包的下载时长。
带宽因素,其实也就是安装包的体积因素,所以,尽量生成高压缩比
的安装包。
Electron应用的package.json
的build
字段,可配置安装包的高压缩比:
{
"build": {
"asar": true,
"compression": "maximum",
...
}
}
在安装包大小一定的情况下,在线升级分为全量更新
和增量更新
全量更新
下载NewVersion的Installer,并覆盖安装
增量更新
- 方法1,每次Release需要生成与
历史所有版本
的diff文件,当Client发起Update请求时,对比OldVersion和NewVersion的差别文件,并打包Response给Client - 方法2,基于Release包打包
经常变动的文件
作为增量升级包,例如app.asar
。于是,如果更新了其他文件的话,只能强制全量更新 - 方法3,对比
installer
单文件差异,然后只下载差异block即可 - 结论:开发麻烦,增加了在线升级的复杂度,性价比低,项目前期不建议
Electron应用如何支持增量更新
Electron-updater采用上述方法3,更新机制和详细日志可以参考附1
的日志
2.2. 用户量
对于活跃用户量很大的应用,灰度发布很有必要,不论客户端还是服务端
如果是项目初期,用户量并不多,功能并不复杂,变动大,而且经常迭代更新,全量发布会减少项目的迭代负担,更早发现问题、解决问题
当然,前提是测试要充分,让用户发现问题是下下策
全量发布
针对所有用户发布
灰度发布
只针对部分用户发布,可以随机20%发布,也可以基于用户Tag发布(前提是有可用的用户Tag)
Electron应用如何支持灰度发布
Electron应用在electron-updater
的基础上,也可以支持灰度发布
,三个点:
-
latest.yml
增加自定义字段,比如grayReleased: 80
- 客户端每次启动,自动获取是否有可用的update,以及其详细信息,即
latest.yml
- 客户端本地随机生成100以内的自然数,如果小于80,则提示升级或自动升级,否则不升级
2.3. 兼容性
强制更新
启动时,自动无感更新
这种情况,一般是服务端的升级已不再支持旧版本,Electron应用的实现机制
- 在
latest.yml
记录服务端支持的最低版本
,例如minVersionSupported: 0.1.0
- 客户端每次启动,自动获取是否有可用的update,以及其详细信息,即
latest.yml
- 客户端比较
Current Version
和minVersionSupported
,决定是否强制更新
可选更新
启动时,自动可选更新
在强制更新
的最后一步,如果CurrentVersion >= minVersionSupported
,用户可选择是否立即更新
启动后,手动检测更新
一般情况下,在客户端会提供一个Page,供用户手动查看当前版本、检测是否有新版本,以及执行立即更新
2.4. 其他问题
2.4.1. 新版本的变更内容
仿照灰度发布
和强制更新
的做法,可以修改latest.yml
来实现
Electron支持Github Release的Release-Notes,对其他server不支持,所以只能用上面workaround
- set electron-updater releaseNotes at build time
https://github.com/electron-userland/electron-builder/issues/1511
2.4.2. 客户端安装失败后怎么办
这个问题很严重,主要是对终端的适配问题,于是,测试工作就显得尤为重要
如果真在用户端发生了,需要具体问题具体分析了
- 如何及时捕获到异常,并告知到服务端
- 如何回滚到旧版本,让用户先用着
3. 构建与发布
3.1. 打包工具
Electron应用的打包和发布,主流工具有3个,可以参考官网介绍
经过各种对比,主要是易用性和生态完整性,决定采用electron-builder,官网:https://www.electron.build
3.2. 自动更新工具
Differences between electron-updater and built-in autoUpdater
- Dedicated release server is not required.
- Code signature validation not only on macOS, but also on Windows.
- All required metadata files and artifacts are produced and published automatically.
- Download progress and staged rollouts supported on all platforms.
- Different providers supported out of the box (GitHub Releases, Amazon S3, DigitalOcean Spaces, Bintray and generic HTTP(s) server).
- You need only 2 lines of code to make it work.
import { autoUpdater } from "electron-updater"
autoUpdater.checkForUpdatesAndNotify()
Electron-updater的优缺点:
优点
- 版本更新检查基于package.json中的version字段值,支持三段式的版本号,无法定制
- 包含在线升级功能,实现了版本检查逻辑及回调、下载安装升级逻辑
- 支持多种版本服务器,包括自搭建的
通用服务器
- 支持alpha/beta/latest版本更新策略
- 支持增量更新,实现了增量更新失败时转全量更新的策略(目前只有windows版的,项目组据说计划增加mac版本的增量更新)
- 支持构建时自动发布版本到服务器
- *支持降级更新
缺点
- 不支持灰度发布(按用户百分比,应该是自己实现,没有现成的)
- 不支持release-notes(只支持github releases,不支持自定义release server),set electron-updater releaseNotes at build time
3.3. Release Server
Electron的打包最终是为了发布,因此支持不同方式的Publish,Release Server有以下类型:
- Bintray
- Generic Server, any HTTP(S) server, such as electron-release-server
- GitHub
- S3, AWS Object Storage Server
- Spaces or Snap Store, the app store for Linux
electron-release-server是一个完善的Generic Server,支持安装包的上传和下载,以及分类管理,自带Web。缺点是,还需要自己手动upload,不能自动publish。
本文采用MinIO(类S3的分布式对象存储服务)为例。
4. 打包开发与测试
4.1. 启动minio server
docker run -p 9999:9000 \
--name minio --rm -d \
-e "MINIO_ROOT_USER=xxx" \
-e "MINIO_ROOT_PASSWORD=wJalrXUtnFENG/bxxR12CYxxxwwEXAMPLEKEY" \
-v /home/xxx/soft/minio/data:/data \
minio/minio server /data
通过浏览器打开MinIO Dashboard:http://10.211.28.93:9999
并创建一个prefix是*的readonly的bucket
,名字是public
,下面会用到
4.2. 自动打包发布
修改package.json
,增加build
字段如下,electron-builder
会读这个字段
增加electron-builder.env
文件,内容如下,存储MinIO的秘钥
# publish to minio witch is s3-likely storage server
AWS_ACCESS_KEY_ID=xxx
AWS_SECRET_ACCESS_KEY=wJalrXUtnFENG/bxxR12CYxxxwwEXAMPLEKEY
执行yarn electron-builder --publish always
即可打包和发布
publish[0].path是auto-updater默认的feedurl,不能包含
${version}
,必须指向最新版本的latest.yml和安装包publish[1]会自动上传安装包到MinIO,主要用于备份
publish[2]只是生成了latest.yml,并不会自动上传,需要自动上传功能,需要通过js调用electron-builder实现。可手动move到http-server的目录下即可,也可以采用electron-release-server
推荐采用MinIO作为release-server,可以自动publish,提高效率
{
"build": {
"appId": "com.foxchat.www",
"asar": true,
"compression": "maximum",
"directories": {
"output": "release/${version}"
},
"electronDownload": {
"mirror": "https://npm.taobao.org/mirrors/electron/"
},
"files": [
"!node_modules",
"node_modules/@sentry/**/*",
"dist/**"
],
"mac": {
"icon": "build/icons/icon.icns",
"artifactName": "${productName}-${os}-${arch}-${version}-setup.${ext}",
"target": [
"dmg"
]
},
"win": {
"icon": "build/icons/logo256.ico",
"artifactName": "${productName}-${os}-${arch}-${version}-setup.${ext}",
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
]
},
"linux": {
"icon": "build/icons",
"target": "deb"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./build/icons/logo256.ico",
"uninstallerIcon": "./build/icons/logo256.ico",
"installerHeaderIcon": "./build/icons/logo256.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "FoxChat",
"deleteAppDataOnUninstall": false
},
"publish": [
{
"provider": "s3",
"bucket": "public",
"endpoint": "http://10.211.28.93:9999",
"channel": "latest",
"path": "/im/artifact/latest/${os}"
},
{
"provider": "s3",
"bucket": "public",
"endpoint": "http://10.211.28.93:9999",
"channel": "latest",
"path": "/im/artifact/${version}/${os}"
},
{
"provider": "generic",
"url": "http://10.211.28.93:9797/artifact"
}
]
},
}
4.3. Tips
修改nsis脚本,可以自定义安装界面
参考:https://github.com/eyasliu/blog/issues/22
5. 客户端自动更新开发
基于electron-updater完成开发,直接上代码
这里补充几点:
-
app.getVersion()
不work的话,可以这样子
import { version } from '../../../package.json'
import { app } from 'electron'
app.getVersion = () => version
- 代码里面的
info
,就是latest.yml
的数据,样例如下
{
version: '0.0.7',
files: [
{
url: 'FoxChat-win-0.0.7-setup.exe',
sha512: '795pdq9/Lt9MuANpM3tChxpCtzsqNe4v2wWyK41kUJ3PABHJns2xo5Jw46CWFOOCEVes57L5lmXV9v4NWNBeXg==',
size: 62858586
}
],
path: 'FoxChat-win-0.0.7-setup.exe',
sha512: '795pdq9/Lt9MuANpM3tChxpCtzsqNe4v2wWyK41kUJ3PABHJns2xo5Jw46CWFOOCEVes57L5lmXV9v4NWNBeXg==',
releaseDate: '2021-06-29T10:09:27.716Z',
releaseNotes: [ '增加在线升级功能', '登录功能优化' ],
minVersionSupported: '0.1.0',
grayReleased: 100,
downloadedFile: 'C:\\Users\\Administrator\\AppData\\Local\\foxchat-updater\\pending\\FoxChat-win-0.0.7-setup.exe'
}
Notification不work,所以不建议用
appUpdater.checkForUpdatesAndNotify()
https://github.com/electron-userland/electron-builder/issues/2700
https://www.electronjs.org/docs/tutorial/notifications测试时的几个路径
自动升级的安装包下载路径:
C:\Users\Administrator\AppData\Local\foxchat-updater
Electron-log的默认路径
C:\Users\Administrator\AppData\Roaming\FoxChat\logs
- TODO,开发一个UI,提高
自动升级功能
的用户体验
import { app, dialog } from 'electron'
import { autoUpdater } from "electron-updater"
import log from 'electron-log'
log.transports.file.level = "debug"
autoUpdater.logger = log
function updateHandle() {
log.info('client version', app.getVersion())
autoUpdater.autoDownload = false
autoUpdater.on('error', (error) => {
dialog.showErrorBox('Error: ', error == null ? "unknown" : (error.stack || error).toString())
})
autoUpdater.on('checking-for-update', () => {
log.info('Checking for update')
});
autoUpdater.on('update-available', (info) => {
log.info('Got a new client version, will auto download it', info)
autoUpdater.downloadUpdate()
})
autoUpdater.on('update-not-available', (info) => {
log.info('Current version is up-to-date', info)
})
autoUpdater.on('update-downloaded', (info) => {
log.info(info)
dialog.showMessageBox({
type: 'info',
title: '软件升级',
message: '发现新版本,是否立即升级?',
buttons: ['是的', '稍后']
}).then((resp) => {
if (resp.response === 0) {
log.info('begin to install new version ...')
autoUpdater.quitAndInstall(true, true)
}
})
})
autoUpdater.on('download-progress', function (progressObj) {
log.debug('download progress', progressObj)
})
autoUpdater.checkForUpdates()
}
updateHandle()
export {updateHandle}
附1,electron-updater的增量更新日志
增量更新的机制
- 对比
old-version.exe.blockmap
文件和new-version.exe.blockmap
文件,计算得到changed blocks
- 基于
changed blocks
,计算得到download blocks
,kind=1
是需要下载的(change-blocks肯定要大于download-blocks) - 下载所有
kind=1
的download blocks
,并与本地文件merge,形成available installer
效果
从下面的log可以,可以看到只下载了7%
大小的文件内容,体积不到4MB:Full: 61,385.34 KB, To download: 4,103.49 KB (7%)
一次客户端升级的完整日志
[2021-06-30 15:27:03.799] [info] client version 0.0.6
[2021-06-30 15:27:03.807] [info] Checking for update
[2021-06-30 15:27:13.150] [info] Found version 0.0.7 (url: FoxChat-win-0.0.7-setup.exe)
[2021-06-30 15:27:13.151] [info] Got a new client version, will auto download it {
version: '0.0.7',
files: [
{
url: 'FoxChat-win-0.0.7-setup.exe',
sha512: '795pdq9/Lt9MuANpM3tChxpCtzsqNe4v2wWyK41kUJ3PABHJns2xo5Jw46CWFOOCEVes57L5lmXV9v4NWNBeXg==',
size: 62858586
}
],
path: 'FoxChat-win-0.0.7-setup.exe',
sha512: '795pdq9/Lt9MuANpM3tChxpCtzsqNe4v2wWyK41kUJ3PABHJns2xo5Jw46CWFOOCEVes57L5lmXV9v4NWNBeXg==',
releaseDate: '2021-06-29T10:09:27.716Z'
}
[2021-06-30 15:27:13.155] [info] Downloading update from FoxChat-win-0.0.7-setup.exe
[2021-06-30 15:27:13.157] [debug] updater cache dir: C:\Users\Administrator\AppData\Local\foxchat-updater
[2021-06-30 15:27:13.164] [info] Download block maps (old: "http://10.211.28.93:9999/public/im/artifact/latest/win/FoxChat-win-0.0.6-setup.exe.blockmap", new: http://10.211.28.93:9999/public/im/artifact/latest/win/FoxChat-win-0.0.7-setup.exe.blockmap)
[2021-06-30 15:27:13.202] [info] File has 195 changed blocks
[2021-06-30 15:27:13.203] [debug] [
{
"kind": 0,
"start": 0,
"end": 350804
},
{
"kind": 1,
"start": 350804,
"end": 430577
},
{
"kind": 0,
"start": 430577,
"end": 463345
},
{
"kind": 1,
"start": 463345,
"end": 490212
},
{
"kind": 0,
"start": 490212,
"end": 10750880
},
{
"kind": 1,
"start": 10750880,
"end": 14595768
},
{
"kind": 0,
"start": 14595640,
"end": 54052515
},
{
"kind": 1,
"start": 54052643,
"end": 54172009
},
{
"kind": 0,
"start": 54171859,
"end": 62463158
},
{
"kind": 1,
"start": 62463308,
"end": 62479157
},
{
"kind": 0,
"start": 62479006,
"end": 62743206
},
{
"kind": 1,
"start": 62743357,
"end": 62858586
}
]
[2021-06-30 15:27:13.220] [info] Full: 61,385.34 KB, To download: 4,103.49 KB (7%)
[2021-06-30 15:27:13.224] [info] Differential download: http://10.211.28.93:9999/public/im/artifact/latest/win/FoxChat-win-0.0.7-setup.exe
[2021-06-30 15:27:13.249] [debug] download range: bytes=350804-430576
[2021-06-30 15:27:13.266] [debug] download progress {
total: 4201972,
delta: 79773,
transferred: 79773,
percent: 1.8984657679775117,
bytesPerSecond: 1899357
}
[2021-06-30 15:27:13.268] [debug] download range: bytes=463345-490211
[2021-06-30 15:27:13.278] [debug] download progress {
total: 4201972,
delta: 106640,
transferred: 106640,
percent: 2.5378560352139425,
bytesPerSecond: 1974815
}
[2021-06-30 15:27:13.388] [debug] download range: bytes=10750880-14595767
[2021-06-30 15:27:13.900] [debug] download progress {
total: 4201972,
delta: 3951528,
transferred: 3951528,
percent: 94.03984605323406,
bytesPerSecond: 5845456
}
[2021-06-30 15:27:16.128] [debug] download range: bytes=54052643-54172008
[2021-06-30 15:27:16.138] [debug] download progress {
total: 4201972,
delta: 3964841,
transferred: 3964841,
percent: 94.3566734856872,
bytesPerSecond: 1361085
}
[2021-06-30 15:27:16.156] [debug] download progress {
total: 4201972,
delta: 106053,
transferred: 4070894,
percent: 96.88055988949951,
bytesPerSecond: 1388436
}
[2021-06-30 15:27:16.218] [debug] download range: bytes=62463308-62479156
[2021-06-30 15:27:16.236] [debug] download progress {
total: 4201972,
delta: 121902,
transferred: 4086743,
percent: 97.2577399373437,
bytesPerSecond: 1356820
}
[2021-06-30 15:27:16.240] [debug] download range: bytes=62743357-62858585
[2021-06-30 15:27:16.263] [debug] download progress {
total: 4201972,
delta: 237131,
transferred: 4201972,
percent: 100,
bytesPerSecond: 1382682
}
[2021-06-30 15:27:16.634] [info] New version 0.0.7 has been downloaded to C:\Users\Administrator\AppData\Local\foxchat-updater\pending\FoxChat-win-0.0.7-setup.exe
[2021-06-30 15:27:32.884] [info] begin to install new version ...
[2021-06-30 15:27:32.885] [info] Install on explicit quitAndInstall
[2021-06-30 15:27:32.886] [info] Install: isSilent: true, isForceRunAfter: true
[2021-06-30 15:27:33.149] [info] Update installer has already been triggered. Quitting application.
[2021-06-30 15:27:43.027] [info] client version 0.0.7
[2021-06-30 15:27:43.210] [info] App starting... 0.0.7