electron在线升级更新的两种方式(整体更新和部分更新)及我是如何实现electron在线升级热更新功能的?(企业级项目已上线)

这篇主要以讲解部分资源在线热更新的实现为核心,electron自带的整体更新的实现较简单,简单说一下即可,如有疑问点的可以自行查阅相关资料或在下面留言给我即可

一.electron的在线升级更新方式都有哪些?

1. electron自带的整体更新方式

这种方式为electron官方的升级更新方式,主要是通过主进程中的autoUpdater模块进行检测升级更新的,此方式也是大家常见的大多数electron应用程序的更新方式。

不过,此方式有一个瑕疵就是,每次更新都是整体更新软件,安装包资源比较大,下载安装包资源会比较费时,体验不是很好(当然了,如果不是很在意,也可以忽略下载时间体验问题)。

再一个就是,在大多数多次更新升级中,一般更新大多的只是变动前端资源文件,主进程一般不变动,所以每次整体更新的话也没有必要,造成资源浪费。

2. 在线热更新方式(只更新渲染进程相关,前端页面等资源,不更新主进程程序)

这种方式是只更新渲染前端相关资源,不会更新主进程相关的东西,所以下载更新的资源会很小,更新起来会很快,因为是在线热更新,更新完成后不用重新启动软件,只需刷新页面重新加载资源即可,所以,这种方式体验效果也比较好。

二.两种更新方式的实现

1. 整体更新

1).实现思路:

在每次启动应用软件时检测更新,在项目的APP.vue文件中发送一个初始加载应用APP的通信消息到主进程中,在main->index.js文件中的主进程里接收初始加载应用APP的通信消息,然后触发检测更新,并在主进程里监听autoUpdater模块里的相关事件及调用相关API即可。

2).代码实现:

更新弹框组件SysUpdate.vue文件代码:

<template>
  <el-dialog
    class="sys-update"
    title="应用更新"
    :visible.sync="showVisible"
    width="500px"
    :show-close="showClose"
  >
    <div class="update-info">
      <p v-show="msg!=''">{{ msg }}</p>
      <el-progress v-show="percent>0" :text-inside="true" :stroke-width="26" :percentage="percent"></el-progress>
    </div>
  </el-dialog>
</template>

<script>
export default {
  name: "SysUpdate",
  props: {
    percent: {
      type: Number,
      required: true,
      default: 0,
    },
    msg: {
      type: String,
    },
    show: {
      type: Boolean,
      require: true,
      default: false,
    },
    showClose: {
      type: Boolean,
      require: true,
      default: false,
    },
  },
  data() {
    return {};
  },
  computed: {
    showVisible: {
      get() {
        return this.show;
      },
      set(val) {
        this.$emit("update:show", val);
      },
    },
  },
};
</script>

<style scoped>
.update-info {
  margin: 20px 0;
  text-align: center;
}
</style>

APP.vue文件中的相关代码:

<template>
  <div id="app">
    <router-view />
    <sys-update
      :show.sync="showUpdate"
      :percent="percent"
      :msg="updateMsg"
      :show-close="showClose"
      ref="sysUpdateRef"
    ></sys-update>
  </div>
</template>

<script>
import { ipcRenderer } from "electron";
import SysUpdate from "@/components/SysUpdate.vue";

export default {
  name: "App",
  components: { SysUpdate },
  data() {
    return {
      percent: 0,
      updateMsg: "检查更新",
      showUpdate: false,
      showClose: false,
    };
  },
  created() {
	ipcRenderer.send("loadApp", "");	//启动时触发更新检测
	ipcRenderer.on("downloadProgress", (event, data) => {	//下载进度
      this.percent = data.percent.toFixed(2);
    });
    ipcRenderer.on("updateSys", (event, data) => {	//更新情况消息通知监听
      this.showClose = false;
      switch (data.status) {
        case -1:
          this.updateMsg = data.msg;
          this.showClose = true;
          this.percent = 0;
          break;
        case 0:
          this.updateMsg = data.msg;
          break;
        case 1:
          this.showUpdate = true;
          this.updateMsg = data.msg;
          this.percent = 1;
          break;
        case 2:
          this.showUpdate = false;
          break;
      }
    });
  },
};
</script>

主进程main->index.js文件中的相关代码:

import { app, ipcMain  } from 'electron'
import { autoUpdater } from 'electron-updater'

const server = 'https://your-deployment-url.com'
const url = `${server}/update/${process.platform}/${app.getVersion()}`

autoUpdater.setFeedURL({ url })

ipcMain.on("loadApp",()=>{
  autoUpdater.checkForUpdates()	//初始启动时检测更新
})

const returnData = {
  error: {status: -1, msg: '检测更新查询异常'},
  checking: {status: 0, msg: '正在检查应用程序更新'},
  updateAva: {status: 1, msg: '检测到新版本,正在下载,请稍后'},
  updateNotAva: {status: 2, msg: '您现在使用的版本为最新版本,无需更新!'},
};

autoUpdater.on('update-downloaded', () => {
   autoUpdater.quitAndInstall(true,true)
})

//更新错误
autoUpdater.on('error', function () {
    sendUpdateMessage(returnData.error)
});

//检查中
autoUpdater.on('checking-for-update', function () {
    sendUpdateMessage(returnData.checking)
});

//发现新版本
autoUpdater.on('update-available', function () {
    sendUpdateMessage(returnData.updateAva)
});

//当前版本为最新版本
autoUpdater.on('update-not-available', function () {
    setTimeout(function () {
        sendUpdateMessage(returnData.updateNotAva)
    }, 1000);
});

//监听下载进度
autoUpdater.on('download-progress', function (progressObj) {
  mainWindow.webContents.send('downloadProgress', progressObj)
  mainWindow.setProgressBar(progressObj.percent / 100);
})

// 通过main进程发送事件给renderer进程,提示更新信息
function sendUpdateMessage(text) {
  mainWindow.webContents.send('updateSys', text)
}

代码中更新相关的官方文档地址:autoUpdater模块 更新应用程序

总结: 上面基本上已经将所有整体更新的核心代码都给了出来,以及实现思路,自行实现项目整体更新功能应该没有多大问题了,如有问题,可自行结合上面和自己查阅相关资料进行实现开发即可。

2. 部分热更新

1).实现思路:

通过在线下载远程服务器的更新压缩包到本地,然后进行解压,将解压的文件保存到安装目录中的目标文件中即可。

通俗将就是:下载压缩包——写文件到本地——解压压缩包到目标位置。

2).实现难点:

【1】如何判断本地资源需不需要更新?
【2】如何获取远程更新包资源?
【3】下载压缩包文件写到本地哪里?
【4】如何将更新包正确解压到正确的目标位置?

3).难点解答:

【1】通过更新包自身的版本号与本地的版本号进行对比来判断是否需要更新,本地缓存的版本号是当前已经更新完后的版本后,每次更新判断都是用当前本地版本号与下一次新的更新包版本号进行对比。

不过这里要注意,初始第一次本地是没有版本号的,我们需要初始化更新一次,并将最新更新包版本号缓存到本地,以便用于下次更新使用;再一点就是,这里所说的版本号并不是项目package.json中的version版本号,而是更新资源包自身定义的版本号,两者不是一回事。

【2】通过nodeJs的http.get()获取远程资源。
【3】推荐写到目标位置的上一级目录即可,当然了,这个可以根据自己需求进行配置改动。
【4】使用第三方工具adm-zip和nodeJs的path模块中的path.resolve(__dirname)来实现解压到目标位置。

4).热更新要更新的文件内容:

一般我们软件安装有个.exe执行程序文件所在目录,可以通过鼠标右击桌面软件图标-选择打开文件所在的位置即可找到。

进去我们会看到当前目录会有一个resources文件夹,而我们热更新要更新的文件在\resources\app\dist\electron\里的所有资源文件,如下图所示:
需要更新的文件

5).效果图:

在刷新页面操作里加了热更新操作,每次刷新页面都会去检测更新,界面图如下所示:

刷新按钮

触发更新,正在更新时,弹框提示,且弹框不能人为关闭,效果图如下:

正在更新

更新成功后,在右下角弹出更新成功提示信息,两秒后自动消失,消失后立马刷新页面,以重新加载更新后的资源文件。

更新成功

6).代码实现:

首先是写的远程获取资源js工具函数文件downloadUnzip.js代码:

//downloadUnzip.js
const http = require('http');

export const httpsGetData = (url, success, error) => {
  // 回调缺省时候的处理
  success = success || function () { };
  error = error || function () { };

  http.get(url, (res) => {
    const statusCode = res.statusCode;
    if (statusCode !== 200) {
      // 出错回调
      error();
      // 消耗响应数据以释放内存
      res.resume();
      return;
    }

    // 压缩包设置数据流为二进制
    res.setEncoding('binary');

    var rawData = '';
    res.on('data', function (chunk) {
      rawData += chunk;
    });

    // 请求结束
    res.on('end', function () {
      // 成功回调
      success(rawData);
    }).on('error', function (e) {
      // 出错回调
      error();
      console.log(e);
    });

  }).on('error', (e) => {
    console.error(e);
  });
}

navBar.vue文件中相关核心代码:

<template>
  <div class="navbar">
	  <i class="el-icon-refresh refrshSty" @click="refreshHandle"></i>
      <el-dialog
        title="程序资源更新"
        :visible.sync="updateVisible"
        :close-on-click-modal="false"
        :close-on-press-escape="false"
        :append-to-body="true"
        :show-close="false"
        width="400px"
      >
        <span>程序资源正在更新,请稍等...</span>
      </el-dialog>
  </div>
</template>

<script>
import { checkResVer } from "@/api/common/common";	//主动请求后台接口获取更新版本号和更新地址
import { httpsGetData } from "@/utils/downloadUnzip";	//导入获取资源工具函数
const AdmZip = require("adm-zip");	//引入解压工具
const fs = require("fs");
const path = require("path");

export default {
	data() {
		return {
		    tmp_version: "",
		    resourceUrl: "",
      		updateVisible: false,
		}
	},
	created() {
	  this.updateCheck();
	},
	methods: {
		// 资源更新检查
    	async updateCheck() {
		  const res = await checkResVer();
	      if (res.data.code == 200) {
	        let rrv = localStorage.getItem("renderResourceVersion");
	        if (!rrv) { //初始进来,本地还没有版本号,缓存当前线上资源版本号到本地,并且更新
	          this.updateVisible = true;
	          this.tmp_version = res.data.data.ver;
	          this.resourceUrl = res.data.data.res;
	          this.updateLoadResource();
	        } else {  //不是初始进来,并且线上版本号与本地版本号不一致,则更新
	          if (rrv !== res.data.data.ver) {
	            this.updateVisible = true;
	            this.tmp_version = res.data.data.ver;
	            this.resourceUrl = res.data.data.res;
	            this.updateLoadResource();
	          }
	        }
	      }
	    },
	    // 下载并解压更新资源
	    updateLoadResource() {
	      httpsGetData(this.resourceUrl, (data) => {
	        // 异步写入压缩文件
	        fs.writeFile(
	          path.resolve(__dirname, "../electron.zip"),
	          data,
	          "binary",
	          (err) => {
	            if (err) {
	              this.updateVisible = false;
	              this.$notify.error({
	                title: "更新失败",
	                message: "资源更新失败,请点击刷新更新资源",
	                duration: 2000,
	                position: "bottom-right",
	                showClose: false,
	              });
	              throw err;
	            }
	            console.log("更新资源文件已保存到本地电脑!");
	            // 解压到指定目录
	            const file = new AdmZip(path.resolve(__dirname, "../electron.zip"));
	            file.extractAllTo(path.resolve(__dirname), true);	//解压工具包中的API调用
	            this.updateVisible = false;
	            localStorage.setItem("renderResourceVersion", this.tmp_version); //将更新后的版本号缓存到本地
	            this.$notify({
	              title: "更新成功",
	              message: "资源已更新,准备重新加载资源",
	              duration: 2000,
	              position: "bottom-right",
	              type: "success",
	              showClose: false,
	            });
	            setTimeout(() => {
	              location.reload();
	            }, 2000);
	          }
	        );
	      });
	    },
	    // 刷新
	    refreshHandle() {
	      location.reload();
	      // 每次刷新页面都主动检测一下渲染资源是否需要更新
	      this.updateCheck();
	    },	
	}
}
</script>

7).相关知识文档及工具资源:

adm-zip第三方npm依赖包
fs.writeFile() API文档地址
http.get() API文档地址
path.resolve() API文档地址

其他资料可自行网上查阅学习。

三.结束语

其实上面最核心的就是理解在线热更新的原理以及明白逻辑代码实现,至于页面的表现形式以及在项目中该在哪里去添加更新检测,该在哪里去触发更新检测等等这些东西,都是可根据自身需求进行修改的,只要满足项目需求即可,不管如何表现,不同的触发方式等等,最终底层逻辑是一样的,正所谓万变不离其宗,说的就是这个意思吧~

好了,到这也该结束了,写这篇文章也花了我不少时间,希望对认真看完的你有所帮助~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值