更新-全量更新

更新

electron的更新一般来说有两种形式,全量和增量,顾名思义全量就是下载咱们打包好的exe文件或者zip文件,进行全面替换。咱们之前说过electron就是用浏览器关上咱们的页面,很多时候咱们的更新可能只会批改渲染过程,那么咱们把咱们的渲染过程的文件给替换了不久更新吗,也就是说增量实际上是替换打包好的html,js等文件。那么更新的形式如下:

  • 主过程批改:全量更新
  • 渲染过程批改:全量或增量更新

增量更新放到下一期来说,本篇是讲全量更新的事,本文旨在对全量更新进行具体的阐明,包含主过程的更新流程和渲染过程展现等以及更新问题如何排查。

更新流程

咱们这里先阐明一下咱们的解决流程:

  • 咱们的渲染过程申请接口,接口返回了更新信息,拿到更新信息进行版本比照以及更新逻辑判断。
  • 渲染过程全量更新通过,把信息传递给主过程
  • 主过程拿到信息实现更新,把更新信息推送给渲染过程
  • 渲染过程进行更新状态以及进度的显示,实现更新

electron-updater

全量更新咱们应用的是electron-updater这个包,进入咱们的我的项目:

npm i -D electron-updater

electron-updater更新只须要把一个url传入,而后它会主动查找${url}/xxx.yml文件,依据这个yml文件检测更新及下载,咱们能够通过其各种事件拿到对应的更新状态,下载实现之后调用autoUpdater.quitAndInstall()重启以后的利用并且装置更新
这个url下是放动态资源的,简略来说就是这个链接相似拜访一个目录,这个目录下须要有更新的文件以及检测更新的文件。
须要哪些货色呢:

  • win: latest.yml,exe文件
  • mac:latest-mac.yml,zip文件

总的来说咱们就是要一个url实现更新,先让渲染过程把这个url传过来吧,咱们先模仿一下更新申请。

更新申请

模仿更新申请

一般来说,更新都是调用后端接口,下面说了更新须要一个url,那么咱们这里就用json文件模仿申请以及模仿更新文件的地址

npm i -g  http-server新建一个目录serverhttp-server ./ -p 4000   // 启一个4000端口的服务http://127.0.0.1:4000server下新建一个index.json来当接口返回数据{  "code": 200,  "success": true,  "data": {    "forceUpdate": false, // 是否强制更新    "fullUpdate": true, // 是否全量更新    "upDateUrl": "http://127.0.0.1:4000/", // electron-updater传入的url    "restart": true,  // 增量更新是否重启    "message": "我要升级成0.0.2", // 更新阐明    "version": "0.0.2" // 版本号  }}浏览器关上http://localhost:4000/,看是否有index.json。咱们在渲染过程用申请获取index.json的数据(这里是我本人做的api申请封装)const api = proxy.$apiapi('http://localhost:4000/index.json', {}, { method: 'get' }).then(res => {  console.log(res)})

打高版本包

而后咱们打一个高版本包,这里是打dev包,批改.env.devVUE_APP_VERSION为0.0.2

npm run build:dev:win64mac同理,将打包好的文件放入server目录win:latest.yml,0.0.2的exemac:latest-mac.yml,0.0.2的zip(留神是zip不是dmg)

跨域解决

不出预料的话会报一个跨域的谬误,毕竟咱们的端口号不同,在electron中咱们的权限要比浏览器中大得多,
咱们能够把同源策略敞开,就不会呈现跨域的问题了,能够失去返回了。

BrowserWindow的webPreferences: {  .....  webSecurity: false}

补充:当然还有其余跨域的解决形式,想理解的话能够看一下我对跨域的阐明链接

渲染过程检测更新

检测更新

以后咱们的版本号的话,是在.env外面设置的,不晓得的可看搭建篇环境变量阐明。开发时不必检测版本更新,所以有个判断。
isClick是自动检测的话没有message提醒,手动有。

<template>  <div class="update">    <div class="version">以后版本为:{{ config.VUE_APP_VERSION }}</div>    <a-button type="primary" @click="upDateClick(true)">检测更新</a-button>  </div></template><script>import cfg from '@/config'import update from '@/utils/update'import { defineComponent, getCurrentInstance } from 'vue'export default defineComponent({  setup() {    const state = reactive({      visible: false,      upDateData: {},      upDateProgress: {        show: false,        percent: 0      }    })    const { proxy } = getCurrentInstance()    const config = cfg    const api = proxy.$api     function upDateClick(isClick) {      api('http://localhost:4000/index.json', {}, { method: 'get' }).then(res => {        console.log(res)        if (cfg.NODE_ENV !== 'development') {          update(config.VUE_APP_VERSION, res).then(() => {            state.upDateData = res            if (res.fullUpdate) {              updateNotice(isClick)            } else {              console.log()            }          }).catch(err => {            if (err.code === 0) {              isClick && message.success('已为最新版本')            }          })        } else {          message.success('请在打包环境下更新')        }      })    }    return {      config,      upDateClick    }  }})</script>

版本比照

这里用以后版本与接口版本进行比照,向主过程发送告诉(更新message弹窗)。

/utils/update.jsexport default (version, data) => {  return new Promise((resolve, reject) => {    const isUpdate = data ? versionCompare(data.version, version) : 0    switch (isUpdate) {      case 0:        console.log('不更新')        reject({ code: isUpdate })        break;      case 1:        if (data.fullUpdate) {          console.log('全量更新')          resolve()        } else {          console.log('增量更新')        }        break      case -1:        console.log('降级')        reject({ code: isUpdate })        break    }  })}// 1为以后版本比更新版本低,0为版本统一,-1为以后版本比更新版本高function versionCompare(stra, strb) {  const straArr = stra.split('.')  const strbArr = strb.split('.')  const maxLen = Math.max(straArr.length, strbArr.length)  let result  let sa  let sb  for (let i = 0; i < maxLen; i++) {    sa = ~~straArr[i]    sb = ~~strbArr[i]    if (sa > sb) {      result = 1    } else if (sa < sb) {      result = -1    } else {      result = 0    }    if (result !== 0) {      return result    }  }  return result}

更新弹窗

检测到更新后咱们显示更新弹窗,依据forceUpdate判断如果是强制更新的话咱们把弹窗的敞开除去,只能点确定。如果不是的话,那么点击勾销后,咱们记录一个更新版本号的localStorage,下次再检测更新时如果有值的话,更新弹窗将不会呈现

<a-modal  v-model:visible="visible"  title="更新"  @ok="upDateOk"  @cancel="upDateCancel"  :closable="!upDateData.forceUpdate"  :maskClosable="false"  :keyboard="!upDateData.forceUpdate"  :destroyOnClose="true">  <p v-html="upDateData.message"></p>  <template #footer v-if="upDateData.forceUpdate">    <div class="footer">      <a-button type="primary" @click="upDateOk">确定</a-button>    </div>  </template></a-modal>调用updateNotice(isClick),这里是按钮点击检测更新,当然你也能够程序自动检测更新,放在App.vue中被动调用updateNotice(false)function updateNotice(isClick) {  if (LgetItem(UPDATE_LIST) && LgetItem(UPDATE_LIST)[state.upDateData.version] && !isClick) { // 用户手动点击检测的话呈现弹窗    return  }  state.visible = true}function upDateOk() {  state.visible = false  window.ipcRenderer.invoke('win-update', {...state.upDateData})}function upDateCancel() {  if (!LgetItem(UPDATE_LIST)) {    LsetItem(UPDATE_LIST, {})  }  LsetItem(UPDATE_LIST, { ...LgetItem(UPDATE_LIST), [state.upDateData.version]: true })}

主过程解决更新

咱们向主过程推送了更新信息win-update

ipcMain.js接管渲染过程的更新信息import checkUpdate from './checkUpdate'ipcMain.handle('win-update', (_, data) => {  checkUpdate(data)})

下面说了electron-updater须要一个url,咱们这里实现一下其更新逻辑,electron-updater有各种更新状态,咱们把这些状态推送renderer-updateMsg给渲染过程,让其有对应的提醒或者进度,autoUpdater.quitAndInstall()被调用后会主动装置重启。

新建checkUpdate.jsimport { autoUpdater } from 'electron-updater'import log from '../config/log.js'import global from '../config/global'autoUpdater.logger = log/** * -1 查看更新失败 0 正在查看更新 1 检测到新版本,筹备下载 2 未检测到新版本 3 下载中 4 下载实现 **/function Message(type, data) {  const sendData = {    type,    data  }  global.sharedObject.win.webContents.send('renderer-updateMsg', sendData)}// 当更新产生谬误的时候触发。autoUpdater.on('error', (err) => {  log.info('更新呈现谬误')  log.info(err.message)  if (err.message.includes('sha512 checksum mismatch')) {    Message(-1, 'sha512校验失败')  }})// 当开始查看更新的时候触发autoUpdater.on('checking-for-update', () => {  log.info('开始查看更新')  Message(0)})// 发现可更新数据时autoUpdater.on('update-available', () => {  log.info('有更新')  Message(1)})// 没有可更新数据时autoUpdater.on('update-not-available', () => {  log.info('没有更新')  Message(2)})// 下载监听autoUpdater.on('download-progress', (progressObj) => {  Message(3, progressObj)})// 下载实现autoUpdater.on('update-downloaded', () => {  log.info('下载实现')  Message(4)  setTimeout(() => { // 重启更新提醒1秒后在进行重启装置    global.willQuitApp = true    autoUpdater.quitAndInstall()  }, 1000)})export default function (data) {  log.info('Update', data)  autoUpdater.setFeedURL(data.upDateUrl)  autoUpdater.checkForUpdates().catch(err => {    log.info('网络连接问题', err)    Message(5, err.code)  })}

渲染过程显示更新状态

在下面的更新弹窗中新增更新进度,其实就是接管主过程的更新状态做个展现:

<a-modal  v-model:visible="upDateProgress.show"  title="下载中"  :closable="false"  :maskClosable="false"  :keyboard="false"  :destroyOnClose="true"  :footer="null">  <a-progress :percent="upDateProgress.percent" status="active" /></a-modal>const message = proxy.$messageonMounted(() => {  window.ipcRenderer.on('renderer-updateMsg', (_, data) => {    switch (data.type) {      case -1:        message.error(data.data)        break      case 0:        message.info('正在查看更新')        break      case 1:        message.destroy()        message.success('已查看到新版本,开始下载')        state.upDateProgress.show = true        break      case 2:        message.destroy()        message.success('无新版本')        break      case 3:        state.upDateProgress.percent = data.data.percent.toFixed(1)        break      case 4:        state.upDateProgress.show = false        message.loading('重启更新中...', 1)        break      case 5:        message.destroy()        message.warning(data.data)        state.upDateProgress.show = false        break      default:        break    }  })})onUnmounted(() => {  window.ipcRenderer.removeListener('renderer-updateMsg')})

开发实现后咱们打个0.0.1版本的dev包(下面咱们放入server文件夹的是0.0.2的dev包),而后检测更新,更新重启后看打印的config是否是0.0.2。

补充

通过下面的步骤,咱们的一个全量更新的流程就算实现了,这里对一些常见问题进行补充。

申请缓存

咱们的申请是一个json文件,咱们可能会批改其中的参数,有时候发现返回还是没变,请将控制台的NetworkDisable cache勾上(申请缓存常见解决形式)

下载缓存

在开发时咱们可能会做开发调试,比方看下载进度呀什么的,先打了一个高版本的放在近程地址,本地反复装置低版本的看更新成果,然而在第一次更新实现后,前面的更新都是霎时实现了,看不到进度,这里是因为electron-updater在更新时会检测本地是否下载过这个高版本,有的话间接用本地的进行装置,咱们能够把这个缓存文件删除掉。AppData这个文件夹呢可能是处于暗藏的,前面挺多中央会用到这个的,能够在顶端查看中勾选暗藏的我的项目让其显示,具体的话百度吧。

electronvuedev这个是你设置的name(本框架在vue.config.js中)win:C:\Users\Administrator(你的用户)\AppData\Local\electronvuedev-updatermac:~/Library/Application Support/Caches/electronvuedev-updater

更新谬误检测

有时候咱们更新时会遇到问题,比方检测出更新,然而下载却不动,或是更新实现后并没有装置重启,这里先说一下比拟常见的几个可能会引发的问题

  • mac端下载进度不动:mac端打包会呈现三个文件,dmg安装包,dmg的zip压缩包以及yml件,比win多一个zip压缩包,mac更新下载的是zip文件,很多同学认为和win的exe一样放(win是exe和yml),把dmg文件放到更新地址下,没有zip文件,这会导致检测到更新,但下载进度却不动。
  • 更新下载后没有重启,关上软件版本也没有变:之前咱们在做窗口敞开时说过,能够在某些事件里用e.preventDefault()阻止窗口的敞开,比方咱们在第二期里应用了:
win.on('close', (e) => {  console.log('close', global.willQuitApp)  if (!global.willQuitApp) {    win.webContents.send('renderer-close-tips', { isMac })    e.preventDefault()  }})

针对于本我的项目,咱们在调用重启更新时,因为咱们设置了这个可能会引起窗口敞开的失败,这里咱们在敞开前设置willQuitApp,让其失常敞开:

global.willQuitApp = trueautoUpdater.quitAndInstall()
  • 其余起因排查:当然不可能只有下面几种起因的,那么咱们如何进行排查呢:

    • 后端谬误收集:autoUpdater的error事件,能够在这个事件中把错误信息传递给后端接口。
    • 增加谬误日志:如果只能本人调试的话,能够用electron-log这个包把autoUpdater的error收集到本地日志中:
    npm i electron-log新建log.jsimport log from 'electron-log'log.transports.file.level = 'silly'log.transports.console.level = false // 禁用console输入export default log在咱们的checkUpdate.js更新中应用import log from '../config/log.js'autoUpdater.logger = logautoUpdater.on('error', (err) => {  log.info('更新呈现谬误')  log.info(err.message)})

    当更新呈现问题后查看对应的log文件

    win:C:\Users\Administrator(你的用户)\AppData\Roaming\<app name>\logsmac: ~/Library/Logs/<app name>

本系列更新只有利用周末和下班时间整顿,比拟多的内容的话更新会比较慢,心愿能对你有所帮忙,请多多star或点赞珍藏反对一下

本文地址:链接
本文github地址:链接