electron 实现增量更新

742 阅读3分钟

增量更新

常见的更新方案

  • 通过接口判断是否需要更新,需要的话通过shell.openExternal(downloadUrl) 打开浏览器进行下载更新
  • 增量更新,通过比对版本。拉取新的压缩包,再覆盖之前的renderer层来实现

思路:

  1. 点击增量更新按钮时renderer层通知main层进行更新
  2. main层比对是否需要更新,需要拉取最新的dist包
  3. 删除之前包,复制新的包进去
  4. 重启应用

需要下载的依赖

npm install axios
npm install extract-zip //用于解压更新文件的zip

代码逻辑

一些层级不太懂的可以去翻阅之前关于electron文章,从0到1搭建electron-vite-demo

  1. renderer层

    src/renderer/pages/index/index.vue 点击发送给main层进行更新

    <template>
      <h1>更新界面 1.1.2</h1>
      <div class="actions">
        <el-button @click="checkUpdate">
          增量更新
        </el-button>
      </div>
    </template>
    <script setup lang="ts">
    import { send, on } from '@renderer/utils/ipcRenderer'function checkUpdate() {
      //* 检查更新方法 发送通信
      send('hot-update') //通信到main层
    }
    ​
    on('hot-update-success', (event, age) => {
      console.log('热更新成功',age)
      //todo 可添加重启方法
    })
    </script>
    
  2. main层 监听renderer的方法 并进行更新

    src/main/services/ipcMain.ts 进行监听

    //* 用于管理main层的监听工具类
    import { BrowserWindow, ipcMain } from "electron";
    ​
    //* 引入通信后需要使用的工具类
    import updater from './hotUpdater';
    ​
    //* 引入工具类
    import { getMainWindowByIpcEvent, getDownloadPath, getHistoryPath } from '../utils/MainWindow'
    ​
    ​
    ​
    function onEvent() { //* 监听从rendener层传送过来的数据
        ipcMain.handle('hot-update', (event, arg) => {
            updater(BrowserWindow.fromWebContents(event.sender))
        })
    }
    ​
    export function Mainfunc() {
        //* 监听事件
        onEvent()
    }
    ​
    //* 用于管理ipc通信
    export default  {
        
    }
    

    src/main/services/hotUpdater.ts 更新方法

    //* 增量更新 热更新
    //* 引入使用的依赖
    import axios from "axios"
    import { join, resolve } from "path"
    import { pipeline } from "stream"
    import { promisify } from "util"
    import { BrowserWindow, app } from 'electron';
    import { gt } from 'semver' //* 用于比对版本大小
    import extract from 'extract-zip' //* 用于解压zip包
    import { copy, createWriteStream, emptyDir, readFile, remove } from "fs-extra";
    import { createHmac } from "crypto";
    ​
    //* 引入需要的配置项
    // import { version } from '../../../package.json'const streamPipeline = promisify(pipeline) //* 用于存放文件
    const request = axios.create() //* 用于请求 拉取文件数据
    const appPath = app.getAppPath() //* 获取app安装的路径
    const updatePath = resolve(appPath, '..', '..', 'update') //* 增量更新的路径 用于存放更新的zip包
    const baseUrl = 'http://localhost:5500/' //* 更新文件下载路径
    const version = "1.0.0"/**
     * 热更新方法
     * @param mainWindow 请求更新的窗口 用于做页面更新使用 
     */
    export default async (mainWindow?: BrowserWindow) => {
        try {
            // 1. 获取版本更新json 判断是否需要更新
            const res = await request({ url: baseUrl + 'hot-update.json' })
            //* 2. 比对版本 如果不需要更新则暂停
            if(!gt(res.data.version, version)) return
            //* 3. 先清空放置更新文件的文件夹
            await emptyDir(updatePath)
            //* 4. 获取更新文件的下载路径下载并存放到更新文件夹中
            const updateFilePath = join(updatePath, res.data.name)
            await download(baseUrl + res.data.name, updateFilePath)
            // 5. 获取数据并比对
            // const buffer = await readFile(updateFilePath)
            // const sha256 = hash(buffer)
            // console.log(sha256, '<===== sha256')
            // if (sha256 !== res.data.hash) throw new Error('sha256 error')
            // 生成临时文件 将下载下来的文件解压到临时文件夹
            const appPathTemp = join(updatePath, 'temp')
            await extract(updateFilePath, { dir: appPathTemp })
            // 删除app包
            await remove(join(`${appPath}`, 'dist'));
            await remove(join(`${appPath}`, 'package.json'));
            //* 更新app包
            await copy(appPathTemp, appPath)
            mainWindow?.webContents.send('hot-update-success', {
                updateFilePath,
                updatePath,
                appPath
            })
            resolve('success')
        } catch (error) {
            console.log('下载过程中出现问题', error)
        }
    }
    ​
    /**
     * @param data 文件流
     * @param type 类型,默认sha256
     * @param key 密钥,用于匹配计算结果
     * @returns {string} 计算结果
     * @author umbrella22
     * @date 2021-03-05
     */
     function hash(data, type = 'sha256', key = 'Sky') {
        const hmac = createHmac(type, key)
        hmac.update(data)
        return hmac.digest('hex')
    }
    ​
    ​
    /**
     * @param url 下载地址
     * @param filePath 文件存放地址
     * @returns {void}
     * @author umbrella22
     * @date 2021-03-05
     */
    async function download(url: string, filePath: string) {
        const res = await request({ url, responseType: "stream" })
        await streamPipeline(res.data, createWriteStream(filePath))
    }
    

    测试是否成功

    需要的修改的地方为hotUpdate.ts中的资源文件地址(baseUrl)

    需要的文件为

    • hot-update.json,用于比对版本和获取更新文件名称
    • dist文件夹包:里面有package.json和之前启动编译的dist包。将其压缩成zip包后放置到资源文件夹中供下载

    源码地址