概述
重构 AUTO-MAS 更新系统为独立更新器,支持二进制 diff 增量更新和静默更新。方案参考 BGI (Better Genshin Impact) 和 Starward 的更新架构。
设计原则(CRITICAL)
- 使用 TypeScript严格模式,禁止
any, 所有类型显式声明
- 前端 Vue 3 Composition API +
<script setup lang="ts">,禁止 Options API
- Ant Design Vue 4.x 组件库,CSS 使用
<style scoped> + CSS 变量 var(--ant-color-*)
- 后端 Python 3.12+ FastAPI,所有函数加完整 type hints,模型用 Pydantic v2
- 新代码遵循 AGPL-3.0 版权声明,文件头统一
- 代码风格对齐现有项目,不做无意义的格式变更
- 拒绝过度工程,非必要不引入的新东西
动机与目标
现状痛点
- 全量下载浪费带宽:每次更新下载 ~500MB 完整 ZIP,即使只改了一个文件
- 无静默更新:UpdateModal 的"后台更新"按钮实际是
// TBD 占位代码
- 耦合过重:更新逻辑在 Python 后端 (
app/services/update.py),如果 Python 环境崩了就无法更新
- 无完整性校验:下载的 ZIP 没有 hash 验证,存在安全风险
- 无回滚机制:更新失败后应用可能处于损坏状态
- 未利用 electron-builder:package.json 已配置
publish.github 但完全没用它
- 进度推送依赖 Python WebSocket:下载进度通过 Python 后端 WebSocket 推送,架构笨重
目标
- 独立更新器进程:脱离 Python 后端,纯 Node.js/Electron 侧独立运行
- 二进制 Diff 增量:使用 HDiffPatch (hpatchz) 生成和应用
.patch 文件,典型更新从 500MB → 5-50MB
- 静默更新:后台自动下载 + 下次启动自动应用,用户全程无感知
- 完整性校验:全文件 SHA-256 hash + manifest 校验
- 回滚支持:更新前自动备份旧文件,失败可回滚
当前架构分析
现有代码位置
| 文件 |
角色 |
frontend/src/composables/useUpdateChecker.ts |
前端轮询检查 (4h),调 /api/update/check |
frontend/src/composables/useUpdateDownload.ts |
下载状态管理,WebSocket 进度接收 |
frontend/src/components/UpdateModal.vue |
更新通知弹窗 |
frontend/src/components/UpdateDownloadModal.vue |
下载进度弹窗 |
frontend/src/services/updateDownloadApi.ts |
取消/切换下载源 API |
frontend/src/composables/updateDownloadSpeed.ts |
低速检测 |
app/services/update.py |
核心:版本检查 + 下载 + 解压 + 安装 |
app/api/update.py |
FastAPI 路由 |
app/models/schema.py |
UpdateCheckIn / UpdateCheckOut |
frontend/package.json |
electron-builder 配置 |
当前数据流
[App.vue] → useUpdateChecker (每4h)
→ POST /api/update/check (Python FastAPI)
→ Mirror酱 API 获取版本信息
→ 有更新 → UpdateModal
→ 用户点"下载" → POST /api/update/download
→ Python 后端下载 ZIP → WebSocket 推送进度
→ UpdateDownloadModal
→ 下载完成 → 用户点"安装" → POST /api/update/install
→ 解压 ZIP → 启动 Inno Setup → KillSelf
关键缺陷
UpdateModal.vue 只有"下载更新"和"暂不更新"两个按钮,没有后台更新按钮
useUpdateDownload.ts 的 background() 只是隐藏模态框,不启动后台任务
install_update() 直接在当前进程解压 ZIP 然后 KillSelf,解压到一半被杀会导致文件不一致
- 多下载源都指向同一个命名格式的完整 ZIP,没有任何差异化
Agent Note: 实现时若发现以上描述与代码实际状态不一致,以实际代码为准并修复。
参考实现
Starward (C# .NET) 更新机制
- 二进制 Diff: HDiffPatch/Snap.HPatch,
HPatch.Patch() 应用 patch
- 文件清单:
ReleaseManifest 包含 Files: List<ReleaseFile> (Path, Size, Hash, CompressedSize, CompressedHash) + DeleteFiles: List<string>
- Diff 模型:
ReleaseInfoDiff (DiffVersion, DiffSize, ManifestUrl),per-file PatchSize, PatchHash, Offset, Length
- 更新窗口:
UpdateWindow 自动启动更新 (OnLoaded → StartUpdate()),无需用户点击
- 文件校验: 每个文件 SHA-256;
CheckFilesAsync 全量校验
- 原子替换:
*.tmp → hash verify → File.Move (NTFS 原子操作)
- 独立更新器:
Starward.Setup 项目,通过 Process.Start 启动主程序
- 自复制:
CopySetupFile 确保更新器自身持久化
- Zstandard 压缩:
SharpCompress.Compressors.ZStandard
- 参考仓库:https://github.com/Scighost/Starward
BGI / MicaSetup (C# .NET) 更新机制
重构方案
总体架构:独立更新器进程
+--------------------------------------------------+
| AUTO-MAS (主程序 - Electron) |
| |
| main.ts 启动时: |
| -> spawn updater.exe --check-silent |
| -> updater.exe 返回: {updateAvailable, version} |
| |
| 运行时: |
| -> 定期 spawn updater.exe --check (静默) |
| -> 或用户手动触发 (显示进度) |
| |
| 退出时: |
| -> 如果有待应用更新: updater.exe --apply |
| |
+------------------+-------------------------------+
| spawn / JSON stdout
+------------------v-------------------------------+
| updater.exe (独立 Node.js 进程, 无窗口) |
| |
| CLI 参数: |
| --check 检查更新 (输出 JSON) |
| --check-silent 静默检查 + 自动下载 |
| --download 下载更新 |
| --apply 应用已下载的更新 |
| --rollback 回滚到上一个版本 |
| --version 显示版本 |
| |
| 核心模块: |
| ReleaseFetcher -> GitHub Releases API |
| ManifestParser -> 解析 release manifest |
| DiffDownloader -> 下载 .patch 文件 |
| PatchApplier -> HDiffPatch 应用 patch |
| FileVerifier -> SHA-256 hash 校验 |
| BackupManager -> 更新前备份,失败回滚 |
+--------------------------------------------------+
发布流程(CI/CD 侧)
1. 构建新版本 -> 产生完整文件集合
2. 生成 ReleaseManifest.json
-> 遍历所有文件 -> {path, size, sha256, compressedSize, compressedSha256}
-> DeleteFiles: 比较上一版本,列出已删除文件
3. 对每个文件,与最近 N 个版本做二进制 diff
-> hdiffz old_file new_file output.patch
-> 生成 DiffManifest_{fromVersion}.json
4. 发布到 GitHub Release
-> ReleaseManifest.json
-> DiffManifest_v5.4.0-beta.1.json 等 (保留最近 5 个版本的 diff)
-> FullPackage.zip (完整包,兜底)
-> updater.exe (更新器自身)
5. 可选:同步到自建 CDN 和 CNB 镜像
数据模型设计(TypeScript)
// 版本信息
interface ReleaseInfo {
version: string
releaseDate: string // ISO 8601
releaseNotes: string // Markdown
isPrerelease: boolean
manifestUrl: string // ReleaseManifest.json URL
fullPackageUrl: string // 完整包 URL (兜底)
fullPackageSize: number
fullPackageHash: string // sha256
minUpdatableVersion: string // 最低可从此版本 diff 更新
}
// 文件清单
interface ReleaseManifest {
version: string
files: ReleaseFile[]
deleteFiles: string[] // 相比上一版本要删除的文件
}
interface ReleaseFile {
path: string // 相对于 app root
size: number
hash: string // SHA-256 hex
compressedSize?: number // Zstd 压缩后大小
compressedHash?: string
patch?: ReleaseFilePatch // 从旧版本 patch 的信息
}
interface ReleaseFilePatch {
fromVersion: string
oldPath: string
oldSize: number
oldHash: string
patchUrl: string
patchSize: number
patchHash: string
}
// 更新状态 (持久化到 updater-state.json)
interface UpdaterState {
currentVersion: string
pendingUpdate?: {
version: string
downloadedFiles: string[]
manifestUrl: string
ready: boolean
}
backupVersion?: string
lastCheck: string
failedAttempts: number
}
CLI 接口设计
# 静默检查(JSON 输出,无 UI)
updater.exe --check --json
# {"updateAvailable": true, "latestVersion": "v5.5.0", "downloadSize": 15728640, ...}
# 静默检查 + 自动下载
updater.exe --check-silent
# 后台下载 diff,状态写入 updater-state.json
# 应用已下载的更新
updater.exe --apply
# 备份 -> 应用 patches -> 校验 -> 清理 -> 启动主程序
# 回滚
updater.exe --rollback
# 还原 .rollback/ 中的文件
# 一步到位(手动触发)
updater.exe --update
静默更新策略
| 场景 |
行为 |
| 用户正在使用,有更新 |
后台静默下载 diff,下载完可选通知 |
| 用户关闭应用 |
检查 updater-state.json,如有 ready → 自动 apply |
| 用户下次启动 |
如果上次已 apply → 正常启动;如有 pending → 提示 |
| 强制更新 |
minUpdatableVersion > 当前版本 → 必须全量更新 |
| 连续失败 > 3 次 |
自动回退到全量下载 |
实现阶段
Phase 1: 独立更新器基础框架
Phase 2: Release API + Manifest
Phase 3: 二进制 Diff 核心
Phase 4: 文件校验与回滚
Phase 5: 静默更新
Phase 6: 迁移与清理
风险与注意事项
风险
- HDiffPatch 平台兼容性:hpatchz 在 Windows x64/ARM64 的可用性
- 大文件 diff 性能:
environment/python/ 下 Python 解释器 (~100MB) 的 diff 耗时
- 版本跳跃:跳过多个版本时 diff 链过长,需 fallback 到全量
- 文件占用:更新时主程序文件可能被 Windows 锁定
- updater 自身更新:updater.exe 自己被更新时的鸡生蛋问题
缓解措施
- 只保留最近 5 个版本的 diff,更老的版本直接全量
- Python 解释器等大依赖单独判断:hash 变化则全量下载该文件,不做 diff
- 参考 Starward 的
CheckProcessAsync 确保主进程已退出
- updater 自身更新:先复制新 updater 到临时路径,由新 updater 继续执行
兼容性注意事项
- 保留旧版 Inno Setup 安装路径注册表项的清理逻辑
- 多下载源的 manifest/diff 文件也应同步到各源
- DEBUG 模式下不启动更新检查(保持现有行为)
概述
重构 AUTO-MAS 更新系统为独立更新器,支持二进制 diff 增量更新和静默更新。方案参考 BGI (Better Genshin Impact) 和 Starward 的更新架构。
设计原则(CRITICAL)
any, 所有类型显式声明<script setup lang="ts">,禁止 Options API<style scoped>+ CSS 变量var(--ant-color-*)动机与目标
现状痛点
// TBD占位代码app/services/update.py),如果 Python 环境崩了就无法更新publish.github但完全没用它目标
.patch文件,典型更新从 500MB → 5-50MB当前架构分析
现有代码位置
frontend/src/composables/useUpdateChecker.ts/api/update/checkfrontend/src/composables/useUpdateDownload.tsfrontend/src/components/UpdateModal.vuefrontend/src/components/UpdateDownloadModal.vuefrontend/src/services/updateDownloadApi.tsfrontend/src/composables/updateDownloadSpeed.tsapp/services/update.pyapp/api/update.pyapp/models/schema.pyUpdateCheckIn/UpdateCheckOutfrontend/package.json当前数据流
关键缺陷
UpdateModal.vue只有"下载更新"和"暂不更新"两个按钮,没有后台更新按钮useUpdateDownload.ts的background()只是隐藏模态框,不启动后台任务install_update()直接在当前进程解压 ZIP 然后 KillSelf,解压到一半被杀会导致文件不一致参考实现
Starward (C# .NET) 更新机制
HPatch.Patch()应用 patchReleaseManifest包含Files: List<ReleaseFile>(Path, Size, Hash, CompressedSize, CompressedHash) +DeleteFiles: List<string>ReleaseInfoDiff(DiffVersion, DiffSize, ManifestUrl),per-filePatchSize,PatchHash,Offset,LengthUpdateWindow自动启动更新 (OnLoaded→StartUpdate()),无需用户点击CheckFilesAsync全量校验*.tmp→ hash verify →File.Move(NTFS 原子操作)Starward.Setup项目,通过Process.Start启动主程序CopySetupFile确保更新器自身持久化SharpCompress.Compressors.ZStandardBGI / MicaSetup (C# .NET) 更新机制
BetterGI.update.exe -I参数/qsilent,/aautomate) 但未完成重构方案
总体架构:独立更新器进程
发布流程(CI/CD 侧)
数据模型设计(TypeScript)
CLI 接口设计
静默更新策略
updater-state.json,如有 ready → 自动 applyminUpdatableVersion> 当前版本 → 必须全量更新实现阶段
Phase 1: 独立更新器基础框架
frontend/updater/目录结构 (独立 tsconfig, 独立构建)updater.exe--check,--apply,--rollback等)updater.log)Phase 2: Release API + Manifest
ReleaseFetcher: 调用 GitHub Releases API 获取最新版本api.github.com/repos/AUTO-MAS-Project/AUTO-MAS/releases/latest)ManifestParser: 下载并解析ReleaseManifest.jsonUpdaterState持久化读写Phase 3: 二进制 Diff 核心
child_process调用 hpatchz CLIkoffi调用 hpatchz.dllDiffDownloader: 根据当前文件 hash 匹配最佳 diffPatchApplier: 应用 .patch 文件,原子替换hpatchz -f old_file new_file output.patchPhase 4: 文件校验与回滚
FileVerifier: SHA-256 hash 校验每个文件BackupManager: 更新前自动备份.rollback/目录*.tmp→ verify →fs.renameDeleteFiles)Phase 5: 静默更新
main.ts集成:启动时 spawnupdater.exe --check-silentbefore-quit→ 如有待应用更新 → spawnupdater.exe --applyPhase 6: 迁移与清理
app/services/update.py和app/api/update.py中的下载/安装逻辑useUpdateChecker改为调用 updater风险与注意事项
风险
environment/python/下 Python 解释器 (~100MB) 的 diff 耗时缓解措施
CheckProcessAsync确保主进程已退出兼容性注意事项