Files
cmautobuy/docs/client/01-requirements.md
T

17 KiB
Raw Blame History

01 Client 产品需求基线

  • 文档状态:基线草案,待需求评审
  • 适用范围:client/
  • 产品类型:Windows 桌面自动化客户端

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

1. 背景与目标

Client 从 Admin 获取拼多多任务,在一台已授权的 Android 设备上通过 uiautomator2 执行商品数据采集或采购操作,并将结果可靠地提交回 Admin。

产品目标:

  1. 在统一界面中查看、搜索和跟踪本机已领取的全部 PDD 任务(含正在做的和做完的)。
  2. 自动领取并串行执行采集任务和采购任务。
  3. 在 Admin 或网络暂时不可用时保存结果并安全重试,不能丢失采集结果或重复采购。
  4. 为失败、异常页面和不确定订单提供可诊断、可恢复的人工处理入口。
  5. 在 Admin 尚未完成时,通过本地模拟接口完成大部分 Client 开发和测试。

2. 用户与外部系统

  • **操作人员:**配置 Admin 和 Android 设备,启动或停止自动获取任务,查看状态和详情,处理异常。
  • **Admin:**创建任务、分配任务、维护任务状态,并接收 Client 执行结果。
  • **Android 设备:**登录并运行拼多多应用,由 Client 独占一个自动化执行会话。
  • **拼多多应用:**被自动化操作的外部系统,不属于本项目控制范围。

3. 产品范围

Client 顶级导航仅包含:

  1. PDD 任务:本机已领取任务的展示、搜索、自动执行和详情查看。
  2. 设置:Admin、Android 设备、自动化、安全和诊断设置。

设备连接不作为独立顶级模块,统一放入设置页。

Client 不显示 Admin 任务池。 本地只保存本机已领取的任务(含执行中和已完成), 不缓存"待领取"的任务。要拿新任务只有一条路——由任务引擎向 Admin 领取。 操作人员想知道队列里还有多少活没派下来,去 Admin 自己的界面看。 这条决定的完整理由见 03 数据模型 §3.1。

4. 任务类型

4.1 采集任务

输入至少包含远程任务编号和商品链接。Client 应采集:

  • 商品编号、商品链接和商品标题;
  • 店铺名称(无障碍树未提供时允许留空并保存诊断证据);
  • 已拼数量及原始显示文字;
  • 评价数量及原始显示文字(首屏没有时,在进入规格面板前有限次缓慢向下浏览补采;到达页面底部或次数上限后仍没有时允许留空);
  • 所有规格维度及其可见值;
  • 每个颜色对应的价格、币种,以及当前控件树显示的规格可用状态;
  • 采集时间、设备及必要诊断产物引用。

当前业务按颜色采价:进入规格面板后逐个选中颜色并立即读取稳定价格;尺码只 收集文字和当前显示的可用状态,不逐个选择、不单独采价。提交时仍以 SKU 规格 组合保存颜色与尺码关系,同一颜色的组合复用该颜色价格, price_observed_at 只记录实际选中的颜色,不得虚构已选尺码。以后需要按尺码 区分价格或组合库存时,必须另行确认并改用 price_granularity=sku。

颜色已售罄、无法选中或未采到稳定价格时,不重试采价,也不阻断后续流程;该 颜色的价格相关字段留空。所有颜色处理完成后仍须采集尺码、保存本地结果并提交 Admin。规格 available 只反映页面控件状态,不能根据价格是否为空推断。

对于“1.2 万+”等近似数值,同时保存规范化数值、原始文字和近似标记。

4.2 采购任务

输入至少包含:

  • 远程任务编号;
  • 商品编号或商品链接;
  • 目标颜色和尺码;
  • 采购数量;
  • 预期价格或最高允许价格;
  • 任务版本及幂等信息。

Client 应执行:

  1. 校验商品、规格、数量、库存和价格保护条件。
  2. 选择指定颜色、尺码和数量。
  3. 正式采购使用远程任务编号更新收货地址末尾的采购编号标记,演练流程不修改地址。
  4. 返回订单提交前状态并再次校验商品、规格、数量和价格。
  5. 在允许真实下单时提交订单。
  6. 获取并核对订单编号和下单时间。
  7. 保存本地结果并提交 Admin。

规格选择先按页面原文精确匹配,再允许繁体、简体等价文字唯一匹配;等价候选不唯一时 必须停止。若商品正确、颜色已经可靠选中,但目标尺码在完整可购买列表中确实不存在, Client 应只读遍历当前颜色的全部第二规格,保留规格名称、页面原文、可用状态和页面顺序, 生成稳定候选编号及快照哈希,交给后续受审计的规格解析流程。遍历未到边界、页面丢失、 候选发生变化,或页面中其实存在目标尺码但点击确认失败时,仍按普通采购失败处理,不能 请求远端猜测。本阶段候选只保存在当前执行内存,不写入 SQLite、日志或控件树产物。

地址更新只允许替换程序生成的末尾标记,不能截断真实地址主体。地址入口、修改按钮、 详细地址输入框、保存按钮或保存结果任一项不唯一、不完整或无法回读时,任务必须在 不可逆标记写入前失败并停止。姓名、手机号、完整地址和原始控件树只用于本次设备会话, 不得写入 SQLite、日志、Gitea、测试固件或文档。

仅凭“我的订单”列表中的最新一条记录不能认定为当前任务订单。订单页必须读到 非空订单编号、有效下单时间和待付款状态;下单时间须位于本地 order_submitted_at 前后 5 分钟,且窗口内只能有一个候选。商品、规格、数量和金额 使用下单前已持久化的确认快照,不要求订单页重复提供。存在多个候选时进入人工处理 状态,不得猜测或重新下单。

5. PDD 任务页需求

页面分为三个稳定区域。

5.1 顶部命令区

  • “获取任务”是持续任务引擎的主操作,启动后按钮文字切换为“停止获取”。
  • 停止只停止领取新任务;当前任务应运行到安全停止点。
  • 搜索条件至少支持任务类型、任务状态和关键词。
  • 关键词搜索任务编号、商品编号和商品标题。
  • “搜索”只查询本地数据库,不触发任务执行。

5.2 中部任务表格

表格显示的是本机已领取的全部任务,包括正在做的和早已做完的。已完成的任务永久保留,不会被清理。

表格要让操作人员一眼看出:这是什么任务、买的什么、什么规格、多少钱、现在到哪一步了。

具体列有哪些、每列怎么显示,以 05 界面交互规范 §5.1 为准(那是唯一权威定义,改列只改那一处)。

本文只规定不可动摇的业务要求:

  • 默认按 updated_at DESC, id DESC 稳定排序。
  • 所有任务可通过增量加载访问,禁止为每个单元格创建常驻复杂控件。
  • pdd_data 是详情数据,不作为隐藏表格列整体加载;表格模型只保留稳定任务编号,查看详情时从数据库读取。
  • 单击行只选择,双击任务行或按 Enter 打开任务详情。

5.3 底部状态区

持续显示:

  • 自动获取是否开启;
  • 当前任务编号和类型;
  • 当前执行步骤;
  • 最近一次领取到任务的时间;
  • 最近一次成功或可恢复错误摘要。

重要错误应同时使用持久信息条提供重试或进入设置的入口,不能只依赖自动消失提示。

6. 设置页需求

设置分为以下组:

  1. **Admin:**服务地址、Client 编号、认证状态、请求超时和测试连接。
  2. **Android 设备:**ADB 地址、拼多多包名、设备测试连接。
  3. **自动化:**轮询周期、任务超时和演练模式。
  4. **安全与诊断:**价格允许偏差、最大购买数量、日志目录、截图/XML 保留周期。
  5. **软件更新:**当前版本、清单地址、账号、密码、保存/检查按钮和稳定状态文字。

密码和访问令牌不得以明文写入普通 SQLite 设置或日志。

7. 任务状态

任务共有 8 个标准状态:claimed、running、result_pending、retry_wait、manual_review、succeeded、failed、cancelled。

retry_wait 只为兼容旧数据和采集进程异常退出保留;自动任务不会选择它,也不会 自动重试失败任务。

没有"待领取"状态——任务在领取成功那一刻才写进本地库,见 03 数据模型 §3.1。

每个状态的中文显示、含义和允许的转换,以 03 数据模型 §7 为准(那是唯一权威定义,写代码和写测试都看那一张表)。

本文只强调两条产品级红线:

  • 任何自动流程都不许把 manual_review 改回执行中状态,必须由人处理。
  • Client 拿到任务就做完。中途 Admin 取消了这个任务,Client 不管,照做完照提交,由人工审核时处理。
  • 程序崩溃后,处于不可逆采购阶段的任务只能执行订单核对,不能重新下单。

8. MVP 范围

MVP 包含:

  • 两模块 Fluent 界面;
  • SQLite 数据库和迁移;
  • 本地任务表格、搜索、筛选和详情;
  • Mock Admin 接口;
  • 任务领取与串行执行;
  • 后台任务协调器、轮询、停止、重试和结果待提交队列;
  • 真实商品采集及 SKU 数据组装;
  • 采购演练模式,完成规格和数量选择但不执行最终下单;
  • 重启恢复、断网、设备断开和重复提交测试。

MVP 之后:

  • 接入真实 Admin HTTP 接口;
  • 经过安全验收后启用真实订单提交;
  • 完成订单核对和人工处理流程;
  • 打包成 exe 并做生产环境验证(策略见 §8.1)。

8.1 打包策略

打包动作属于 MVP 之后,但策略现在就定下来,避免写代码时留下不好打包的结构。

工具: PyInstaller 6.x,one-dir 模式(不是 one-file)。 one-file 每次启动都要解压到临时目录,启动慢,出错几乎没法排查;one-dir 缺什么文件一眼就能看见。

产出目录结构:

CMAutoBuy/                    整个文件夹拷到任何机器都能用
├── Launcher.exe              双击这个启动;它只负责启动主程序
├── app/                      主程序和运行时依赖,升级时整体替换
│   ├── CMAutoBuy.exe
│   ├── version.txt
│   └── dependencies/
│       ├── python310.dll
│       ├── PyQt5/Qt5/
│       │   ├── bin/          Qt5Core.dll / Qt5Gui.dll / Qt5Widgets.dll …
│       │   └── plugins/platforms/qwindows.dll
│       ├── qfluentwidgets/   qss 样式、字体、图标资源
│       ├── vendor/android-platform-tools/windows/
│       │   ├── adb.exe
│       │   ├── AdbWinApi.dll
│       │   └── AdbWinUsbApi.dll
│       └── …
└── data/                     本地数据,升级时保留(见 [03 数据模型](03-data-model.md) §2.1)
    ├── client.db
    ├── logs/
    └── artifacts/

主程序使用 PyInstaller one-dir,内部依赖目录通过 --contents-directory dependencies 固定命名。根目录的 Launcher.exe 是独立轻量启动器,为后续“关闭主程序后再替换 app/”保留边界;当前版本不联网、不下载,也不自动更新。

当前升级方式: 关闭程序后只替换 app/,Launcher.exe 和 data/ 原样不动。 数据库靠 PRAGMA user_version 自动迁移,见 03 §2.3。完整便携包 用于首次安装;更新包只包含 app/,不得包含 data/。

每次构建在 client/release/ 生成以下发布文件:

  • CMAutoBuy_<版本号>.zip:在线更新包;
  • 自动采集采购工具_<版本号>.zip:带版本号的完整便携包;
  • 自动采集采购工具.zip:与带版本号便携包内容完全相同的下载别名;
  • autobuy_manifest.json:Client 固定读取的在线更新清单;
  • autobuy_manifest_<版本号>.json:与固定清单内容完全相同的归档副本。

清单中的更新包引用带版本号的文件名,便携包固定引用 自动采集采购工具.zip,并记录版本、字节大小和 SHA256;SHA256 用于发现下载 损坏,不等同于发布者身份认证。

**在线更新:**设置页默认显示已确认的发布清单地址和账号,密码由操作人员首次 填写并保存到 Windows 凭据管理器;URL 和账号作为非敏感设置保存在 SQLite,密码 不回填明文。保存完整配置后,Client 启动时在后台检查一次,也可手动检查。发现 新版本后必须由用户确认下载;下载、校验和安全解压在后台线程完成,只写入 data/update/。程序不会强制退出,用户下次通过 Launcher.exe 启动时才替换 app/。Launcher 保留一份 app.old/,目录移动失败或新版本没有写入健康标记时 恢复旧版本。更新包必须与清单同源且重定向不得跨源。一般地址只允许 HTTPS;当前 固定发布主机按已确认方案临时允许默认端口 HTTP 和 Basic Authentication。URL 不得包含账号密码、查询参数或片段,认证密码不得写入源码、Git、SQLite 或日志。

0.1.0 的 Launcher 没有应用在线更新的能力,因此第一次升级到带在线更新的 0.2.0 仍需人工替换一次完整程序;从 0.2.0 开始才可使用上述流程。当前没有 清单数字签名,发布服务器整体失陷不在 SHA256 的保护范围内,签名需要另建工单。

已知风险点(打包工单必须逐项验证):

风险 症状
Qt 平台插件没收集到 启动即报 could not find or load the Qt platform plugin "windows"
qfluentwidgets 资源没收集到 能启动,但界面全白、控件没样式
项目内置 ADB 或配套 DLL 没收集到 启动时弹窗提示,设备操作保持中断
uiautomator2 初始化流程 打包后没有 python -m uiautomator2 init 这条命令,需确认辅助 App 怎么装
杀软误报 空白版本信息的 exe 最容易被拦。必须给 exe 加图标和版本信息(产品名、版本号)。根治需要代码签名证书
装到 C:\Program Files\ 无写权限,数据被重定向到 VirtualStore,设置改了不生效

具体的 PyInstaller 参数和 hook 配置跟依赖版本强相关,不在本文档固化, 由打包工单在真实 Windows 环境上验证后写进归档。

9. 非目标

  • 不开发 Admin 管理界面或 Admin 数据库。
  • 不自动注册、登录或绕过拼多多验证码、风控和安全机制。
  • 不自动完成支付;当前采购范围止于创建订单并获取订单信息。
  • MVP 不支持一台 Client 同时并行控制多台设备。
  • 不保证拼多多未公开界面在所有版本中保持兼容,必须通过版本化适配和诊断产物处理变化。

10. 非功能需求

  • 所有网络、数据库长操作和 uiautomator2 操作不得阻塞 Qt 主线程。
  • 同一设备同一时间只执行一个任务。
  • 任务领取、结果提交和采购操作必须具有去重或幂等保护。
  • 结果先本地持久化,再提交 Admin;Admin 暂时不可用不能导致重新采购。
  • 时间在数据库和接口中使用带时区的 ISO 8601,内部优先使用 UTC,界面转换为本地时间。
  • 金额以人民币分的整数保存,禁止使用浮点数作为业务金额。
  • 任务、日志和诊断信息不得包含密码、访问令牌、Cookie 或不必要的个人信息。

11. 待确认事项

这些还没定下来,但不许因此停工。先按"临时默认值"做,等定了再按工单改。

# 待确认什么 临时默认值(先这么做) 定下来之前会卡住什么
1 Admin 的认证方式和 Client 注册方式 Mock Gateway 不做认证;HTTP 实现预留 Authorization: Bearer 请求头,token 从设置读 只卡真实 HTTP 联调,不卡 MVP
3 价格允许偏差 默认 0 分(即实际价格必须等于预期价格才继续),可在设置页改 不卡。先按最严的来
4 是否存在颜色、尺码之外的第三个规格维度 按"可能有任意多个维度"实现,用 dimensions 数组,不要写死两个 不卡。写死才会返工

第 4 条特别注意:即使目前见到的商品都只有颜色和尺码两个维度,也不许把数据结构写成两个固定字段。见 03 数据模型 §8.1。

已定案(不再是待确认项):

  • Admin 新建采购任务固定为 execution_mode=live,不再提供演练选择;Client 不提供手工 live 授权,身份、已选 Android 设备和真实采购 Adapter 就绪时自动声明 live 并领取任务。
  • Admin 只分配任务给指定 Client,Client 不读全局任务池。见 04 §5.1。
  • 没有租约、没有心跳、没有状态回查。 任务流程只有领取、提交结果、提交失败三个调用;设置页另有无任务副作用的幂等 Client 登记调用。见 04 §1、§4.1。
  • 本地只存已领取任务,已完成任务永久保留。诊断产物(截图、XML、日志)仍按 diagnostics.retention_days 清理,两者不是一回事。见 03 §3.1。