1.使用到的nodejs模块
① fs模块,对要进行热更新的文件进行处理
② request模块 进行网络请求,获取热更新包
③ adm-zip-iconv模块 解压文件
④ crypto模块 对热更新包进行MD5检测,判断完整性
⑤ ini 模块 读取ini文件
备注:ini和adm-zip-iconv,request模块需要自己npm inistall ,其他两个模块nwjs都会自带
2.新建一个nodejs项目
① 新建一个空文件夹,在空文件夹下启动cmd窗口 运行
npm init -y
文件夹会生成一个packag.json,配置和修改json文件,添加nwjs打开窗口设置
{
"name": "hotUp",//该名称不要跟你项目的package.json中的name同名
"main": "updateExe.html",//等下要启动的热更新页面
"window": {
"title": "基于nwjs的热更新exe",
"icon": "static/icon.png",//给启动的热更新页面设置图标
"toolbar": false,//去除nwjs自身的顶部选项卡
"frame": false,//使用nwjs的无框模式
"width": 770,
"height": 320,
"max_height": 320,
"max_width": 770,
"min_height": 320,
"min_width": 770,
"position": "center",
"transparent": true//透明化窗口
}
}
以上json的备注内容,为方便阅读才写上去,实际当中,不应该存在,否则启动时会报错
② 新建一个html页面,取名为上面json中main的名称,内容如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body{
margin:0;
padding:0;
background-color: transparent;
}
body::-webkit-scrollbar{
display:none;
}
.app{
background-color: transparent;
padding:5px;
margin:auto;
width: 750px;
height: 300px;
}
.main{
margin:20px auto;
text-align: center;
}
</style>
</head>
<body>
<div class="app">
<div class="main">更新中,请稍后......</div>
</div>
<script type="text/javascript">
var fs = require('fs');
var request = require('request');
var path = require('path');
var AdmZip = require('adm-zip-iconv');
const crypto = require('crypto');
var ini = require('ini');
var win = nw.Window.get()
win.hide();//默认热更新文件页面隐藏,当有热更新时才显示
//基础文件夹路径
//const __Tempdirname = path.resolve('./');//在与nwjs打包成exe后,这种方法获取到的是系统的临时目录
const __dirname = path.dirname(process.execPath)//获取打包后exe的当前文件夹路径
//打开其他的exe
var exec = require('child_process').execFile;
var fun =function(){
win.hide();
//要启动的exe程序
exec(path.join(__dirname ,'index.exe'), function(err, data) {
console.log("关闭当前exe")
win.close();
});
}
//检查版本的路径
const checkUrl = "/checkVersion/?";
//获取热更新包的路径
const updateUrl = "/updateVersion/?";
//临时文件夹名称
let dirname = "temp";
var nowVersion = false;//版本号
var baseURL = false;//服务器名称
//删除文件或文件夹(reservePath和path一致时,保留最外层文件夹)
function delFile(path, reservePath="") {
if (fs.existsSync(path)) {
if (fs.statSync(path).isDirectory()) {//是文件夹
let files = fs.readdirSync(path);//读取文件夹内所有文件,并且遍历
files.forEach((file, index) => {
let currentPath = path + "/" + file;
if (fs.statSync(currentPath).isDirectory()) {//是文件夹递归
delFile(currentPath, reservePath);
}else{
console.log("删除文件")
fs.unlinkSync(currentPath);//删除文件
}
});
if (path != reservePath) {
console.log(path)
console.log("删除temp")
fs.rmdirSync(path);
}
} else {//是文件
fs.unlinkSync(path);
}
}
}
hasNew();//检查是否有更新包
//检查是否有新更新包
function hasNew(){
fs.readFile(path.join(__dirname,"setting.ini"),'utf8',function(err,data){
if(err){
console.log("缺少配置文件")
win.show();
return;
}
let parseData = ini.parse(data);
//获取本地版本
nowVersion = parseData["version_info"].version;//当前项目exe的版本号
baseURL = parseData["version_info"].baseURL;//后台地址
if(!nowVersion || !baseURL){
console.log(parseData)
win.show();
return;
}else{
//获取是否有新版本号
request.get(baseURL + checkUrl + 'version='+ nowVersion,function(err,response,body){
if(err){
console.log("网络异常")
win.show();
return;
}
var bodyInfo = JSON.parse(response.body)
if(bodyInfo.code == 200){
//有新版本可以热更新
if(bodyInfo.version != nowVersion){
win.show();
//开始更新
connetNet();
}
if(bodyInfo.version == nowVersion){
fun();//启动index.app
}
}else{
console.log("返回不是200")
win.show();
return;
}
})
}
})
}
//更新结束
function updateEnd(msg, finish=false){
console.log(msg);
if(finish){
//在此检查是否更新
hasNew();
//fun();//启动index.app
}
}
//开始更新
function connetNet(){
//判断当前路径下,是否有同名临时文件夹
fs.access(path.join(__dirname,dirname), fs.constants.F_OK, (err) => {
if(!err){
dirname = "temp"+ Date.parse(new Date());
}
//先创建一个临时文件夹temp
fs.mkdir(path.join(__dirname,dirname), { recursive: true }, (err) => {
if (err) {
updateEnd("创建临时文件夹失败")
return;
};
//下载文件(发送当前版本给后台)
request.get(baseURL + updateUrl + 'version='+ nowVersion,function(err,response,body){
if(err){
updateEnd("网络异常,请检查网络")
return;
}
//返回,热更新包路径,和热更新包MD5
var bodyInfo = JSON.parse(response.body)
if(bodyInfo.code == 200){
//获取热更新包,并且在本地临时文件夹内存储为latest.zip(后台返回的也必须是一个热更新包)
request.get(baseURL + bodyInfo.downUrl).pipe(fs.createWriteStream(path.join(__dirname,dirname,"latest.zip")).on("close",function(){
console.log("文件下载结束")
//检查文件完整性
var stream = fs.createReadStream(path.join(__dirname,dirname,"latest.zip"))
var fsHash = crypto.createHash('md5');
stream.on('data',function(d){
fsHash.update(d)
})
stream.on("end",function(){
var md5v = fsHash.digest("hex");
//判断后台返回的zip文件的MD5值和临时文件夹内的latest.zip的MD5值是否一致
if(bodyInfo.md5Value && md5v != bodyInfo.md5Value){
console.log("md5不一样");
updateEnd("压缩包不完整")
//删除临时文件夹
delFile(path.join(__dirname,dirname));
}else{
console.log("开始替换");
//删除原来文件
delFile(path.join(__dirname,"index.exe"))//项目exe
delFile(path.join(__dirname,"setting.ini"))//这个文件夹内保存,当前版本号,和后台地址
//替换文件
var zip = new AdmZip(path.join(__dirname,dirname,"latest.zip"), "GBK");
zip.extractAllTo(__dirname, true)
//删除临时文件夹
delFile(path.join(__dirname,dirname));
updateEnd("更新完成",true)
}
})
})).on('error', function(err) {
updateEnd("网络异常,请检查网络")
return;
})
}else{
//删除临时文件夹
delFile(path.join(__dirname,dirname));
updateEnd("更新失败")
}
})
});
});
}
</script>
</body>
</html>
③ 下载所需模块
npm install ini
npm install request
npm install adm-zip-iconv
3.打包
① 选中项目文件夹内的所有内容,打包成app.zip(一定得是zip压缩包)
② 将app.zip修改成app.nw,并且拖到nwjs的目录中
③ 在nwjs的目录中启动cmd,并运行
copy /b nw.exe+app.nw app.exe
4.建立setting.ini文件,并且放到nwjs目录中
[version_info]
version=1.0.0
baseURL=http://www.xxxxxxx.com
baseURL为后台地址,version是项目exe的版本号
5.修改app.exe的使用权限
① 使用ResourceHacker.exe打开app.exe,找到如下文件的位置,修改权限为requireAdministrator
替换后,点击上面的三角形运行,并ctrl+s 保存,会重新生成app.exe
6.最后的目录结构如图
7.使用Inno Setup Compiler将以上内容打包成安装包exe
安装包exe通第5步。更改请求权限
以上所有内容就完成了一个带有热更新的项目。自己的项目在index.exe中,有新版本的时候,将新的index.exe和setting.ini打包成zip文件放在服务器,并在后台修改检测版本时,返回最新版本号。当app.exe检测到有新更新包时,会下载更新包文件,并替换掉原来的index.exe和setting.ini。当没有新更新包时,不会显示,会启动index.exe。
后期会将后台部分补上。有问题可留言,记得点个赞,谢谢!