【踩坑记录】Electron+vue实现热更新

引出问题

目前需要实现electron热更新问题,需要静默更新

实现思路

  1. 新增版本文件
  2. 远程获取版本和下载地址,进行对比
  3. 发现需要更新后,进行update.asar下载
  4. 将update移动到res目录下与app.asar共存
  5. 开启子进程,开启exe,exe内容如下
  6. 写一个bat转换为exe,内容为将进程关闭,删除原有asar,将update.asar重命名为app.asar,重启主线程
  7. 完成更新

创建脚本文件

@echo off
taskkill /f /im %1
timeout /T 1 /NOBREAK
del /f /q /a %2\app.asar
ren %2\update.asar app.asar
start ""  %3
taskkill /f /im startUpdate.exe

使用工具:Bat To Exe Converter 将bat打包为exe文件

生成exe后可以手动先试一下
.\resources\upgured\startUpdate.exe windowspay .\resources windowspay.exe

核心代码如下

downloadFile.js(下载资源文件)

import axios from "axios";
import fs from "fs";

export default (url, downloadFilePath) => {
  console.log("保存文件位置:" + downloadFilePath);
  return new Promise(async (resolve, reject) => {

    try {
      let { data } = await axios({
        url: url,
        responseType: 'arraybuffer',
      })
      await fs.promises.writeFile(downloadFilePath, data, 'binary');
      resolve()
    } catch (error) {
      reject(error)
    }
  })
}

sudoPrompt.js(执行exe脚本)

const { exec } = require('child_process');

export default (param) => {
  return new Promise((resolve, reject) => {
    let bat = param.join(" ");
    console.log(bat);
    exec(bat, (err, stdout, stderr) => {
      if (err) {
        console.log(err);
        reject(err)
        return;
      }
      console.log(stdout);
      console.log(stderr);
    });
    resolve();
  })
}

upgruedScheduleEvent.js(核心代码)

import schedule from 'node-schedule'
import api from '../../api'
import hos from '../../../../store/hos'
import axios from "axios";
import fs from "fs";
import versionContrast from "./versionContrast";
import downloadFile from "./downloadFile";
import sudoPrompt from "./sudoPrompt";
import { app, ipcMain } from 'electron';

let cron = '*/1 * * * *';
let resources = process.resourcesPath
let versionFilePath = resources + '\\upgured\\version';
let execFile = resources + '\\upgured\\startUpdate.exe';
let appExeFileload = app.getPath('exe');
let processName = process.env.VUE_APP_PRODUCTNAME + '.exe'
let downloadNewFileName = resources + '\\update.asar'
let upgruedUrl = ''
let remoteVersion = ''
let hasDownloadUpgruedFile = false


export default (sendMsg2RendererByHotUpgrued) => {
    bootstrapUpgrued(api.upgrued + "/" + hos.mac, sendMsg2RendererByHotUpgrued);
}

function bootstrapUpgrued(url, sendMsg2RendererByHotUpgrued) {
    schedule.scheduleJob(cron, function () {
        axios.get(url).then(res => {
            remoteVersion = res.data.data.version;
            upgruedUrl = res.data.data.upgruedUrl;
            let oldVersion = fs.readFileSync(versionFilePath).toString();
            console.log(remoteVersion, upgruedUrl, oldVersion);
            let isDownload = versionContrast(remoteVersion, oldVersion);
            if (isDownload == -1 && !hasDownloadUpgruedFile) {
                console.log("开始进行更新操作");
                sendMsg2RendererByHotUpgrued()
            } else {
                console.log("无需更新操作");
            }
        }).catch(err => {
            console.log(err)
        })
    });
}

ipcMain.handle("sendMsg2MainByHotUpgrued", async (event, someArgument) => {
    const result = await downloadUpgruedFileAndUpdate(someArgument)
    return result
})

let downloadUpgruedFileAndUpdate = (someArgument) => {
    hasDownloadUpgruedFile = true;
    downloadFile(upgruedUrl, downloadNewFileName).then(() => {
        weiteVersion(remoteVersion).then(async () => {
            //.\resources\upgured\startUpdate.exe windowspay .\resources windowspay.exe
            await sudoPrompt(
                [execFile, processName, resources, appExeFileload]
            )
        })
    }).catch(err => {
        console.log("下载有误");
        console.log(err);
    })
}

let weiteVersion = (remoteVersion) => {
    return new Promise(async (resolve, reject) => {
        fs.writeFile(versionFilePath, remoteVersion, (err) => {
            if (err) {
                console.log('读取文件失败', err.message)
                reject(err.message)
            }
            console.log('版本文件更新成功!')
            resolve()
        })
    })

}



versionContrast.js(对比版本)

export default (v1, v2) => {
    //补位0,或者使用其它字符
    const ZERO_STR = '000000000000000000000000000000000000000000';
    if (v1 === v2) {
        return 0;
    }
    let len1 = v1 ? v1.length : 0;
    let len2 = v2 ? v2.length : 0;
    if (len1 === 0 && len2 === 0) {
        return 0;
    }
    if (len1 === 0) {
        return 1;
    }
    if (len2 === 0) {
        return -1;
    }
    const arr1 = v1.split('.');
    const arr2 = v2.split('.');
    const length = Math.min(arr1.length, arr2.length);
    for (let i = 0; i < length; i++) {
        let a = arr1[i];
        let b = arr2[i];
        if (a.length < b.length) {
            a = ZERO_STR.substr(0, b.length - a.length) + a;
        } else if (a.length > b.length) {
            b = ZERO_STR.substr(0, a.length - b.length) + b;
        }
        if (a < b) {
            return 1;
        } else if (a > b) {
            return -1;
        }
    }
    if (arr1.length < arr2.length) {
        return 1;
    } else if (arr1.length > arr2.length) {
        return -1;
    }
    return 0;
}

实现更新后弹出遮罩层

实现思路

  1. 编写遮罩层代码(app.vue)
  2. 主线程主动使用ipc通知渲染线程渲染代码

main.js(主线程与渲染线程交互)

import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import App from './App.vue'
import router from "../router/routes.js"
import i18n from './i18n/index.js';
import hos from '../store/hos.js'
import card from '../store/card.js'
import { ipcRenderer } from 'electron'
// import "element-plus/dist/index.css"; // 引入组件样式

//Vue.prototype.$api = process.env.NODE_ENV == 'development' ? "/invoice" : "127.0.0.1:8080/tool";

const app1 = createApp(App)
app1.use(i18n);
app1.use(router);
app1.use(ElementPlus);
app1.config.globalProperties.$hos = hos;
app1.config.globalProperties.$card = card;
app1.mount('#app');

//重点看这里
ipcRenderer.on('sendMsg2RendererByHotUpgrued', (event, param1, param2) => {
    console.log("sendMsg2RendererByHotUpgrued", "监听开始");
    hotUpgruedHandle()
    ipcRenderer.invoke('sendMsg2MainByHotUpgrued', 'start download')
})

App.vue(弹出遮罩层)

<template>
  <div id="app">
    <div :id="hotUpgrued?'PageLoadingEffect':''">
      <div :class="hotUpgrued?'loading':''">div>
      <div :class="hotUpgrued?'loading-content':''">{{hotUpgrued?'更新中,请勿触碰':''}}div>
    div>
    <router-view>router-view>
  div>
template>

<script>
export default {
  name: 'app',
  data(){
    return{
      hotUpgrued:false
    }
  },
  mounted() {
    window.hotUpgruedHandle = this.hotUpgruedHandle;
  },
  methods: {
    hotUpgruedHandle() {
      this.hotUpgrued = true;
    }
  }
}
script>
<style scoped>
#PageLoadingEffect {
  position: absolute;
  left: 0px;
  top: 0px;
  width: 100%;
  height: 100%;
  background-color: rgba(112, 112, 112, 0.7);
  z-index: 99999;
}

.loading {
  margin-top: 900px;
  margin-left: 535px;
  display: block;
  position: relative;
  width: 26px;
  height: 30px;

  animation: rectangle infinite 1s ease-in-out -0.2s;

  background-color: #000;
}

.loading:before,
.loading:after {
  position: absolute;
  width: 26px;
  height: 30px;
  content: "";
  background-color: #000;
}

.loading:before {
  left: -40px;
  animation: rectangle infinite 1s ease-in-out -0.4s;
}

.loading:after {
  right: -40px;
  animation: rectangle infinite 1s ease-in-out;
}

@keyframes rectangle {
  0%,
  80%,
  100% {
    height: 40px;
    box-shadow: 0 0 #000;
  }

  40% {
    height: 50px;
    box-shadow: 0 -20px #000;
  }
}

.loading-content{
  margin: 50px 380px;
  font-size: 40px;
  font-weight: 700;
}
style>

你可能感兴趣的:(前端组件,vue.js,electron,javascript)