研究Electron自動(dòng)更新 系列三【近8k字】
這是繼《研究 Electron 自動(dòng)更新》系列的最后一篇,感謝大家的耐心閱讀。
系列一從自動(dòng)更新的方案深入地講解了其中的原理,另外還講解了兩種打包方式。
系列二列舉了開發(fā)中出現(xiàn)的三個(gè)問題,分別是“Can not find Squirrel”、“安裝目錄中packages文件夾和Update.exe程序找不到”和“Error: spawn UNKNOWN”,從不同角度分析并且作了解答。
本文就繼續(xù)系列二,再講講遇到的其他問題。
開發(fā)中存在的問題
(四) Error Downloading Update: Command failed: 4294967295
1. 背景
自動(dòng)更新過程中出現(xiàn)“Error Downloading Update: Command failed: 4294967295”的報(bào)錯(cuò),這個(gè) error 和系列二中的問題3 “Error: spawn UNKNOWN” 很類似,因?yàn)檫@個(gè)問題很常見,所以我要挑出來講。
2. 原因分析
這個(gè)問題在 Squirrel.Windows 的 issues(https://GitHub.com/Squirrel/Squirrel.Windows/issues/833)中也有,
其中的回答繞不過一點(diǎn):程序的錯(cuò)誤,遠(yuǎn)程發(fā)布文件是空的或損壞影響我們的更新。
3. 解決方式
對(duì)于開發(fā)者來說,我需要重新上傳新的安裝程序。還有可能是更新服務(wù)器提供的下載 nupkg 的 url 出錯(cuò),這個(gè)需要通過 SquirrelSetup.log 去仔細(xì)檢查,不難的。
對(duì)于用戶來說,可能需要先卸載后重新安裝新的版本。
(五) 更新后,老版本沒有被替換
1. 背景
如果當(dāng)前電腦上的應(yīng)用版本是 0.0.1,服務(wù)器上最新是 0.0.2。自動(dòng)更新完成后,多出來一個(gè)新版本的目錄 app-0.0.2,但是沒有覆蓋 xxx 項(xiàng)目,桌面快捷方式打開的還是 xxx 項(xiàng)目里的舊版本。
究其原因,歸咎于 nsis 沒有集成 updateManage 機(jī)制。系列一和二已經(jīng)描述過,就不再贅述。

圖 7 安裝目錄
2. 解決方案
向服務(wù)器每隔一段時(shí)間發(fā)送當(dāng)前版本的請(qǐng)求,詢問其是否有新版本的應(yīng)用( setFeedURL和checkForUpdates方法實(shí)現(xiàn));當(dāng)有更新進(jìn)入 error、checking-for-update、update-available和update-not-available這些鉤子方法時(shí),寫入日志;更新進(jìn)入 update-downloaded,提示用戶更新完成,手動(dòng)重啟。然后,啟動(dòng)一個(gè)子進(jìn)程去執(zhí)行bat腳本,替換安裝目錄下面的舊版本。
xxx 項(xiàng)目的更新代碼,見 update.js:
import {autoUpdater} from 'electron'
// 服務(wù)器地址
const server = 'XXXXXXX'
const url = `${server}/update/${process.platform}/${app.getVersion()}/stable`
logger.info(`url:${url}`)
// 設(shè)置請(qǐng)求地址
autoUpdater.setFeedURL({
url
})
logger.info(`process.ExecPath:${process.ExecPath}`)
// 檢查更新
setInterval(() => {
autoUpdater.checkForUpdates()
logger.info('checkForUpdates')
}, 900000)
const appName = '應(yīng)用更新'
const message = {
error: '檢查更新出錯(cuò)',
checking: '正在檢查更新……',
updateAva: '下載更新包成功',
updateNotAva: '現(xiàn)在使用的就是最新版本,不用更新',
downloaded: '更新完成,請(qǐng)手動(dòng)重啟'
}
autoUpdater.on('error', error => {
logger.error('There was a problem updating the application')
logger.error(error)
})
.on('checking-for-update', function () {
logger.info('當(dāng)開始檢查更新的時(shí)候觸發(fā)')
})
.on('update-available', function () {
logger.info('當(dāng)有可用更新時(shí)發(fā)出,更新會(huì)自動(dòng)下載')
})
.on('update-not-available', function () {
logger.info('暫無更新')
})
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
logger.info('update-downloaded')
logger.info(`releaseNotes:${releaseNotes}`)
logger.info(`releaseName:${releaseName}`)
dialog.showMessageBox({
type: 'info',
buttons: ['確定'],
title: appName,
message: process.platform === 'win32' ? releaseNotes : releaseName,
detail: message.downloaded
}).then((returnValue) => {
if (returnValue.response === 0) {
fs.writeFile('../releaseName.txt', releaseName, (err) => {
if (err) {
logger.error(err)
throw err
} else {
var ls
ls = childProcess.spawn('libs/Windows/adb/adb', ['kill-server'])
ls.stdout.on('data', function (data) {
logger.info('stdout: ' + data)
})
ls.stderr.on('data', function (data) {
logger.error('stderr: ' + data)
})
ls.on('exit', function (code) {
logger.info('目錄替換程序開始運(yùn)行')
// 地址
const a = process.cwd()
logger.info('a ' + a)
const arr = a.split('\\')
logger.info('arr ' + arr)
const pre = a.slice(0, -arr[arr.length - 1].length)
logger.info('pre' + pre)
process.chdir(pre)
childProcess.Exec(`start /min "" "${pre}replace.bat" ${releaseName}`)
setTimeout(() => {
logger.info('xxx項(xiàng)目退出')
app.quit()
}, 1000)
})
}
})
}
})
})
xxx 項(xiàng)目新版本替換舊版本的腳本,見 Replace.bat:
chcp 65001
echo **更新即將完成,請(qǐng)勿關(guān)閉窗口!** >> replace.log
ping -n 5 127.0.0.1 >> replace.log
COPY ".\xxx項(xiàng)目\Uninstall xxx項(xiàng)目.Exe" app-%1 >> replace.log
COPY ".\xxx項(xiàng)目\uninstallerIcon.ico" app-%1 >> replace.log
RD /q /s ".\xxx項(xiàng)目" >> replace.log
ren app-%1 "xxx項(xiàng)目" >> replace.log
del "xxx項(xiàng)目.Exe" >> replace.log
exit
(六) Update.exe之外的操作無日志
1. 背景
主進(jìn)程中加入 console,僅僅打印在終端上,并不能持久化日志。
更新過程中產(chǎn)生的日志都存儲(chǔ)在 SquirrelSetup.log 中,但是僅僅只是 Update.exe 產(chǎn)出的日志??墒呛芏嗖襟E需要輸出更多的日志。自動(dòng)化工具中的部分更新日志采用 log4js 方案,將不同的日志類型輸出在不同文件中。
2. 解決方案
xxx 項(xiàng)目的日志配置,見 log4js.js:
const log4js = require('log4js')
const programName = 'xxx項(xiàng)目'
log4js.configure({
appenders: {
console: { // 記錄器1:輸出到控制臺(tái)
type: 'console'
},
log_file: { // 記錄器2:輸出到文件
type: 'file',
filename: `./logs/${programName}.log`,
maxLogSize: 20971520,
backups: 3,
encoding: 'utf-8'
},
data_file: { // :記錄器3:輸出到日期文件
type: 'dateFile',
filename: `./logs/${programName}`,
alwaysIncludePattern: true,
daysToKeep: 7,
pattern: 'yyyy-MM-dd-hh.log',
encoding: 'utf-8'
},
error_file: { // :記錄器4:輸出到error log
type: 'dateFile',
filename: `./logs/${programName}_error`,
alwaysIncludePattern: true,
daysToKeep: 7,
pattern: 'yyyy-MM-dd-hh.log',
encoding: 'utf-8'
}
},
categories: {
default: {
appenders: ['data_file', 'console', 'log_file'],
level: 'info'
}, // 默認(rèn)log類型,輸出到控制臺(tái) log文件 log日期文件 且登記大于info即可
production: {
appenders: ['data_file'],
level: 'warn'
}, // 生產(chǎn)環(huán)境 log類型 只輸出到按日期命名的文件,且只輸出警告以上的log
console: {
appenders: ['console'],
level: 'debug'
}, // 開發(fā)環(huán)境 輸出到控制臺(tái)
debug: {
appenders: ['console', 'log_file'],
level: 'debug'
}, // 調(diào)試環(huán)境 輸出到log文件和控制臺(tái)
error_log: {
appenders: ['error_file'],
level: 'error'
} // error 等級(jí)log 單獨(dú)輸出到error文件中 任何環(huán)境的errorlog 將都以日期文件單獨(dú)記錄
}
})
module.exports = log4js
總結(jié)
解決的方式可能會(huì)很多,但是需要采用一種適合自己的方式鉆研到底,堅(jiān)持不懈,就一定能得到收獲。
文中介紹了當(dāng)前存在的問題、自動(dòng)更新的方案、打包的兩種方式和開發(fā)中存在的問題。其中,xxx 項(xiàng)目采用的是 squirrel.windows 的更新機(jī)制和 nsis 的自定義安裝策略。
通過 electron-builder 將兩者配置后,產(chǎn)出不同的安裝程序 setup.exe 和更新程序 nupkg。然后將 nsis 的 setup.exe 和 squirrel.windows 中的 nupkg 上傳到 electron-release-server 中。利用 electron-release-server 定時(shí)檢查策略,對(duì)比本地版本和線上版本,自動(dòng)下載依賴和程序,進(jìn)行更新并且替換,做到用戶無感知,操作不繁瑣。
開發(fā)中遇到問題其實(shí)不止這些,由于篇幅問題,所以我總結(jié)了一部分常見的問題。
最后,希望大家一定要點(diǎn)贊三連。
可以閱讀我的其他文章,見blog地址
一個(gè)學(xué)習(xí)編程技術(shù)的公眾號(hào)。常常推送高質(zhì)量的優(yōu)秀博文、開源項(xiàng)目、實(shí)用工具、面試技巧、編程學(xué)習(xí)資源等等。目標(biāo)是做到個(gè)人技術(shù)與公眾號(hào)一起成長(zhǎng)。歡迎大家關(guān)注,一起進(jìn)步,走向全棧大佬的修煉之路
