技术文档中心
首页
React
Vue
TypeScript
Kotlin
React Native
Electron
Android
首页
React
Vue
TypeScript
Kotlin
React Native
Electron
Android
  • 基础入门

    • Electron 学习指南
    • 窗口管理
    • 进程通信
    • 对话框
  • 进阶内容

    • 菜单和托盘
    • 打包发布
    • Electron 自动更新
  • 框架集成

    • React + Electron
    • Vue + Electron

Electron 自动更新

electron-updater 简介

electron-updater 是 Electron 应用实现自动更新的最佳方案,支持 Windows、macOS 和 Linux。

特点

  • 自动下载更新
  • 后台静默更新
  • 增量更新支持
  • 多种发布平台支持(GitHub、S3、通用服务器)
  • 代码签名验证

基础配置

安装依赖

yarn add electron-updater

package.json 配置

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "electron/main.js",
  "build": {
    "appId": "com.example.myapp",
    "productName": "我的应用",
    "publish": [
      {
        "provider": "github",
        "owner": "your-username",
        "repo": "your-repo"
      }
    ]
  }
}

主进程实现

基础更新逻辑

// electron/main.js
const { app, BrowserWindow } = require('electron')
const { autoUpdater } = require('electron-updater')

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true
    }
  })

  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
  
  // 应用启动后检查更新
  autoUpdater.checkForUpdatesAndNotify()
})

// 更新事件监听
autoUpdater.on('checking-for-update', () => {
  console.log('检查更新中...')
})

autoUpdater.on('update-available', (info) => {
  console.log('发现新版本:', info.version)
})

autoUpdater.on('update-not-available', () => {
  console.log('当前已是最新版本')
})

autoUpdater.on('download-progress', (progress) => {
  console.log(`下载进度: ${progress.percent.toFixed(2)}%`)
})

autoUpdater.on('update-downloaded', (info) => {
  console.log('更新下载完成')
  // 5秒后自动安装
  setTimeout(() => {
    autoUpdater.quitAndInstall()
  }, 5000)
})

autoUpdater.on('error', (err) => {
  console.error('更新错误:', err)
})

完整更新管理器

// electron/updater.js
const { autoUpdater } = require('electron-updater')
const { dialog } = require('electron')

class UpdateManager {
  constructor(mainWindow) {
    this.mainWindow = mainWindow
    this.setupAutoUpdater()
  }

  setupAutoUpdater() {
    // 配置更新服务器
    autoUpdater.setFeedURL({
      provider: 'github',
      owner: 'your-username',
      repo: 'your-repo'
    })

    // 禁用自动下载
    autoUpdater.autoDownload = false
    
    // 禁用自动安装
    autoUpdater.autoInstallOnAppQuit = false

    this.bindEvents()
  }

  bindEvents() {
    autoUpdater.on('checking-for-update', () => {
      this.sendStatusToWindow('checking-for-update')
    })

    autoUpdater.on('update-available', (info) => {
      this.sendStatusToWindow('update-available', info)
      this.showUpdateDialog(info)
    })

    autoUpdater.on('update-not-available', () => {
      this.sendStatusToWindow('update-not-available')
    })

    autoUpdater.on('download-progress', (progress) => {
      this.sendStatusToWindow('download-progress', progress)
    })

    autoUpdater.on('update-downloaded', (info) => {
      this.sendStatusToWindow('update-downloaded', info)
      this.showInstallDialog()
    })

    autoUpdater.on('error', (err) => {
      this.sendStatusToWindow('error', err.message)
      dialog.showErrorBox('更新错误', err.message)
    })
  }

  sendStatusToWindow(status, data = null) {
    if (this.mainWindow) {
      this.mainWindow.webContents.send('update-status', { status, data })
    }
  }

  async showUpdateDialog(info) {
    const result = await dialog.showMessageBox(this.mainWindow, {
      type: 'info',
      title: '发现新版本',
      message: `发现新版本 ${info.version}`,
      detail: info.releaseNotes || '是否立即下载更新?',
      buttons: ['稍后', '立即更新'],
      defaultId: 1,
      cancelId: 0
    })

    if (result.response === 1) {
      autoUpdater.downloadUpdate()
    }
  }

  async showInstallDialog() {
    const result = await dialog.showMessageBox(this.mainWindow, {
      type: 'info',
      title: '更新已下载',
      message: '更新已下载完成',
      detail: '是否立即重启应用安装更新?',
      buttons: ['稍后', '立即重启'],
      defaultId: 1,
      cancelId: 0
    })

    if (result.response === 1) {
      autoUpdater.quitAndInstall(false, true)
    }
  }

  checkForUpdates() {
    autoUpdater.checkForUpdates()
  }

  downloadUpdate() {
    autoUpdater.downloadUpdate()
  }

  quitAndInstall() {
    autoUpdater.quitAndInstall(false, true)
  }
}

module.exports = UpdateManager

在主进程中使用

// electron/main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const UpdateManager = require('./updater')

let mainWindow
let updateManager

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true
    }
  })

  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
  
  // 初始化更新管理器
  updateManager = new UpdateManager(mainWindow)
  
  // 延迟检查更新(避免启动时卡顿)
  setTimeout(() => {
    updateManager.checkForUpdates()
  }, 3000)
})

// IPC 通信
ipcMain.handle('check-for-updates', () => {
  updateManager.checkForUpdates()
})

ipcMain.handle('download-update', () => {
  updateManager.downloadUpdate()
})

ipcMain.handle('quit-and-install', () => {
  updateManager.quitAndInstall()
})

渲染进程集成

Preload 脚本

// electron/preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('updaterAPI', {
  checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
  downloadUpdate: () => ipcRenderer.invoke('download-update'),
  quitAndInstall: () => ipcRenderer.invoke('quit-and-install'),
  onUpdateStatus: (callback) => {
    ipcRenderer.on('update-status', (event, data) => callback(data))
  }
})

React 更新组件

// src/components/UpdateNotification.jsx
import { useState, useEffect } from 'react'
import './UpdateNotification.css'

function UpdateNotification() {
  const [updateInfo, setUpdateInfo] = useState(null)
  const [status, setStatus] = useState('')
  const [progress, setProgress] = useState(0)

  useEffect(() => {
    if (!window.updaterAPI) return

    window.updaterAPI.onUpdateStatus((data) => {
      setStatus(data.status)

      switch (data.status) {
        case 'update-available':
          setUpdateInfo(data.data)
          break
        case 'download-progress':
          setProgress(data.data.percent)
          break
        case 'update-downloaded':
          setUpdateInfo(data.data)
          break
      }
    })
  }, [])

  const handleCheckUpdate = () => {
    window.updaterAPI.checkForUpdates()
  }

  const handleDownload = () => {
    window.updaterAPI.downloadUpdate()
  }

  const handleInstall = () => {
    window.updaterAPI.quitAndInstall()
  }

  if (status === 'checking-for-update') {
    return (
      <div className="update-notification">
        <p>正在检查更新...</p>
      </div>
    )
  }

  if (status === 'update-available' && updateInfo) {
    return (
      <div className="update-notification">
        <h3>发现新版本 {updateInfo.version}</h3>
        <p>{updateInfo.releaseNotes}</p>
        <button onClick={handleDownload}>立即下载</button>
      </div>
    )
  }

  if (status === 'download-progress') {
    return (
      <div className="update-notification">
        <p>正在下载更新...</p>
        <div className="progress-bar">
          <div 
            className="progress-fill" 
            style={{ width: `${progress}%` }}
          />
        </div>
        <p>{progress.toFixed(2)}%</p>
      </div>
    )
  }

  if (status === 'update-downloaded') {
    return (
      <div className="update-notification">
        <h3>更新已下载完成</h3>
        <p>重启应用以安装更新</p>
        <button onClick={handleInstall}>立即重启</button>
      </div>
    )
  }

  return (
    <div className="update-notification">
      <button onClick={handleCheckUpdate}>检查更新</button>
    </div>
  )
}

export default UpdateNotification

Vue 更新组件

<!-- src/components/UpdateNotification.vue -->
<template>
  <div class="update-notification">
    <div v-if="status === 'checking-for-update'">
      <p>正在检查更新...</p>
    </div>

    <div v-else-if="status === 'update-available' && updateInfo">
      <h3>发现新版本 {{ updateInfo.version }}</h3>
      <p>{{ updateInfo.releaseNotes }}</p>
      <button @click="handleDownload">立即下载</button>
    </div>

    <div v-else-if="status === 'download-progress'">
      <p>正在下载更新...</p>
      <div class="progress-bar">
        <div 
          class="progress-fill" 
          :style="{ width: `${progress}%` }"
        />
      </div>
      <p>{{ progress.toFixed(2) }}%</p>
    </div>

    <div v-else-if="status === 'update-downloaded'">
      <h3>更新已下载完成</h3>
      <p>重启应用以安装更新</p>
      <button @click="handleInstall">立即重启</button>
    </div>

    <div v-else>
      <button @click="handleCheckUpdate">检查更新</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const status = ref('')
const updateInfo = ref(null)
const progress = ref(0)

onMounted(() => {
  if (!window.updaterAPI) return

  window.updaterAPI.onUpdateStatus((data) => {
    status.value = data.status

    switch (data.status) {
      case 'update-available':
        updateInfo.value = data.data
        break
      case 'download-progress':
        progress.value = data.data.percent
        break
      case 'update-downloaded':
        updateInfo.value = data.data
        break
    }
  })
})

const handleCheckUpdate = () => {
  window.updaterAPI.checkForUpdates()
}

const handleDownload = () => {
  window.updaterAPI.downloadUpdate()
}

const handleInstall = () => {
  window.updaterAPI.quitAndInstall()
}
</script>

<style scoped>
.update-notification {
  padding: 20px;
  background: #f5f5f5;
  border-radius: 8px;
}

.progress-bar {
  width: 100%;
  height: 20px;
  background: #e0e0e0;
  border-radius: 10px;
  overflow: hidden;
  margin: 10px 0;
}

.progress-fill {
  height: 100%;
  background: #4caf50;
  transition: width 0.3s;
}

button {
  padding: 8px 16px;
  background: #2196f3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background: #1976d2;
}
</style>

发布配置

GitHub Releases

{
  "build": {
    "publish": [
      {
        "provider": "github",
        "owner": "your-username",
        "repo": "your-repo",
        "releaseType": "release"
      }
    ]
  }
}

私有服务器

{
  "build": {
    "publish": [
      {
        "provider": "generic",
        "url": "https://your-server.com/updates"
      }
    ]
  }
}

Amazon S3

{
  "build": {
    "publish": [
      {
        "provider": "s3",
        "bucket": "your-bucket",
        "region": "us-east-1"
      }
    ]
  }
}

发布流程

1. 更新版本号

{
  "version": "1.0.1"
}

2. 构建应用

yarn electron:build

3. 发布到 GitHub

# 使用 GitHub Token
export GH_TOKEN="your-github-token"
yarn electron:build --publish always

4. 创建 Release

在 GitHub Releases 页面创建新版本,上传构建产物:

  • app-1.0.1.exe (Windows)
  • app-1.0.1.dmg (macOS)
  • app-1.0.1.AppImage (Linux)
  • latest.yml / latest-mac.yml (更新配置文件)

增量更新

Windows NSIS 配置

{
  "build": {
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": ["x64"]
        }
      ]
    },
    "nsis": {
      "differentialPackage": true
    }
  }
}

macOS 配置

{
  "build": {
    "mac": {
      "target": ["dmg", "zip"]
    }
  }
}

代码签名

Windows 签名

{
  "build": {
    "win": {
      "certificateFile": "cert.pfx",
      "certificatePassword": "password",
      "signingHashAlgorithms": ["sha256"],
      "sign": "./sign.js"
    }
  }
}

macOS 签名

{
  "build": {
    "mac": {
      "identity": "Developer ID Application: Your Name (TEAM_ID)",
      "hardenedRuntime": true,
      "gatekeeperAssess": false,
      "entitlements": "build/entitlements.mac.plist",
      "entitlementsInherit": "build/entitlements.mac.plist"
    }
  }
}

更新策略

强制更新

autoUpdater.on('update-available', async (info) => {
  const result = await dialog.showMessageBox({
    type: 'info',
    title: '必须更新',
    message: '发现新版本,必须更新后才能继续使用',
    buttons: ['立即更新'],
    defaultId: 0
  })

  autoUpdater.downloadUpdate()
})

autoUpdater.on('update-downloaded', () => {
  autoUpdater.quitAndInstall(false, true)
})

静默更新

autoUpdater.autoDownload = true
autoUpdater.autoInstallOnAppQuit = true

app.whenReady().then(() => {
  autoUpdater.checkForUpdatesAndNotify()
})

定时检查

// 每小时检查一次
setInterval(() => {
  autoUpdater.checkForUpdates()
}, 60 * 60 * 1000)

版本比较

const semver = require('semver')

autoUpdater.on('update-available', (info) => {
  const currentVersion = app.getVersion()
  const newVersion = info.version

  // 主版本更新(强制)
  if (semver.major(newVersion) > semver.major(currentVersion)) {
    // 强制更新
    autoUpdater.downloadUpdate()
  }
  // 次版本更新(推荐)
  else if (semver.minor(newVersion) > semver.minor(currentVersion)) {
    // 提示用户更新
    showUpdateDialog(info)
  }
  // 补丁更新(可选)
  else {
    // 静默下载
    autoUpdater.downloadUpdate()
  }
})

测试更新

本地测试

// electron/main.js
if (process.env.NODE_ENV === 'development') {
  autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml')
}
# dev-app-update.yml
provider: generic
url: http://localhost:8080/updates

启动本地服务器

# 在 dist 目录启动服务器
cd dist
python -m http.server 8080

错误处理

autoUpdater.on('error', (error) => {
  console.error('更新错误:', error)

  // 网络错误
  if (error.message.includes('net::')) {
    dialog.showErrorBox('网络错误', '无法连接到更新服务器')
  }
  // 签名验证失败
  else if (error.message.includes('signature')) {
    dialog.showErrorBox('安全错误', '更新包签名验证失败')
  }
  // 其他错误
  else {
    dialog.showErrorBox('更新错误', error.message)
  }
})

日志记录

const log = require('electron-log')

autoUpdater.logger = log
autoUpdater.logger.transports.file.level = 'info'

// 日志位置
// macOS: ~/Library/Logs/your-app/main.log
// Windows: %USERPROFILE%\AppData\Roaming\your-app\logs\main.log
// Linux: ~/.config/your-app/logs/main.log

完整示例

查看完整的自动更新示例:

  • electron-updater-example
  • electron-builder 文档
最近更新: 2026/2/24 16:53
Contributors: hailong
Prev
打包发布