文章

Electron客户端自动更新简介

Windows 上用 electron-builder 打 NSIS 包,electron-updater 走 HTTPS 静态源更新。差分只少下数据块,形态仍是全量覆盖安装,并说明 latest.yml 与 Nginx。

Electron客户端自动更新简介

Electron客户端自动更新简介

桌面端用 electron-builder 打 Windows NSIS 安装包,用 electron-updater 走 generic HTTPS 静态源做自动更新。本文说明能力边界(blockmap 差分下载仍是全量安装)、latest.yml 发版目录、客户端检查 / 下载 / 安装流程、Nginx 配置,以及本地 SQLite 与升级的协同。流量上可以少下一些块;产品形态仍是「下载完整安装包再覆盖安装」。

参考与延伸阅读:


目录


1. 能力边界

要点结论
resources/app-update.yml打包后的自动更新配置:provider、查询 URL、本机缓存目录名
是否用于更新是,供 electron-updater 读取
文件级热补丁不支持;不能只换 app.asar 或某个 dll 就完成升级
独立「小补丁包」合并进安装目录默认不支持,需自研补丁合并器
Windows 差分有完整安装包与 .blockmap 时,可只拉变化数据块,本地合成后再跑 NSIS
服务端形态HTTPS 静态目录(或对象存储 + CDN)托管 latest.yml、安装包、.blockmap;不必为检查更新单独写业务 API
是否有新版本只认 {url}/latest.yml 的 version,与本地 app.getVersion() 比较

用户感知仍是「下载更新 → 退出安装」,不是进程内热替换。

若未来要真正的小补丁,需要另做架构,例如壳与大资源分离、按组件版本从 CDN 拉取,或自研差分格式与回滚。正式环境建议先落地 generic 静态源 + blockmap。


2. blockmap 差分下载

假设已装 v2.0.0,服务器发布 v2.0.1:客户端用新旧 blockmap 对比,只拉变化块,本地合成完整 setup.exe,再跑 NSIS 覆盖安装。

场景差分收益
只改 Electron 壳或少量前端资源,大体积附属资源未变中等偏大(相对整包)
附属运行时被整体重打包,二进制布局漂移偏小到中等
升级跨度大,或模型、引擎、vendor 等大资源变更接近全量
服务器未上传 .blockmap只能全量下载安装包
过早删除旧版安装包或 .blockmap差分可能失败,回退全量或失败

blockmap 按内容块对比,不认「逻辑只改了一行」。PyInstaller、AOT、整体重打 sidecar 一旦重出,即使业务只改几个模块,产物布局也常整段变化。规划带宽与用户体验时,按「用户可能接近全量下载」准备;差分是优化项,不是承诺。

务实做法:

  1. 仍上传 .blockmap(零成本,大资源未变时能省一截)。
  2. 发版带宽按接近全量准备。
  3. 真要小补丁,优先壳与大资源分离,而不是指望 blockmap 单独解决问题。

3. 本地库结构迁移

客户端本地库不在安装包里随 exe「打补丁脚本」下发;数据在用户目录,覆盖安装默认保留。

做法是否推荐说明
安装包外再附独立 .sql,靠运维或用户手跑不推荐桌面端无法保证时机,漏跑则崩溃
固化在代码里,启动时自动迁移推荐与安装包版本绑定,升级打开即迁移

建议约定:

  1. 加法变更(加表 / 加列 / 加索引):写在启动路径里,幂等执行(CREATE TABLE IF NOT EXISTS、探测缺列再 ALTER TABLE … ADD COLUMN)。
  2. 显式版本号(中期):schema_version 或 PRAGMA user_version,迁移步骤编号;多库可各管各库。
  3. 破坏性变更(删列 / 改类型 / 拆表):在代码里做「建新表 → 拷数据 → 改名」事务;慎用,需可回滚或至少可跳过坏库并提示。
  4. 不要把迁移 SQL 只放在发版说明里让用户手跑;也不要依赖「差分包夹带 sql」——更新形态是整包安装,迁移应跟新版本二进制走。
  5. 与自动更新的关系:装完新版本首次启动 → 新代码跑迁移 → 旧数据保留。迁移失败应打日志并给出可恢复路径(备份 db 文件),而不是卡在半安装状态。

4. 更新配置与构建产物

打包安装后,应用根下常见:

1
<安装目录>/resources/app-update.yml

内容由 electron-builder.yml 的 publish 写入,形如:

1
2
3
provider: generic
url: https://updates.example.com/app-updates/
updaterCacheDirName: my-app-updater
字段作用
provider: generic通用 HTTP(S) 静态源(不是 GitHub Releases)
url更新元数据与安装包所在目录的根 URL
updaterCacheDirName本机更新缓存目录名(Windows 多在 %LOCALAPPDATA%\<name>-updater)

开发态可用 dev-app-update.yml,配合 forceDevUpdateConfig 在未打包时指向同一套更新源做联调。

构建产物文件名由 artifactName 等配置决定,常见约定:

1
2
3
my-app-${version}-setup.exe
my-app-${version}-setup.exe.blockmap
latest.yml

版本号与 package.json 的 semver 对齐;若带 build metadata,以实际 dist/ 文件名为准。


5. 服务端静态发版

需要不需要
可 HTTPS 访问的静态站点(Nginx / OSS+CDN / 对象存储静态托管)为「检查更新」单独写 REST API
每次发版放齐 latest.yml、安装包、.blockmap用数据库存版本号(客户端只认 latest.yml)
正确的 MIME、Range(差分下载)、缓存策略把未签名内测包裸放公网

业务 API(登录、会员、强制最低版本)是另一条线:解决「太旧禁止用」;自动更新解决「有新包可装」。更新源通常公开可读。

目录约定:

1
2
3
4
5
6
7
8
https://updates.example.com/app-updates/
├── latest.yml
├── my-app-2.0.0-setup.exe
├── my-app-2.0.0-setup.exe.blockmap
├── my-app-2.0.1-setup.exe
├── my-app-2.0.1-setup.exe.blockmap
├── my-app-2.0.2-setup.exe
└── my-app-2.0.2-setup.exe.blockmap

要点:

  1. latest.yml 每次发版覆盖写,始终指向当前最新。
  2. 历史 setup 与 blockmap 暂时保留(建议至少最近 2~3 个正式版),方便差分与补装。
  3. latest.yml 内 path / files[].url 必须与目录里真实文件名一致。

发版清单:

1
2
3
4
5
1. 改 electron-builder.yml publish.url → 正式 HTTPS 地址
2. 打 Windows 安装包
3. 上传 dist/ 中的 latest.yml、setup.exe、.blockmap
4. 用浏览器或 curl 验证三者均为 200,且 latest.yml 的 version 正确
5. 用低版本客户端走检查 → 下载 → 安装

发 2.0.2 后,latest.yml 示意(字段以 electron-builder 实际生成为准):

1
2
3
4
5
6
7
8
version: 2.0.2
files:
  - url: my-app-2.0.2-setup.exe
    sha512: <base64>
    size: 185000000
path: my-app-2.0.2-setup.exe
sha512: <base64>
releaseDate: '2026-07-20T02:00:00.000Z'

size 为示意字节数。客户端比较本地 app.getVersion() 与 latest.yml 的 version,有更新则按 path 下载。


6. 客户端检查下载与安装

electron-updater 常见接法:

  • autoDownload = false;收到 update-available 且用户未关闭自动更新时,由业务代码调用 downloadUpdate。
  • autoInstallOnAppQuit = false:下完不自动装,等用户确认后 quitAndInstall。
  • allowPrerelease = false。
  • 设置项关闭自动更新时可跳过启动检查,且不自动下载;仍可提供手动「检查更新」。
  • 主进程与渲染进程通过 IPC 协作,常见通道:update:check / update:download / update:status / update:install。

6.1 启动自动检查

打包态(已安装包,非纯开发态):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
应用启动
    │
    ├─ setupAutoUpdater()
    │     ├─ 读 resources/app-update.yml(或开发态 forceDevUpdateConfig)
    │     └─ 绑定 checking / available / progress / downloaded / error
    │
    ├─ 自动更新已关闭?
    │     └─ 是 → 跳过启动检查(仍可手动检查)
    │
    └─ 否 → checkForUpdates()
              │
              ▼
         GET {url}/latest.yml
              │
              ├─ 网络或证书失败 → updater error
              ├─ version ≤ 当前 → update-not-available
              └─ version > 当前 → update-available
                        │
                        ├─ 通知 UI
                        └─ 自动更新已开启?
                              ├─ 否 → 等用户点下载
                              └─ 是 → downloadUpdate()

6.2 下载

1
2
3
4
5
6
7
8
9
10
11
12
13
downloadUpdate()
    │
    ├─ 请求 latest.yml 中的 setup.exe(及 blockmap)
    │     ├─ 有旧版缓存且新旧 blockmap 可用
    │     │     └─ 差分:只拉变化块 → 本地合成完整 setup.exe
    │     └─ 否则全量下载 setup.exe
    │
    ├─ download-progress → UI / 任务栏进度
    │
    └─ update-downloaded
          ├─ 包落在本机 updater 缓存目录
          ├─ 通知 UI「已下载,可安装」
          └─ 等待用户确认(不自动退出安装)

6.3 安装

1
2
3
4
5
6
7
8
9
10
用户确认安装(UI → update:install)
    │
    ▼
quitAndInstall(false, true)
    │
    ├─ 退出当前进程
    ├─ 拉起已下载的 NSIS setup.exe(覆盖安装)
    │     └─ 默认保留用户数据(数据在用户目录,不在安装目录)
    │
    └─ 安装结束 → 再打开 → 版本变为新 version

6.4 版本演进

1
2
3
4
5
6
7
8
时刻 A  用户安装 2.0.0;latest.yml → 2.0.0
时刻 B  运维上传 2.0.1 的 exe 与 blockmap,覆盖 latest.yml
        保留 2.0.0 文件利于差分
        2.0.0 客户端检查后下载并安装 → 2.0.1
时刻 C  再发 2.0.2,覆盖 latest.yml;建议仍保留 2.0.1
        仍停在 2.0.0 的用户一次检查即可直达 2.0.2,不必先装 2.0.1
        2.0.0 → 2.0.2 跨度大则差分接近全量
        2.0.1 → 2.0.2 差分通常更划算

6.5 与强制最低版本的关系

1
2
3
4
5
6
7
自动更新线                          业务版本线(登录 / 业务 API)
        │                              │
        ▼                              ▼
  latest.yml 有更新包              服务端判定版本过低
  → 引导下载安装                   → 禁止登录 / 提示升级
        │                              │
        └──────── 最低版本不得高于当前 latest ─┘

两条线职责不同。产品侧需约定最低版本不高于当前 latest.yml 所指向的版本,否则用户会被拦却装不到新包。


7. Nginx 静态源配置

假设:

  • 域名:updates.example.com
  • 物理目录:/var/www/app-updates/
  • 客户端 publish.url:https://updates.example.com/app-updates/
  • 已上传 2.0.0 / 2.0.1 / 2.0.2 的 exe 与 blockmap,latest.yml 指向 2.0.2

避免 alias 与正则组合踩坑,用 root + 前缀:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
    listen 443 ssl http2;
    server_name updates.example.com;

    root /var/www;

    location /app-updates/ {
        client_max_body_size 2g;

        location = /app-updates/latest.yml {
            add_header Cache-Control "no-cache, must-revalidate" always;
            default_type text/yaml;
        }

        location ~* \.(exe|blockmap)$ {
            add_header Cache-Control "public, max-age=31536000, immutable" always;
            add_header Accept-Ranges bytes always;
            default_type application/octet-stream;
        }
    }
}

磁盘布局:

1
2
3
4
5
6
7
8
/var/www/app-updates/
├── latest.yml
├── my-app-2.0.0-setup.exe
├── my-app-2.0.0-setup.exe.blockmap
├── my-app-2.0.1-setup.exe
├── my-app-2.0.1-setup.exe.blockmap
├── my-app-2.0.2-setup.exe
└── my-app-2.0.2-setup.exe.blockmap
步骤发布 2.0.1再发 2.0.2
1上传 2.0.1 的 exe 与 .blockmap上传 2.0.2 的 exe 与 .blockmap
2覆盖 latest.yml(version=2.0.1)再覆盖(version=2.0.2)
3curl 确认 latest.yml确认已是 2.0.2
4保留 2.0.0 文件至少保留 2.0.1;2.0.0 可按策略延后删除
5用 2.0.0 客户端测升级用 2.0.0 与 2.0.1 各测一次到 2.0.2

校验:

1
2
3
4
curl -fsSL https://updates.example.com/app-updates/latest.yml
curl -I https://updates.example.com/app-updates/my-app-2.0.2-setup.exe
curl -I -H "Range: bytes=0-1023" \
  https://updates.example.com/app-updates/my-app-2.0.2-setup.exe

后两条应分别看到 200、以及 206 Partial Content(或等价的 Range 支持)。

electron-builder.yml:

1
2
3
publish:
  provider: generic
  url: https://updates.example.com/app-updates/

开发态测同一源时同步 dev-app-update.yml。改完 URL 必须重新打安装包:已装旧包里的 app-update.yml 仍是旧地址。对象存储 + CDN 的等价要求:关闭 latest.yml 长缓存、开启 HTTP Range。


8. 正式环境启用清单

  1. 准备 HTTPS 静态目录(Nginx 见上一节,或 OSS+CDN 等价配置)。
  2. 修改 publish.url 与开发态更新配置。
  3. 打安装包,上传 latest.yml、exe、blockmap。
  4. 确认新安装包内 resources/app-update.yml 已是正式地址。
  5. 低版本客户端验证:检查 → 下载 → 安装。
  6. 与强制最低版本、发版节奏、是否隐藏「检查更新」入口一并定稿。

9. 路径与运维注意

位置说明
安装目录 resources/app-update.yml打包后运行时更新配置
electron-builder.yml → publish构建时写入更新源
dev-app-update.yml开发态更新源
主进程 updater 模块检查 / 下载 / 安装逻辑
本机 updater 缓存大致 %LOCALAPPDATA%\<updaterCacheDirName>
用户数据通常在用户目录,覆盖安装默认保留

运维注意:

  1. 务必同时上传 blockmap,否则 Windows 只能全量下安装包。
  2. 不要过早删旧版 exe / blockmap;建议至少留最近 2~3 个正式版。
  3. HTTPS 证书须有效;自签易导致检查或下载失败。
  4. latest.yml 不要长缓存;安装包文件名带版本,可长缓存。
  5. 开启 HTTP Range(206),否则差分能力打折。
  6. 更新与「服务端强制最低版本」是两条线:最低版本不得高于当前 latest。
  7. 安装包很大时优先保证带宽;差分只减轻大资源未变时的下载量。
  8. 改 publish.url 后必须重新打包装发;旧包不会自动改更新源地址。

10. 小结

要点结论
产品形态全量 NSIS 覆盖安装,不是热更新
差分Windows blockmap 可少下数据块,规划仍按接近全量
服务端HTTPS 静态目录即可,核心是 latest.yml
客户端比较 app.getVersion() 与 latest.yml 的 version
本地库启动时代码迁移,不要靠手跑 SQL
配置变更改更新源 URL 后须重打安装包
本文由作者按照 CC BY 4.0 进行授权