更新 - 全量更新
更新
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
新建一个目录 server
http-server ./ -p 4000 // 启一个 4000 端口的服务 http://127.0.0.1:4000
server 下新建一个 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.$api
api('http://localhost:4000/index.json', {}, {method: 'get'}).then(res => {console.log(res)
})
打高版本包
而后咱们打一个高版本包,这里是打 dev 包,批改 .env.dev
的VUE_APP_VERSION
为 0.0.2
npm run build:dev:win64
mac 同理,将打包好的文件放入 server 目录
win:latest.yml,0.0.2 的 exe
mac: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.js
export 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.js
import {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.$message
onMounted(() => {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 文件,咱们可能会批改其中的参数,有时候发现返回还是没变,请将控制台的 Network
的Disable cache
勾上(申请缓存常见解决形式)
下载缓存
在开发时咱们可能会做开发调试,比方看下载进度呀什么的,先打了一个高版本的放在近程地址,本地反复装置低版本的看更新成果,然而在第一次更新实现后,前面的更新都是霎时实现了,看不到进度,这里是因为 electron-updater
在更新时会检测本地是否下载过这个高版本,有的话间接用本地的进行装置,咱们能够把这个缓存文件删除掉。AppData 这个文件夹呢可能是处于暗藏的,前面挺多中央会用到这个的,能够在顶端查看中勾选暗藏的我的项目让其显示,具体的话百度吧。
electronvuedev 这个是你设置的 name(本框架在 vue.config.js 中)win:C:\Users\Administrator(你的用户)\AppData\Local\electronvuedev-updater
mac:~/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 = true
autoUpdater.quitAndInstall()
-
其余起因排查:当然不可能只有下面几种起因的,那么咱们如何进行排查呢:
- 后端谬误收集:autoUpdater 的 error 事件,能够在这个事件中把错误信息传递给后端接口。
- 增加谬误日志:如果只能本人调试的话,能够用
electron-log
这个包把 autoUpdater 的 error 收集到本地日志中:
npm i electron-log 新建 log.js import 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 = log autoUpdater.on('error', (err) => {log.info('更新呈现谬误') log.info(err.message) })
当更新呈现问题后查看对应的 log 文件
win:C:\Users\Administrator(你的用户)\AppData\Roaming\<app name>\logs mac: ~/Library/Logs/<app name>
本系列更新只有利用周末和下班时间整顿,比拟多的内容的话更新会比较慢,心愿能对你有所帮忙,请多多 star 或点赞珍藏反对一下
本文地址:链接
本文 github 地址:链接