当前位置: 首页 > news >正文

【踩坑记录】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>

相关文章:

  • Python采集某网站m3u8内容,美女我来了~
  • VS code配置C语言环境
  • 【面试题】请你谈谈MySQL性能调优的方法
  • 自动驾驶技术平台分享:百度Apollo开放平台8.0再升级,更简单,更便捷,更高效
  • 黑客比程序员高在哪里?
  • 前端大屏常用的几种适配方案
  • Unity3d C#实现类似于王者荣耀技能读条和CD冷却的功能(含源码)
  • 专项测试实战 | 如何测试 App 流畅度(基于 FPS 和丢帧率)?
  • 对于synchronized你了解多少?
  • Java8 Stream详细用法介绍
  • maven 继承和聚合的区别
  • Qt5操作Office及Word读写实例
  • 【数据结构】优先级队列(堆)
  • Qt+C++ TCP发送接收信息客户端与服务端窗体
  • C语言仅凭自学能到什么高度?
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 【翻译】babel对TC39装饰器草案的实现
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • docker python 配置
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • ESLint简单操作
  • Intervention/image 图片处理扩展包的安装和使用
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • PHP CLI应用的调试原理
  • python学习笔记-类对象的信息
  • SQL 难点解决:记录的引用
  • 彻底搞懂浏览器Event-loop
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 电商搜索引擎的架构设计和性能优化
  • 回顾 Swift 多平台移植进度 #2
  • 设计模式 开闭原则
  • 项目管理碎碎念系列之一:干系人管理
  • 扩展资源服务器解决oauth2 性能瓶颈
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • #### go map 底层结构 ####
  • #AngularJS#$sce.trustAsResourceUrl
  • #vue3 实现前端下载excel文件模板功能
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (南京观海微电子)——COF介绍
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (实战篇)如何缓存数据
  • (转)【Hibernate总结系列】使用举例
  • (转)我也是一只IT小小鸟
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • 、写入Shellcode到注册表上线
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .NET CLR Hosting 简介
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET国产化改造探索(一)、VMware安装银河麒麟
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • ::before和::after 常见的用法