electron-egg实现全量更新和增量更新(下)
接着上一章节实现了全量更新,现在开始实现增量更新,electron-egg官网增量更新是需要付费的插件,本着不付费的原则,在全量更新的基础上实现一个增量更新。
根据官网,增量更新只更新app.asar 文件的条件,我们只需要把打包安装之后的软件中的app.asar文件替换成最新的就行了,这样就实现了增量更新,所以,在实现增量更新之前,需要在配置中,把 "asar": true, 设置为true, 打开cmd/builder.json 文件。
同时实现增量更新之前需要使用两个脚本文件,分别对应windows和macos系统下,执行app.asar替换的脚本,这里我只写了windows和macos系统的,其他的可以自行扩展,在build/extraResources
目录下新建bat(自己取名)目录再创建两个文件 replace.sh 和 replace.vbs (这里为什么不是.bat ,因为如果使用bat在执行替换的一瞬间会有命令框的弹出,这样的体验效果不太好,所以使用VBScript脚本语言实现替换就不会导致有命令款弹出),话不多说直接上代码:
replace.sh 代码(代码比较简单,可自行扩展):
#!/bin/bash sleep 2 #指定要操作的目录 TARGET_DIR=$1 #进入指定目录 cd "$TARGET_DIR" || { echo "无法进入目录 $TARGET_DIR"; exit 1; } #检查并删除app.asar文件 if [ -f "app.asar" ]; then rm "app.asar" echo "已删除 app.asar 文件" else echo "app.asar 文件不存在" fi #检查并重命名app.asar-temp文件 if [ -f "app.asar-temp" ]; then mv "app.asar-temp" "app.asar" echo "已将 app.asar-temp 重命名为 app.asar" else echo "app.asar-temp 文件不存在" fi
replace.vbs 代码:
' 定义等待函数,模拟 timeout 命令 Sub Wait(seconds) WScript.Sleep seconds * 1000 End Sub ' 获取命令行参数 Dim args, arg1 Set args = WScript.Arguments arg1 = args(0) ' 等待1秒 Wait 1 ' 尝试删除 app.asar 文件 Dim fso, filePath Set fso = CreateObject("Scripting.FileSystemObject") filePath = arg1 & "\app.asar" If fso.FileExists(filePath) Then On Error Resume Next fso.DeleteFile filePath, True If Err.Number <> 0 Then WScript.Echo "Failed to delete app.asar, continuing..." Err.Clear End If On Error GoTo 0 End If ' 尝试重命名 app.asar-temp 为 app.asar filePath = arg1 & "\app.asar-temp" Dim newFilePath newFilePath = arg1 & "\app.asar" If fso.FileExists(filePath) Then On Error Resume Next fso.MoveFile filePath, newFilePath If Err.Number <> 0 Then WScript.Echo "Failed to rename app.asar-temp, continuing..." Err.Clear End If On Error GoTo 0 End If ' 清理对象 Set fso = Nothing
注意:在macos系统中打包:
chmod +x replace.sh
对于app.asar这个文件,是可以使用工具解决出来源代码的,我们在打包的时候,可以使用严格加密模式,这样就不用担心被别人解压出来源代码,使用electron-egg自带的:
更改 cmd/bin.js文件,将加密部分更改一下:
/** * 加密 */ encrypt: { frontend: { type: 'strict', files: [ './public/dist/**/*.(js|json)', ], cleanFiles: ['./public/dist'], confusionOptions: { compact: true, stringArray: true, stringArrayEncoding: ['none'], stringArrayCallsTransform: true, numbersToExpressions: true, target: 'browser', } }, electron: { type: 'strict', files: [ './public/electron/**/*.(js|json)', ], cleanFiles: ['./public/electron'], specificFiles: [ './public/electron/main.js', './public/electron/preload/bridge.js', ], confusionOptions: { compact: true, stringArray: true, stringArrayEncoding: ['rc4'], deadCodeInjection: false, stringArrayCallsTransform: true, numbersToExpressions: true, target: 'node', } } },
话不多说,准备工作做完之后直接开搞增量更新了,在 electron/service/os/目录下创建 increment_updater.js ,
const { app: electronApp } = require('electron'); const { spawn, exec } = require('child_process'); const { is } = require('ee-core/utils'); const { logger } = require('ee-core/log'); const { getMainWindow, setCloseAndQuit } = require('ee-core/electron'); const Ps = require('ee-core/ps'); const isDev = process.env.NODE_ENV const path = require('path'); const fs = require('fs'); const http = require('http') const logPath = electronApp.getPath('logs') const { LocalStorageService } = require('../LocalStorage'); //获取资源路径 const resourcePath = isDev === 'dev' ? process.resourcesPath : process.resourcesPath // 设置app.asar替换文件路径 const localAsarTemp = path.join(resourcePath, 'app.asar-temp') // 下载文件的地址 const asarUrl = is.windows() ? `http://cos.xxxxx.com/software/windows/ceshi/app.asar`: `http://cos.xxxxx.com/software/macos/ceshi/app.asar` /** * 增量更新 * @class */ class IncrementUpdaterService { /** * notSuccess | success */ init() { let data = { downloadSuccess: "notSuccess" } if (!fs.existsSync(LocalStorageService.getLocalConfigFile())) { fs.writeFileSync(LocalStorageService.getLocalConfigFile(), JSON.stringify(data), 'utf8') logger.info('配置文件不存在,已自动创建') } else { logger.info('监测到配置文件') } return null } /** * 替换 app.asar文件 * @returns {Promise<void>} */ async replaceAsarFile() { let localBatFile if((is.windows())){ localBatFile = path.join(Ps.getExtraResourcesDir(),'./bat/replace.vbs') }else if((is.macOS())){ localBatFile = path.join(Ps.getExtraResourcesDir(),'./bat/replace.sh') fs.chmodSync(localBatFile,0o755) //给sh文件添加可执行权限 } // fs.chmodSync(localBatFile,0o755) const childOut = fs.openSync(path.join(logPath, './out.log'), 'a') const childErr = fs.openSync(path.join(logPath, './err.log'), 'a') const child = await spawn(`"${localBatFile}"`,[`"${resourcePath}"`], { detached: true, // 允许子进程独立 通常与 unref() 方法一起使用,以确保父进程退出时不会等待子进程。 shell: true, //当设置为 true 时,命令会在系统的默认 shell(如 cmd.exe 在 Windows 或 /bin/sh 在 Unix 系统)中执行。 windowsHide:true, // stdio: ['ignore', "pipe", "pipe"] stdio: ['ignore', childOut, childErr] }) LocalStorageService.setLocalConfig() // child.stderr?.on('data', data => logger.info('stderr', data)) child.unref() } /** * 检测asar文件是否存在,存在则返回文件大小 * @param path */ async getAsarLength(path) { return new Promise((resolve)=>{ try { fs.stat(path,{ sudo: true, admin: true }, (err, stats) => { if (err) { logger.error('获取asar文件大小失败', err) resolve(0) } else { logger.info('获取asar文件大小成功') resolve(stats.size) } }) }catch (e){ logger.error('获取asar文件大小失败', e) resolve(0) } }) } /** * 下载 app.asar * @returns {Promise<void>} */ async downloadAsar() { this.init() //配置文件初始化 const size = await this.getAsarLength(localAsarTemp) const fileStream = fs.createWriteStream(localAsarTemp) await http.get(asarUrl, res => { const headers = res.headers; if (Number(headers['content-length']) !== Number(size)) { res.pipe(fileStream) fileStream .on('finish', () => { LocalStorageService.setLocalConfig('success') //下载完成执行退出 setTimeout(() => { const data = { status: 6, desc: '检测到有更新,退出软件时自动更新' } this.sendStatusToWindow(data) },1000) }) .on('error', error => { // 删除文件 fs.unlink(localAsarTemp, () => { logger.error('下载出现异常,已中断', error) }) }) } else { res.resume(); LocalStorageService.setLocalConfig('notSuccess') } }) } /** * 向前端发消息 */ sendStatusToWindow(content = {}) { const textJson = JSON.stringify(content); const channel = 'custom/app/updater'; const win = getMainWindow(); win.webContents.send(channel, textJson); } } IncrementUpdaterService.toString = () => '[class IncrementUpdaterService]'; const incrementUpdaterService = new IncrementUpdaterService(); module.exports = { IncrementUpdaterService: incrementUpdaterService };
修改 controller中的 framework.js,添加代码:
const {app: electronApp} = require("electron");
const { LocalStorageService } = require('../service/LocalStorage');
const { IncrementUpdaterService } = require('../service/os/increment_updater');
//类里面添加一个方法
//这个方法下载远程app.asar文件
downloadAsar() {
IncrementUpdaterService.downloadAsar();
}
//在底部添加主进程退出监听
/**
* 监听桌面退出
*/
electronApp.addListener('quit',(event)=>{
// logger.info('应用程序退出开始执行删除操作')
if(LocalStorageService.getLocalConfig().downloadSuccess === 'success'){
IncrementUpdaterService.replaceAsarFile();
//新增更新记录弹窗
// localStorage.setItem('updateHandleModel', 'hasUpdate')
LocalStorageService.setLocalStorage({
updateHandleModel: 'hasUpdate'
})
}
})
FrameworkController.toString = () => '[class FrameworkController]';
修改 frontend/src/views/framework/updater/index.vue 文件,修改代码:
//修改init方法 const init = () => { ipc.removeAllListeners(specialIpcRoute.appUpdater); ipc.on(specialIpcRoute.appUpdater, (event, result) => { result = JSON.parse(result); console.log(result) //有更新提示信息 if (result.status === 5 && result.localStorageMsg && result.localStorageMsg === 'hasUpdate') { dialogTableVisible.value = true ipc.invoke(ipcApiRoute.framework.setLocalStorage) }else if (result.status === 3) { progress.value = result.desc; percentNumber.value = result.percentNumber; } else if(result.status === 1){ //有可用更新,弹出框 if(result.versionType === 'smallUpdate'){ ipc.invoke(ipcApiRoute.framework.downloadAsar) }else if(result.versionType === 'largeUpdate'){ open.value = true } versionType.value = result.versionType; }else if(result.status === 4){ ElMessage({ message: '下载已经完成,正准备自动更新', type: 'success', }) percentShow.value = true //显示更新提示信息 tool.value.data.set('updateHandleModel','hasUpdate') }else if (result.status === 6) { ElMessage({ message: result.desc, type: 'warning', }) } }) }
之后打包桌面软件,先打包一个2.0.2的版本,安装好之后将生成好的app.asar上传到云空间,然后把 latest.yml ee-win-2.0.2-x64.exe.blockmap 这两个文件一起上传上去,再打包一个2.0.1的版本本地安装,打开桌面软件测试:
已经下载好了临时文件 退出时会自动替换文件,至此增量更新完成