相关文章推荐
帅气的投影仪  ·  [email protected] ...·  3 周前    · 
踢足球的茶壶  ·  Android ...·  1 年前    · 
爱笑的马铃薯  ·  备忘 MSB4018 ...·  1 年前    · 

大家好,我是杨成功。

在上一篇文章《 手撸 Electron 自动更新,再繁琐也要搞懂它 》中,我们详细介绍了 Electron 自动更新的全流程。

简单来说,就是打包生成 latest.yml 文件和安装包,并上传到服务器。客户端打开时访问服务端的 latest.yml 地址,判断是否有新版本,有则自动下载新包并更新。

对于以上的更新流程,很多朋友在评论区留言表示不够灵活,比如:

  • 只能自动更新吗?能不能通过后端接口来判断是否更新?
  • 必须要用 latest.yml 吗?能不能绕开它?
  • 如何区分 Window、Mac、Mac M1 三种安装包?
  • 下面我们一一解答这些问题,并手撸一个更灵活的自动更新接口。

    如何本地测试更新?

    在开发阶段想测试一下检测更新的流程走没走通,可能不太好测试,因为 Electron 默认在开发环境下会绕过更新检测。

    开发环境下 Electron 启动后,如果接入了自动更新,主进程控制台会打印下面的信息:

    Skip checkForUpdates because application is not packed and dev update config is not forced

    意思是当前是开发环境,未打包,所以绕过检测。Electron 通过 app.isPackaged 的值来判断是否打包,那么在开发环境下,我们可以修改一下这个值:

    import { app } from 'electron';
    // 未打包时是开发环境
    if (!app.isPackaged) {
      Object.defineProperty(app, 'isPackaged', {
        get: () => true,
    

    重新运行,大概率会看到第二个错误:

    Error: ENOENT: no such file or directory /xxxx/app-update.yml

    因为没有打包嘛,所以找不到 app-update.yml 这个文件,索性我们就创建一个。在根目录下创建一个 dev-update.yml 文件(文件名可自定义),写入配置:

    provider: generic
    updaterCacheDirName: demo-updater # 下载目录
    

    然后在开发环境指定这个配置文件地址:

    import { app } from 'electron';
    import path from "path";
    if(!app.isPackaged) {
      autoUpdater.updateConfigPath = path.join(__dirname, "../../dev-update.yml");
    

    重新运行项目,会发现检测更新的逻辑可以正常执行了。

    能不能绕开 latest.yml,走后端接口?

    可能大家希望的检查更新流程是这样:

    调用后端的 API 接口,接口返回 JSON 格式数据,包含最新的版本号和安装包下载地址。将该版本号与本地版本号做对比,如果不一样则表示有更新,并执行下载。

    然而 electron-updater 是通过 latest.yml 文件来获取版本号等信息。latest.yml 是一个配置文件,内容如下:

    version: 1.0.2
    files:
      - url: elapp_1.0.2.exe
        sha512: xxxxxx
        size: 72716511
    path: elapp_1.0.2.exe
    sha512: xxxxxx
    releaseDate: '2023-11-29T02:28:28.032Z'
    

    大家想绕过它,可能是因为 YAML 文件的内容看不太懂,或者与接口格式不匹配。其实它就是一个普通的配置文件,转换成 JSON 格式如下:

    "version": "1.0.2", "files": [ "url": "elapp_1.0.2.exe", "sha512": "xxxxxx", "size": "72716511" "path": "elapp_1.0.2.exe"

    可能有人会问:配置文件可以改成 latest.json 吗,这样后端就可以动态返回了。

    我仔细查阅过文档,目前只支持 YAML 文件,不支持 JSON,所以 latest.yml 无法绕开。但这只是官方说法,咱还是有办法滴。

    经过大量测试,我发现把 latest.yml 文件的内容手动改成 JSON 格式也是可以的。这样的话,就可以写一个接口来模拟 latest.yml 的地址。

    假设 latest.yml 的访问路由是 /ele-app/latest.yml,那么写一个接口如下:

    const app = require('express')()
    app.get('/ele-app/latest.yml', (req, res, next) => {
      let resinfo = {
        version: "1.0.25",
        path: "xxx_1.0.25.exe"
        sha512: "xxxxxx"
      res.send(resinfo)
    

    该接口就是自定义的检测更新接口。接口返回值中至少要包含 versionpathsha512 三个属性(与 latest.yml 中的配置保持一致)。这样我们不需要上传 latest.yml 文件了,用该接口替代即可。

    基于该检测更新接口,接下来我们逐步实现自定义更新流程。

    如何区分 Windows 和 Mac 系统

    对于 Windows 和 Mac 两个系统的更新,electron-updater 使用不同的配置文件,分别是 latest.ymllatest-mac.yml

    从上一步的检测更新接口来看,不同的配置文件就是不同的路由,我们改造接口如下:

    app.get('/ele-app/:platform', (req, res, next) => {
      let { platform } = req.params
      let resinfo = null
      // 返回 Windows 配置
      if(platform == 'latest.yml') {
        resinfo = {
          version: "1.0.2",
          path: "xxx_1.0.2.exe"
          sha512: "xxxxxx"
      // 返回 Mac 配置
      if(platform == 'latest-mac.yml') {
        resinfo = {
          version: "1.0.3",
          path: "xxx_1.0.3.dmg"
          sha512: "xxxxxx"
      if(!resinfo) {
        resinfo = { code: 400, msg: '参数错误' }
      res.send(resinfo)
    

    上面代码中,使用动态路由返回 Windows 和 Mac 的配置,同时兼容了两个平台的更新检测。

    完整的自定义更新流程

    经过上面的介绍,自定义检测更新的关键思路已经讲清楚了。下面我们梳理一下完整的更新流程。

    (1)打包各个平台的安装包,上传服务器。

    如何打包在上一篇介绍过,就不展开说了。注意的是:现在你只需要上传安装包,不需要上传 latest.yml 文件。

    (2)在主进程中设置更新地址,并手动控制更新。

    假设我们编写的检测更新接口已经部署,设置方法如下:

    import { autoUpdater } from 'electron-updater';
    // 设置检测更新的地址
    autoUpdater.setFeedURL('http://[xxx]/ele-app');
    // 不自动下载
    autoUpdater.autoDownload = false;
    // 触发检测
    autoUpdater.checkForUpdatesAndNotify().catch();
    // 监听到可更新
    autoUpdater.on('update-available', (info) => {
      // info 是检测更新接口返回的数据
      if (info.can_download) {
        // can_download 是自定义属性
        autoUpdater.downloadUpdate();
    

    (3)编写检测更新接口,返回配置。

    返回的配置我们可以自定义,假设返回结果如下:

    "version": "1.0.2", "path": "http://xxx/xxx_1.0.2.exe", "sha512": "xxxxxx", "can_download": false

    上述示例中,接口返回了自定义属性 can_download,我们在第(2)步中使用了该属性,用于判断是否执行下载更新。

    通过这种方式,即便我们更新了安装包,也可以自由决定是否要下载安装。

    这里有一个小惊喜:path 属性的值可以是一个完整的安装包地址,这样可以把安装包上传到任意地方。如果值是一个文件名,那么会以第(2)步中 setFeedURL() 方法设置的地址为前缀。

    提醒:sha512 属性的值必须从打包生成的 latest.yml 中获取,不可以随意写,否则在安装时不能通过检验,会报这个错:

    Error: sha512 checksum mismatch

    特别篇:Mac 如何区分 Intel 和 M1?

    Mac 系统有两种软件包,分别对应 M1 芯片和 Intel 芯片,两者不兼容。一般打包时我们也会构建两种安装包。

    那么在检测更新时,我们就需要返回适配当前系统的安装包。但不管是 M1 还是 Intel 都使用 latest-mac.yml 这一个配置文件,该如何区分呢?

    这个时候就要从主进程中获取参数,然后传给接口了。步骤如下:

    (1)在主进程中,获取系统架构,并通过请求头传给检测更新接口。

    import { autoUpdater } from 'electron-updater';
    // 添加请求头
    autoUpdater.requestHeaders = {
      elearch: process.arch,
    

    (2)在接口中接收参数,并在返回 Mac 配置中判断:

    app.get('/ele-app/:platform', (req, res, next) => {
      let { platform } = req.params
      let { elearch } = req.headers
      if(platform == 'latest-mac.yml') {
        if(elearch == 'arm64') {
          // M1
          return {...}
        } else {
          // Intel
          return {...}
    

    有了上面的逻辑,我们在打包时就可以单独打两个包,分别上传,然后在检测时返回不同的下载地址。

    本文使用自定义接口的方式,绕过了 latest.yml 配置文件,使自动更新的灵活性更高,也更符合我们的常规习惯,但是需要维护一个更新接口。

    如果不想维护接口,那么上传 latest.yml 文件的方式更适合你。

    📣 📣 我的新书《前端开发实战派》快要出版了,在公众号精选文章 《我写了一本书》下评论,我会选择 3 位朋友赠书。

    如果有小伙伴在本文中遇到了疑问,欢迎加我微信 ruidoc 拉你进读者群交流咨询,或者关注我的公众号 程序员成功 查看更多文章。

    再次感谢您的阅读~