0 文章相关项目源码
项目地址
1 存放需更新文件的 node 服务
1.2.1 创建入口文件里的package.json
文件(其余文件自行创建,无需命令行)
1 创建一个空文件夹 2 进入空文件夹,打开终端 3 在终端输入 npm init
,会提示让输入名字 4 名字内不能存在大写 5 根据提示输入完就能看到文件夹内多了个 package.json
文件,此时就可以下载配置包了
1.1 目录架构
| -- . gitignore
| -- app. js
| -- package . json
| -- packages
| -- win
1.2 入口文件
const httpServer = require ( 'http-server' ) ;
const path = require ( 'path' ) ;
const port = 8091 ;
httpServer
. createServer ( {
root : path. resolve ( __dirname, './' ) ,
} )
. listen ( port) ;
console. log ( ` 静态文件服务器运行在 http://localhost: ${ port} ` ) ;
1.3 存放待更新的安装包
在与 app.js 同级目录创建packages与packages/win文件夹,里面放置electron-build打包出来的以下文件: electron-build打包出来的以下文件来源:npm run build,如果没有这个文件(可能命令跟本示例不一样,只要是使用的 electron-build 配置打包出来的即可),麻烦移步这篇文章:https://blog.csdn.net/xzy__007/article/details/134708289?spm=1001.2014.3001.5502 -- 直接看 3.4.2 自定义安装的exe程序步骤
builder- debug. yml
builder- effective- config. yaml
exe- dist Setup 1.0 .1 . exe
exe- dist Setup 1.0 .1 . exe. blockmap
latest. yml
上述文件来源:源码内与 node-server 文件平级文件 exe 文件内执行以下代码
cd vue- userList
npm run build2
npm run build
1.4 启动微服务
node . \app. js
1.5 检查服务是否启动成功
打开浏览器,在网址栏输入 http://localhost:8091/packages/win/ 看到以下页面就代表启动完毕
2 写有关自动更新配置
2.1 自动更新相关逻辑
/ exe/ auto- update. js
const { app, dialog, ipcRenderer, ipcMain } = require ( 'electron' )
const path = require ( 'path' )
const { autoUpdater } = require ( 'electron-updater' )
async function sleep ( ms ) {
return new Promise ( ( resolve ) => {
const timer = setTimeout ( ( ) => {
resolve ( true ) ;
clearTimeout ( timer) ;
} , ms) ;
} ) ;
}
let updateValue = null
let downloadProgress = null
const autoUpdateApp = async ( mainWindow ) => {
await sleep ( 3000 ) ;
autoUpdater. disableWebInstaller = false ;
autoUpdater. autoDownload = false ;
autoUpdater. autoInstallOnAppQuit = false
autoUpdater. updateConfigPath = path. join ( __dirname, "./dev-update.yml" )
autoUpdater. checkForUpdates ( ) ;
autoUpdater. on ( "error" , ( error ) => {
console. log ( [ "检查更新失败" , error] ) ;
} ) ;
autoUpdater. on ( "update-available" , ( info ) => {
console. log ( "检查到有更新,开始下载新版本" ) ;
const { version } = info;
console. log ( ` 最新版本为: ${ version} ` ) ;
updateValue = info
autoUpdater. downloadUpdate ( ) ;
} ) ;
autoUpdater. on ( "update-not-available" , ( ) => {
updateValue = null
console. log ( "没有可用更新" ) ;
} ) ;
autoUpdater. on ( "download-progress" , async ( progress ) => {
console. log ( progress, 'downloadPercent' ) ;
const downloadPercent = Math. round ( progress. percent * 100 ) / 100 ;
downloadProgress = downloadPercent
} ) ;
autoUpdater. on ( "update-downloaded" , ( res ) => {
console. log ( "下载完毕, 提示安装更新" , res) ;
dialog. showMessageBox ( {
type : 'warning' ,
title : '更新提示' ,
message : '下载完成' ,
buttons : [ '更新' , '取消' ] ,
cancelId : 1
} ) . then ( res => {
if ( res. response == 0 ) {
autoUpdater. quitAndInstall ( )
}
} )
} ) ;
}
module. exports = {
autoUpdateApp,
} ;
electron- builder. json 添加以下代码
"publish" : {
"provider" : "generic" ,
"updaterCacheDirName" : "dev-updater" ,
"url" : "http://localhost:8091/packages/win/"
}
-- -- -- -- - 修改后的代码 -- -- -- -- -- -
{
"appId" : "exe-dome.example.myapp" ,
"productName" : "exe-dist" ,
"directories" : {
"output" : "exe-dist"
} ,
"win" : {
"target" : "nsis" ,
"icon" : "./icon/icon.jpg" ,
"artifactName" : "${productName} Setup ${version}.${ext}"
} ,
"nsis" : {
"oneClick" : false ,
"perMachine" : true ,
"allowToChangeInstallationDirectory" : true
} ,
"publish" : {
"provider" : "generic" ,
"updaterCacheDirName" : "dev-updater" ,
"url" : "http://localhost:8091/packages/win/"
}
}
2.1.1 启动electron,看效果
此时 先启动vue服务,再启动electron 服务
cd vue- userList
npm run server
npm run start
此时等待3秒后应该会看到这个界面 但实际看到的却是控制台的一串报错,这是因为electron开发环境是不支持自动更新的,只需按以下配置,模拟成生产环境即可 报错信息:Skip checkForUpdates because application is not packed and dev update config is not forced
2.1.2 将开发环境设置成已打包状态
按顺序找到以下文件,没有就 创建 / 写 出来,具体位置参考示例源码
provider : generic
updaterCacheDirName : dev- updater
url : "http://localhost:8091/packages/win/"
if ( ! app. isPackaged) {
Object. defineProperty ( app, 'isPackaged' , {
get : ( ) => true ,
} ) ;
}
autoUpdater. updateConfigPath = path. join ( __dirname, "./dev-update.yml" )
此时如果本地版本小于自动更新服务存放的版本就会触发自动更新 版本由 package.json中的 version 属性控制 此时就可以正常进行自动更新了
3 由页面主动控制是否进行下载更新
上面写的,虽然实现了自动更新,但不能主动去触发自动更新,并且不经过用户同意就直接进行了下载,下载完才通知是否更新 下面将实现,由用户主动选择是否更新,如果选了否就不进行下载,同时支持主动检查是否有新版本
3.1 主进程加入事件监听与事件回调ipcMain.handle
方法,暴漏electron的方法
此次写法与示例代码的 master分支,渲染进程被动调用主进程的暴漏方法不同,此次将渲染进程将主动向主进程发出请求以此获得回调,官方对此是有说明的 但由于本次直接暴漏了 ipcRenderer
方法,此做法官方也是不推荐的
master分支完整开发文档,被动接收主进程暴漏方法
auto- update. js
const { app, dialog, ipcRenderer, ipcMain } = require ( 'electron' )
const path = require ( 'path' )
const { autoUpdater } = require ( 'electron-updater' )
async function sleep ( ms ) {
return new Promise ( ( resolve ) => {
const timer = setTimeout ( ( ) => {
resolve ( true ) ;
clearTimeout ( timer) ;
} , ms) ;
} ) ;
}
let updateValue = null
let downloadProgress = null
const autoUpdateApp = async ( mainWindow ) => {
await sleep ( 3000 ) ;
autoUpdater. disableWebInstaller = false ;
autoUpdater. autoDownload = false ;
autoUpdater. autoInstallOnAppQuit = false
autoUpdater. checkForUpdates ( ) ;
autoUpdater. on ( "error" , ( error ) => {
console. log ( [ "检查更新失败" , error] ) ;
} ) ;
autoUpdater. on ( "update-available" , ( info ) => {
console. log ( "检查到有更新,开始下载新版本" ) ;
const { version } = info;
console. log ( ` 最新版本为: ${ version} ` ) ;
updateValue = info
} ) ;
autoUpdater. on ( "update-not-available" , ( ) => {
updateValue = null
console. log ( "没有可用更新" ) ;
} ) ;
autoUpdater. on ( "download-progress" , async ( progress ) => {
console. log ( progress, 'downloadPercent' ) ;
const downloadPercent = Math. round ( progress. percent * 100 ) / 100 ;
downloadProgress = downloadPercent
} ) ;
autoUpdater. on ( "update-downloaded" , ( res ) => {
console. log ( "下载完毕, 提示安装更新" , res) ;
dialog. showMessageBox ( {
type : 'warning' ,
title : '更新提示' ,
message : '下载完成' ,
buttons : [ '更新' , '取消' ] ,
cancelId : 1
} ) . then ( res => {
if ( res. response == 0 ) {
autoUpdater. quitAndInstall ( )
}
} )
} ) ;
}
ipcMain. handle ( 'check-for-updates' , async ( event, arg ) => {
return updateValue;
} ) ;
ipcMain. handle ( 'download-progress' , async ( event, arg ) => {
return downloadProgress;
} ) ;
const checkForUpdates = ( ) => {
autoUpdater. checkForUpdates ( ) ;
}
const downloadUpdate = ( ) => {
autoUpdater. downloadUpdate ( ) ;
}
module. exports = {
autoUpdateApp,
checkForUpdates,
downloadUpdate,
} ;
eventConfig. js 正常来说是往main. js 里添加,不过本示例项目把所有的事件方法都抽离到了eventConfig. js里,根据自己结构自行调整
const { downloadUpdate, checkForUpdates } = require ( './auto-update' )
ipcMain. on ( 'checkForUpdates' , ( ev, val ) => {
checkForUpdates ( )
} )
ipcMain. on ( 'notifyUpdate' , ( ev, val ) => {
downloadUpdate ( )
} )
preload. js
const { contextBridge, ipcRenderer } = require ( "electron" )
contextBridge. exposeInMainWorld ( 'ipcRenderer' , {
ipcRenderer : ipcRenderer
} ) ;
3.2 渲染进程使用ipcRenderer.invoke 主进程使用 ipcMain.handle 监听事件以获取回调
/ exe/ vue- userList/ src/ header. vue
< template>
< div class = "header" >
< el- tooltip class = "item" effect= "dark" content= "最小化到系统托盘" placement= "bottom" >
< span class = "no-drag el-icon-circle-close" @click= "addMinimize(false)" > < / span>
< / el- tooltip>
< el- tooltip class = "item" effect= "dark" content= "最小化" placement= "bottom" >
< span class = "no-drag el-icon-minus" @click= "addMinimize(true)" > < / span>
< / el- tooltip>
< el- tooltip class = "item" effect= "dark" : content= "windowStatus ? '窗口化' : '全屏'" placement= "bottom" >
< span class = "no-drag" : class = "windowStatus ? 'el-icon-copy-document' : 'el-icon-full-screen'" @click= "addWindowStatus" > < / span>
< / el- tooltip>
< el- tooltip class = "item" effect= "dark" content= "关闭程序" placement= "bottom" >
< span class = "no-drag el-icon-close" @click= "addCloseWin" > < / span>
< / el- tooltip>
< / div>
< / template>
< script>
const versions = window. versions;
const { ipcRenderer } = window. ipcRenderer
export default {
data ( ) {
return {
versions,
windowStatus : false ,
updateAvailable : null ,
progressSetInterval : null
} ;
} ,
mounted ( ) {
this . initIPCListener ( )
} ,
methods : {
addMinimize ( stata ) {
versions. minimize ( stata) ;
} ,
addWindowStatus ( ) {
this . windowStatus = ! this . windowStatus;
versions. windowStatus ( ) ;
} ,
addCloseWin ( ) {
versions. closeWin ( ) ;
} ,
initIPCListener ( ) {
setTimeout ( ( ) => {
ipcRenderer. invoke ( 'check-for-updates' ) . then ( ( status ) => {
if ( status !== null ) {
this . updateConfirmation ( status)
} else {
}
} ) . catch ( ( error ) => {
console. error ( error) ;
} ) ;
} , 3000 ) ;
} ,
updateProgress ( ) {
} ,
updateConfirmation ( updateAvailable ) {
this . $confirm ( ` 最新版本为: ${ updateAvailable. version} ,是否进行升级? ` , '更新' , {
confirmButtonText : '确定' ,
cancelButtonText : '取消' ,
closeOnPressEscape : false ,
closeOnClickModal : false ,
type : 'warning'
} ) . then ( ( ) => {
this . $message ( {
type : 'success' ,
message : '开始下载!'
} ) ;
ipcRenderer. send ( 'notifyUpdate' )
this . updateProgress ( )
} ) . catch ( ( ) => { } ) ;
} ,
} ,
} ;
< / script>
< style lang= "less" >
. header {
background- color: pink;
text- align: right;
- webkit- app- region: drag;
span {
padding : 5px 0 ;
font- size: 20px;
margin- right: 20px;
color : red;
}
}
. no- drag {
- webkit- app- region: no- drag;
}
< / style>
3.3 检查更新功能
使用 ipcRenderer.send() 方法向主进程发起通知,主进程使用ipcMain.on()接收到通知
/ exe/ vue- userList/ src/ header. vue 将代码进行修改
< template>
< div class = "header" >
< el- tooltip class = "item" effect= "dark" content= "检查更新" placement= "bottom" >
< span class = "no-drag el-icon-refresh" @click= "appUpdate()" > < / span>
< / el- tooltip>
< el- tooltip class = "item" effect= "dark" content= "最小化到系统托盘" placement= "bottom" >
< span class = "no-drag el-icon-circle-close" @click= "addMinimize(false)" > < / span>
< / el- tooltip>
< el- tooltip class = "item" effect= "dark" content= "最小化" placement= "bottom" >
< span class = "no-drag el-icon-minus" @click= "addMinimize(true)" > < / span>
< / el- tooltip>
< el- tooltip class = "item" effect= "dark" : content= "windowStatus ? '窗口化' : '全屏'" placement= "bottom" >
< span class = "no-drag" : class = "windowStatus ? 'el-icon-copy-document' : 'el-icon-full-screen'" @click= "addWindowStatus" > < / span>
< / el- tooltip>
< el- tooltip class = "item" effect= "dark" content= "关闭程序" placement= "bottom" >
< span class = "no-drag el-icon-close" @click= "addCloseWin" > < / span>
< / el- tooltip>
< / div>
< / template>
< script>
const versions = window. versions;
const { ipcRenderer } = window. ipcRenderer
export default {
data ( ) {
return {
versions,
windowStatus : false ,
updateAvailable : null ,
progressSetInterval : null
} ;
} ,
mounted ( ) {
this . initIPCListener ( )
} ,
methods : {
addMinimize ( stata ) {
versions. minimize ( stata) ;
} ,
addWindowStatus ( ) {
this . windowStatus = ! this . windowStatus;
versions. windowStatus ( ) ;
} ,
addCloseWin ( ) {
versions. closeWin ( ) ;
} ,
initIPCListener ( ) {
setTimeout ( ( ) => {
ipcRenderer. invoke ( 'check-for-updates' ) . then ( ( status ) => {
if ( status !== null ) {
this . updateConfirmation ( status)
} else {
}
} ) . catch ( ( error ) => {
console. error ( error) ;
} ) ;
} , 3000 ) ;
} ,
updateProgress ( ) {
} ,
appUpdate ( ) {
ipcRenderer. send ( 'checkForUpdates' )
this . initIPCListener ( )
} ,
updateConfirmation ( updateAvailable ) {
this . $confirm ( ` 最新版本为: ${ updateAvailable. version} ,是否进行升级? ` , '更新' , {
confirmButtonText : '确定' ,
cancelButtonText : '取消' ,
closeOnPressEscape : false ,
closeOnClickModal : false ,
type : 'warning'
} ) . then ( ( ) => {
this . $message ( {
type : 'success' ,
message : '开始下载!'
} ) ;
ipcRenderer. send ( 'notifyUpdate' )
this . updateProgress ( )
} ) . catch ( ( ) => { } ) ;
} ,
} ,
} ;
< / script>
< style lang= "less" >
. header {
background- color: pink;
text- align: right;
- webkit- app- region: drag;
span {
padding : 5px 0 ;
font- size: 20px;
margin- right: 20px;
color : red;
}
}
. no- drag {
- webkit- app- region: no- drag;
}
< / style>