一个项目正式交付客户使用了,对于版本的升级和代码更新是个一直困扰的问题,由于之前一直都是内部人员使用,可以让他们每次都下载完整压缩包(压缩后大概 40-50MB),但对于正式的终端用户,这个体验是不可接受。 参考过 Electron 官方文档,里面提及的方案兼容性并不好(Linux 不支持),需要准备的东西不少,而且交互上也不满意;本文介绍方案的细节及中间遇到的一些坑。
流程图
- 通过
app.getVersion()
获取到 App 的本地版本号,这个版本号来源于package.json
; - 获取线上的 App 版本信息,此处我们设计了两个版本信息(如图例子);
2.1. “1.3.0” 为核心版本号,该版本号用于判断是否需要重新下载完整包(图中所示的对应不同平台有不同的下载地址,为 zip 扩展名的压缩包,至于为什么用 zip,下文会提及),比如当 Electron 核心版本升级,或者增加了 node_modules 依赖等情况;
2.2. “1.3.3” 为热更新版本号,这种情况只需要更新特定的文件列表(图中所示的 upgrade_1.3.3.tar.gz),通常包含
package.json
、src/*
等; 2.3.description
分别表示核心版本和热更新版本的描述; - 对比本地版本号 (
appVersion
) 和线上的版本号 (onlineVersion
),下面是版本号比较的代码:
/* v1 为本地版本号,v2 为线上版本号 */
const compareVersion = (v1, v2) => {
const vs1 = v1.toString().split('.'), vs2 = v2.toString().split('.');
if (vs1.length !== vs2.length) {
// 版本格式不一致
return true;
}
for (let i = 0; i < vs1.length; i) {
let diff = parseInt(vs2[i], 10) - parseInt(vs1[i], 10);
if (diff < 0) {
// vs1 其中一个版本号段小于 vs2
return false;
}
if (diff > 0) {
// vs2 其中一个版本号段大于 vs1
return true;
}
}
// 版本一致
return false;
};
- 3.1. 对比本地版本号和线上核心版本号 (
onlineVersion.version
),如果小于线上版本号,则激活浏览器并进入下载,同时退出 App; 3.2. 否则进行对比本地版本号和热更新版本号 (onlineVersion.upgrade.version
),如果小于线上版本号,则下载热更新包; - 根据 (
upgrade.url
) 下载热更新包、升级、重启 App,全部代码:
const { app } = require('electron'),
fs = require('fs-extra'), // 用于扩展内置 fs 方法
request = require('request'), // 用于发起下载请求
tar = require('tar'); // 用于执行 tar 解压缩
//...
// 下载热更新包代码
new Promise((resolve, reject) => {
request({
url: onlineVersion.upgrade.url,
encoding: null // encoding 为 null 使 body 生成为一个 Buffer
}, (error, res, body) => {
try {
if (error || res.statusCode !== 200) {
throw '请求失败';
}
// 保存到临时目录,temp 为 Electron 用户可写目录
let tempPath = app.getPath('temp');
let dir = fs.mkdtempSync(`${tempPath}/upgrade_`);
// 创建 Buffer 流并解压
let stream = new require('stream').Readable();
stream.push(body);
stream.push(null);
stream.pipe(tar.extract({
sync: true,
cwd: dir
})).on('close', () => {
// 解压完毕,复制更新文件
fs.copySync(dir, __dirname); // 解压至指定的目录,这里用 __dirname 为例
fs.removeSync(dir); // 删除临时目录
// 返回 true 表示需要重启
resolve(true);
});
} catch (e) {
reject('更新文件下载失败,请联系管理员');
}
}
}).then(result => {
if (result) {
app.relaunch({ args: process.argv.slice(1) }); // 重启
app.exit(0);
}
}).catch(e => {
// e 错误
});
作者:玉
链接:https://juejin.im/post/5c8b51fa5188257e8e232d13
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
坑
- Windows 下可以直接把更新文件保存到 App 目录(没有权限问题),而在 Mac OSX 下,App 目录是无权写入的,而临时文件路径必须通过 app.getPath() 来获取,常用
temp
或者userData
目录来保存解压文件; - 压缩包我使用了 zip 一个原因是 Windows 下默认就能支持 zip 包解压,另一个主要原因是文件名编码问题,最初用 tar 来压缩文件,对于英文文件名是正常的,对于中文文件名,在 Mac OSX 下打包后在 Windows 是无法被正确解开的。而貌似 7z 生成的 zip 包会保存文件名字符集并能被 Windows 正确解开,所以在 gulp 中使用了 7z 来产生 zip 压缩包:
const child = require('child_process');
child.exec(`7z a -tzip -r ./app-{platform}_${packageInfo.version}.zip ./中文的应用程序名-${platform}`, (error, stdout, stderr) => {
//...
});
而对于热更新包,由于源代码都是使用英文名,所以用 tar 来压缩没毛病:
代码语言:javascript复制const tar = require('tar');
tar.create({
gzip: true,
file: `./upgrade_${packageInfo.version}.tar.gz`,
cwd: './build'
}, [ 'package.json', 'src' ]).then(() => { // 保存 package.json 和 src 目录
//...
});