feat: 实现 Client 在线更新与安全回退 (#93)

This commit is contained in:
chengma
2026-08-10 12:32:39 +08:00
parent c24fac964e
commit 6d6829e374
14 changed files with 1609 additions and 18 deletions
+14 -1
View File
@@ -134,6 +134,7 @@ Client 应执行:
2. **Android 设备:**ADB 地址、拼多多包名、设备测试连接。
3. **自动化:**轮询周期、任务超时、最大重试次数和演练模式。
4. **安全与诊断:**价格允许偏差、最大购买数量、日志目录、截图/XML 保留周期。
5. **软件更新:**当前版本、HTTPS 更新清单地址、检查按钮和稳定状态文字。
密码和访问令牌不得以明文写入普通 SQLite 设置或日志。
@@ -211,7 +212,19 @@ CMAutoBuy/ 整个文件夹拷到任何机器都能用
每次构建在 `client/release/` 生成更新包、完整便携包和
`autobuy——manifest.json`。清单至少记录版本、文件名、字节大小和 SHA256;SHA256
用于发现下载损坏,不等同于发布者身份认证。联网更新和清单签名必须另建工单。
用于发现下载损坏,不等同于发布者身份认证。
**在线更新:**操作人员在设置页填写完整 HTTPS 清单地址并主动检查。发现新版本后
必须由用户确认下载;下载、校验和安全解压在后台线程完成,只写入
`data/update/`。程序不会强制退出,用户下次通过 `Launcher.exe` 启动时才替换
`app/`。Launcher 保留一份 `app.old/`,目录移动失败或新版本没有写入健康标记时
恢复旧版本。更新包必须与清单同源;重定向后仍必须是 HTTPS;不允许忽略 TLS
证书错误,也不在更新地址中保存账号密码。
更新地址还不得包含查询参数或片段,避免 token 一类凭据被持久化。
`0.1.0` 的 Launcher 没有应用在线更新的能力,因此第一次升级到带在线更新的
`0.2.0` 仍需人工替换一次完整程序;从 `0.2.0` 开始才可使用上述流程。当前没有
清单数字签名,发布服务器整体失陷不在 SHA256 的保护范围内,签名需要另建工单。
**已知风险点**(打包工单必须逐项验证):
+20
View File
@@ -145,6 +145,8 @@ client/
- Repository 封装 SQLite,界面和自动化代码不得直接拼接业务 SQL。
- PDD Adapter 封装设备连接、页面识别、采集和采购。
- ArtifactStore 保存失败截图、无障碍 XML 和结构化诊断文件。
- `UpdateService` 只负责 HTTPS 清单、下载校验和安全暂存;正在运行的主程序不替换
自身,目录替换与回退只由下一次启动的 `Launcher.exe` 执行。
## 5. 线程模型
@@ -161,6 +163,10 @@ Qt 主线程
结果提交工作线程或同一任务线程的独立队列
└── Outbox 重试,不重复执行 PDD 操作
更新工作线程
├── 检查 HTTPS 清单
└── 下载、SHA256 校验和安全解压到 data/update/
```
- `[必须]` QWidget 只能在 Qt 主线程创建和访问。
@@ -168,6 +174,8 @@ Qt 主线程
- `[必须]` 后台信号只传递不可变数据、稳定编号或轻量视图模型。
- `[必须]` 点“停止获取”后不再领取新任务,当前任务在定义的安全点退出。
- `[建议]` 关闭窗口时应选择停止、等待或后台继续;MVP 默认安全停止并持久化状态。
- `[必须]` 更新检查和下载使用独立 `QObject + moveToThread` Worker;下载完成只提示
下次启动生效,不得为了更新强制中断采集或采购任务。
### 5.1 Worker 模板(项目统一写法,照抄即可)
@@ -282,6 +290,18 @@ def _on_finished(self, remote_task_id: str, result) -> None:
注意:**断开信号只是不更新界面,任务的数据该落库还是要落库**。落库由应用层负责,和界面在不在没关系——这正是"结果先写本地再提交 Admin"的意义,见 §8。
### 5.3 在线更新启动顺序
```text
主程序:HTTPS 清单 → 用户确认 → 下载并校验 → data/update/app.new + pending.json
Launcher:确认主程序未运行 → app 改名 app.old → app.new 改名 app → 启动主程序
主程序:窗口成功创建 → 写 healthy.json
Launcher:健康标记正确则完成;提前退出则恢复 app.old
```
更新 ZIP 只允许 `app/` 内容,拒绝绝对路径、`..`、反斜杠路径和符号链接。Launcher
不联网、不处理凭据,也不自更新。
## 6. 任务引擎状态
任务协调器状态:
+12
View File
@@ -296,6 +296,16 @@ class TaskTableModel(QAbstractTableModel):
- 保留天数;
- 打开日志目录。
### 软件更新
- 显示只读的当前版本;
- 清单地址使用带可见标签的单行输入框,只允许完整 HTTPS URL;
- “检查更新”同时保存已验证的非敏感清单地址;检查和下载期间按钮防重复;
- 状态文字稳定显示未配置、检查中、已是最新、发现新版、下载进度、已准备、失败和
恢复建议;
- 发现新版本时使用有明确“下载更新 / 暂不下载”的确认框;下载完成不强制重启,
提示操作人员完成当前任务后自行关闭并重新启动。
设置采用显式“保存设置”或项目统一的即时保存模式,不能在同一页面随机混用。MVP 推荐显式保存,验证失败时保留输入并聚焦第一个错误字段。
## 9. 状态与反馈
@@ -311,6 +321,8 @@ class TaskTableModel(QAbstractTableModel):
| 任务失败 | 行状态、详情和信息条,不显示原始堆栈 |
| 多个订单候选 | 状态改为“需要人工处理”,打开详情决策 |
| 普通任务成功 | 更新行和状态区,不弹“成功”对话框 |
| 更新检查或下载失败 | 软件更新卡片保留地址并显示原因和重试方式,当前程序不变 |
| 更新下载完成 | 软件更新卡片持续提示“下次启动生效”,不强制关闭程序 |
### 9.1 错误提示模板(照抄即可)
+15
View File
@@ -185,6 +185,20 @@ Artifact 写入前应脱敏,数据库只保存引用。保留周期由设置
- 迟到的 Admin 响应不得覆盖更新的本地执行状态。
- 数据库损坏、磁盘写满和只读目录必须产生可操作错误,不能继续下单。
### 8.1 在线更新安全
- 清单和更新包只允许 HTTPS,不提供忽略证书错误的开关;URL 不得包含账号密码、
查询参数或片段,避免凭据随地址写入 SQLite 或错误提示。
- 更新包必须与最终清单 URL 同源,并同时校验清单声明的字节大小和 SHA256。
- 清单、压缩包和解压后总大小均有限制;ZIP 只能包含安全的 `app/` 内容,拒绝路径
穿越、绝对路径、反斜杠路径和符号链接。
- 下载和解压只写 `data/update/`;`client.db`、日志、Artifact 和其他设置不得进入
替换范围。
- Launcher 发现主程序仍在运行时不得替换目录;替换失败必须恢复旧 `app`,新版本
未通过健康检查时恢复 `app.old`。
- SHA256 不证明发布者身份。当前发布服务器整体失陷不在保护范围内,正式扩大分发
前应另行评估签名清单和代码签名。
## 9. 性能和响应性
- 表格使用模型/视图和增量加载,不为每个单元格创建 QWidget。
@@ -207,6 +221,7 @@ Artifact 写入前应脱敏,数据库只保存引用。保留周期由设置
| 6 | Windows 干净环境启动测试通过 | **未打包版本**:找一台没装过本项目的机器,照 [00 上手指南](00-getting-started.md) 从头走一遍。**已打包版本**:把整个文件夹拷到一台**没装 Python** 的机器上双击 `Launcher.exe` | 开发者 |
| 6b | 升级不丢数据(仅打包版本) | 关闭程序并只换掉 `app/`,保留 `Launcher.exe` 和 `data/`;启动后任务、日志、设置都还在 | 开发者 |
| 6c | 发布清单与产物一致 | `autobuy——manifest.json` 中的版本、文件名、字节大小和 SHA256 与实际压缩包一致,更新包中没有 `data/` | 开发者 |
| 6d | 在线更新和回退通过 | 使用上一版本目录检查新版本、下载、下次启动替换、健康标记、文件占用失败和自动回退;确认数据库未被覆盖 | 开发者 |
| 7 | 日志和产物无敏感信息 | 翻一遍日志和 `artifacts/`,确认没有 token、Cookie、密码、收货人信息 | 开发者 |
| 8 | 开源和商业许可证已确认 | 新增依赖的许可证是否允许本项目的使用方式 | **项目负责人**(不是开发者自己判断) |
| 9 | 真实下单版本额外满足采购安全门禁 | 逐条核对 §3 的 8 项 | **项目负责人 + 操作人员共同确认** |