热更新_热更新 | electron热更新

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

开始分析

  • 我最开始是这么想的

5937c6881c4b420d561faba48404ff0a.png

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

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

于是我又整理了下思路。

  • 然后就有了想法

1c44b6287811dff40b5996a4f7462204.png

f9316fb50399eba0f2cabe3f7d9f6f22.png

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版权协议,转载请附上原文出处链接和本声明。