Electron
第五章:更新最佳实践
第五章:更新最佳实践
目录
灰度发布完整实现
灰度策略
灰度发布(Gradual Rollout / Canary Release)是控制新版本影响范围的核心手段。
灰度发布策略分类:
1. 百分比灰度
├── 10% 用户先收到更新
├── 确认无异常后扩大到 50%
└── 最终 100% 全量
2. 白名单灰度
├── 内部员工先用
├── 种子用户群体
└── 然后全量
3. 渠道灰度
├── canary 渠道(每日构建,内部测试)
├── beta 渠道(预发布,外部测试)
└── stable 渠道(正式版,全量用户)
4. 条件灰度
├── 特定操作系统(如先推 macOS)
├── 特定地区
└── 特定用户属性
服务端实现
// update-server.js — 灰度发布服务
class GradualRollout {
constructor() {
this.config = {
currentStable: '1.2.5',
rollout: {
version: '1.3.0',
startedAt: '2024-01-20T00:00:00Z',
rules: [
// 规则按优先级排列
{
type: 'whitelist',
users: ['user-001', 'user-002', 'dev-team'],
enabled: true,
},
{
type: 'percentage',
value: 30, // 30% 的用户
enabled: true,
},
{
type: 'platform',
platforms: ['darwin'], // 先推 macOS
enabled: false,
}
],
paused: false, // 紧急暂停开关
metrics: {
crashRate: 0.02, // 当前崩溃率
maxCrashRate: 0.05, // 超过此值自动暂停
}
}
}
}
// 判断用户是否应该收到更新
shouldUpdate(userId, platform, currentVersion) {
const { rollout } = this.config
// 紧急暂停
if (rollout.paused) return false
// 已经是最新版或更新版
if (this.compareVersions(currentVersion, rollout.version) >= 0) return false
// 检查崩溃率
if (rollout.metrics.crashRate > rollout.metrics.maxCrashRate) {
console.warn('崩溃率超标,自动暂停灰度')
rollout.paused = true
this.alertOps('灰度自动暂停:崩溃率超标')
return false
}
// 按规则匹配
for (const rule of rollout.rules) {
if (!rule.enabled) continue
switch (rule.type) {
case 'whitelist':
if (rule.users.includes(userId)) return true
break
case 'percentage':
if (this.getUserBucket(userId) < rule.value) return true
break
case 'platform':
if (rule.platforms.includes(platform)) return true
break
}
}
return false
}
// 用户分桶(确保同一用户每次得到相同结果)
getUserBucket(userId) {
const hash = crypto.createHash('md5').update(userId).digest('hex')
return parseInt(hash.substring(0, 8), 16) % 100
}
// 版本比较
compareVersions(v1, v2) {
const a = v1.split('.').map(Number)
const b = v2.split('.').map(Number)
for (let i = 0; i < 3; i++) {
if (a[i] > b[i]) return 1
if (a[i] < b[i]) return -1
}
return 0
}
alertOps(message) {
// 发送告警到运维渠道
console.error('[ALERT]', message)
}
}
// API 端点
app.get('/api/check-update', (req, res) => {
const { userId, platform, version } = req.query
const rollout = new GradualRollout()
if (rollout.shouldUpdate(userId, platform, version)) {
res.json({
hasUpdate: true,
version: rollout.config.rollout.version,
url: `https://cdn.yourapp.com/releases/v${rollout.config.rollout.version}`,
})
} else {
res.json({ hasUpdate: false })
}
})
灰度推进流程
灰度推进流程(典型 5 天):
Day 0: 内部测试
┌────────────────────────────────────┐
│ 白名单: 开发团队 + QA (20人) │
│ 观察: 功能正确性、崩溃率 │
└────────────────────────────────────┘
│ OK?
▼
Day 1: 种子用户
┌────────────────────────────────────┐
│ 百分比: 5% │
│ 观察: 崩溃率 < 0.5%、用户反馈 │
└────────────────────────────────────┘
│ OK?
▼
Day 2: 扩大范围
┌────────────────────────────────────┐
│ 百分比: 30% │
│ 观察: 性能指标、错误日志 │
└────────────────────────────────────┘
│ OK?
▼
Day 3-4: 大范围推送
┌────────────────────────────────────┐
│ 百分比: 70% │
└────────────────────────────────────┘
│ OK?
▼
Day 5: 全量
┌────────────────────────────────────┐
│ 百分比: 100% │
│ 更新 stable 版本号 │
└────────────────────────────────────┘
任何阶段发现问题:
┌────────────────────────────────────┐
│ 暂停灰度 (paused: true) │
│ 分析问题 │
│ 修复后重新开始灰度 │
│ 或回滚到上一个 stable 版本 │
└────────────────────────────────────┘
差分更新原理
bsdiff 算法
bsdiff 二进制差分算法:
原理:
1. 对旧文件进行后缀排序
2. 找到新文件和旧文件之间的最长公共子序列
3. 生成 diff 文件(只包含差异部分)
流程:
旧文件 (old.bin, 100MB)
新文件 (new.bin, 102MB)
│
▼
bsdiff old.bin new.bin patch.bin
│
▼
补丁文件 (patch.bin, 5MB) ← 只有差异部分
│
▼
bspatch old.bin new.bin patch.bin ← 客户端合成
│
▼
恢复出完整的 new.bin
优势:
- 对可执行文件特别有效(通常压缩到 5-20%)
- 成熟的算法,被 Chrome、Firefox 使用
劣势:
- 需要旧文件参与合成
- 合成过程 CPU 密集
- 客户端必须有完整的旧版本
blockmap(electron-updater 使用的方案)
blockmap 差分更新原理:
将文件分割为固定大小的块(默认 64KB):
旧版本:
┌──────┬──────┬──────┬──────┬──────┐
│ B1 │ B2 │ B3 │ B4 │ B5 │
│ h:a1 │ h:b2 │ h:c3 │ h:d4 │ h:e5 │
└──────┴──────┴──────┴──────┴──────┘
新版本:
┌──────┬──────┬──────┬──────┬──────┬──────┐
│ B1 │ B2' │ B3 │ B4 │ B5 │ B6 │
│ h:a1 │ h:f6 │ h:c3 │ h:d4 │ h:e5 │ h:g7 │
└──────┴──────┴──────┴──────┴──────┴──────┘
对比:
B1: a1==a1 → 跳过 ✓
B2: b2!=f6 → 下载 ↓ (64KB)
B3: c3==c3 → 跳过 ✓
B4: d4==d4 → 跳过 ✓
B5: e5==e5 → 跳过 ✓
B6: (新增) → 下载 ↓ (64KB)
总下载: 128KB 而非 384KB (6 blocks × 64KB)
节省: 67%
blockmap 文件格式:
{
"version": 2,
"files": [{
"name": "MyApp-Setup-1.1.0.exe",
"offset": 0,
"checksums": [
"a1a1a1...", // B1 的 hash
"f6f6f6...", // B2' 的 hash
"c3c3c3...", // B3 的 hash
...
],
"sizes": [65536, 65536, 65536, ...]
}]
}
强制更新策略
最低版本号机制
// 强制更新的完整实现
class ForceUpdateManager {
constructor(mainWindow) {
this.mainWindow = mainWindow
this.checkInterval = null
}
async check() {
try {
// 从服务器获取最低要求版本
const response = await fetch('https://api.yourapp.com/min-version')
const { minVersion, message, severity } = await response.json()
// severity: 'critical' | 'recommended' | 'optional'
const currentVersion = app.getVersion()
if (this.isOlderThan(currentVersion, minVersion)) {
switch (severity) {
case 'critical':
// 安全漏洞等:强制更新,无法跳过
this.showCriticalUpdate(message)
break
case 'recommended':
// 重要更新:强烈推荐,但可延迟
this.showRecommendedUpdate(message)
break
case 'optional':
// 可选更新:温和提醒
this.showOptionalUpdate(message)
break
}
}
} catch (err) {
console.error('检查强制更新失败:', err)
}
}
showCriticalUpdate(message) {
// 替换窗口内容为更新界面
this.mainWindow.loadFile('force-update.html')
// 禁止关闭(除非通过任务管理器)
this.mainWindow.on('close', (event) => {
event.preventDefault()
dialog.showMessageBoxSync(this.mainWindow, {
type: 'warning',
title: '需要更新',
message: '当前版本存在安全问题,必须更新后才能继续使用。',
buttons: ['知道了'],
})
})
// 自动开始下载
autoUpdater.checkForUpdatesAndNotify()
autoUpdater.on('update-downloaded', () => {
// 倒计时 10 秒自动安装
setTimeout(() => {
autoUpdater.quitAndInstall(false, true)
}, 10 * 1000)
})
}
showRecommendedUpdate(message) {
const result = dialog.showMessageBoxSync(this.mainWindow, {
type: 'info',
title: '推荐更新',
message: message || '有重要更新可用',
buttons: ['立即更新', '稍后提醒'],
defaultId: 0,
})
if (result === 0) {
autoUpdater.checkForUpdatesAndNotify()
} else {
// 24 小时后再提醒
setTimeout(() => this.check(), 24 * 60 * 60 * 1000)
}
}
showOptionalUpdate(message) {
// 只显示非侵入式通知
new Notification({
title: '有新版本可用',
body: message || '点击查看更新详情',
}).show()
}
isOlderThan(v1, v2) {
const a = v1.split('.').map(Number)
const b = v2.split('.').map(Number)
for (let i = 0; i < 3; i++) {
if (a[i] < b[i]) return true
if (a[i] > b[i]) return false
}
return false
}
}
错误回滚机制
多层回滚策略
回滚机制全景:
Layer 1: 自动回滚(崩溃检测)
┌──────────────────────────────────────────────────────┐
│ 新版本启动后 30 秒内崩溃 2 次 → 自动恢复旧版本 │
│ 实现: sentinel 文件 + 崩溃计数 │
└──────────────────────────────────────────────────────┘
Layer 2: 用户触发回滚
┌──────────────────────────────────────────────────────┐
│ 用户在设置中手动选择 "回退到上一个版本" │
│ 实现: 保留旧版本文件 + 一键切换 │
└──────────────────────────────────────────────────────┘
Layer 3: 服务端回滚
┌──────────────────────────────────────────────────────┐
│ 运维将 latest.yml 指向旧版本 │
│ 所有用户下次检查更新时 "更新" 到旧版本 │
│ 实现: 修改更新服务器配置 │
└──────────────────────────────────────────────────────┘
Layer 4: 紧急热修复
┌──────────────────────────────────────────────────────┐
│ 不回滚,而是快速发布修复版本 │
│ 通过强制更新推送到所有用户 │
│ 实现: 紧急发布流程 + 强制更新 │
└──────────────────────────────────────────────────────┘
自动回滚实现
// auto-rollback.js
const SENTINEL_PATH = path.join(app.getPath('userData'), '.update-sentinel')
const MAX_CRASHES = 2
const STABILITY_WINDOW = 30000 // 30秒
class AutoRollback {
constructor(bundleManager) {
this.bm = bundleManager
}
onAppStart() {
const sentinel = this.readSentinel()
if (!sentinel) return // 没有待验证的更新
if (sentinel.status === 'verifying') {
sentinel.launches = (sentinel.launches || 0) + 1
if (sentinel.launches > MAX_CRASHES) {
// 连续崩溃太多次,回滚
console.error('检测到更新后连续崩溃,执行回滚')
this.performRollback(sentinel)
return
}
// 更新启动计数
this.writeSentinel(sentinel)
// 启动稳定性计时器
setTimeout(() => {
// 如果 30 秒后还在运行,标记为稳定
sentinel.status = 'stable'
sentinel.launches = 0
this.writeSentinel(sentinel)
console.log('更新验证通过,版本稳定')
// 清理旧版本备份
this.bm.cleanup(2)
}, STABILITY_WINDOW)
}
}
onUpdateApplied(version) {
this.writeSentinel({
version,
status: 'verifying',
launches: 0,
appliedAt: Date.now(),
})
}
performRollback(sentinel) {
console.warn(`回滚: v${sentinel.version} → 上一个版本`)
const previousPath = this.bm.rollback()
// 清除 sentinel
fs.unlinkSync(SENTINEL_PATH)
// 上报回滚事件
this.reportRollback(sentinel)
// 重启应用
app.relaunch()
app.exit(0)
}
reportRollback(sentinel) {
// 上报到监控系统
fetch('https://api.yourapp.com/telemetry/rollback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
version: sentinel.version,
launches: sentinel.launches,
platform: process.platform,
arch: process.arch,
})
}).catch(() => {})
}
readSentinel() {
try {
return JSON.parse(fs.readFileSync(SENTINEL_PATH, 'utf-8'))
} catch {
return null
}
}
writeSentinel(data) {
fs.writeFileSync(SENTINEL_PATH, JSON.stringify(data))
}
}
用户体验设计
更新 UI 设计原则
好的更新体验:
┌─────────────────────────────────────────────────────┐
│ │
│ 1. 不打断用户工作流 │
│ ✅ 后台下载,完成后温和通知 │
│ ❌ 弹窗阻断,强制用户立即决定 │
│ │
│ 2. 透明告知 │
│ ✅ 清楚展示更新内容、大小、预计时间 │
│ ❌ 只说"有更新"不说更新了什么 │
│ │
│ 3. 给予控制权 │
│ ✅ "稍后提醒" / "下次启动时安装" │
│ ❌ 只有 "立即安装" 一个选项 │
│ │
│ 4. 进度可见 │
│ ✅ 下载进度、速度、剩余时间 │
│ ❌ 转圈圈,不知道要等多久 │
│ │
│ 5. 优雅降级 │
│ ✅ 网络断开时静默重试,不报错 │
│ ❌ 弹出技术性错误信息 │
│ │
└─────────────────────────────────────────────────────┘
更新通知类型
通知类型与场景:
┌────────────────────────────────────────────────────┐
│ 场景 1: 后台下载完成 │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 🔄 更新就绪 │ │
│ │ │ │
│ │ v1.3.0 已下载完成 │ │
│ │ 包含 3 项改进和 5 个 bug 修复 │ │
│ │ │ │
│ │ [下次启动时安装] [查看详情] │ │
│ └─────────────────────────────────────┘ │
├────────────────────────────────────────────────────┤
│ 场景 2: 正在下载 │
│ │
│ 状态栏小图标: │
│ ⬇ 下载更新 45% (2.3 MB/s) │
│ 不弹窗,不打断 │
├────────────────────────────────────────────────────┤
│ 场景 3: 强制更新 │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ ⚠️ 需要更新 │ │
│ │ │ │
│ │ 当前版本存在安全问题,必须更新后 │ │
│ │ 才能继续使用。 │ │
│ │ │ │
│ │ 正在下载... 67% │ │
│ │ ████████████████░░░░░░░░ │ │
│ └─────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
A/B 测试集成
// A/B 测试与更新结合
// 服务端: 不同用户组收到不同版本
{
"experiments": {
"new-editor": {
"control": {
"version": "1.2.5",
"percentage": 50
},
"treatment": {
"version": "1.3.0-experiment.1",
"percentage": 50
}
}
}
}
// 客户端: 上报实验数据
class ABTestUpdater {
constructor(userId) {
this.userId = userId
this.experimentGroup = this.getExperimentGroup()
}
getExperimentGroup() {
// 根据 userId hash 确定分组
const hash = crypto.createHash('md5').update(this.userId).digest('hex')
const bucket = parseInt(hash.substring(0, 8), 16) % 100
return bucket < 50 ? 'control' : 'treatment'
}
async checkUpdate() {
const response = await fetch(
`${serverUrl}/check-update?` +
`userId=${this.userId}&` +
`group=${this.experimentGroup}&` +
`version=${app.getVersion()}`
)
return response.json()
}
// 上报指标(更新后的行为数据)
reportMetric(event, data) {
fetch(`${serverUrl}/telemetry`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: this.userId,
group: this.experimentGroup,
version: app.getVersion(),
event,
data,
timestamp: Date.now(),
})
}).catch(() => {})
}
}
监控与报警
关键指标
更新相关的关键监控指标:
┌─────────────────────────────────────────────────────┐
│ │
│ 1. 更新覆盖率 │
│ - 各版本用户数量和比例 │
│ - 最新版本的覆盖率随时间变化 │
│ - 目标: 发布后 7 天覆盖 90% │
│ │
│ 2. 更新成功率 │
│ - 检查成功率 (网络/服务器问题) │
│ - 下载成功率 (网络中断/磁盘空间) │
│ - 安装成功率 (权限/文件锁定) │
│ - 目标: > 99% │
│ │
│ 3. 崩溃率 │
│ - 各版本的崩溃率 │
│ - 更新后 vs 更新前的崩溃率对比 │
│ - 目标: < 0.5% │
│ │
│ 4. 回滚率 │
│ - 自动回滚次数 │
│ - 用户手动回滚次数 │
│ - 目标: < 1% │
│ │
│ 5. 性能指标 │
│ - 更新包下载耗时 │
│ - 更新安装耗时 │
│ - 更新后首次启动耗时 │
│ │
└─────────────────────────────────────────────────────┘
遥测实现
// telemetry.js — 更新遥测
class UpdateTelemetry {
constructor(serverUrl) {
this.serverUrl = serverUrl
this.sessionId = crypto.randomUUID()
}
// 上报事件
async report(event, data = {}) {
try {
await fetch(`${this.serverUrl}/telemetry`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId: this.sessionId,
event,
data: {
...data,
version: app.getVersion(),
platform: process.platform,
arch: process.arch,
locale: app.getLocale(),
},
timestamp: new Date().toISOString(),
})
})
} catch {
// 遥测失败不应影响应用
}
}
// 预定义事件
updateCheckStarted() { this.report('update.check.started') }
updateCheckFailed(error) { this.report('update.check.failed', { error }) }
updateAvailable(version) { this.report('update.available', { version }) }
updateNotAvailable() { this.report('update.not_available') }
downloadStarted(version) { this.report('update.download.started', { version }) }
downloadProgress(percent) { this.report('update.download.progress', { percent }) }
downloadCompleted(version, duration) {
this.report('update.download.completed', { version, duration })
}
downloadFailed(error) { this.report('update.download.failed', { error }) }
installStarted(version) { this.report('update.install.started', { version }) }
installCompleted(version) { this.report('update.install.completed', { version }) }
installFailed(error) { this.report('update.install.failed', { error }) }
rollbackTriggered(fromVersion, toVersion) {
this.report('update.rollback', { fromVersion, toVersion })
}
}
报警规则
报警规则配置:
┌──────────────────────────────────────────────────────┐
│ 规则 1: 崩溃率突增 │
│ 条件: 最新版本崩溃率 > 2% 且持续 10 分钟 │
│ 动作: 暂停灰度 + 通知开发团队 │
│ 级别: P1 (紧急) │
├──────────────────────────────────────────────────────┤
│ 规则 2: 更新成功率下降 │
│ 条件: 更新成功率 < 95% 且持续 30 分钟 │
│ 动作: 检查更新服务器状态 │
│ 级别: P2 (高) │
├──────────────────────────────────────────────────────┤
│ 规则 3: 回滚率异常 │
│ 条件: 回滚率 > 5% │
│ 动作: 暂停灰度 + 通知开发团队 │
│ 级别: P1 (紧急) │
├──────────────────────────────────────────────────────┤
│ 规则 4: 版本碎片化 │
│ 条件: 发布 14 天后最新版覆盖率 < 50% │
│ 动作: 检查更新推送策略 │
│ 级别: P3 (中) │
└──────────────────────────────────────────────────────┘
实际案例分析
案例 1: VS Code 的更新策略
VS Code 更新方案:
1. 使用 electron-updater + GitHub Releases
2. 稳定版月更,Insiders 版日更
3. 背景静默下载
4. 下载完成后在状态栏提示 "重启以更新"
5. 用户可以随时点击重启,也可以继续工作
6. 下次启动时自动安装
亮点:
- 扩展系统独立于应用更新
- 设置同步减少重新配置成本
- 发行说明清晰详尽
案例 2: Slack 的更新策略
Slack 更新方案:
1. 静默自动更新
2. 用户几乎无感知
3. 下载完成后等待用户空闲
4. 在空闲时自动重启更新
5. 重启后恢复到之前的工作状态
亮点:
- 极低的用户打扰
- 状态保持和恢复
- 快速的更新节奏
案例 3: 国内应用的混合方案
典型国内 Electron 应用:
1. 壳层更新: electron-updater
- 低频 (每月)
- 静默下载 + 下次启动安装
2. 前端热更新: Web Bundle / asar 替换
- 高频 (每周甚至每天)
- 通过灰度逐步推送
- 不需要重启
3. 强制更新: 最低版本号控制
- 安全漏洞时触发
- 必须更新才能使用
4. CDN 加速: 阿里云 OSS / 腾讯 COS
- 国内访问速度快
- 成本可控
深入理解
更新策略的总体架构
完整的更新架构:
┌─────────────────────────────────────────────────────────┐
│ 更新服务 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────────┐ │
│ │ 全量更新 │ │ 热补丁 │ │ 灰度/A-B 测试控制 │ │
│ │ 服务 │ │ 服务 │ │ 服务 │ │
│ └────┬─────┘ └────┬─────┘ └──────────┬────────────┘ │
│ │ │ │ │
│ └──────────────┴────────────────────┘ │
│ │ │
│ CDN 分发层 │
└──────────────────────┼───────────────────────────────────┘
│
HTTPS 下载
│
┌──────────────────────┼───────────────────────────────────┐
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ UpdateManager │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ electron │ │ asar │ │ web bundle │ │ │
│ │ │ -updater │ │ patcher │ │ manager │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ 回滚 │ │ 灰度 │ │ 遥测上报 │ │ │
│ │ │ 管理器 │ │ 客户端 │ │ │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ Electron App │
└──────────────────────────────────────────────────────────┘
常见问题
Q1: 灰度发布如何确保用户分组一致?
使用用户 ID 的 hash 分桶,而不是随机数。这样同一用户每次检查更新时都属于同一个分组。
Q2: 差分更新失败怎么办?
回退到全量下载。差分更新是优化手段,不是必须路径。
Q3: 如何处理用户长期不更新?
设置最低版本号。低于最低版本的用户在启动时会被强制更新。
Q4: 更新过程中断电/崩溃怎么办?
- 下载中断:支持断点续传
- 安装中断:使用原子操作(rename),要么完全成功要么不影响旧版本
- 启动失败:sentinel 机制自动回滚
Q5: 多个更新方案如何协调?
优先级和互斥关系:
electron-updater (全量) — 最低频,最高优先级
asar 热补丁 — 中频,壳版本兼容时使用
web bundle 更新 — 最高频,壳版本兼容时使用
规则:
- 全量更新时,清除所有热补丁和 bundle 缓存
- asar 补丁后,需要检查 web bundle 兼容性
- web bundle 更新独立于其他两种
实践建议
1. 起步方案
刚开始做更新?按这个顺序:
Phase 1: 基础 (Week 1)
└── electron-updater + GitHub Releases
最简单,覆盖 80% 的需求
Phase 2: 优化 (Month 1)
└── 添加灰度发布(百分比灰度)
添加更新遥测
添加回滚机制
Phase 3: 热更新 (Month 2-3)
└── Web Bundle 独立更新
增量下载
CDN 加速
Phase 4: 生产化 (Month 3+)
└── A/B 测试
完整监控报警
自动灰度推进
2. 安全清单
□ 所有下载使用 HTTPS
□ 文件 SHA256 校验
□ 代码签名(macOS/Windows)
□ 不允许版本降级(防回滚攻击)
□ 更新服务器的访问控制
□ manifest 签名验证
□ 定期安全审计
3. 运维清单
发布前:
□ 构建所有平台的安装包
□ 签名校验
□ 测试升级路径(至少 2 个旧版本)
□ 准备 release notes
□ 灰度规则配置
发布中:
□ 上传到 CDN / GitHub Releases
□ 更新 manifest.json
□ 开始灰度(10%)
□ 监控崩溃率和错误日志
发布后:
□ 逐步扩大灰度
□ 持续监控 7 天
□ 确认覆盖率达标
□ 清理旧版本
本章小结
本章汇总了 Electron 应用更新的所有最佳实践:
- 灰度发布:百分比 + 白名单 + 渠道,逐步推进
- 差分更新:blockmap / bsdiff 减少下载量
- 强制更新:最低版本号保障安全
- 自动回滚:sentinel 机制检测崩溃并恢复
- 用户体验:后台下载、温和通知、给予控制权
- 监控报警:覆盖率、成功率、崩溃率、回滚率
- A/B 测试:数据驱动的版本决策
至此,Part 2(热更新)全部完成。掌握这些知识,你可以为 Electron 应用构建一套生产级的更新系统。