Electron项目更新(增量与全量)

说明

版本1.2.3,1变化为全量更新,会卸载重装(这里为显式安装,可以修改为静默安装),2和3变化走的增量更新,将win-unpacked\resources\app下的文件(如果没有动依赖,可以忽略node_modules)打包成app.zip,放到更新服务器。无论全量还是增量,都要将latest.yml放到更新服务器,全量需要放exe,增量可以只放zip。

使用

1.npm i -S electron-updater electron-log lodash uuid request,npm i -D electron
2.7z.exe与7z.dll放到项目根目录
3.静态资源服务器,https://myupdate.com
4.配置electron-builder

module.exports = {
    asar: false,
    extraFiles: ['7z.dll', '7z.exe'],
    win: {
      target: 'nsis',
    },
    publish: {
      provider: 'generic',
      url: 'https://myupdate.com',
    },
}

5.主进程代码

import { BrowserWindow } from 'electron';
import upgrade from 'upgrade.ts';
const window = new BrowserWindow();
upgrade('https://myupdate.com', window)
upgrade.ts
import type { BrowserWindow } from 'electron';
import type { UpdateInfo } from 'electron-updater';
import { throttle } from 'lodash';
import { v4 } from 'uuid';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { execFile } from 'node:child_process';
import { app, dialog } from 'electron';
import { NsisUpdater } from 'electron-updater';
import * as ElectronLog from 'electron-log';

const APP_INST_DIR = app.isPackaged ? path.resolve(app.getAppPath(), '../..') : app.getAppPath();
const EXE_7Z = path.resolve(APP_INST_DIR, '7z.exe');

let mainWindow: BrowserWindow;
let hotUpdate: boolean;

/**
 * 下载热更新包,zip包
 * @param {string} url - 下载地址
 * @returns {Promise<string>} - 下载完成后的文件路径
 */
function downloadZip(url: string) {
  return new Promise<string>(async (resolve, reject) => {
    let total = 0,
      progress = 0;
    const setProgressBar = throttle(() => {
      const value = ((progress / total) * 100).toFixed(2);
      mainWindow.title = `下载中...${value}%`;
      mainWindow && mainWindow.setProgressBar(progress / total);
    }, 100);
    try {
      const request = await import('request');
      const zip = path.resolve(app.getPath('temp'), `${v4()}.zip`);
      request
        .default(url)
        .on('error', (error: Error) => {
          reject(error);
        })
        .on('close', () => {
          resolve(zip);
        })
        .on('response', ({ headers }) => {
          total = Number(headers['content-length']);
        })
        .on('data', (data) => {
          progress += data.length;
          setProgressBar();
        })
        .pipe(fs.createWriteStream(zip));
    } catch (error) {
      reject(error);
    }
  });
}

function replaceResources(zip: string, target: string) {
  return new Promise<void>((resolve, reject) => {
    if (!fs.existsSync(EXE_7Z)) {
      reject(new Error(`${EXE_7Z}不存在`));
      return;
    }

    if (!mainWindow.isDestroyed()) {
      mainWindow.title = '解压中...';
      mainWindow.setProgressBar(2);
    }

    try {
      execFile(EXE_7Z, ['x', '-aoa', `-o${target}`, zip], { windowsHide: true }, (error) => {
        if (!mainWindow.isDestroyed()) {
          mainWindow.title = path.basename(app.getPath('exe')).replace('.exe', '');
          mainWindow.setProgressBar(-1);
        }
        if (error) {
          reject(error);
          return;
        }
        resolve();
      });
    } catch (error) {
     if (!mainWindow.isDestroyed()) {
        mainWindow.title = path.basename(app.getPath('exe')).replace('.exe', '');
        mainWindow.setProgressBar(-1);
      }
      reject(error);
    }
  });
}

function installAfterQuit(file: string) {
  app.once('before-quit', (event) => {
    ElectronLog.info('app.onBeforeQuit event.preventDefault');
    event.preventDefault();
    try {
      dialog.showMessageBoxSync(mainWindow, {
        title: '版本更新',
        message: '应用程序将在后台执行更新,完成后会自动退出程序,请勿强制关闭程序',
        type: 'info',
      });
    } catch (error) {
      //
    }

    replaceResources(file, app.getAppPath())
      .then(() => {
        ElectronLog.info('热更新稍后重启替换资源成功,退出程序');
      })
      .catch(() => {
        ElectronLog.info('热更新稍后重启替换资源错误');
      })
      .finally(() => {
        app.quit();
      });
  });
}

function installAndRelaunch(file: string) {
  try {
    dialog.showMessageBoxSync(mainWindow, {
      message: '应用程序将在后台执行更新,完成后会自动重启程序,请勿强制关闭程序',
      title: '版本更新',
      type: 'info',
    });
  } catch (error) {
    //
  }

  replaceResources(file, app.getAppPath())
    .then(() => {
      ElectronLog.error('立即更新替换资源成功');
    })
    .catch((error) => {
      ElectronLog.error(`立即更新替换资源失败,${error.message}`);
    })
    .finally(() => {
      app.relaunch();
      app.quit();
    });
}

function dialogUpdateAvailable(info: UpdateInfo) {
  return new Promise<boolean>((resolve) => {
    dialog
      .showMessageBox(mainWindow, {
        title: '版本更新',
        message: '有新版本发布,是否立即下载?',
        detail: `当前版本:${app.getVersion()},最新版本:${info.version}`,
        type: 'info',
        buttons: ['取消更新', '立即下载'],
        defaultId: 1,
      })
      .then(({ response }) => {
        resolve(response === 1);
      })
      .catch(() => {
        resolve(false);
      });
  });
}

function dialogUpdateDownloaded() {
  return new Promise<boolean>((resolve) => {
    dialog
      .showMessageBox(mainWindow, {
        title: '版本更新',
        message: '新版本已下载完毕,是否立即重启?',
        type: 'info',
        buttons: ['稍后重启', '立即重启'],
        defaultId: 1,
      })
      .then(({ response }) => {
        resolve(response === 1);
      })
      .catch(() => {
        resolve(false);
      });
  });
}

export default function (url: string, win: BrowserWindow) {
  mainWindow = win;

  const autoUpdater = new NsisUpdater({
    url,
    provider: 'generic',
  });

  autoUpdater.autoDownload = false;
  autoUpdater.installDirectory = APP_INST_DIR;

  // 开始检查更新
  autoUpdater.on('checking-for-update', () => {
    ElectronLog.info('autoUpdater.onCheckingForUpdate');
  });

  // 检查更新出错
  autoUpdater.on('error', (error, message) => {
    dialog.showMessageBox(win, {
      title: '更新失败',
      message: error.message,
      detail: message,
      type: 'error',
    });
    ElectronLog.info('autoUpdater.onError', error.message, message);
  });

  // 检查到新版本
  autoUpdater.on('update-available', async (info: UpdateInfo) => {
    ElectronLog.info(`autoUpdater.onUpdateAvailable,${info.version}`);
    ElectronLog.info(info);
    if (Number(info.version.split('.')[0]) > Number(app.getVersion().split('.')[0])) {
      ElectronLog.info('全量更新');
      hotUpdate = false;
    } else {
      ElectronLog.info('增量更新');
      hotUpdate = true;
    }

    const isDownload = await dialogUpdateAvailable(info);
    if (!isDownload) {
      ElectronLog.info('取消更新');
      return;
    }

    ElectronLog.info('立即下载');

    if (hotUpdate) {
      const packUrl = `${url}/app.zip`;

      downloadZip(packUrl)
        .then(async (file) => {
          autoUpdater.emit('update-downloaded', {
            downloadedFile: file,
            version: '',
            files: [],
            path: '',
            sha512: '',
            releaseDate: '',
          });
        })
        .catch((reason) => {
          ElectronLog.error(`下载热更新包失败,${reason.message}`);
          dialog
            .showMessageBox(win, {
              title: '版本更新',
              message: '新版本下载失败,请检查网络是否正常',
              detail: `${reason.message}`,
              type: 'error',
            })
            .then(() => 0)
            .catch(() => 0);
        });
    } else {
      autoUpdater.downloadUpdate();
    }
  });

  // 已经是新版本
  autoUpdater.on('update-not-available', (info: UpdateInfo) => {
    ElectronLog.info(`autoUpdater.onUpdateNotAvailable,${JSON.stringify(info)}`);
  });

  // 更新下载中
  autoUpdater.on('download-progress', (info) => {
    const value = info.percent.toFixed(2);
    mainWindow.setProgressBar(info.percent / 100);
    mainWindow.title = `下载中...${value}%`;
  });

  // 更新下载完毕
  autoUpdater.on('update-downloaded', async (event) => {
    ElectronLog.info(`autoUpdater.onUpdateDownloaded,${JSON.stringify(event)}`);
    mainWindow.title = app.getPath('exe').replace('.exe', '');
    const isRelaunch = await dialogUpdateDownloaded();
    if (isRelaunch) {
      if (hotUpdate) {
        installAndRelaunch(event.downloadedFile);
      } else {
        autoUpdater.quitAndInstall(false, false);
      }
    } else {
      if (hotUpdate) {
        installAfterQuit(event.downloadedFile);
      } else {
        autoUpdater.autoInstallOnAppQuit = true;
      }
    }
  });

  autoUpdater.checkForUpdates();
}

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值