Electron-vue 系列之自动更新与手动更新

文章目录

  • Electron-vue 系列之自动更新与手动更新
  • 一、主要插件
    • 1. electron-builder
    • 2. electron-updater
  • 二、各项配置
    • 1. package.json中publish配置
    • 2. 升级包配置
    • 3. 本地http服务器启动
    • 三、关键代码编写
    • 1. autoUpdate.js 文件
    • 2、前端页面 update.vue
    • 3. 在主进程中引入自动升级
    • 4. 实现效果
    • 5、关于新版本发布的一些信息
    • 五、注意事项
    • 六、额外配置项详解
    • 1. 控制流程的 api
    • 2. 一些配置项

一、主要插件

1. electron-builder

npm install electron-builder --save

2. electron-updater

npm install electron-updater --save

二、各项配置

1. package.json中publish配置

代码如下(示例):

publish:{
 provider:"generic",
	 // url:"http://xxx.xxx.xxx.xxx/xxx/xxx", 
	 // 升级包在服务器地址,不用指向具体的升级包文件
	 // 在本地起的http服务器
	 url: "http://127.0.0.1:8888/update/client"
},

2. 升级包配置

升级过程中主要是通过比对latest.yml 中的哈希码,如果和本地不同,再去拉去最新的安装包进行安装
因此,需将 electron-builder 打包的以下三个文件放在服务器同一文件夹下,以本地服务器为例,如下:
Electron-vue 系列之自动更新与手动更新

3. 本地http服务器启动

以本文为例,在update文件上一级目录打开cmd窗口,输入以下命令:

// 端口号 8888
npx http-server -p 8888

使用提供的任一路径均可访问,效果如下:
Electron-vue 系列之自动更新与手动更新
Electron-vue 系列之自动更新与手动更新
若服务器出现如下字样,代表链接服务器成功
Electron-vue 系列之自动更新与手动更新

三、关键代码编写

1. autoUpdate.js 文件


const {app, ipcMain, BrowserWindow, dialog} = require('electron')
const build = require("../vue.config")
// 注意这个autoUpdater不是electron中的autoUpdater
const {autoUpdater, CancellationToken} = require('electron-updater')
const logger = require('electron-log')
const { info } = require('sass')
const path = require('path')
const fs = require('fs-extra')
const serveControll = require("./api/event/serveControll")
// const config = require('../package.json')
// 更新地址
const updateURL = build.pluginOptions.electronBuilder.builderOptions.publish.url // 安装包下载地址
// 检测更新,在你想要检查更新的时候执行,renderer事件触发后的操作自行编写
function handleUpdate(mainWindow, callback) {
  // 若执行删除操作,每次检查更新都会重新下载更新包,
  // 若不执行删除操作,在已有更新包的情况下,会直接跳过下载事件,直接进行安装操作
  deleteUpdate();
  const message = {
    error: {status: -1,msg:'检查更新出错'},
    checking: {status: 0,msg:'正在检查更新……'},
    updateAva: {status: 1,msg:'检测到新版本,正在下载……'},
    updateNotAva: {status: -1,msg:'已经是最新版本'},
    // updateDownload:{status: 2,msg: '正在下载'}
  }
  // 设置是否自动下载,默认是true,当点击检测到新版本时,会自动下载安装包,所以设置为false
  autoUpdater.autoDownload = false 
  autoUpdater.autoInstallOnAppQuit = false;  // 如果安装包下载好了,当应用退出后是否自动安装更新
  // 设置版本更新服务器地址
  autoUpdater.setFeedURL(updateURL)
  // 更新发生错误时触发
  autoUpdater.on('error', function() {
    logger.error("检查更新出错")
    sendUpdateMessage(message.error)
  })
  // 开始检查更新事件
  autoUpdater.on('checking-for-update', function() {
    logger.info("开始检查更新")
    sendUpdateMessage(message.checking)
  })
  // 没有可更新版本
  autoUpdater.on('update-not-available', function(info) {
    logger.info("已经是最新的版本")
    sendUpdateMessage(message.updateNotAva)
  })
  // 发现可更新版本
  autoUpdater.on('update-available', function(info) {
    logger.debug("发现新版本")
    logger.info("新版本信息:",info)
    // 获取当前版本信息
    // logger.info("localVersion---->",config.version) 
    sendUpdateMessage(message.updateAva)
    mainWindow.webContents.send('update-available',info);
  })
  // 更新下载进度事件
  autoUpdater.on('download-progress', function(progressObj) {
    logger.debug('下载进度事件 ... ')
    logger.info("progressObj--->",progressObj)
    let info = {
      bytesPerSecond: progressObj.bytesPerSecond,
      percent: progressObj.percent,
      transferred: progressObj.transferred,
      total: progressObj.total
    }
    mainWindow.webContents.send('downloadProgress', info)
  })
  // 下载监听
  autoUpdater.on('update-downloaded', function(event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
    logger.info("下载完毕")
    let data = {
      releaseDate,
      releaseName,
      releaseNotes,
      updateUrl,
      quitAndUpdate
    }
    logger.info("releaseInfo---->",data)
    // autoUpdater.quitAndInstall();
    // callback()
    // 接收到立即更新的信号,退出程序并更新
    ipcMain.on('isUpdateNow', (e, arg) => {
      serveControll.stopServer(); // 关闭后台服务
      logger.info(arg)
      logger.info("开始更新")
      // 3秒后退出并安装,可控制
      setTimeout(()=>{
        autoUpdater.quitAndInstall();
      },3000)      
    })
    mainWindow.webContents.send("isUpdateNow",data)
  })
  // 自动执行更新检查
  // autoUpdater.checkForUpdates();
  // 检查更新
  ipcMain.on('checkForUpdate', () => {
    // 执行自动更新检查
    logger.info("执行更新检查")
    autoUpdater.checkForUpdates()
  })
  ipcMain.on('downloadUpdate', () => {
    // 下载
    logger.info("下载操作执行")
    // autoUpdater.downloadUpdate()
    autoUpdater.downloadUpdate().then((downloadPath) => {
      logger.info("download path:",downloadPath);
    }).catch((e) => {
      logger.info(e)
    })
  })
  // 立即安装
  ipcMain.on('handleUpdateNow', (e, arg) => {
    serveControll.stopServer(); // 关闭后台服务
    logger.info(arg)
    // logger.info("开始更新")
    console.log("开始安装")
    // 3秒后退出并安装,可控制
    setTimeout(()=>{
      autoUpdater.quitAndInstall();
    },3000)      
  })
  // 向渲染进程发送消息
  function sendUpdateMessage(text){
    mainWindow.webContents.send("message",text)
  }
}
// 更新前先删除本地已经下载的更新包文件
function deleteUpdate(){
  let updateCacheDirName = "sdp-desktop-client-updater"
  // 更新包下载路径
  const updatePendingPath = path.join(autoUpdater.app.baseCachePath,updateCacheDirName,'pending');
  logger.info("updatePendingPath=",updatePendingPath)
  // 删除本地安装包
  fs.emptyDir(updatePendingPath)
}
module.exports = {
  handleUpdate,
}

2、前端页面 update.vue

<template>
   <div class="systemExample width100 height100">
        <main>
            <div class="right-side">
                <div class="doc">
                    <div class="title alt">您可以点击的按钮测试功能</div>
                    <el-button type="primary" round @click="CheckUpdate">检查更新, 不可用于开发环境</el-button>
                    <!-- <el-progress :percent="this.percent" v-show="show">更新进度</el-progress> -->
                    <el-button type="primary" round @click="downloadUpdate">手动下载更新文件</el-button>
                    <el-progress :text-inside="true" :stroke-width="24" :percentage="this.percent"></el-progress>
                </div>
            </div>
        </main>
    </div>
</template>
<script>
    import event from "../api/event/event"
    import build from "../../vue.config"
    import config from "../../package.json"
    import logger from '../api/event/logger';
    let ipcRenderer = require("electron").ipcRenderer;
    export default {
        name: "systemExample",
        data(){
            return{
            percent: 0,
            show:'true'
        }},
        mounted () {
            // 主进程返回检查状态
            ipcRenderer.on("message",(e,data) => {
                console.log("status--->",data.status)
                switch(data.status){
                    // 检查更新出错 or 已经是最新版本
                    case -1:
                        this.$message.error(data.msg);
                        break;
                    // 正在检查更新
                    case 0:
                        this.$message({
                            message: data.msg,
                            type:"warning"
                        })
                        break;
                    // 检测到新本版
                    case 1:
                        this.$confirm("检测到新版本,是立即下载","提示",{
                            closeOnClickModal: false, // 禁止点击遮罩关闭弹框
                            closeOnPressEscape: false, // 禁止按 ECS 建古纳比弹框
                            confirmButtonText: '确定',
                            cancelButtonText: '取消',
                            type: 'warning',
                            }).then(()=>{
                                logger.info("确定下载新版本")
                                ipcRenderer.send('downloadUpdate')
                            }).catch(()=>{
                                logger.info("取消下载新版本")
                                this.$message({                               
                                message: "取消下载",
                                type:"warning"
                                })
                            })
                    // 正在下载
                    case 2:
                        this.$message({
                            message: data.msg,
                            type:"warning"
                        });
                }
            });
            // 有可用更新包
            ipcRenderer.on("update-available",(e,info) => {
                // 获取当前版本信息
                console.log("当前版本=",config.version)
                console.log("info--->",info)
            })
            // 更新进度
            ipcRenderer.on('downloadProgress',(e,progressObj) => {
                console.log("progressObj--->",progressObj);
                logger.info("progressObj--->",progressObj);               
                this.percent = (progressObj.percent).toFixed(2) || 0;
                console.log("this.percent---->",Math.trunc(this.percent))
                if(Math.trunc(this.percent) === 100){
                    this.show = false
                    // this.$confirm("下载完成,是否立即更新","提示",{
                    //     closeOnClickModal: false, // 禁止点击遮罩关闭弹框
                    //     closeOnPressEscape: false, // 禁止按 ECS 键关闭弹框
                    //     confirmButtonText: '确定',
                    //     cancelButtonText: '取消',
                    //     type: 'warning',
                    // }).then(()=>{
                    //     logger.info("下载完成,选择立即更新")
                    //     ipcRenderer.send('isUpdateNow');
                    //     this.$message({
                    //       type: 'success',
                    //       message: '更新成功'  
                    //     })
                    // }).catch(()=>{
                    //     logger.info("下载完成,选择取消更新")
                    //     this.$message({
                    //         type:'info',
                    //         message: '取消更新'
                    //     })
                    // })
                }
            });
            // 是否立即下载
            ipcRenderer.on('isUpdateNow',(e,data)=>{
                console.log("data---->",data);
                this.$confirm('下载已完成,是否立即安装','提示',{
                    closeOnClickModal: false, // 禁止点击遮罩关闭弹框
                    closeOnPressEscape: false, // 禁止按 ECS 键关闭弹框
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }) .then(()=>{
                    logger.info("下载完成,立即安装")
                    ipcRenderer.send('isUpdateNow');
                    this.$message({
                        type: 'success',
                        message: '成功下载'
                    })
                }).catch(()=>{
                    logger.info("下载完成,取消安装")
                    this.$message({
                        type:'info',
                        message: '已取消更新'
                    })
                })
            })        
        },
        methods: {           
            // 下面方法点击按钮开始检查更新,
            // 若要实现应用打开就开始检查更新,将此文件写在项目的根页面,执行这个方法即可!
            CheckUpdate() {
                ipcRenderer.send("checkForUpdate")
                console.log("url--->",build.pluginOptions.electronBuilder.builderOptions.publish.url);
            },
            // 手动下载
            downloadUpdate(){               
                logger.info("触发手动下载")
                // ipcRenderer.on('downloadProgress',(event,progressObj)=>{
                //     this.percent = progressObj.percent.toFixed(2) || 0
                //     console.log("this.percent---->",Math.trunc(this.percent))
                //     console.log(Math.trunc(this.percent) === 100)
                //     if(Math.trunc(this.percent) === 100){
                //         console.log('开始更新')
                //     }
                // })
                ipcRenderer.send("downloadUpdate")
            },           
        },
    }
</script>
<style scoped>
    .el-progress{
        padding: 20px 10px 0 10px;
    }
</style>

3. 在主进程中引入自动升级

第一种,窗口创建完成时触发,每次打开客户端都会触发检查更新操作

import autoUpdate from "./autoUpdate"
function createWindow(){
	...
}
app.on("ready",()=>{
	createWindow();    
	// 执行自动更新
	autoUpdate.handleUpdate(mainWindow)
})

第二种,在createWindow() 函数中引入

import autoUpdate from "./autoUpdate"
function createWindow(){
	...
	// 执行自动更新
	autoUpdate.handleUpdate(mainWindow)
}

4. 实现效果

本文以手动触发为例进行效果演示
Electron-vue 系列之自动更新与手动更新
Electron-vue 系列之自动更新与手动更新

5、关于新版本发布的一些信息

1、在检测新版本时,希望可以获取到新版本的一些信息,例如:版本信息,发布时间,发布名称等
那么这些信息从什么地方获取呐:latest.yml
在将新版本部署到服务器前,可以手动在中插入相关信息,例如:

version: 1.0.1
files:
  - url: xxx-client-1.0.1-win32-x64.exe
    sha512: hS2FJZxKC4KmC6cDsOaUSKRyQ0SwE5Ifc5qm2/WrIFPmEyOstbgM8r43r/Og+aq1s6/0df4PFLtSObfz67RFqA==
    size: 63494070
path: sdp-desktop-client-1.0.1-win32-x64.exe
sha512: hS2FJZxKC4KmC6cDsOaUSKRyQ0SwE5Ifc5qm2/WrIFPmEyOstbgM8r43r/Og+aq1s6/0df4PFLtSObfz67RFqA==
releaseName:'xxx客户端'
releaseNotes: '1.0.1’
releaseDate: '2022-05-09T02:48:42.853Z'

2、如果想要进行灰度发布,可以在 latest.yml 中插入stagingPercentage 字段,取值范围为 [0,100]

五、注意事项

1、progress 进度条事件可能不会触发,由于服务器在本地,下载速度过快,进度条事件不会触发,但可以打印 log 查看参数

2、autoUpdate.js 文件中的delete函数不启用的情况下:
(1)自动更新环节已经下载安装包在本地,取消更新,安装包保留
(2)再次点击手动更新事件时,会优先检测到本地安装包,不会触发下载过程,会直接跳到下载完毕环节,可以通过关闭服务器来测试此环节
(3)如果想每次测试更新都重新下载安装包,可以启用delete函数
(4)此处打印的 downloadPath 即更新安装包的路径,windows环境下默认为:

C:\Users\xx\AppData\Local\appName-updater

该路径下的pending文件夹中存放的即是安装更新包
Electron-vue 系列之自动更新与手动更新

 autoUpdater.downloadUpdate().then((downloadPath) => {
      logger.info("download path:",downloadPath);
    }).catch((e) => {
      logger.info(e)
    })

以下是我在测试过程中打印的日志作为参考:
Electron-vue 系列之自动更新与手动更新
5、
如果想使用灰度发布(staged rollouts),可以在latest.yml里插入stagingPercentage字段,取值范围为0~100
在打包过程中没有找到可以插入releaseNotes的地方。可以在部署前修改latest.yml,手动插入这个字段:

// 例如
releaseNotes:'this is 1.0.1 version',
releaseDate:'2022-05-11 16:00:00' 

六、额外配置项详解

1. 控制流程的 api

// 主要控制流程的 api
	// 执行一次检查更新
	autoUpdater.checkForUpdates() 
	// 执行一次检查更新,如有新的可用更新,自动弹出一个⾃带的通知提⽰告诉⽤户有新的更新
	autoUpdater.checkForUpdatesAndNotify() 
	// 执行下载安装包
	autoUpdater.downloadUpdate(CancellationToken) 
	// 退出应用并安装,isSilent 是否静默更新,isForceRunAfter 更新完后是否立即运行,默认为true
	autoUpdater.quitAndInstall(isSilent,isForceRunAfter) 

2. 一些配置项

	// 有可用更新包时,是否自动下载更新包
	autoUpdater.autoDownload = true 
	// 如果安装包下载好了,当应用推出后是否自动安装更新
	autoUpdater.autoInstallOnAppQuit = true 
	// 是否接受开发版,测试版之类的版本号
	autoUpdater.allowPrerelease= false 
	// 是否可以回退版本,比如从开发版,降低到旧的稳定版
	autoUpdater.allowDowngrade = false