自动更新
主要流程
自动更新程序需要解决以下问题:
- 它需要知道服务端最新的版本信息;
- 它需要知道是否有最新版本,也就是版本比较问题;
- 在有更新版本的时候,需要从哪下载更新包;
- 下载的更新包是否是遭到篡改问题;
- 它需要能够执行更新程序,在更新完毕后自动启动程序以便用户继续使用;
第一个问题可以配置一个最新版本的文件,存放在服务端,这样客户端在每次启动的时候就需要读取版本文件的URL地址,获取URL地址的版本信息SVersion.json;
第二个问题:在比较版本的时候,需要在SVersion.json中读取到框架版本号和应用版本号,程序在启动的时候进行比较版本号,发现有更新;
第三个问题:如果发现有更新的程序,需要从SVersion.json中获取最新的框架安装包或者最新的应用安装包下载地址,然后下载更新包;
第四个问题:对于下载的安装包,需要用MD5来计算哈希值,与SVersion.json中的哈希值进行比对,这样就验证了有效性。
第五个问题:运行下载的安装包,程序本身退出,在更新包安装完毕能够启动程序。
主要流程如下所示:
服务端
在服务端某个URL路径存放的更新配置文件(SVersion.json),需要记录框架版本号和应用版本号;同时最新框架更新包和应用更新包的下载地址以及他们的MD5哈希值,下面有个示例:
//SVersion.json
{
"iOS": {
},
"android": {
},
"windows": {
"resourceVersion": "1.5.1",//应用版本号
"Version": "0.37.5",//框架版本号
"resourceUrl": "http://192.168.30.53:8087/update/app1.5.1.exe",//应用安装包下载路径
"updateUrl": "http://192.168.30.53:8087/update/Setup.exe",//框架安装包下载路径
"frameSign":"db59cea7137cc92a399c99f6ba84f955",//框架安装包的哈希值
"appSign":"dfcc0af9ddef11388cbbaa7148bc07df"//应用安装包的哈希值
}
}
在服务端同时要存放最新框架更新包和应用更新包,同时对安装包进行计算哈希值,下面的代码就是计算MD5哈希值的示例:
var crypto = require('crypto')
var fs = require('fs')
function md5File (filename, cb) {
if (typeof cb !== 'function') throw new TypeError('Argument cb must be a function')
var output = crypto.createHash('md5')
var input = fs.createReadStream(filename)
input.on('error', function (err) {
cb(err)
})
output.once('readable', function () {
cb(null, output.read().toString('hex'))
})
input.pipe(output)
}
//计算框架安装包的哈希值
md5File('E:/electron_app/SetUp.exe',function (err, hash) {
console.log('frame exe hash result is '+hash);
});
//计算应用安装包的哈希值
md5File('E:/electron_app/SetUp_App.exe',function (err, hash) {
console.log('app exe hash result is '+hash);
});
在cmd控制台中用node.exe对其进行运行,得到16进制字符串的哈希值,然后存储到SVersion.json中,发布到服务端。
erayt-updater组件
为了实现自动更新,我们写了个组件erayt-updater,该组件能够根据配置文件的URL地址参数和框架版本号等参数,自动比较版本,进行下载安装包并比对哈希值,运行安装包等功能。但是该组件运行在程序的启动过程中,需要在主进程中调用。
安装组件
npm install erayt-updater --save
方法
- setFeedURL方法
该方法主要是传入参数:SVersion.json的URL地址;框架版本号;应用版本号和更新包下载的本地存储路径。
var auto = require('erayt-updater');
auto.setFeedURL({
updateURL:AUTO_UPDATE_URL,//SVersion.json的URL
frameVer:process.versions.electron,//electron 版本
appVer:app.getVersion(),//app 版本
downloadPath:app.getPath('downloads')//更新包下载存储的路径
});
- checkForUpdates方法
该方法主要功能是进行版本比对;下载安装包和安装包的哈希值验证的功能。该方法使用的前提是利用setFeedURL方法设置好了参数。
auto.checkForUpdates();
- quitAndInstall方法
该方法是创建子进程执行下载文件夹里面的更新包程序功能,这样主框架调用退出程序就可以安装更新包了。
console.log('本应用程序退出,更新程序即将安装');
auto.quitAndInstall(exePath);
app.quit();
事件
- 事件:'update-error'
当更新发生错误的时候触发,参数err是错误信息
auto.on('update-error',(err)=>{
console.error('自动更新期间出现错误;%s',err);
//启动显示主窗口
});
- 事件:'checking-for-update'
当开始检查更新的时候触发,无参数。
auto.on('checking-for-update',()=>{
console.log('开始进行自动更新');
});
- 事件:'update-available'
当发现一个可用更新的时候触发,更新包下载会自动开始,参数:
isframe:是主框架需要更新还是应用程序包需要更新;
version:最新更新的版本号;
downloadurl:最新更新包的下载地址;
originMd5:更新包的MD5哈希值
auto.on('update-available',(isframe,version,downloadurl,originMd5)=>{
var tips = isframe ? '主框架程序' : '应用程序';
console.log(tips+'有可用的更新,更新版本为:%s,更新地址为%s,更新程序MD5值为%s',version,downloadurl);
});
- 事件:‘update-user-cancel’
用户取消了本次更新触发的事件
auto.on('update-user-cancel',()=> {
console.log('用户取消更新');
//启动显示主窗口
});
- 事件:'update-not-available'
当没有可用更新的时候触发。
auto.on('update-not-available',()=> {
console.log('没有可用的应用更新');
//启动显示主窗口
});
- 事件:'update-downloaded'
在更新下载完成的时候触发,参数为更新包下载到本地的绝对路径
auto.on('update-downloaded',(localpath)=>{
console.info('更新程序已经下载完成,下载路径为%s',localpath);
setTimeout(()=> {
console.log('本应用程序退出,更新程序即将安装');
auto.quitAndInstall(localpath);//调用该exe执行
return app.quit();//本程序退出
},1000);
});
使用erayt-updater
组件erayt-updater是在electron的主进程里面使用的,主进程也就是运行package.json里的main脚本的进程,并且electron官网也建议我们在app的will-finish-launching事件中启动自动更新,所以我们在主进程的app的will-finish-launching事件回调函数中调用erayt-updater组件的checkForUpdates方法来开始更新。同时我们需要给用户显示是否有更新、更新的版本信息或者更新出错信息,所以需要开发者制作个窗口来显示。
更新信息界面
- 在更新界面需要有更新信息;更新出错信息;还有下载进度信息;以下都是我自己写的测试界面,主要是利用bootstrap来写的界面,只有两个html页面,第一个更新页面能够弹出modal框显示下载信息,第二个出错页面就是显示出错信息内容。
启动更新界面:
更新进度界面:在有更新的时候,在启动界面中弹出BootStrap模态框来显示下载进度和更新的版本信息。
更新错误界面:在出错页面中,显示更新出错信息。
自动更新示例
首先安装erayt-updater组件,然后制作两个更新的html页面,在主进程的main.js里面的app事件中启动更新,监听erayt-updater组件事件,然后显示在更新界面中,如果没有更新、更新出错与用户取消更新则显示主界面,存在更新并下载完更新包调用更新包退出程序。
//显示更新信息
var showUpdateInfo =function(msg)
{
if(windows.load)//windows.load就是上面的等待html的BrowserWindow对象
windows.load.webContents.executeJavaScript('gUpdateInfo("'+msg+'")');
};
app.on('will-finish-launching', function () {
auto.setFeedURL({
updateURL:'http://192.168.30.53:8087/update/SVersion.json',
frameVer:'0.37.5',
appVer:'1.5.1',
downloadPath:app.getPath('downloads')
});
});
app.on('ready', function () { //在ready事件中才能创建窗口,在事件回调中创建主窗口和更新窗口
windows.createLoadWindow();
windows.createMainWindow();
auto.checkForUpdates(); //启动自动更新
});
auto.on('update-error',(err)=>{
var errinfo= '自动更新期间出现错误;'+err;
showUpdateInfo(errinfo);
windows.load.close();//关闭更新窗口
showMain();//显示主窗口
});
auto.on('checking-for-update',()=>console.log('checking-for-update'));
auto.on('update-available',(isframe,version,downloadurl)=>{
var tips = isframe ? '主框架' : '资源包';
var msg =tips+'有可用的更新,更新版本为:'+version+',更新地址为'+downloadurl;
if(windows.load)
windows.load.webContents.executeJavaScript('gShowModal("'+tips+'有可用的更新,更新版本为:'+version+'")');
});
//当没有更新的时候校验框架和app的哈希值
auto.on('update-not-available',()=> {
console.info('没有可用的应用更新');
showUpdateInfo("没有可用的应用更新");
windows.load.close();//关闭更新窗口
showMain();//显示主窗口
});
auto.on('update-user-cancel',()=> {
console.info('用户取消更新');
windows.load.close();//关闭更新窗口
showMain();//显示主窗口
});
auto.on('update-progress',(percent)=> {
if(windows.load)
windows.load.webContents.executeJavaScript('updateProgress('+percent+')');//更新进度条
});
//安装更新程序
auto.on('update-downloaded',(localpath)=>{
showUpdateInfo("更新程序已经下载完成,准备安装");
app.emit('update-over');
setTimeout(()=> {
console.info('本应用程序退出,更新程序即将安装');
auto.quitAndInstall(localpath);//执行安装程序/
return app.quit();//退出主程序
},1000);
});