electron自动检测更新

0 文章相关项目源码

项目地址

1 存放需更新文件的 node 服务

1.2.1 创建入口文件里的package.json文件(其余文件自行创建,无需命令行)

  • 1 创建一个空文件夹
  • 2 进入空文件夹,打开终端
  • 3 在终端输入 npm init,会提示让输入名字
    在这里插入图片描述
  • 4 名字内不能存在大写
    在这里插入图片描述
  • 5 根据提示输入完就能看到文件夹内多了个 package.json文件,此时就可以下载配置包了
  • 在这里插入图片描述
    在这里插入图片描述

1.1 目录架构

|-- .gitignore
|-- app.js // 微服务
|-- package.json
|-- packages
    |-- win // 放置更新包

1.2 入口文件

// app.js
const httpServer = require('http-server');
const path = require('path');

const port = 8091;

httpServer
  .createServer({
    // root 是当前文件夹的父文件夹
    root: path.resolve(__dirname, './'),
  })
  .listen(port);

console.log(`静态文件服务器运行在 http://localhost:${port}`);

1.3 存放待更新的安装包

  • 在与 app.js 同级目录创建packages与packages/win文件夹,里面放置electron-build打包出来的以下文件:
  • electron-build打包出来的以下文件来源:npm run build,如果没有这个文件(可能命令跟本示例不一样,只要是使用的 electron-build 配置打包出来的即可),麻烦移步这篇文章:https://blog.csdn.net/xzy__007/article/details/134708289?spm=1001.2014.3001.5502 -- 直接看 3.4.2 自定义安装的exe程序步骤
    在这里插入图片描述
builder-debug.yml
builder-effective-config.yaml
exe-dist Setup 1.0.1.exe // 1.0.1 为版本号,由node-server 文件平级文件 exe 文件的 package.json 文件 version 属性控制
exe-dist Setup 1.0.1.exe.blockmap
latest.yml

在这里插入图片描述

  • 上述文件来源:源码内与 node-server 文件平级文件 exe 文件内执行以下代码
cd vue-userList // 注意此时目录为 exe/vue-userList
npm run build2
// 会生成vue-dist文件,此时再去exe目录执行 
npm run build

1.4 启动微服务

node .\app.js 

1.5 检查服务是否启动成功

  • 打开浏览器,在网址栏输入 http://localhost:8091/packages/win/ 看到以下页面就代表启动完毕
    在这里插入图片描述

2 写有关自动更新配置

2.1 自动更新相关逻辑

/exe/auto-update.js

const { app, dialog, ipcRenderer, ipcMain } = require('electron')
const path = require('path')
const { autoUpdater } = require('electron-updater')

async function sleep (ms) {
  return new Promise((resolve) => {
    const timer = setTimeout(() => {
      resolve(true);
      clearTimeout(timer);
    }, ms);
  });
}

let updateValue = null
let downloadProgress = null
const autoUpdateApp = async (mainWindow) => {
  // 等待 3 秒再检查更新,确保窗口准备完成,用户进入系统
  await sleep(3000);
  autoUpdater.disableWebInstaller = false;
  // 这个写成 false,写成 true 时,可能会报没权限更新
  autoUpdater.autoDownload = false;
  //退出时自动安装更新
  autoUpdater.autoInstallOnAppQuit = false
  // 开发环境测试自动更新使用
  autoUpdater.updateConfigPath = path.join(__dirname, "./dev-update.yml")
  // 每次启动自动更新检查更新版本
  autoUpdater.checkForUpdates();

  autoUpdater.on("error", (error) => {
    console.log(["检查更新失败", error]);
  });
  // 当有可用更新的时候触发。 更新将自动下载。
  autoUpdater.on("update-available", (info) => {
    console.log("检查到有更新,开始下载新版本");
    const { version } = info;
    console.log(`最新版本为: ${version}`);
    updateValue = info
    autoUpdater.downloadUpdate();
  });
  // 当没有可用更新的时候触发,其实就是啥也不用做
  autoUpdater.on("update-not-available", () => {
    updateValue = null
    console.log("没有可用更新");
  });
  // 下载更新包的进度,可以用于显示下载进度与前端交互等
  autoUpdater.on("download-progress", async (progress) => {
    console.log(progress, 'downloadPercent');
    // console.log(progress);
    // 计算下载百分比
    const downloadPercent = Math.round(progress.percent * 100) / 100;
    // 实时同步下载进度到渲染进程,以便于渲染进程显示下载进度
    downloadProgress = downloadPercent
  });
  // 在更新下载完成的时候触发。
  autoUpdater.on("update-downloaded", (res) => {
    console.log("下载完毕, 提示安装更新", res);
    dialog.showMessageBox({
      type: 'warning',
      title: '更新提示',
      message: '下载完成',
      buttons: ['更新', '取消'],
      cancelId: 1
    }).then(res => {
      if (res.response == 0) {
        // 下载完成之后,关闭所有窗口开始更新
        autoUpdater.quitAndInstall()
      }
    })
  });
}

module.exports = {
  autoUpdateApp,
};
electron-builder.json  添加以下代码

  "publish": {
    "provider": "generic",
    "updaterCacheDirName": "dev-updater",
    "url": "http://localhost:8091/packages/win/"
  }
  
 --------- 修改后的代码 -----------
 
 {
  "appId": "exe-dome.example.myapp",
  "productName": "exe-dist",
  "directories": {
    "output": "exe-dist" 
  },
  "win": {
    "target": "nsis",
    "icon": "./icon/icon.jpg",
    "artifactName": "${productName} Setup ${version}.${ext}"
  },
  "nsis": {
    "oneClick": false,
    "perMachine": true,
    "allowToChangeInstallationDirectory": true
  },
  "publish": {
    "provider": "generic",
    "updaterCacheDirName": "dev-updater",
    "url": "http://localhost:8091/packages/win/"
  }
}

2.1.1 启动electron,看效果

  • 此时 先启动vue服务,再启动electron 服务
cd vue-userList // 注意此时目录为 exe/vue-userList
npm run server
// 会生成vue-dist文件,此时再去exe目录执行 
npm run start
  • 此时等待3秒后应该会看到这个界面
    在这里插入图片描述
  • 但实际看到的却是控制台的一串报错,这是因为electron开发环境是不支持自动更新的,只需按以下配置,模拟成生产环境即可
    报错信息:Skip checkForUpdates because application is not packed and dev update config is not forced

2.1.2 将开发环境设置成已打包状态

  • 按顺序找到以下文件,没有就 创建 / 写 出来,具体位置参考示例源码
// dev-update.yml
provider: generic
updaterCacheDirName: dev-updater // 下载的安装包存放路径  C:\Users\xxx\AppData\Local\dev-updater
url: "http://localhost:8091/packages/win/" // 自动更新文件存放地址 - 具体请看 node-server 文件文档
// main.js  解开这段代码的注释
  if (!app.isPackaged) { 
    Object.defineProperty(app, 'isPackaged', { 
      get: () => true, 
    }); 
  }
// auto-update.js 解开这段代码的注释
autoUpdater.updateConfigPath = path.join(__dirname, "./dev-update.yml")
  • 此时如果本地版本小于自动更新服务存放的版本就会触发自动更新
  • 版本由 package.json中的 version 属性控制
  • 此时就可以正常进行自动更新了

3 由页面主动控制是否进行下载更新

  • 上面写的,虽然实现了自动更新,但不能主动去触发自动更新,并且不经过用户同意就直接进行了下载,下载完才通知是否更新
  • 下面将实现,由用户主动选择是否更新,如果选了否就不进行下载,同时支持主动检查是否有新版本

3.1 主进程加入事件监听与事件回调ipcMain.handle方法,暴漏electron的方法

  • 此次写法与示例代码的 master分支,渲染进程被动调用主进程的暴漏方法不同,此次将渲染进程将主动向主进程发出请求以此获得回调,官方对此是有说明的
    在这里插入图片描述
  • 但由于本次直接暴漏了 ipcRenderer方法,此做法官方也是不推荐的
    在这里插入图片描述

master分支完整开发文档,被动接收主进程暴漏方法

auto-update.js

const { app, dialog, ipcRenderer, ipcMain } = require('electron')
const path = require('path')
const { autoUpdater } = require('electron-updater')

async function sleep (ms) {
  return new Promise((resolve) => {
    const timer = setTimeout(() => {
      resolve(true);
      clearTimeout(timer);
    }, ms);
  });
}

let updateValue = null
let downloadProgress = null
const autoUpdateApp = async (mainWindow) => {
  // 等待 3 秒再检查更新,确保窗口准备完成,用户进入系统
  await sleep(3000);
  autoUpdater.disableWebInstaller = false;
  // 这个写成 false,写成 true 时,可能会报没权限更新
  autoUpdater.autoDownload = false;
  //退出时自动安装更新
  autoUpdater.autoInstallOnAppQuit = false
  // 开发环境测试自动更新使用
  // autoUpdater.updateConfigPath = path.join(__dirname, "./dev-update.yml")
  // 每次启动自动更新检查更新版本
  autoUpdater.checkForUpdates();

  autoUpdater.on("error", (error) => {
    console.log(["检查更新失败", error]);
  });
  // 当有可用更新的时候触发。 更新将自动下载。
  autoUpdater.on("update-available", (info) => {
    console.log("检查到有更新,开始下载新版本");
    const { version } = info;
    console.log(`最新版本为: ${version}`);
    updateValue = info
  });
  // 当没有可用更新的时候触发,其实就是啥也不用做
  autoUpdater.on("update-not-available", () => {
    updateValue = null
    console.log("没有可用更新");
  });
  // 下载更新包的进度,可以用于显示下载进度与前端交互等
  autoUpdater.on("download-progress", async (progress) => {
    console.log(progress, 'downloadPercent');
    // console.log(progress);
    // 计算下载百分比
    const downloadPercent = Math.round(progress.percent * 100) / 100;
    // 实时同步下载进度到渲染进程,以便于渲染进程显示下载进度
    downloadProgress = downloadPercent
  });
  // 在更新下载完成的时候触发。
  autoUpdater.on("update-downloaded", (res) => {
    console.log("下载完毕, 提示安装更新", res);
    dialog.showMessageBox({
      type: 'warning',
      title: '更新提示',
      message: '下载完成',
      buttons: ['更新', '取消'],
      cancelId: 1
    }).then(res => {
      if (res.response == 0) {
        // 下载完成之后,关闭所有窗口开始更新
        autoUpdater.quitAndInstall()
      }
    })
  });
}

// 给渲染进程消息进行反馈回调
ipcMain.handle('check-for-updates', async (event, arg) => {
  // 在这里处理检查更新的逻辑
  return updateValue; // 返回更新状态
});

// 反馈更新进度
ipcMain.handle('download-progress', async (event, arg) => {
  // 在这里处理检查更新的逻辑
  return downloadProgress; // 返回更新状态
});

// 检查更新
const checkForUpdates = () => {
  autoUpdater.checkForUpdates();
}

// 用户确定是否下载更新
const downloadUpdate = () => {
  autoUpdater.downloadUpdate();
}

module.exports = {
  autoUpdateApp,
  checkForUpdates,
  downloadUpdate,
};
eventConfig.js 正常来说是往main.js 里添加,不过本示例项目把所有的事件方法都抽离到了eventConfig.js里,根据自己结构自行调整

const { downloadUpdate, checkForUpdates } = require('./auto-update')

// 监听以下事件,触发auto-update.js的更新方法
// 通知应用检查是否有更新
ipcMain.on('checkForUpdates', (ev, val) => {
  checkForUpdates()
})

// 通知应用更新
ipcMain.on('notifyUpdate', (ev, val) => {
  downloadUpdate()
})
  • 暴漏 ipcRenderer方法
preload.js

const { contextBridge, ipcRenderer } = require("electron")

// 向渲染进程暴漏 ipcRenderer 方法 - 会导致渲染进程拥有完全访问主进程API的权限
contextBridge.exposeInMainWorld('ipcRenderer', {
  ipcRenderer: ipcRenderer
});

3.2 渲染进程使用ipcRenderer.invoke 主进程使用 ipcMain.handle 监听事件以获取回调

  • 具体使用
/exe/vue-userList/src/header.vue

<template>
  <div class="header">
    <el-tooltip class="item" effect="dark" content="最小化到系统托盘" placement="bottom">
      <span class="no-drag el-icon-circle-close" @click="addMinimize(false)"></span>
    </el-tooltip>
    <el-tooltip class="item" effect="dark" content="最小化" placement="bottom">
      <span class="no-drag el-icon-minus" @click="addMinimize(true)"></span>
    </el-tooltip>
    <el-tooltip class="item" effect="dark" :content="windowStatus ? '窗口化' : '全屏'" placement="bottom">
      <span class="no-drag" :class="windowStatus ? 'el-icon-copy-document' : 'el-icon-full-screen'" @click="addWindowStatus"></span>
    </el-tooltip>
    <el-tooltip class="item" effect="dark" content="关闭程序" placement="bottom">
      <span class="no-drag el-icon-close" @click="addCloseWin"></span>
    </el-tooltip>
  </div>
</template>
<script>
// 来源根目录  preload.js
const versions = window.versions; // electron 暴漏的主进程事件
const { ipcRenderer } = window.ipcRenderer
export default {
  data () {
    return {
      versions, // 主进程暴漏的事件集合  具体看根目录preload.js文件
      windowStatus: false, // 窗口化   true 全屏  false 窗口化
      updateAvailable: null,
      progressSetInterval: null
    };
  },
  mounted () {
    this.initIPCListener()

  },
  methods: {
    // ------------- 通过主进程预加载脚本暴漏方法方式 ---------------

    // 最小化 / 最小化到系统托盘
    addMinimize (stata) {
      // true 最小化 false 最小化到系统托盘
      versions.minimize(stata);
    },
    // 窗口最大化/还原
    addWindowStatus () {
      // 窗口状态切换后 -- 切换图标
      this.windowStatus = !this.windowStatus;
      versions.windowStatus();
    },
    addCloseWin () {
      versions.closeWin();
    },

    // ----------------- 以下采用向主进程发消息获得回调方式 ----------------

    // 向主进程更新程序发送信息获得更新包详情回调
    initIPCListener () {
      // 主进程检查更新存在 3 秒等待延迟
      setTimeout(() => {
        // 检查是否有新版本
        ipcRenderer.invoke('check-for-updates').then((status) => {
          if (status !== null) {
            this.updateConfirmation(status)
          } else {
            // this.$message.warning('已是最新版本')
          }
        }).catch((error) => {
          console.error(error);
        });
      }, 3000);
    },
    // 更新进度
    updateProgress () {
    //  this.progressSetInterval = setInterval(() => {
    //     ipcRenderer.invoke('download-progress').then((status) => {
    //       if(status == 100){
              // console.log('下载完成');            
    //       }
    //     })
    //   }, 100);
    },
    // 更新确认提示
    updateConfirmation (updateAvailable) {
      this.$confirm(`最新版本为: ${updateAvailable.version},是否进行升级?`, '更新', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        closeOnPressEscape: false,
        closeOnClickModal: false,
        type: 'warning'
      }).then(() => {
        this.$message({
          type: 'success',
          message: '开始下载!'
        });
        // 通知主进程下载更新包
        ipcRenderer.send('notifyUpdate')
        this.updateProgress()
      }).catch(() => { });
    },

  },
};
</script>
<style lang="less">
.header {
  background-color: pink;
  text-align: right;
  -webkit-app-region: drag;
  span {
    padding: 5px 0;
    font-size: 20px;
    margin-right: 20px;
    color: red;
  }
}
.no-drag {
  -webkit-app-region: no-drag;
}
</style>

3.3 检查更新功能

  • 使用 ipcRenderer.send() 方法向主进程发起通知,主进程使用ipcMain.on()接收到通知
/exe/vue-userList/src/header.vue 将代码进行修改

<template>
  <div class="header">
    <el-tooltip class="item" effect="dark" content="检查更新" placement="bottom">
      <span class="no-drag el-icon-refresh" @click="appUpdate()"></span>
    </el-tooltip>
    <el-tooltip class="item" effect="dark" content="最小化到系统托盘" placement="bottom">
      <span class="no-drag el-icon-circle-close" @click="addMinimize(false)"></span>
    </el-tooltip>
    <el-tooltip class="item" effect="dark" content="最小化" placement="bottom">
      <span class="no-drag el-icon-minus" @click="addMinimize(true)"></span>
    </el-tooltip>
    <el-tooltip class="item" effect="dark" :content="windowStatus ? '窗口化' : '全屏'" placement="bottom">
      <span class="no-drag" :class="windowStatus ? 'el-icon-copy-document' : 'el-icon-full-screen'" @click="addWindowStatus"></span>
    </el-tooltip>
    <el-tooltip class="item" effect="dark" content="关闭程序" placement="bottom">
      <span class="no-drag el-icon-close" @click="addCloseWin"></span>
    </el-tooltip>
  </div>
</template>
<script>
// 来源根目录  preload.js
const versions = window.versions; // electron 暴漏的主进程事件
const { ipcRenderer } = window.ipcRenderer
export default {
  data () {
    return {
      versions, // 主进程暴漏的事件集合  具体看根目录preload.js文件
      windowStatus: false, // 窗口化   true 全屏  false 窗口化
      updateAvailable: null,
      progressSetInterval: null
    };
  },
  mounted () {
    this.initIPCListener()

  },
  methods: {
    // ------------- 通过主进程预加载脚本暴漏方法方式 ---------------

    // 最小化 / 最小化到系统托盘
    addMinimize (stata) {
      // true 最小化 false 最小化到系统托盘
      versions.minimize(stata);
    },
    // 窗口最大化/还原
    addWindowStatus () {
      // 窗口状态切换后 -- 切换图标
      this.windowStatus = !this.windowStatus;
      versions.windowStatus();
    },
    addCloseWin () {
      versions.closeWin();
    },

    // ----------------- 以下采用向主进程发消息获得回调方式 ----------------

    // 向主进程更新程序发送信息获得更新包详情回调
    initIPCListener () {
      // 主进程检查更新存在 3 秒等待延迟
      setTimeout(() => {
        // 检查是否有新版本
        ipcRenderer.invoke('check-for-updates').then((status) => {
          if (status !== null) {
            this.updateConfirmation(status)
          } else {
            // this.$message.warning('已是最新版本')
          }
        }).catch((error) => {
          console.error(error);
        });
      }, 3000);
    },
    // 更新进度
    updateProgress () {
    //  this.progressSetInterval = setInterval(() => {
    //     ipcRenderer.invoke('download-progress').then((status) => {
    //       if(status == 100){
              // console.log('下载完成');            
    //       }
    //     })
    //   }, 100);
    },
    // 主动检查更新
    appUpdate () {
      // 由于
      ipcRenderer.send('checkForUpdates')
      // 再次向主进程发送消息获取更新信息回调
      this.initIPCListener()
    },
    // 更新确认提示
    updateConfirmation (updateAvailable) {
      this.$confirm(`最新版本为: ${updateAvailable.version},是否进行升级?`, '更新', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        closeOnPressEscape: false,
        closeOnClickModal: false,
        type: 'warning'
      }).then(() => {
        this.$message({
          type: 'success',
          message: '开始下载!'
        });
        // 通知主进程下载更新包
        ipcRenderer.send('notifyUpdate')
        this.updateProgress()
      }).catch(() => { });
    },

  },
};
</script>
<style lang="less">
.header {
  background-color: pink;
  text-align: right;
  -webkit-app-region: drag;
  span {
    padding: 5px 0;
    font-size: 20px;
    margin-right: 20px;
    color: red;
  }
}
.no-drag {
  -webkit-app-region: no-drag;
}
</style>
  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用的内容,实现 Electron 自动更新的步骤如下: 1. 首先,需要使用 electron-builder 和 electron-updater 来实现自动更新效果。在 Windows 环境中,可以使用 electron-builder 配合 electron-updater 来进行自动更新。 2. 使用 electron-builder 构建生成 `latest.yml`(mac 软件是 `latest-mac.yml`)以及安装包等文件。在构建完成后,将这些文件放在一个静态资源服务器上,并指向这些文件的根路径。 3. 项目启动后,在 Electron 应用中使用 electron-updater 来检查更新。如果检测到有新的版本可用,electron-updater 会自动进行更新操作。 具体操作步骤如下: 1. 安装 electron-builder。在命令行中运行 `npm install electron-builder --save-dev`。 2. 将 `latest.yml`(mac 软件是 `latest-mac.yml`)以及安装包放置在一个可以访问的地方,例如 GitHub 或自己的服务器。确保路径能够被访问到。 3. 安装 electron-updater。在命令行中运行 `npm install electron-updater --save`。 4. 安装 electron-log。在命令行中运行 `npm install electron-log`。 5. 根据自己的项目框架,安装相应的依赖。例如,如果使用 Electron 和 Vue 进行开发,可以通过命令行安装 electron 和 vue:`npm i electron` 和 `npm i vue`。 6. 在项目中引入 electron-updater,并使用其提供的方法来检查更新。根据 electron-updater 的文档进行配置和使用。 总结来说,以上是实现 Electron 自动更新的步骤。通过使用 electron-builder 构建程序并将相关文件放置在可访问的地方,再使用 electron-updater 进行自动更新检查和操作,就可以实现 Electron 自动更新的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Electron自动更新](https://blog.csdn.net/minshiwang/article/details/126887948)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [使用electron-update实现electron应用的自动更新](https://blog.csdn.net/qq_45279180/article/details/118362287)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值