diff --git a/docs/task/93-client在线更新与安全回退.md b/docs/task/93-client在线更新与安全回退.md new file mode 100644 index 0000000..0c68de4 --- /dev/null +++ b/docs/task/93-client在线更新与安全回退.md @@ -0,0 +1,78 @@ +# 93 Client:实现安全的在线检查、下载和下次启动更新 + +- 类型:需求 +- 父级大工单:#1 +- 所属 MVP / 版本:MVP 后续 / 在线更新 +- 状态:已完成 +- 日期:2026-08-10 +- Gitea 工单:http://ilaer.eicp.net:8418/chengma/cmautobuy/issues/93 + +## 背景与目标 + +#92 已建立 `Launcher.exe + app/ + data/` 发布结构,但 Client 仍需人工取得并 +替换更新包。本任务增加用户主动检查和下载更新的能力,并保证更新不阻塞界面、 +不覆盖本地数据,也不强制中断正在执行的采集或采购任务。 + +## 最终方案 + +- 设置页显示当前版本、HTTPS 清单地址、检查按钮和稳定的更新状态。 +- 检查和下载使用 `QObject + moveToThread`,发现新版本后先由用户确认。 +- 清单地址只允许不含凭据、查询参数和片段的 HTTPS URL;更新包必须同源,并校验 + 大小、SHA256、版本和 ZIP 路径。 +- 更新包安全暂存到 `data/update/app.new`,不会直接替换运行中的程序。 +- 下次通过 `Launcher.exe` 启动时才替换 `app`,旧版保留为 `app.old`;移动失败或 + 新版本未通过健康检查时恢复旧版。 +- Client 版本升级为 `0.2.0`。由于 `0.1.0` 的 Launcher 不具备更新能力,第一次 + 升级到 `0.2.0` 仍须人工替换完整发布目录,从 `0.2.0` 起才支持在线更新。 + +最终实现与建单方案一致。清单 URL 额外禁止查询参数,避免 token 一类凭据随地址 +写入 SQLite 或错误提示。 + +## 改了哪些 + +- `client/src/update_service.py`:检查清单、下载校验、安全解压和待更新状态。 +- `client/src/update_ui_event.py`:在线更新后台 Worker、确认框、进度和生命周期。 +- `client/src/settings_ui.py`、`settings_ui_event.py`:设置页软件更新区域及事件装配。 +- `client/build_tools/launcher.py`:下次启动替换、运行识别、健康确认和失败回退。 +- `client/src/ui_main.py`、`version.py`:启动健康标记和 `0.2.0` 版本号。 +- `client/test/test_update_service.py`、`test_update_ui_event.py`、`test_packaging.py`: + 更新服务、界面线程和 Launcher 测试。 +- `docs/client/01-requirements.md`、`02-architecture.md`、`05-ui-specification.md`、 + `06-quality-security.md`:同步需求、架构、界面和安全基线。 + +## 验收结果 + +| 验收标准 | 结果 | +|---|---| +| 设置页可配置 HTTPS 清单地址,非法地址有明确提示 | 通过 | +| 检查和下载不阻塞主线程,且防止重复启动 | 通过 | +| 新版本由用户确认下载,完成后不强制重启 | 通过 | +| 校验大小、SHA256、版本、HTTPS 和同源 | 通过 | +| 拒绝越界路径、绝对路径、反斜杠路径和符号链接 | 通过 | +| 更新只写 `data/update`,本地 `data` 内容保持不变 | 通过 | +| Launcher 保留 `app.old`,支持移动失败和健康失败回退 | 通过 | +| 已运行的主程序不会被 Launcher 替换 | 通过 | +| 真实下单开关和 Admin 业务接口未改变 | 通过 | +| 自动测试、打包和旧版升级冒烟通过 | 通过 | + +## 测试 + +- 执行的命令: + - 在 `client/` 执行 + `C:/Python310/python.exe -m pytest test -q --disable-warnings` + - 在 `client/` 执行 `.\build_client.bat` + - 在临时发布目录使用最终 `Launcher.exe` 执行 0.1.0 → 0.2.0 升级冒烟 +- 结果:256 项测试通过;0.2.0 发布产物生成成功;升级后当前 `app` 为 0.2.0, + `app.old` 为 0.1.0,`data` 哨兵保留,健康标记生成,待更新状态清除。 +- **没验证到的部分**:尚无真实 HTTPS 静态发布地址,未执行真实公网下载;尚未在 + 全新且未安装 Python 的 Windows 机器上人工验证;重复 Launcher 的提示由单元 + 测试覆盖,未人工点击原生提示框。 + +## 遗留问题 + +- 当前 SHA256 用于发现下载损坏,不能证明发布者身份。清单签名和 Windows 代码 + 签名不在本工单范围内。 + +## 相关提交 + +- `6d6829e` feat: 实现 Client 在线更新与安全回退 (#93)