自动更新


主要流程

自动更新程序需要解决以下问题:

  • 它需要知道服务端最新的版本信息;
  • 它需要知道是否有最新版本,也就是版本比较问题;
  • 在有更新版本的时候,需要从哪下载更新包;
  • 下载的更新包是否是遭到篡改问题;
  • 它需要能够执行更新程序,在更新完毕后自动启动程序以便用户继续使用;
    第一个问题可以配置一个最新版本的文件,存放在服务端,这样客户端在每次启动的时候就需要读取版本文件的URL地址,获取URL地址的版本信息SVersion.json;

第二个问题:在比较版本的时候,需要在SVersion.json中读取到框架版本号和应用版本号,程序在启动的时候进行比较版本号,发现有更新;

第三个问题:如果发现有更新的程序,需要从SVersion.json中获取最新的框架安装包或者最新的应用安装包下载地址,然后下载更新包;

第四个问题:对于下载的安装包,需要用MD5来计算哈希值,与SVersion.json中的哈希值进行比对,这样就验证了有效性。

第五个问题:运行下载的安装包,程序本身退出,在更新包安装完毕能够启动程序。

主要流程如下所示:

 自动更新  - 图1

服务端

在服务端某个URL路径存放的更新配置文件(SVersion.json),需要记录框架版本号和应用版本号;同时最新框架更新包和应用更新包的下载地址以及他们的MD5哈希值,下面有个示例:

  1. //SVersion.json
  2. {
  3. "iOS": {
  4. },
  5. "android": {
  6. },
  7. "windows": {
  8. "resourceVersion": "1.5.1",//应用版本号
  9. "Version": "0.37.5",//框架版本号
  10. "resourceUrl": "http://192.168.30.53:8087/update/app1.5.1.exe",//应用安装包下载路径
  11. "updateUrl": "http://192.168.30.53:8087/update/Setup.exe",//框架安装包下载路径
  12. "frameSign":"db59cea7137cc92a399c99f6ba84f955",//框架安装包的哈希值
  13. "appSign":"dfcc0af9ddef11388cbbaa7148bc07df"//应用安装包的哈希值
  14. }
  15. }

在服务端同时要存放最新框架更新包和应用更新包,同时对安装包进行计算哈希值,下面的代码就是计算MD5哈希值的示例:

  1. var crypto = require('crypto')
  2. var fs = require('fs')
  3. function md5File (filename, cb) {
  4. if (typeof cb !== 'function') throw new TypeError('Argument cb must be a function')
  5. var output = crypto.createHash('md5')
  6. var input = fs.createReadStream(filename)
  7. input.on('error', function (err) {
  8. cb(err)
  9. })
  10. output.once('readable', function () {
  11. cb(null, output.read().toString('hex'))
  12. })
  13. input.pipe(output)
  14. }
  15. //计算框架安装包的哈希值
  16. md5File('E:/electron_app/SetUp.exe',function (err, hash) {
  17. console.log('frame exe hash result is '+hash);
  18. });
  19. //计算应用安装包的哈希值
  20. md5File('E:/electron_app/SetUp_App.exe',function (err, hash) {
  21. console.log('app exe hash result is '+hash);
  22. });

在cmd控制台中用node.exe对其进行运行,得到16进制字符串的哈希值,然后存储到SVersion.json中,发布到服务端。

erayt-updater组件

为了实现自动更新,我们写了个组件erayt-updater,该组件能够根据配置文件的URL地址参数和框架版本号等参数,自动比较版本,进行下载安装包并比对哈希值,运行安装包等功能。但是该组件运行在程序的启动过程中,需要在主进程中调用。

安装组件

  1. npm install erayt-updater --save

方法

  • setFeedURL方法
    该方法主要是传入参数:SVersion.json的URL地址;框架版本号;应用版本号和更新包下载的本地存储路径。
  1. var auto = require('erayt-updater');
  2. auto.setFeedURL({
  3. updateURL:AUTO_UPDATE_URL,//SVersion.json的URL
  4. frameVer:process.versions.electron,//electron 版本
  5. appVer:app.getVersion(),//app 版本
  6. downloadPath:app.getPath('downloads')//更新包下载存储的路径
  7. });
  • checkForUpdates方法
    该方法主要功能是进行版本比对;下载安装包和安装包的哈希值验证的功能。该方法使用的前提是利用setFeedURL方法设置好了参数。
  1. auto.checkForUpdates();
  • quitAndInstall方法
    该方法是创建子进程执行下载文件夹里面的更新包程序功能,这样主框架调用退出程序就可以安装更新包了。
  1. console.log('本应用程序退出,更新程序即将安装');
  2. auto.quitAndInstall(exePath);
  3. app.quit();

事件

  • 事件:'update-error'

当更新发生错误的时候触发,参数err是错误信息

  1. auto.on('update-error',(err)=>{
  2. console.error('自动更新期间出现错误;%s',err);
  3. //启动显示主窗口
  4. });
  • 事件:'checking-for-update'

当开始检查更新的时候触发,无参数。

  1. auto.on('checking-for-update',()=>{
  2. console.log('开始进行自动更新');
  3. });
  • 事件:'update-available'
    当发现一个可用更新的时候触发,更新包下载会自动开始,参数:

isframe:是主框架需要更新还是应用程序包需要更新;

version:最新更新的版本号;

downloadurl:最新更新包的下载地址;

originMd5:更新包的MD5哈希值

  1. auto.on('update-available',(isframe,version,downloadurl,originMd5)=>{
  2. var tips = isframe ? '主框架程序' : '应用程序';
  3. console.log(tips+'有可用的更新,更新版本为:%s,更新地址为%s,更新程序MD5值为%s',version,downloadurl);
  4. });
  • 事件:‘update-user-cancel’

用户取消了本次更新触发的事件

  1. auto.on('update-user-cancel',()=> {
  2. console.log('用户取消更新');
  3. //启动显示主窗口
  4. });
  • 事件:'update-not-available'

当没有可用更新的时候触发。

  1. auto.on('update-not-available',()=> {
  2. console.log('没有可用的应用更新');
  3. //启动显示主窗口
  4. });
  • 事件:'update-downloaded'

在更新下载完成的时候触发,参数为更新包下载到本地的绝对路径

  1. auto.on('update-downloaded',(localpath)=>{
  2. console.info('更新程序已经下载完成,下载路径为%s',localpath);
  3. setTimeout(()=> {
  4. console.log('本应用程序退出,更新程序即将安装');
  5. auto.quitAndInstall(localpath);//调用该exe执行
  6. return app.quit();//本程序退出
  7. },1000);
  8. });

使用erayt-updater

组件erayt-updater是在electron的主进程里面使用的,主进程也就是运行package.json里的main脚本的进程,并且electron官网也建议我们在app的will-finish-launching事件中启动自动更新,所以我们在主进程的app的will-finish-launching事件回调函数中调用erayt-updater组件的checkForUpdates方法来开始更新。同时我们需要给用户显示是否有更新、更新的版本信息或者更新出错信息,所以需要开发者制作个窗口来显示。

更新信息界面

  • 在更新界面需要有更新信息;更新出错信息;还有下载进度信息;以下都是我自己写的测试界面,主要是利用bootstrap来写的界面,只有两个html页面,第一个更新页面能够弹出modal框显示下载信息,第二个出错页面就是显示出错信息内容。
    启动更新界面:

 自动更新  - 图2

更新进度界面:在有更新的时候,在启动界面中弹出BootStrap模态框来显示下载进度和更新的版本信息。

 自动更新  - 图3

更新错误界面:在出错页面中,显示更新出错信息。

 自动更新  - 图4

自动更新示例

首先安装erayt-updater组件,然后制作两个更新的html页面,在主进程的main.js里面的app事件中启动更新,监听erayt-updater组件事件,然后显示在更新界面中,如果没有更新、更新出错与用户取消更新则显示主界面,存在更新并下载完更新包调用更新包退出程序。

  1. //显示更新信息
  2. var showUpdateInfo =function(msg)
  3. {
  4. if(windows.load)//windows.load就是上面的等待html的BrowserWindow对象
  5. windows.load.webContents.executeJavaScript('gUpdateInfo("'+msg+'")');
  6. };
  7. app.on('will-finish-launching', function () {
  8. auto.setFeedURL({
  9. updateURL:'http://192.168.30.53:8087/update/SVersion.json',
  10. frameVer:'0.37.5',
  11. appVer:'1.5.1',
  12. downloadPath:app.getPath('downloads')
  13. });
  14. });
  15. app.on('ready', function () { //在ready事件中才能创建窗口,在事件回调中创建主窗口和更新窗口
  16. windows.createLoadWindow();
  17. windows.createMainWindow();
  18. auto.checkForUpdates(); //启动自动更新
  19. });
  20. auto.on('update-error',(err)=>{
  21. var errinfo= '自动更新期间出现错误;'+err;
  22. showUpdateInfo(errinfo);
  23. windows.load.close();//关闭更新窗口
  24. showMain();//显示主窗口
  25. });
  26. auto.on('checking-for-update',()=>console.log('checking-for-update'));
  27. auto.on('update-available',(isframe,version,downloadurl)=>{
  28. var tips = isframe ? '主框架' : '资源包';
  29. var msg =tips+'有可用的更新,更新版本为:'+version+',更新地址为'+downloadurl;
  30. if(windows.load)
  31. windows.load.webContents.executeJavaScript('gShowModal("'+tips+'有可用的更新,更新版本为:'+version+'")');
  32. });
  33. //当没有更新的时候校验框架和app的哈希值
  34. auto.on('update-not-available',()=> {
  35. console.info('没有可用的应用更新');
  36. showUpdateInfo("没有可用的应用更新");
  37. windows.load.close();//关闭更新窗口
  38. showMain();//显示主窗口
  39. });
  40. auto.on('update-user-cancel',()=> {
  41. console.info('用户取消更新');
  42. windows.load.close();//关闭更新窗口
  43. showMain();//显示主窗口
  44. });
  45. auto.on('update-progress',(percent)=> {
  46. if(windows.load)
  47. windows.load.webContents.executeJavaScript('updateProgress('+percent+')');//更新进度条
  48. });
  49. //安装更新程序
  50. auto.on('update-downloaded',(localpath)=>{
  51. showUpdateInfo("更新程序已经下载完成,准备安装");
  52. app.emit('update-over');
  53. setTimeout(()=> {
  54. console.info('本应用程序退出,更新程序即将安装');
  55. auto.quitAndInstall(localpath);//执行安装程序/
  56. return app.quit();//退出主程序
  57. },1000);
  58. });