logo

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

开始分析

  • 我最开始是这么想的
    D3PJpt.jpg

我觉得逻辑没问题啊,于是开始查找资料,发现electron有自带的热更新,研究了一上午,发现好像并没有怎么好用啊,还发现似乎自能保存在c盘,那用户不安装在c盘咋整。果断换了方法。

于是开始研究用node实现,发现似乎可行,操作难度不大,我整理了好多的问题,都给一一解决了,但是最后一步把我卡死了。因为文件资源在占用中,不能替换,必须关闭程序,那么关闭程序就不会执行后面替换的代码了。一度陷入了沉思。

于是我又整理了下思路。

  • 然后就有了想法
    D3PY1P.jpg
    D3PNX8.jpg

1、听说electron对于app与app.asar的优先度不同,app优先于app.asar,可以借这个方法来实现。但是好像比较绕,那就先放弃了。

2、似乎可以监听electron的close事件,然后执行程序。不过用了下,用处不大,因为我app.quit()后,进程关闭了。

3、通过子进程实现。先打开子进程,然后关闭主进程,执行子进程的代码。或者通过子进程调用另外的文件。感觉可行。

最后的思路

重新收拾心情,轻装上路。

先判断版本号,如果本地的版本号小于线上版本号,获取文件地址,然后下载文件地址的文件,以流的形式下载并写入临时文件,当然,如果不存在临时文件的话,先创建一个,有的话,就直接写入。然后对保存的文件进行重命名。最后执行子进程调用bat复制文件的命令,同时关闭主进程,然后打开程序,完成更新。如果是后端服务,则调用bat执行stop服务的命令,然后复制文件,执行start的命令,最后打开程序,以完成更新。

最后的文件代码

这里是main.js入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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下载更新的文件,里面有一些无用的代码,懒得删….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178

//文件下载
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

1
2
3
4
5
6
7
8
9
@echo off

set filePath=%~dp0
echo %filePath%
cd %filePath%
%~d0

cd %filePath%
start xxx.exe

嗯,就这样执行了热更新的操作,不用用户执行卸载安装等操作,只需要同意更新就完事了。

我是菜鸡前端

我是一个菜鸡程序员,勿喷。有更好的想法,可以互相交流

完善的链接传送门 完善的热更新