Appearance
版本更新
版本更新主要有两种形式,一是用electron-updater,而是通过替换app.asar文件实现 electron-updater:可以更新整个应用,包括electron版本升级 asar:只更新app.asar文件,主要为前端内容和主进程内容,无法升级electron版本
搭建一个简易的更新服务器
初始化一个项目,并安装 express
bash
npm init -y
pnpm install express
在package.json中添加启动命令
json
"scripts": {
"start": "node ./server.js"
}
在根目录创建server.js文件,并添加以下代码
js
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;
// 设置静态文件目录
app.use(express.static(path.join(__dirname, 'public')));
// 启动服务器
app.listen(port, () => {
console.log(`服务器正在运行在 http://localhost:${port}`);
});
创建public文件夹,将要更新的内容放入文件夹,稍后会讲更新内容的来源
├── public
│ ├── ElectronLearn-1.0.1-arm64.dmg // 新版本文件
│ ├── ElectronLearn-1.0.1-arm64.dmg.blockmap // 文件分块信息
│ └── latest-mac.yml // 保存了版本信息,文件名称,文件校验信息等
│ └── app.zip // app.asar压缩文件
│ └── update.json // 更新信息描述
└── server.js
update.json
json
{
"version": "1.0.1",
"release_notes": "修复bug",
"update_type": "hot" // 标识 走electron-updater自动更新还是 asar替换更新
}
使用electron-updater更新
配合electron-builder使用,实现自动更新,文档
- 安装
bash
pnpm install electron-updater
- 在package.json中配置更新地址
json
"build": {
//……
"directories": {
//……
},
"publish": [
{
"provider": "generic",
"url": "http://localhost:3000/"
}
]
}
- 在主进程触发更新
js
import { app } from 'electron'
import updater from "electron-updater"
/*
* 全量更新
*/
function checkFullUpdate(callBack){
const { autoUpdater } = updater
// 设置更新地址,已经在package.json中配置了
// autoUpdater.setFeedURL(ossUrl)
// 检查更新
autoUpdater.checkForUpdates()
// 监听更新状态
autoUpdater.on('update-available', () => {
callBack({ state: 'available', msg: '发现新版本' })
})
autoUpdater.on('update-not-available', () => {
callBack({ state: 'error', msg: '当前已是最新版本' })
})
autoUpdater.on('update-downloaded', () => {
callBack({ state: 'success', msg: '更新完成,即将重启应用' })
})
autoUpdater.on('error', (err) => {
callBack({ state: 'error', msg: '更新失败,请联系管理员', err })
})
// 下载进度
autoUpdater.on('download-progress', (progress) => {
callBack({ state: 'donwloading', progress })
})
}
export {
checkFullUpdate
}
替换app.asar文件,实现手动更新
在打包时,除了生成安装包,还会生成一个未打包文件夹win是win-unpacked
,mac是mac-arm64
,在里面有个Resources文件夹,找到里面的app.asar文件,这个文件就打包了前端代码和主进程代码。 在应用的安装位置也可以找到app.asar文件,在应用运行时会自动解压app.asar,加载里面的代码。 所以将新版本的app.asar文件替换到安装位置,即实现了更新。
mac:'/Applications/ElectronLearn.app/Contents/Resources/app.asar'
win:'C:\\Program Files\\electron-learn\\resources\\app.asar'
更新信息
将app.asar压缩为app.zip,并保存到更新服务器上,修改版本信息
json
{
"version": "1.0.1",
"release_notes": "修复bug",
"update_type": "hot" // 标识 走electron-updater自动更新还是 asar替换更新
}
安装用到的库
shell
pnpm i adm-zip # 解压缩
pnpm i progress-stream # 显示下载进度
在应用中获取更新信息,下载并替换app.asar文件
js
import { app } from 'electron'
import updater from "electron-updater"
import AdmZip from 'adm-zip'
import progress from 'progress-stream'
import fs from 'fs'
import axios from 'axios'
import path from 'path'
const ossUrl = 'http://localhost:3000/' // 更新文件的地址
const versionInfoUrl = 'http://localhost:3000/update.json' // 更新信息的接口地址
let updateing = false // 当前正在更新
/**
* 检查更新
* @param {*} callBack
*/
const checkUpdate = (callBack) => {
if (updateing) {
return
}
updateing = true
axios.get(versionInfoUrl).then(res=>{
const { version, update_type } = res.data
const localVersion = app.getVersion()
if (version && version !== localVersion) {
// 判断是热更新asar还是全量更新electron-updater
if (update_type == 'hot') {
checkHotUpdate(callBack)
} else if (update_type == 'full') {
// 全量更新
checkFullUpdate(callBack)
}
} else {
updateing = false
callBack({state: 'error', msg: '当前已是最新版本'})
}
}).catch(err=>{
callBack({state: 'error', msg: '更新版本号失败,请联系管理员', err})
}).finally(()=>{
updateing = false
})
}
/**
* 热更新 下载
*/
const checkHotUpdate = async(callBack) => {
const downloadUrl = `${ossUrl}app.zip`
// 获取下载保存位置
const savePath = app.getPath('userData')
// 获取当前应用的asar包路径
const asarPath = path.dirname(app.getAppPath())
downloadFile(downloadUrl,`${savePath}app.zip`,(progress)=>{
callBack({ state: 'donwloading', progress:{ percent: progress.percentage, bytesPerSecond: progress.speed } })
}).then(()=>{
const unzip = new AdmZip(`${savePath}app.zip`) // 下载压缩更新包
unzip.extractAllTo(`${asarPath}`, /* overwrite*/ true) // 解压替换本地文件
callBack({ state: 'success', msg: '更新完成,即将重启应用' })
}).catch(err=>{
callBack({ state: 'error', msg: '下载失败,请联系管理员', err })
}).finally(()=>{
updateing = false
})
}
async function downloadFile(url, outputPath, progressCallback) {
const writer = fs.createWriteStream(outputPath);
const { data, headers} = await axios({
url,
method: 'GET',
responseType: 'stream'
});
const totalLength = headers['content-length'];
const prg = progress({ time: 200, length: totalLength });
prg.on('progress', (res) => {
progressCallback(res)
})
// 将响应数据流写入文件
data.pipe(prg).pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
}
/*
* 全量更新
*/
function checkFullUpdate(callBack){
const { autoUpdater } = updater
// 设置更新地址,已经在package.json中配置了
// autoUpdater.setFeedURL(ossUrl)
// 检查更新
autoUpdater.checkForUpdates()
// 监听更新状态
autoUpdater.on('update-available', () => {
callBack({ state: 'available', msg: '发现新版本' })
})
autoUpdater.on('update-not-available', () => {
callBack({ state: 'error', msg: '当前已是最新版本' })
})
autoUpdater.on('update-downloaded', () => {
callBack({ state: 'success', msg: '更新完成,即将重启应用' })
})
autoUpdater.on('error', (err) => {
callBack({ state: 'error', msg: '更新失败,请联系管理员', err })
})
// 下载进度
autoUpdater.on('download-progress', (progress) => {
callBack({ state: 'donwloading', progress })
})
}
export {
checkUpdate
}
更新遇到的问题
- 打包后运行一直报错,缺少xxx包,换成npm后就解决
- electron-updater,mac
.dmg
更新失败,mac需要.zip
的文件来更新 - electron-updater,mac需要签名后才能更新
- electron-updater,win需要重走安装流程,容易被杀毒软件拦截
- 很多文章说替换app.asar文件会有权限问题,实测win是完全没问题的,mac有电脑失败,但多是是成功的
当前应用各文件夹的位置
获取当前应用的配置文件位置app.getPath('userData')
mac : /Users/xiaoyan/Library/Application Support/应用name
Win: 'C:\\Users\\Administrator\\AppData\\Roaming\\应用name'
获取当前应用的位置app.getAppPath()
mac:'/Applications/应用name.app/Contents/Resources/app.asar'
win:'C:\\Program Files\\应用name\\resources\\app.asar'
log文件默认位置
mac: /Users/xiaoyan/Library/Logs/应用name/
Win: \Users\Administrator\AppData\Roaming\应用name\logs