Electron桌面应用自动更新怎么实现

文章导读
Electron 桌面应用自动更新,核心不是“写一段下载代码”,而是把版本来源、包体元数据、下载与校验、安装时机、失败回退连成一条可维护的链路。通常建议先明确你的分发渠道(自有服务器、对象存储、GitHub Releases 或企业内网),再决定是用社区常见的 autoUpdater 类方案,还是自己写主进程下载逻辑配合安装器。不同方案对签名、平台差异和权限的要求差别很大,先确认环境再动手,比先抄
📋 目录
  1. 一 先确认当前场景和目标
  2. 二 按这个顺序处理
  3. 三 改完后怎么验证
  4. 四 容易忽略的边界
A A

Electron 桌面应用自动更新,核心不是“写一段下载代码”,而是把版本来源、包体元数据、下载与校验、安装时机、失败回退连成一条可维护的链路。通常建议先明确你的分发渠道(自有服务器、对象存储、GitHub Releases 或企业内网),再决定是用社区常见的 autoUpdater 类方案,还是自己写主进程下载逻辑配合安装器。不同方案对签名、平台差异和权限的要求差别很大,先确认环境再动手,比先抄代码更重要。

Electron 自动更新的可行路径是:主进程定时拉取版本清单,比对本地版本后下载差量或全量包,校验完整性并在退出或重启时安装。适用场景是你能控制发布端且安装包具备签名条件;需要确认三件事:更新源可访问、包体能被校验、失败时能回退到旧版本。不具备签名或无法保证更新源稳定时,建议先做“提示有新版本+跳转下载页”的轻量方案,而不是强推静默更新。

先确认当前场景和目标

自动更新是否值得做,取决于更新频率和用户规模。如果应用只在少数内网机器上运行、每次发版都能人工安装,那么做完整自动更新的维护成本可能高于收益;如果用户分散、发版频繁、旧版本造成的兼容问题多,自动更新才有明确价值。开始前需要确认以下信息:

  • 分发渠道:更新包放在哪里,是否支持 HTTPS 和稳定的文件命名,是否允许携带版本清单文件。
  • 目标平台:Windows、macOS、Linux 的安装方式和权限模型不同,macOS 通常要求应用签名,Windows 安装器类型也会影响静默安装能否成功。
  • 发布流程:谁来生成安装包、谁负责上传、版本号从哪里读取,版本号规则必须唯一且单调递增。
  • 回退能力:旧版本安装包是否保留,用户更新失败后能否重新安装旧版。
  • 灰度与强制更新:是否需要按用户分组控制更新范围,是否存在必须强制升级的最低版本。

判断条件可以简化成一句:如果你能稳定产出一个“版本号+安装包+校验值”的清单,并且用户机器有权限执行安装,那么自动更新可以落地;否则先把发布流程理顺。

按这个顺序处理

第一步,确定版本清单的格式和托管位置。清单是更新链路的事实来源,建议用一个 JSON 文件描述最新版本、各平台安装包地址和校验值。示例片段如下,字段名和值需要替换成你自己的实际内容:

{
  "version": "1.4.0",
  "notes": "修复若干问题",
  "platforms": {
    "win32": {
      "url": "https://your-domain.example/app/MyApp-1.4.0-setup.exe",
      "sha256": "替换为真实校验值",
      "minVersion": "1.2.0"
    },
    "darwin": {
      "url": "https://your-domain.example/app/MyApp-1.4.0.dmg",
      "sha256": "替换为真实校验值",
      "minVersion": "1.2.0"
    }
  }
}

这份清单建议放在能被应用读取的静态地址上,发布时由构建脚本生成,避免手工填写出错。minVersion 表示低于该版本必须先升级,可用于处理不兼容变更。

第二步,在主进程里实现检查逻辑。不要把更新检查放在渲染进程,渲染进程没有文件系统权限,也容易受页面生命周期影响。一个通用的主进程骨架如下,重点是版本比对、下载、校验三步分开,任一步失败都不进入安装:

const { app, dialog } = require('electron');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const https = require('https');

const MANIFEST_URL = 'https://your-domain.example/app/latest.json';

function compareVersion(a, b) {
  const pa = a.split('.').map(Number);
  const pb = b.split('.').map(Number);
  for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
    const x = pa[i] || 0;
    const y = pb[i] || 0;
    if (x !== y) return x > y ? 1 : -1;
  }
  return 0;
}

async function checkForUpdate(platformKey) {
  const manifest = await fetchJson(MANIFEST_URL);
  if (compareVersion(manifest.version, app.getVersion()) <= 0) return null;
  const entry = manifest.platforms[platformKey];
  if (!entry) return null;
  return entry;
}

function sha256File(filePath) {
  return new Promise((resolve, reject) => {
    const hash = crypto.createHash('sha256');
    const stream = fs.createReadStream(filePath);
    stream.on('data', chunk => hash.update(chunk));
    stream.on('error', reject);
    stream.on('end', () => resolve(hash.digest('hex')));
  });
}

async function downloadAndVerify(url, expectedSha, destPath) {
  await downloadTo(url, destPath);
  const actual = await sha256File(destPath);
  if (actual !== expectedSha) {
    fs.unlinkSync(destPath);
    throw new Error('checksum mismatch');
  }
}

app.whenReady().then(async () => {
  const update = await checkForUpdate(process.platform);
  if (!update) return;
  const dest = path.join(app.getPath('temp'), path.basename(update.url));
  await downloadAndVerify(update.url, update.sha256, dest);
  // 此处再根据平台调用对应安装方式,或提示用户重启安装
});

代码里的 fetchJson 和 downloadTo 需要你自己按网络库补齐。关键是校验必须在安装之前完成,校验不通过就删除文件并记录日志,不要继续弹出“更新失败”后还保留半包。

第三步,处理安装时机。Windows 的安装包通常在下载完成后启动安装器并退出当前进程;macOS 的 dmg 不适合直接静默覆盖,需要结合签名和安装辅助工具确认可行性。如果暂时无法做静默安装,稳妥做法是下载并校验成功后提示用户“重启并安装”,由用户主动确认,避免应用被强制关闭导致数据丢失。

Electron桌面应用自动更新怎么实现

第四步,给检查加上节流和开关。应用启动时检查一次、之后按固定间隔检查即可,不要在每次窗口聚焦时都请求清单。建议在配置中保留一个关闭更新的开关,方便内网部署或排查问题时停用。

改完后怎么验证

验证要覆盖“无更新、有新版本、下载失败、校验失败、安装成功”五种路径。可以先在本机用两个版本号构造测试清单:把清单里的 version 写成等于当前版本,确认应用不会提示更新;再把 version 提高一档,观察是否进入下载流程。验证动作和预期信号包括:

  • 清单请求:观察主进程日志是否发起请求、HTTP 状态是否成功、解析出的版本号是否与预期一致。
  • 版本比对:把本地版本设为高于清单版本,确认应用不下载任何文件,说明比对方向没有写反。
  • 校验环节:故意把清单里的 sha256 改成错误值,确认下载的文件被删除且没有进入安装步骤,日志中应出现校验不通过记录。
  • 安装时机:确认安装触发时应用当前状态是否符合预期,是否有未保存的数据提示,是否存在重复启动安装器的情况。
  • 回退:用旧版本安装包重装一次,确认应用仍可启动,说明旧版本没有被发布流程删除。

如果条件允许,在一台干净的测试机上完整走一遍从旧版到新版的流程,比只在开发机上验证更有意义,因为权限、路径和签名问题往往只在干净环境里暴露。

容易忽略的边界

第一,签名与系统策略。macOS 对未签名或签名不一致的应用限制较多,Windows 的 SmartScreen 也可能拦截未知来源安装包。签名配置不完整时,自动更新可能在下载成功后被系统阻止,这类情况需要先确认签名链路再决定是否推进自动更新。

第二,更新源与网络环境。企业内网机器可能无法访问公网更新源,建议为更新地址提供可配置项,或者在部署时替换成内网镜像地址。更新源不稳定时,应用需要能容忍请求超时并降级为“下次再检查”,而不是反复弹窗。

第三,版本号与回滚。自动更新默认只升不降,如果新版本出现严重问题,需要人工发布更高版本号来覆盖,不能指望用户自动回退。因此发布前保留上一版安装包,并在清单中保留 minVersion 语义,是成本较低的保护措施。

第四,不适用条件。如果应用运行在无安装权限的受限账户下,或者安装包依赖的运行时环境无法自动替换,自动更新可能无法完成,此时建议回退为“检查更新+引导用户手动下载安装”的方案,而不是强行静默更新。