feat: 实现 Client 在线更新与安全回退 (#93)
This commit is contained in:
@@ -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 的保护范围内,签名需要另建工单。
|
||||
|
||||
**已知风险点**(打包工单必须逐项验证):
|
||||
|
||||
|
||||
@@ -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. 任务引擎状态
|
||||
|
||||
任务协调器状态:
|
||||
|
||||
@@ -296,6 +296,16 @@ class TaskTableModel(QAbstractTableModel):
|
||||
- 保留天数;
|
||||
- 打开日志目录。
|
||||
|
||||
### 软件更新
|
||||
|
||||
- 显示只读的当前版本;
|
||||
- 清单地址使用带可见标签的单行输入框,只允许完整 HTTPS URL;
|
||||
- “检查更新”同时保存已验证的非敏感清单地址;检查和下载期间按钮防重复;
|
||||
- 状态文字稳定显示未配置、检查中、已是最新、发现新版、下载进度、已准备、失败和
|
||||
恢复建议;
|
||||
- 发现新版本时使用有明确“下载更新 / 暂不下载”的确认框;下载完成不强制重启,
|
||||
提示操作人员完成当前任务后自行关闭并重新启动。
|
||||
|
||||
设置采用显式“保存设置”或项目统一的即时保存模式,不能在同一页面随机混用。MVP 推荐显式保存,验证失败时保留输入并聚焦第一个错误字段。
|
||||
|
||||
## 9. 状态与反馈
|
||||
@@ -311,6 +321,8 @@ class TaskTableModel(QAbstractTableModel):
|
||||
| 任务失败 | 行状态、详情和信息条,不显示原始堆栈 |
|
||||
| 多个订单候选 | 状态改为“需要人工处理”,打开详情决策 |
|
||||
| 普通任务成功 | 更新行和状态区,不弹“成功”对话框 |
|
||||
| 更新检查或下载失败 | 软件更新卡片保留地址并显示原因和重试方式,当前程序不变 |
|
||||
| 更新下载完成 | 软件更新卡片持续提示“下次启动生效”,不强制关闭程序 |
|
||||
|
||||
### 9.1 错误提示模板(照抄即可)
|
||||
|
||||
|
||||
@@ -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 项 | **项目负责人 + 操作人员共同确认** |
|
||||
|
||||
Reference in New Issue
Block a user