Files
cmbuyer/docs/03-tech-stack.md
T

17 KiB
Raw Blame History

技术栈(Tech Stack)

「用什么」的统一速查表。选型与理由在此集中维护;「怎么把它们搭起来」见 架构设计。未定项必须标为待定,不要让 agent 在代码里自行决定。

本项目是双产品结构:采购服务位于 admin/,采购工具位于 client/;两套技术栈独立, 通过 HTTP 契约耦合。

一、采购服务(网页端,admin/,Go)

维度 选型 状态 理由 / 说明
语言 Go 1.23+ 已定 单二进制部署,前序项目同栈,无运行时依赖
HTTP 框架 gin 已定 前序项目已验证;路由、中间件、绑定够用
页面渲染 Go html/template(服务端渲染) 已定 管理后台交互密度低,不引入前端构建链
UI 样式 手写 CSS,单文件 已定 无构建步骤;原型见 docs/design/
数据库 SQLite(mattn/go-sqlite3) 已定 单机部署,单写入者;并发压力低
迁移 goose 已定 前序项目已验证
管理端鉴权 Session Cookie + CSRF Token 已定 服务端渲染的标准做法
设备端鉴权 Bearer Token + 设备绑定 已定 桌面端是机器身份,不用 Cookie
对象存储 本地文件系统 + SHA-256 寻址 已定 MVP 单机;接口留抽象以便后续换 S3
测试 go test 已定 标准库足够
部署 单二进制 + 数据目录 已定 运营电脑本机运行

二、采购工具(Windows 桌面端,client/,Python)

维度 选型 状态 理由 / 说明
语言 Python 3.11+ 已定 AI 生态原生;uiautomator2 官方语言
真机控制 uiautomator2 已定 前序项目真机验证过选规格与订单确认页读取
传输 ADB(USB 或 WiFi) 已定 uiautomator2 3.x 走 adb 通道,ip:port 与 USB serial 同等对待
桌面 GUI PySide6 已定 前序项目已验证;执行员需要看设备状态和批次进度
截图处理 Pillow 已定 判断页面是否渲染完成,避免保存白屏壳层
HTTP 客户端 标准库 http.client 已定 只直连 127.0.0.1:8080;不读代理、不跟随重定向、不做隐藏重试或连接池
可恢复状态 标准库 sqlite3(WAL / FULL) 已定 request/slot 先落库再 HTTP;每个操作独立连接,恢复读取使用单一事务快照
秘密保护 Windows Current User DPAPI 已定 token 原始 32 字节只以绑定 profile+device/attempt context 的密文 BLOB 入库;非 Windows 不降级
单实例 Windows Global\ named mutex 已定 以规范数据库路径 hash 命名,先于 DPAPI/SQLite 取得,覆盖同用户跨 session
Excel 不引入 已定 Excel 解析移到采购服务;采购工具不再直接读表
测试 unittest(标准库) 已定 前序项目 171 项测试均用标准库,无需 pytest
打包 pyinstaller 已定 交付给运营电脑;开发期依赖

三、AI 辅助(P1,MVP 不启用)

维度 选型 状态 说明
调用位置 采购工具 已定 PC 有算力;改 prompt 不需要重新打包
provider 待定 待定 需先确认预算与合规;不得由 agent 自行选定
凭据存储 采购工具本机配置文件,不入库、不上传 已定 采购服务不保存、不代理、不下发任何模型凭据
输入 待 V2 单独定义 待定 MVP 不调用 AI;内部原始截图许可不自动扩大到外部模型

四、决策记录与演进

  • 不做前端框架。 管理后台的交互密度不值得引入构建链和状态管理。若将来出现复杂 实时视图再评估,届时以整页替换为单位迁移,不做半 SPA。
  • SQLite 而不是 Postgres。 MVP 单机、单写入者。出现多实例或跨机访问需求时再迁移; 数据访问层不得写死 SQLite 方言。
  • Excel 解析放采购服务而不是采购工具。 建单入口集中在一处才能统一审计。代价是要在 Go 侧重写表头校验和行级报错,不能直接复用前序项目的 Python 实现。
  • 采购工具不持有业务权威。 金额上限、授权有效性、任务状态流转的判定权在采购服务; 采购工具本地校验只作为第二道防线,两边不一致时一律转人工。
  • HTTP 不做自动重试。 claim/renew/evidence 的重放权属于持有 durable 幂等槽的恢复门面;底层每次 方法最多一个请求。401 修复 Bearer 后、结果不明或重启恢复都必须复用原 key 与原 body/file。
  • 客户端 SQLite 只保存恢复事实。 append-only session/claim/renew/evidence history 不是服务端任务 权威;它的作用是阻止崩溃、并发或状态损坏导致第二次领取、换图或换 key。
  • 不引入 pytest / 不引入 ORM。 同一职责不并存两套方案。

五、构建与运行命令

init.ps1 已由 T-003 在 Windows PowerShell 实际验证:它优先复用合规的既有采购工具虚拟环境 (本机实际为 Python 3.12);仅在虚拟环境不存在时,才从 Python Launcher 的已安装版本中确定性选择 最高的 Python 3.11+ 创建它,以 editable 方式安装采购工具,并跑两端 离线门禁。桌面 GUI 和真机流程不属于该入口的验收范围。

用途 采购服务(admin/) 采购工具(client/)
安装依赖 go mod download Windows 运行 ./init.ps1;它复用合规 venv,或选择 Python Launcher 中最高的 Python 3.11+ 创建 venv 后执行 pip install -e .
本地开发 go run ./cmd/server .\.venv\Scripts\python.exe -m cmbuyer_client
构建 go build ./... .\.venv\Scripts\python.exe -m pip wheel --no-deps . --wheel-dir <输出目录>,再运行 .\.venv\Scripts\python.exe scripts/verify_wheel_metadata.py <wheel 路径>
测试 go test ./... .\.venv\Scripts\python.exe -m unittest discover -s tests -t .
静态检查 go vet ./... .\.venv\Scripts\python.exe -m compileall -q src tests scripts

T-101 设备基线取证(2026-08-04 已完成人工双通道验收)

client/scripts/capture_device_baseline.py 只允许对手工明确填写的 ADB serial 做连接前核验、 设备型号 / Android / 拼多多版本读取、截图和 dump_hierarchy(compressed=False)。它不打开商品、 不读取页面判据,也不执行采购、下单或付款动作。多个在线通道必须完成 getprop 物理身份比对: 同一手机 USB + WiFi 同时在线,或任一在线通道的身份读取失败,都会 fail closed,不能随机继续。

人工验收前,先由人把手机切换到不含收货地址、手机号、支付信息或其他无关隐私的安全页面,再在 adb devices -l 中手工复制一个在线 serial;USB 和 WiFi 分别验收,且每次只保留一个通道在线。 WiFi 通道必须由人先行建立;脚本禁止 adb connect、adb disconnect 或自动重连。以下命令中的尖括号 必须替换为该次人工确认的实际 serial,不能省略或改成自动选择:

# 仓库根目录;先手工确认设备状态,命令本身只读 ADB 清单
D:\Portable\adb\adb.exe devices -l

# USB:粘贴该次 devices -l 显示的 USB serial
.\client\.venv\Scripts\python.exe client\scripts\capture_device_baseline.py --serial <USB_SERIAL> --output-dir "$env:LOCALAPPDATA\cmbuyer\artifacts\T-101\usb-baseline" --timeout 10 --adb D:\Portable\adb\adb.exe

# WiFi:由人先建立 WiFi ADB 通道、断开 USB 后,粘贴该次 devices -l 显示的 WiFi serial
.\client\.venv\Scripts\python.exe client\scripts\capture_device_baseline.py --serial <WIFI_SERIAL> --output-dir "$env:LOCALAPPDATA\cmbuyer\artifacts\T-101\wifi-baseline" --timeout 10 --adb D:\Portable\adb\adb.exe

成功时输出目录仅包含截图、完整 XML 与不含页面正文的 manifest.json(设备元数据、通道、时间、 文件 SHA-256 和 serial 哈希)。--timeout 约束 ADB 命令、ADB socket 及 takeScreenshot / dumpWindowHierarchy(compressed=False, max_depth=50) 的公开 JSON-RPC 调用;uiautomator2 初始化仍有 上游固定启动上限。截图 Base64 仅兼容 RPC 返回值中的空格、TAB、CR、LF,其余字符 仍严格拒绝。XML 仅留在本机明确指定的证据目录;人工必须先在本地检查截图/XML,再只记录路径 和哈希,不得把原始证据提交 Git。USB 与 WiFi 已分别由人完成取证和隐私检查,设备型号、Android、 拼多多版本、产物路径及 SHA-256 已记录到 T-101;上述命令保留用于可审计的复现与排障。

T-102 按链接打开商品取证(2026-08-04 已完成人工真机验收)

client/scripts/capture_product_open.py 只接受 https://mobile.yangkeduo.com/goods.html?goods_id=<纯数字> 的唯一 canonical 表示。解析后由 goods_id 再次重建 URL,并以参数数组执行显式限定 com.xunmeng.pinduoduo 的 Android VIEW intent;没有任意 URL、任意 shell 或其他 App 控件操作入口。运行拼多多版本必须精确等于 T-101 已 取证的 8.17.0,版本失配会在 intent 前停止。

# 仓库根目录;USB 或 WiFi 每次只保留一个通道在线,再手工复制该次 serial
D:\Portable\adb\adb.exe devices -l
.\client\.venv\Scripts\python.exe client\scripts\capture_product_open.py --serial <SERIAL> --url "https://mobile.yangkeduo.com/goods.html?goods_id=<GOODS_ID>" --output-dir "$env:LOCALAPPDATA\cmbuyer\artifacts\T-102\product-open-<GOODS_ID>" --timeout 10 --adb D:\Portable\adb\adb.exe

脚本在 intent 成功后,以 --timeout 为明确上限只读轮询前台 package;只有观察到拼多多才采集截图与 完整 XML,超时仍 fail closed。这一等待只解决 App 异步切换,不根据 Activity、节点文本或旧项目常量 声称已到详情页。成功目录以原子方式发布,manifest 仅记录 goods_id、canonical URL、 设备/App 非敏感元数据、受限命令摘要、文件路径和 SHA-256,不含原始 serial、Activity 或页面正文。 必须由人本地确认截图对应目标商品并检查截图/XML 无地址、手机号、支付信息或其他无关隐私;原始 证据不得提交 Git。T-102 已由人确认 goods_id 958756616606 对应目标商品并完成截图/XML 隐私检查, 设备、版本、证据路径与 SHA-256 已记录到任务执行记录。

T-103 规格面板状态取证与受控选择

client/scripts/capture_sku_panel_spike.py 是历史人工声明状态的只读取证工具:只采集人已准备好的截图和 完整 XML,不打开链接或面板,不点击、滑动、输入、选择规格或读取价格。它保留的 panel-opened-target-preselected 等枚举只描述旧证据目录,不能作为当前生产 classifier 的事实来源。 运行拼多多版本必须精确为 8.17.0,前台 package 必须是拼多多;目标目录已存在、截图/XML 无效或 超时均不得覆盖已有内容或发布半成品。

T-103 已证明当前衣服商品没有独立「规格/已选」入口。T-110 只批准拼多多 8.17.0、goods_id 937122477375 上经真机确认的底部购买区第一行精确唯一 快要抢光 + 金额 作为受控规格面板入口; 商品内容区同名小字必须零点击,“免拼购买 / 单独购买 / 直接拼成”等其他文案不能凭经验复用。

2026-08-05 五组 live 证据纠正了旧的“面板打开即自动预选”假设:真实初态颜色与尺码均未选,选择 目标颜色后尺码仍未选且不在视口;S 非目标态与恢复 M 目标态的 exact selected 切换已由人确认。 当前实现必须分别匹配未选初态、仅目标颜色态、S 态和 M 态的完整 profile,不得跨状态拼装价格、摘要、 容器或坐标。显示尺码只能由证据绑定、私有、一次且不重试的窄 reveal 能力完成;在取得 reveal 前后 M 均未选的动作证据前,不得实现通用或猜测性滑动。

该动作证据使用独立 client/scripts/capture_sku_reveal_spike.py,不改造历史只读采集器,也不接入 生产 Flow。CLI 不接收颜色、尺码、坐标或手势参数;它只在严格命中“目标颜色已选、尺码隐藏且未选” 后执行一次证据绑定的固定手势,随后只读采集 before/after。M 意外选中、尺码仍隐藏、颜色/摘要漂移、 RPC 结果不明且无法调和或提交区风险均不得发布,成功后保留面板等待人工核对。

PDD 规格面板不可避免显示收货区域和手机号。项目所有者确认 cmbuyer 是内部系统,正式流程允许把 页面原始截图上传采购服务供已登录管理员查看,不做遮罩或裁剪。完整 XML 仍只留在 %LOCALAPPDATA%\cmbuyer\artifacts\...\raw;采购工具运行时在内存中读取当前页面树,但只返回规格、 选中态、价格和页面状态摘要,不把地址或手机号解析为业务字段,也不写入日志、Git 或 Vikunja。

旧 t103-privacy-v5 派生物只保留为历史证据,不能覆盖上述 live 状态。闸门一只在目标颜色与 M 均精确读回后,从已取证滚动态面板的只读当前价角色读取十进制 12.88;原价 29.88、未选初态价格、 详情页数字和底部“提交订单 ¥12.88”都不得成为价格候选。

为加快 MVP,T-103 不再修改或调用截图遮罩器。最小 fixture 从上述本机 live 证据中只提取获准判据节点, 不含地址、手机号或支付凭据;SkuSelectionFlow 在本机实时页面树上读取允许字段,完整页面树不落日志、 不上传。T-204 直接接入原始截图上传,资产标记 privacy_tier=INTERNAL_RAW,只允许 已认证设备写入、已登录管理员读取;不上传 XML,不允许外部支付页或支付凭据。

这个范围调整不授权 T-103 调整数量、进入确认页、点击“提交订单”或触碰支付控件。T-103 最终由人 运行受控入口、目标颜色、一次 reveal、目标 M、读价和一次 Back 的完整真机验收;生产业务最终为单趟, 但能力仍按 T-103 / T-105 / T-107 分段取证。

Windows 的标准入口是仓库根 ./init.ps1。它要求 Go、两端目录及其哨兵文件存在;已有合规 client/.venv 时,所有采购工具检查与 validator 都使用该解释器。只有 venv 不存在时,才从 py -0p 枚举的版本中确定性选择最高的 Python 3.11+ 创建它;没有合规版本时明确失败,绝不回退默认 python。 它执行 admin 的 go mod download / test / vet / build、client 的 editable install / 包导入 / unittest / compileall。既有 client/.venv 若不是 Python 3.11+ 会明确失败,不会自动覆盖用户环境。init.sh 保持等价门禁语义;WSL 或 Unix 环境缺少所需 Go、Python 3.11+ 或项目文件时必须非零退出,语法通过或 Unix 成功不构成 Windows / 真机验收。

采购工具的运行时依赖只维护在 client/requirements.txt。client/pyproject.toml 通过 setuptools 动态读取该文件生成 wheel 的 Requires-Dist,避免两份依赖列表漂移; scripts/verify_wheel_metadata.py 会验证生成 wheel 已声明全部这些依赖。

当前 Windows 默认 python 仍可能指向 Python 3.10,不满足采购工具的 Python 3.11+ 下限; 不得把未加版本选择器的 python 当作采购工具命令。统一入口优先使用既有合规 venv,仅在需要创建时 自动选择 Launcher 中最高的合规版本;本机 Python 3.12 与 3.14 均已验证,主工作区当前选择 Python 3.14。桌面 GUI 与真机流程不属于 T-003 验收范围。

Windows PowerShell 差异:

# Go 工具链固定用本机版本,避免自动下载
$env:GOTOOLCHAIN = "local"

六、验证矩阵与构建产物

层级 触发条件 命令 / 操作 通过证据
任务相关验证 每个任务必跑 改 admin/ 跑 go test ./... + go vet ./...;改 client/ 跑 .venv\Scripts\python.exe -m unittest discover -s tests -t . + .venv\Scripts\python.exe -m compileall -q src tests scripts 退出码 0、测试数
完整门禁 发布前;修改 HTTP 契约、数据库 schema、依赖或构建配置时;跨端改动时 两端全部测试 + 静态检查 + 两端构建 退出码 0、测试数、产物路径
人工 / 设备验收 任何涉及真机页面判据、下单动作或付款路径的任务 连接真机执行,记录设备型号、Android 版本、拼多多版本、goods_id、截图与页面 XML 路径 人工结论 + 证据文件路径
  • 任务相关验证不能省略。跨端契约改动必跑完整门禁——两端会同时坏。
  • 真机验收只能由人完成。 agent 不得据自身判断把需要真机的任务标为 DONE。
  • 真机验收必须记录拼多多 App 版本。页面判据与版本绑定,换版本即失效。
  • 交付采购工具安装包时记录产物路径与 SHA-256;版本号不能单独证明部署的是本次构建。

七、依赖纪律

  • 新增第三方依赖前,先说明用途、替代方案和维护成本,并同步本文。
  • 依赖随任务按需引入,不为「将来可能用到」提前添加。
  • 不确定的技术选型先更新本文,再进入代码。
  • 不允许同一职责并存两套框架。