Files
cmautobuy/docs/client/06-quality-security.md
T

13 KiB
Raw Blame History

06 Client 质量、安全与测试基线

  • 文档状态:基线草案,待质量评审
  • 适用范围:Client 源码、配置、数据库、自动化和发布产物

本文档中 [必须] / [建议] / [待定] 的含义见 文档索引。没有标注的默认是 [必须]。看不懂的词查 术语表。

1. 质量目标

  • 同一台 Client 不重复执行或重复提交同一个采购任务;崩溃重启后也不重复下单。
  • 网络和 Admin 故障不丢失已完成结果。
  • 设备或 PDD 页面异常可诊断,不以错误坐标继续执行高风险操作。
  • 所有阻塞工作离开 Qt 主线程,窗口在长任务期间保持可用。
  • 代码、接口、数据库和诊断产物不泄露凭据及不必要的个人数据。

2. 测试分层

2.1 单元测试

必须覆盖:

  • 任务状态转换和非法转换;
  • 金额、数量、价格上限和规格校验;
  • Admin DTO 与 pdd_data JSON 验证;
  • SQLite Repository、迁移和默认排序;
  • Outbox 幂等、退避和崩溃恢复;
  • 销量、评价和价格文字解析;
  • 控件树坐标解析、规格匹配和选中状态判断;
  • 订单候选匹配规则。

PDD 解析测试优先使用脱敏的 XML 固件,不要求每次连接真实设备。

2.2 集成测试

使用临时 SQLite 和 Mock Admin 覆盖:

  • 原子领取和重复领取;
  • 提交一个 Admin 侧已取消的任务,仍被接受并标记 succeeded;
  • 采集/采购任务分派;
  • 结果先落库再进入 Outbox;
  • Admin 超时后重试相同幂等键;
  • 程序在提交前、下单阶段和提交后异常退出;
  • 窗口关闭后后台结果不会更新已销毁界面。

2.3 界面测试

  • Python 语法编译;
  • 离屏实例化主窗口;
  • 表格模型排序、筛选和增量加载;
  • 开始/停止按钮防重复;
  • Ctrl+F、Enter 和 Tab 顺序;
  • 加载、空、错误、离线、运行和人工处理状态;
  • 浅色、深色、高对比度和 100%–200% 缩放。

2.4 设备测试

真实设备测试必须显式标记,不应混入默认快速测试:

  • PDD 首页、商品页、规格面板和订单列表识别;
  • 不同商品规格数量、滚动方式和缺货状态;
  • 登录失效、验证码、网络慢、页面变化和弹窗;
  • 采集完整性;
  • 采购演练及订单核对。

2.5 契约和端到端测试

  • Mock Gateway 与 Http Gateway 对同一接口测试集通过。
  • Admin 提供可测试环境后执行版本、错误和幂等契约测试,含"已取消任务的结果仍被接受"。
  • 端到端测试从任务领取开始,以 Admin 确认结果和本地状态一致结束。

3. 采购安全门禁

真实下单开关默认关闭。全部满足后才能通过独立工单启用:

  1. Admin 原子领取和结果幂等已经通过联合测试。
  2. Outbox 断网和重启恢复测试通过。
  3. 商品编号、标题、规格、数量、库存和价格保护校验通过。
  4. 最终下单前后均有持久化步骤标记。
  5. 进程在不可逆阶段退出后只执行订单核对,不会重新下单。
  6. 订单匹配能识别唯一候选;多个候选进入人工处理。
  7. 演练模式在代表性商品和设备上通过验收。
  8. 操作人员明确确认真实下单范围和安全设置。

真实下单不得通过调试参数、默认配置或界面误操作意外开启。 当前版本的 ClaimCapabilities 仅接受 purchase_mode=dry_run,采购 Adapter 不提供提交订单或付款方法。开发者不能把自动化测试通过当成真实 下单门禁放行;仍需项目负责人和实际操作人员共同确认。

3.1 当前自动化安全检查

  • 关键手机动作前已持久化 current_step。
  • 无不可逆标记的中断会先关闭旧运行,新运行使用新 attempt_id。
  • 有不可逆标记的中断只调用只读核对,采购 Adapter 调用次数为 0。
  • 已落库 Outbox 只补交,不重跑手机流程。
  • 停止、设备断开、验证码、登录失效和结果不确定都保留稳定错误和诊断。
  • purchase_mode=live 会在数据对象创建时被拒绝。
  • 正式 uiautomator2 采购 Adapter 只点击商品页采购入口和目标规格, enter_confirmation 与 stop_before_submit 均为只读检查。
  • 单元测试必须断言到达最终确认页后没有产生第二次底部按钮点击。

上述只证明演练和恢复代码的默认安全性,不代表真实下单已放行。

4. 自动化防护

  • 点击前必须确认包名、页面类型、目标语义和坐标边界。
  • 高风险点击应在最新控件树上重新定位,不使用长时间缓存的坐标。
  • 规格选择后读取最新控件树并验证选中状态。
  • 最终下单前重新读取实际价格、数量和规格。
  • 页面出现验证码、登录、支付、权限请求或未知模态层时停止并转人工处理。
  • uiautomator2 连接由单一工作线程独占,任务间清理临时状态。
  • 所有循环具有超时、最大滑动次数和可取消检查。

运营提醒(不是代码规则): Admin 超时重派可能导致同一任务被两台 Client 各下一单, 产生重复的未付款订单。人工审核时取消多余订单即可。但未付款订单长期堆积可能触发平台风控, 需要操作人员留意,不要放着不管。

将来若要在程序里防这个,正确的加固点是最终下单前的最后一次校验(本节已要求重新读取价格、 数量和规格,顺带确认任务有效性即可),不要改回周期性回查 Admin 状态。

5. 错误分类

建议使用稳定错误代码:

分类 示例
ADMIN_* 认证失败、超时、请求无效、幂等冲突
DEVICE_* 设备离线、ADB 失败、应用未启动
PDD_PAGE_* 页面超时、未知页面、登录失效、验证码
PDD_DATA_* 标题缺失、规格不完整、价格解析失败
PURCHASE_* 规格缺货、价格超限、下单状态不确定
ORDER_* 未找到订单、多个候选、详情不匹配
DB_* 迁移失败、写入失败、数据库锁超时
OUTBOX_* 提交失败、幂等冲突、结果被拒绝

错误反馈必须说明发生了什么、已保留什么以及下一步是什么。原始异常堆栈仅写入诊断日志,不直接展示给普通用户。

6. 日志与诊断

日志至少包含:

  • 时间、级别、模块和稳定事件名;
  • client_id、remote_task_id、attempt_id 和请求编号;
  • 当前步骤、持续时间、结果和稳定错误代码;
  • Admin 响应状态,但不记录认证头和完整敏感正文。

建议事件:

  • task_claim_started/succeeded/empty/failed
  • task_claimed
  • task_step_changed
  • task_result_persisted
  • outbox_send_started/succeeded/failed
  • purchase_irreversible_step_entered
  • order_reconcile_completed

失败 Artifact 目录建议:

artifacts/<remote_task_id>/<attempt_id>/
├── metadata.json
├── hierarchy.xml
├── screenshot.png
└── steps.jsonl

Artifact 写入前应脱敏,数据库只保存引用。保留周期由设置控制,删除不得影响任务结果和审计字段。

7. 凭据与隐私

  • Admin 必须使用 HTTPS;生产环境不得允许静默忽略证书错误。
  • Token 优先保存到 Windows 凭据管理器或由安全环境注入。
  • data/ 目录是明文的,且设计上就是"整个拷走",凭据绝不能写进去,见 03 §2.1。
  • 设置页只能显示认证状态或掩码值。
  • 日志、数据库、截图、XML、Gitea 工单和 docs/task 都不得包含 Token、Cookie、密码或支付信息。
  • 只保存完成任务所需的订单编号和时间,不采集无关收货人、地址、电话等个人数据。
  • 不实现验证码、风控或平台安全机制绕过。

8. 数据可靠性

  • 数据库迁移前创建可恢复备份或采用可回滚迁移步骤。
  • 所有可写文件只能落在 data/ 下,路径一律通过 data_dir() 取,见 03 数据模型 §2.2。
  • 每个任务状态变化和执行记录在同一事务内保持一致。
  • result_pending 任务必须存在对应结果和 Outbox 记录。
  • Outbox 发送成功前不得删除结果正文。
  • 迟到的 Admin 响应不得覆盖更新的本地执行状态。
  • 数据库损坏、磁盘写满和只读目录必须产生可操作错误,不能继续下单。

8.1 在线更新安全

  • 一般清单和更新包只允许 HTTPS;按已确认部署方案,只有固定发布主机的默认端口 临时允许 HTTP Basic Authentication,任何其他 HTTP 地址仍拒绝。
  • URL 不得包含账号密码、查询参数或片段。URL 和账号可写 SQLite;密码只能写入 Windows 凭据管理器,不得进入源码、Git、SQLite、日志、异常文本、工单或文档。
  • 不同协议、主机或端口使用不同的系统凭据目标;修改服务器地址后必须为新服务器 重新输入密码,不能把旧服务器密码自动发送到新地址。
  • HTTP Basic Authentication 不能防止同网段监听或中间人攻击;主机白名单只限制 Client 误连范围,不能替代 TLS,发布服务器具备条件后应迁移到 HTTPS。
  • 更新包必须与最终清单 URL 同源,并同时校验清单声明的字节大小和 SHA256。
  • 清单、压缩包和解压后总大小均有限制;ZIP 只能包含安全的 app/ 内容,拒绝路径 穿越、绝对路径、反斜杠路径和符号链接。
  • 下载和解压只写 data/update/;client.db、日志、Artifact 和其他设置不得进入 替换范围。
  • Launcher 发现主程序仍在运行时不得替换目录;替换失败必须恢复旧 app,新版本 未通过健康检查时恢复 app.old。
  • SHA256 不证明发布者身份。当前发布服务器整体失陷不在保护范围内,正式扩大分发 前应另行评估签名清单和代码签名。

9. 性能和响应性

  • 表格使用模型/视图和增量加载,不为每个单元格创建 QWidget。
  • 搜索、排序和分页使用索引支持的 SQLite 查询。
  • 高频进度信号需要节流,避免每次 XML 节点变化都刷新界面。
  • 大型 XML 解析、截图和文件写入位于工作线程。
  • 目标基线:普通本地搜索在典型数据量下保持即时反馈,任务运行时界面可持续操作。

10. 发布门禁

每个版本至少满足下面全部条件。"谁负责"这一列是为了让你知道哪些不用自己扛——不是你负责的项,去找对应的人,不要自己拍板放行。

# 门禁项 怎么验证 谁负责
1 依赖版本已固定,且能重复安装 client/requirements.txt 里每一行都带 ==;在干净环境执行 pip install -r requirements.txt 能装成功 开发者
2 全部默认单元测试和集成测试通过 跑测试命令,不允许有跳过而没说明的用例 开发者
3 UI 离屏冒烟测试通过 见 client/AGENTS.md §验证 第 2 条 开发者
4 数据库从上一发布版本迁移成功 拿上一版本的 client.db 副本启动新版本,数据不丢 开发者
5 Mock Admin 契约测试通过 Mock 和 HTTP 两个实现跑同一套测试 开发者
6 Windows 干净环境启动测试通过 未打包版本:找一台没装过本项目的机器,照 00 上手指南 从头走一遍。已打包版本:把整个文件夹拷到一台没装 Python 的机器上双击 Launcher.exe 开发者
6b 升级不丢数据(仅打包版本) 关闭程序并只换掉 app/,保留 Launcher.exe 和 data/;启动后任务、日志、设置都还在 开发者
6c 发布清单与产物一致 autobuy_manifest.json 与对应版本化清单内容相同;其中版本、文件名、字节大小和 SHA256 与实际压缩包一致,更新包中没有 data/ 开发者
6d 在线更新和回退通过 使用上一版本目录检查新版本、下载、下次启动替换、健康标记、文件占用失败和自动回退;确认数据库未被覆盖 开发者
7 日志和产物无敏感信息 翻一遍日志和 artifacts/,确认没有 token、Cookie、密码、收货人信息 开发者
8 开源和商业许可证已确认 新增依赖的许可证是否允许本项目的使用方式 项目负责人(不是开发者自己判断)
9 真实下单版本额外满足采购安全门禁 逐条核对 §3 的 8 项 项目负责人 + 操作人员共同确认

第 8、9 项开发者不要自己判断放行。新增依赖时把包名和许可证类型报给项目负责人;真实下单开关必须由项目负责人和实际操作的人一起确认,见 §3。

11. 任务完成定义

单元任务只有同时满足以下条件才算完成:

  1. 工单验收标准逐项通过。
  2. 代码、迁移、测试和必要文档同步完成。
  3. 没有静默跳过的测试或未说明的真实设备假设。
  4. 变更不泄露敏感信息,也不扩大自动化权限。
  5. Gitea 工单更新最终结果和提交哈希。
  6. 完成记录归档到 docs/task。