
因为公司项目需求,需要做一个检查更新的功能,但是不需要让用户重新安装,只需要替换更新的部分,重启应用完成更新。
开始分析
- 我最开始是这么想的

我觉得逻辑没问题啊,于是开始查找资料,发现electron有自带的热更新,研究了一上午,发现好像并没有怎么好用啊,还发现似乎自能保存在c盘,那用户不安装在c盘咋整。果断换了方法。
于是开始研究用node实现,发现似乎可行,操作难度不大,我整理了好多的问题,都给一一解决了,但是最后一步把我卡死了。因为文件资源在占用中,不能替换,必须关闭程序,那么关闭程序就不会执行后面替换的代码了。一度陷入了沉思。
于是我又整理了下思路。
- 然后就有了想法


1、听说electron对于app与app.asar的优先度不同,app优先于app.asar,可以借这个方法来实现。但是好像比较绕,那就先放弃了。
2、似乎可以监听electron的close事件,然后执行程序。不过用了下,用处不大,因为我app.quit()后,进程关闭了。
3、通过子进程实现。先打开子进程,然后关闭主进程,执行子进程的代码。或者通过子进程调用另外的文件。感觉可行。
最后的思路
重新收拾心情,轻装上路。
先判断版本号,如果本地的版本号小于线上版本号,获取文件地址,然后下载文件地址的文件,以流的形式下载并写入临时文件,当然,如果不存在临时文件的话,先创建一个,有的话,就直接写入。然后对保存的文件进行重命名。最后执行子进程调用bat复制文件的命令,同时关闭主进程,然后打开程序,完成更新。如果是后端服务,则调用bat执行stop服务的命令,然后复制文件,执行start的命令,最后打开程序,以完成更新。
最后的文件代码
这里是main.js入口文件
const electron = require('electron')
const path = require("path");
const _axios = require('axios')
const update = require('./dowload')
const {
app,
BrowserWindow,
Menu,
dialog,
} = electron
...这里是创建electron窗口,以及锁死窗口登操作...
// 热更新检查
_axios({
url: 'xxxxx/version', //这里是检查更新的地址
method: 'get',
}).then(res => {
if (res.data.errorCode == 0) {
const localVersion = app.getVersion()
console.log('localVersion', localVersion);
const onlineVersion = res.data.data.name
console.log('onlineVersion', onlineVersion);
console.log(' res.data.data.httpPath', res.data.data.httpPath)
if (localVersion < onlineVersion) {
const dialogOpts = {
type: 'info',
buttons: ['立即更新', '稍后更新'],
title: '更新提醒',
message: `您有新的更新!`,
detail: `内容如下:` + `${res.data.data.attach}`
}
dialog.showMessageBox(dialogOpts).then((returnValue) => {
if (returnValue.response === 0) {
update(res.data.data.httpPath)
}
})
}
}
})
这里是dowload.js下载更新的文件,里面有一些无用的代码,懒得删….
//文件下载
const fs = require('fs')
const path = require("path");
const axios = require('axios')
const fsPromises = require('fs').promises
const package = require('../package.json')
const {
exec,
spawn
} = require('child_process');
const electron = require('electron')
const {
app,
} = electron
// 获取真实的绝对路径
const dirPathO = path.join(__dirname).split('resources')
console.log('dirPathO', dirPathO);
const relativePath = dirPathO[0];
console.log('relativePath', relativePath);
// 获取一个绝对路径的文件夹
const dirPath = path.join(relativePath, "/dowload");
console.log('dirPath', dirPath);
// 热更新
const update = (url) => {
fs.access(dirPath, (err) => {
console.log('err', err);
if (err) { //如果文件不存在,就创建这个文件
fs.mkdir(dirPath, (err) => {
console.log(err);
if (!err) {
console.log('dowload file create success!');
dowloadFile(url)
}
});
} else {
//如果这个文件已经存在
dowloadFile(url)
}
})
}
let num = 0
// 文件重命名
const reName = (name, newName, suffix) => {
num++
console.log('begin to rename!');
fs.rename(relativePath + 'dowload/' + name, relativePath + 'dowload/' + newName + '.' + suffix, err => {
console.log('error', err);
if (err) {
if (num <= 1) {
console.log('rename again!');
reName(name, newName, suffix)
} else {
console.log('rename failed!', err);
}
} else {
console.log('rename success!');
reviseVersion(name, newName, suffix)
}
})
}
// 调用脚本复制
const shellReName = (name, newName, suffix) => {
let bat, shellPath
if (suffix == 'asar') {
shellPath = path.join(relativePath, "/copy.bat");
console.log('shellPath', shellPath);
} else {
shellPath = path.join(relativePath, "/copyzjg.bat");
console.log('shellPath', shellPath);
}
bat = spawn(shellPath);
bat.stdout.on('data', (data) => {
app.quit()
console.log('data', data);
// copyFile(newName, suffix)
});
bat.stderr.on('data', (data) => {
console.error('error', data);
});
bat.on('exit', (code) => {
console.log(`子进程退出,退出码 ${code}`);
});
}
// 移动文件
const copyFile = (newName, suffix) => {
const copiedPath = relativePath + 'dowload/' + newName + '.' + suffix;
const resultPath = relativePath + 'resources/' + newName + '.' + suffix;
fsPromises.copyFile(copiedPath, resultPath)
.then(() => {
console.log('copyFile success!');
openProgram()
}).catch((err) => {
console.log('copyFile failed!');
console.log(err);
});
}
// 修改package的version号码
const reviseVersion = (name, newName, suffix) => {
package.version = name
console.log('version change success!');
console.log('package', package);
shellReName(name, newName, suffix)
}
// 打开指定文件程序
const openProgram = () => {
const path = relativePath + '/jixin.exe'
exec(path, (err, data) => {
if (err) {
console.error('exe open failed', err);
return;
}
console.log('exe open success', data.toString());
});
}
// 下载文件
const dowloadFile = (url) => {
console.log('url', url);
// 获取文件名称
const name = url.split('/').pop().split('_')[0]
console.log(name);
// 获取文件后缀
const suffix = url.split('/').pop().split('_').pop().split('.').pop()
console.log('suffix', suffix);
axios({
method: 'get',
url,
timeout: 10 * 60 * 1000,
maxContentLength: Infinity,
responseType: 'stream',
})
.then(res => {
let w, newName;
w = fs.createWriteStream(relativePath + 'dowload/' + name)
res.data.pipe(w)
return new Promise((resolve, reject) => {
w.on('finish', () => {
console.log('This file is over end!');
if (suffix == 'asar') {
newName = 'app'
// 重命名下载的文件
reName(name, newName, suffix)
} else {
newName = 'zjg_2d'
// 重命名下载的文件
reName(name, newName, suffix)
}
resolve()
})
w.on('error', (err) => {
console.log('error', err);
reject()
})
})
})
}
module.exports = update
这里是copy.bat
@echo off
set filePath=%~dp0
echo %filePath% @echo off cd %filePath%
%~d0
cd %filePath%
start xxx.exe
我是菜鸡前端
我是一个菜鸡程序员,勿喷。有更好的想法,可以互相交流
版权声明:本文为weixin_29485887原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。