Skip to content

版本更新

版本更新主要有两种形式,一是用electron-updater,而是通过替换app.asar文件实现 electron-updater:可以更新整个应用,包括electron版本升级 asar:只更新app.asar文件,主要为前端内容和主进程内容,无法升级electron版本

搭建一个简易的更新服务器

初始化一个项目,并安装 express

bash
npm init -y
pnpm install express

在package.json中添加启动命令

json
"scripts": {
  "start": "node ./server.js"
}

在根目录创建server.js文件,并添加以下代码

js
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;

// 设置静态文件目录
app.use(express.static(path.join(__dirname, 'public')));

// 启动服务器
app.listen(port, () => {
  console.log(`服务器正在运行在 http://localhost:${port}`);
});

创建public文件夹,将要更新的内容放入文件夹,稍后会讲更新内容的来源

├── public
│   ├── ElectronLearn-1.0.1-arm64.dmg // 新版本文件
│   ├── ElectronLearn-1.0.1-arm64.dmg.blockmap // 文件分块信息
│   └── latest-mac.yml // 保存了版本信息,文件名称,文件校验信息等
│   └── app.zip // app.asar压缩文件
│   └── update.json // 更新信息描述
└── server.js

update.json

json
{
  "version": "1.0.1",
  "release_notes": "修复bug",
  "update_type": "hot" // 标识 走electron-updater自动更新还是 asar替换更新
}

使用electron-updater更新

配合electron-builder使用,实现自动更新,文档

  1. 安装
bash
pnpm install electron-updater
  1. 在package.json中配置更新地址
json
"build": {
  //……
  "directories": {
    //……
  },
  "publish": [
      {
        "provider": "generic",
        "url": "http://localhost:3000/"
      }
    ]
}
  
  1. 在主进程触发更新
js
import { app } from 'electron'
import updater from "electron-updater"
/*
 * 全量更新
 */
function checkFullUpdate(callBack){
  const { autoUpdater } = updater
  // 设置更新地址,已经在package.json中配置了
  // autoUpdater.setFeedURL(ossUrl)

  // 检查更新
  autoUpdater.checkForUpdates()

  // 监听更新状态
  autoUpdater.on('update-available', () => {
    callBack({ state: 'available', msg: '发现新版本' }) 
  })

  autoUpdater.on('update-not-available', () => {
    callBack({ state: 'error', msg: '当前已是最新版本' })
  })

  autoUpdater.on('update-downloaded', () => {
    callBack({ state: 'success', msg: '更新完成,即将重启应用' })
  })

  autoUpdater.on('error', (err) => {
    callBack({ state: 'error', msg: '更新失败,请联系管理员', err })
  })

  // 下载进度
  autoUpdater.on('download-progress', (progress) => {
    callBack({ state: 'donwloading', progress })
  })

}

export {
  checkFullUpdate
}

替换app.asar文件,实现手动更新

在打包时,除了生成安装包,还会生成一个未打包文件夹win是win-unpacked,mac是mac-arm64,在里面有个Resources文件夹,找到里面的app.asar文件,这个文件就打包了前端代码和主进程代码。 在应用的安装位置也可以找到app.asar文件,在应用运行时会自动解压app.asar,加载里面的代码。 所以将新版本的app.asar文件替换到安装位置,即实现了更新。

mac:'/Applications/ElectronLearn.app/Contents/Resources/app.asar'
win:'C:\\Program Files\\electron-learn\\resources\\app.asar' 

更新信息

将app.asar压缩为app.zip,并保存到更新服务器上,修改版本信息

json
{
  "version": "1.0.1",
  "release_notes": "修复bug",
  "update_type": "hot" // 标识 走electron-updater自动更新还是 asar替换更新
}

安装用到的库

shell
pnpm i adm-zip # 解压缩
pnpm i progress-stream # 显示下载进度

在应用中获取更新信息,下载并替换app.asar文件

js
import { app } from 'electron'
import updater from "electron-updater"
import AdmZip from 'adm-zip'
import progress from 'progress-stream'
import fs from 'fs'
import axios from 'axios'
import path from 'path'

const ossUrl = 'http://localhost:3000/' // 更新文件的地址
const versionInfoUrl = 'http://localhost:3000/update.json' // 更新信息的接口地址

let updateing = false // 当前正在更新

/**
 * 检查更新
 * @param {*} callBack
 */
const checkUpdate = (callBack) => {
    if (updateing) {
      return
    }
    updateing = true
    axios.get(versionInfoUrl).then(res=>{
      const { version, update_type } = res.data
      const localVersion = app.getVersion()

      if (version && version !== localVersion) {
        // 判断是热更新asar还是全量更新electron-updater
        if (update_type == 'hot') {
          checkHotUpdate(callBack)
        } else if (update_type == 'full') {
          // 全量更新
          checkFullUpdate(callBack)
        }
      } else {
        updateing = false
        callBack({state: 'error', msg: '当前已是最新版本'})
      }
    }).catch(err=>{
      callBack({state: 'error', msg: '更新版本号失败,请联系管理员', err})
    }).finally(()=>{
      updateing = false
    })
}

/**
  * 热更新 下载
  */
const checkHotUpdate = async(callBack) => {
  const downloadUrl = `${ossUrl}app.zip`

  // 获取下载保存位置
  const savePath = app.getPath('userData')
  // 获取当前应用的asar包路径
  const asarPath = path.dirname(app.getAppPath())

  downloadFile(downloadUrl,`${savePath}app.zip`,(progress)=>{
    callBack({ state: 'donwloading', progress:{ percent: progress.percentage, bytesPerSecond: progress.speed } })
  }).then(()=>{
    const unzip = new AdmZip(`${savePath}app.zip`) // 下载压缩更新包
    unzip.extractAllTo(`${asarPath}`, /* overwrite*/ true) // 解压替换本地文件
    callBack({ state: 'success', msg: '更新完成,即将重启应用' })
  }).catch(err=>{
    callBack({ state: 'error', msg: '下载失败,请联系管理员', err })
  }).finally(()=>{
    updateing = false
  })
}

async function downloadFile(url, outputPath, progressCallback) {
  const writer = fs.createWriteStream(outputPath);
  const { data, headers} = await axios({
    url,
    method: 'GET',
    responseType: 'stream'
  });

  const totalLength = headers['content-length'];
  const prg = progress({ time: 200, length: totalLength });
  prg.on('progress', (res) => {
    progressCallback(res)
  })
  // 将响应数据流写入文件
  data.pipe(prg).pipe(writer)

  return new Promise((resolve, reject) => {
    writer.on('finish', resolve);
    writer.on('error', reject);
  });
}


/*
 * 全量更新
 */
function checkFullUpdate(callBack){
  const { autoUpdater } = updater
  // 设置更新地址,已经在package.json中配置了
  // autoUpdater.setFeedURL(ossUrl)

  // 检查更新
  autoUpdater.checkForUpdates()

  // 监听更新状态
  autoUpdater.on('update-available', () => {
    callBack({ state: 'available', msg: '发现新版本' }) 
  })

  autoUpdater.on('update-not-available', () => {
    callBack({ state: 'error', msg: '当前已是最新版本' })
  })

  autoUpdater.on('update-downloaded', () => {
    callBack({ state: 'success', msg: '更新完成,即将重启应用' })
  })

  autoUpdater.on('error', (err) => {
    callBack({ state: 'error', msg: '更新失败,请联系管理员', err })
  })

  // 下载进度
  autoUpdater.on('download-progress', (progress) => {
    callBack({ state: 'donwloading', progress })
  })

}

export {
  checkUpdate
}

更新遇到的问题

  1. 打包后运行一直报错,缺少xxx包,换成npm后就解决
  2. electron-updater,mac.dmg更新失败,mac需要.zip的文件来更新
  3. electron-updater,mac需要签名后才能更新
  4. electron-updater,win需要重走安装流程,容易被杀毒软件拦截
  5. 很多文章说替换app.asar文件会有权限问题,实测win是完全没问题的,mac有电脑失败,但多是是成功的

当前应用各文件夹的位置

获取当前应用的配置文件位置app.getPath('userData')

mac :  /Users/xiaoyan/Library/Application Support/应用name

Win:   'C:\\Users\\Administrator\\AppData\\Roaming\\应用name'

获取当前应用的位置app.getAppPath()

mac:'/Applications/应用name.app/Contents/Resources/app.asar'

win:'C:\\Program Files\\应用name\\resources\\app.asar' 

log文件默认位置

mac: /Users/xiaoyan/Library/Logs/应用name/

Win: \Users\Administrator\AppData\Roaming\应用name\logs

Released under the MIT License.