1. 前言
本文主要介绍如何手动实现客户端应用的自动全量更新,通过改造electron-updater实现版本,系统等字段的动态判断更新。
实现后台配置不同设备,版本,系统的版本更新,可自行在后台配置定向更新,批量更新等。
2. 框架
采用electron-egg框架来进行的自动更新,一部分工作框架自带,只需要进行相应的自动更新修改即可。
3. 后台
3.1. 接口
匹配latest.yml的get请求,这个目前是必须的,具体的实现由后端代码自行处理。
kotlin
/**
* 适配Electron的更新,使用Get方式请求
*/
@GetMapping("ee/latest.yml", produces = ["application/json"])
open fun eeLatest(appLatestRequest: AppLatestRequest): Any? {
val appUpdate = appUpdateService.eeLatest(appLatestRequest)
if(appUpdate == null) {
return AppUpdateResponse(false)
} else {
return AppUpdateResponse(true, appUpdate.version, appUpdate.downloadUrl, appUpdate.sha512)
}
}
kotlin
/**
* APP更新接口
* 优先匹配单设备,在匹配全量的更新记录
*/
fun check(appUpdateRequest: AppUpdateRequest): AppUpdate? {
// 查找有没有直接设备匹配的更新记录,如果没有就查找所有
var appUpdates = appUpdateMapper.selectListByQuery(
QueryWrapper.create()
.ge(AppUpdate::version, appUpdateRequest.version)
.eq(AppUpdate::deviceId, appUpdateRequest.deviceId)
)
if(appUpdates.isEmpty()) {
appUpdates = appUpdateMapper.selectListByQuery(
QueryWrapper.create()
.ge(AppUpdate::version, appUpdateRequest.version)
.eq(AppUpdate::deviceId, "all")
)
return appUpdates.filter {
if(!it.checkRule.isNullOrEmpty()) {
// 模板表达式匹配
return@filter AviatorEvaluator.compile(it.checkRule, true).execute(
BeanUtil.beanToMap(appUpdateRequest)
) as Boolean
}
return@filter true
}.firstOrNull()
} else {
return appUpdates.filter {
if(!it.checkRule.isNullOrEmpty()) {
// 模板表达式匹配
return@filter AviatorEvaluator.compile(it.checkRule, true).execute(
BeanUtil.beanToMap(appUpdateRequest)
) as Boolean
}
return@filter true
}.firstOrNull()
}
}
3.2. 规则
下发的规则字段匹配可能会非常多,经常变动。这个时候如果通过简单的字段来匹配的话,需要扩充很多字段,需要手动匹配逻辑(大于某个版本,小于某个版本)来进行处理。采用aviator表达式引擎来进行表达式计算,返回表达式是true或者false来决定是否适配这一条配置。
lua
AviatorEvaluator.compile(it.checkRule, true).execute(
BeanUtil.beanToMap(appUpdateRequest)
)
通过配置的校验规则进行判断是否可以下发,参数来源于请求参数。

3.3. 返回JSON定义
kotlin
class AppUpdateResponse(
val canDownload: Boolean? = null,
val version : String? = null,
val path : String? = null,
val sha512 : String? = null) {
}
必须有version,path,sha512字段匹配,canDownload是后台增加用户判断是否需要下载的判断。
3.4. 更新表定义
sql
-- auto-generated definition
create table t_app_update
(
id int auto_increment
primary key,
version varchar(16) null comment '版本号',
device_id varchar(64) null comment '设备id',
download_url varchar(255) null comment '下载地址',
sha512 varchar(256) null comment '加签值',
check_rule varchar(255) null comment '校验规则',
platform varchar(20) null comment '平台 windows mac linux',
open_flag tinyint default 1 null comment '开启标记 1- 开启 0 -关闭',
tentant_code varchar(16) null comment '商户号',
add_time datetime null,
update_time datetime null
)
engine = InnoDB
charset = utf8mb4;
3.5. downloadUrl
download_url需要放置到外网一个可以访问的地址,用于后续进行下载。本项目采用minio对外提供文件下载地址。
4. 前台
4.1. 更新配置
核心是关闭自动更新,手动配置feedUrl,让请求到后台更新接口上,返回对应的json配置信息
4.1.1. 强制测试版本更新
ini
// 强制测试版本更新
autoUpdater.forceDevUpdateConfig = true;
4.1.2. 关闭自动更新
ini
autoUpdater.autoDownload = false;
4.1.3. 设置FeedUrl
ini
const version = electronApp.getVersion();
Log.info('[addon:autoUpdater] current version: ', version);
// 设置下载服务器地址
let server = cfg.options.url;
let lastChar = server.substring(server.length - 1);
server = lastChar === '/' ? server : server + "/" + "?version=" + version + "&platform=" + Os.platform();
Log.info('[addon:autoUpdater] server: ', server);
cfg.options.url = server;
try {
autoUpdater.setFeedURL(cfg.options);
} catch (error) {
Log.error('[addon:autoUpdater] setFeedURL error : ', error);
}
4.2. 更新监听
需要用到electron和web中的ipc通信,互相进行消息传递。
web | node | app.checkUpdate 自动或者手动触发更新 |
---|---|---|
node | checkUpdate 通过调用feedUrl,检查是否需要更新 | |
node | web | update-available 有可用更新,通过web弹窗提示 |
node | app.downloadUpdate 开始更新 | |
node | web | download-progress 更新进度,同步提示给web进行进度条展示 |
node | update-downloaded 下载完成,直接触发系统级别的安装 |
4.2.1. 更新状态值
yaml
const status = {
error: -1,
available: 1,
noAvailable: 2,
downloading: 3,
downloaded: 4,
}
4.3. 弹窗提示
需要监听是否有更新和更新进度两块,这两个合二为一。
arduino
ipc.on('app.updater', (event, info) => {
const infoN = JSON.parse(info)
console.log('app.updater', infoN)
// 弹窗提示用户进行更新
if (infoN.status == 1) {
// 改为确认框
ElMessageBox.confirm('发现新版本,是否立即更新?', '更新提示', {
confirmButtonText: '更新',
cancelButtonText: '稍后更新',
type: 'warning'
}).then((res) => {
// 用户点击了更新按钮
console.log('用户点击了更新按钮', res)
if (res === 'confirm') {
ipc.invoke('app.downloadUpdate')
openProgressDialog()
}
}).catch(() => {
// 用户点击了稍后更新按钮
});
} else if(infoN.status == 3) {
updateProgress(infoN.percentNumber)
}
})
<el-dialog
v-model="showDialog"
title="下载进度"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
width="30%"
>
<el-progress
:percentage="percentNumber"
:stroke-width="18"
:text-inside="true"
/>
</el-dialog>
5. 整体思路
5.1. 成果

更新为自动更新,通过ipc调用node,传递token过去
ipc.invoke('app.checkUpdate', userStore.getToken)
5.2. 结果
至此整个功能更新功能基本完成,后续需要开发一个更新页面,用来配置更新条目。适配不同版本,不同设备,不同平台的更新,相应的就可以做到定向更新,灰度更新等功能。