docs: document purchaser device ownership (#328)

QiuSW
2026-09-21 10:21:38 +08:00
parent 275caa3067
commit 753d035495
64 changed files with 1 additions and 6712 deletions
File diff suppressed because it is too large Load Diff
-501
@@ -1,501 +0,0 @@
# 架构与代码地图
## MVP 架构
```text
go-admin-ui
│ REST:PDD商品、规则、任务、任务详情
▼
go-admin Server ───────── 数据库
│ ├─ pdd_product
│ REST:注册/心跳/任务 ├─ collection_rule
│ ├─ collection_task(任务+结果)
▼ └─ dimension/value/color_price/sku 子表
Android Portal/Agent
├─ 注册、心跳与设备串行锁
├─ 领取指定任务或空闲领取未指定任务
├─ 执行任务内的 URL 与规则快照
└─ 提交结构化结果或明确错误
```
## 最小闭环
1. 管理员添加 PDD URL,服务端规范化 URL 并提取唯一 `goods_id`。
2. 管理员创建规则;规则创建后立即可用于创建任务。
3. 管理员从一个 PDD 商品创建任务,可指定设备,也可留空等待空闲设备领取。
4. 任务固化 URL、goods_id 和完整规则快照。
5. Android 串行执行并把结果写回同一条任务;规格与 SKU 写入任务结果子表。
6. 同一事务把 `completed` 结果全量覆盖到 PDD 商品最新档案,把 `completed_partial` 明确采集到的字段和规格安全合并;`failed` 不修改商品。
7. 管理员在任务详情查看原始结构化结果,也可在 PDD 商品页面查看和人工覆盖最新商品资料。
## 关键设计
- `pdd_product.goods_id` 唯一;重复时提示商品已存在,不重复新增。
- 规则没有草稿、发布和版本流程;软删除只阻止创建新任务。
- 已有任务不依赖规则当前状态,始终执行自身 `rule_snapshot`。
- `collection_task` 同时保存任务生命周期和结果摘要,不建立 `collection_result` 主表。
- 同一 PDD 商品最多存在一个 `pending` 或 `running` 任务。
- 指定设备只能由该设备领取;未指定任务由在线空闲设备原子领取。
- 重置在事务中清空原结果,保留 URL、goods_id、规则和设备快照,状态恢复为 `pending`。
- Android 本地互斥与服务端原子领取共同保证单设备串行。
- 正式采购在 Android 本地 SQLite 事务中先保存不可逆状态、稳定服务端请求 ID 和脱敏最终确认快照,再通知服务端并只允许一次创建订单点击;重启后只重放服务端标记、只读核单或上报,不再次点击。
- 原始控件树和截图不持久化;Android Agent 端第一期不使用 OCR/VLM。服务端 SYB 登录验证码识别是唯一例外,见 [#48](https://git.ilapage.cn/OPC/goauto/issues/48)。
- 蝦皮规格映射独立保存在 `shopee_product.specs_json`:颜色只能选择关联 PDD 当前可选颜色,允许多个蝦皮颜色共用一个 PDD 颜色。Admin 商品详情的“一键匹配颜色和尺码”在服务端统一计算两个维度:保留当前仍有效的已确认映射,将唯一确定匹配及达到阈值、具备理由且候选仍有效的 AI 匹配,在重新校验规格上下文后于同一事务直接写为 `confirmed`,无需人工确认;低置信度或无结果保持未匹配,Provider 异常或上下文变化时不写入任何本次结果。PDD 目标规格消失后页面标记失效,映射保存和采购创建均拒绝继续使用;无需新增数据库表或 Android 能力。
## 最小业务数据
| 表 | 必要内容 |
|---|---|
| `agent_device` | 唯一 `install_id`、设备信息、状态、Token 摘要、版本化能力、最后心跳 |
| `agent_app_release` / `agent_app_release_setting` | 私有 APK 的版本、SHA-256、大小、说明、创建人,以及当前版本单例指针 |
| `pdd_product` | 唯一 `goods_id`、当前 URL、标题、店铺、数字销量/评价、三态状态和通用多维 `specs_json` 最新值 |
| `collection_rule` | `id`、`name`、`content_json`、创建/更新时间、`deleted_at` |
| `purchase_rule` / `purchase_rule_setting` | 采购规则历史与当前规则单例指针;新任务复制规则快照,运行时无常量回退 |
| `collection_task` | 商品/设备外键、五态状态、URL/goods_id/规则快照、租约、结果摘要、错误和时间 |
| `collection_dimension` | 任务、维度键、名称、排序 |
| `collection_dimension_value` | 维度、值、排序 |
| `collection_color_price` | 任务、颜色、该颜色统一使用的整数分价格 |
| `collection_sku` | 任务、整数分价格、可用性、完整性 |
| `collection_sku_value` | SKU 与规格值的多对多关联 |
| `pdd_account` | 可选的账号调度引用,只保存名称和状态,不保存凭据 |
| `purchase_task` | 商品外键和不可变快照、执行模式、状态/租约 guard、价格边界、订单、人工支付复核、物流与回填事实 |
| `purchase_task_attempt` | `task_id + attempt_id` 幂等执行记录、阶段、规则哈希、固化规格决策和结构化错误 |
> #241 新增:`server/app/goauto/purchase/order_backfill.go` 与 `order_backfill_handler.go` 提供 `POST /api/agent/v1/purchase-tasks/order-backfill`(Device Token 鉴权,逐条事务、逐条结果);`server/app/goauto/models/purchase_order_guard.go` 在 `PurchaseTask.BeforeSave` 上全局强制订单号唯一,以 `purchase_rule_setting` 单例行串行化订单号分配,避免新增唯一索引迁移;`purchasecontract.ParseAddressSuffix` 为 `AddressSuffix` 的反解,通过回比而非负向前瞻实现(Go RE2 不支持前瞻)。
| `ai_matching_setting` | 唯一单例的启用状态、OpenAI-compatible Base URL、模型、超时、内部部署明文 API Key 和更新人;仅管理员设置接口可以读取该字段 |
`collection_task` 的状态仅为 `pending`、`running`、`completed`、`completed_partial`、`failed`。设备身份和心跳表属于 Agent 领取任务的必要基础,不承载 PDD 业务数据。
设备以 JSON 数组保存最后一次注册或心跳上报的版本化能力。v1 任务兼容未上报能力的旧 Agent;v2 任务在创建指定设备任务、获取下一任务、领取和开始四个边界重复校验能力,避免旧 APK 执行未知规则。
数据库使用两个可空 guard 列表达跨数据库唯一约束:活动任务的 `active_slot=1`,运行中设备的 `device_run_slot=1`;终态记录对应列为 `NULL`。复合唯一索引据此保证同商品最多一个活动任务、同设备最多一个运行中任务,同时允许保留任意数量的终态历史任务。状态与 guard 列还有数据库检查约束,必须在同一条状态变更语句中更新。
采购表使用同一 guard 思路:`purchase_task.active_slot` 保证同一 SYB 明细最多一个活动任务,`device_run_slot` 保证设备串行,`account_run_slot` 在账号已知时保证账号串行。账号未知是合法状态。正式任务必须引用 SYB 的规则由模型钩子和后续创建服务双重校验;MySQL 8.4 不允许 `syb_product_id` 同时参与带参照动作的外键和跨字段 CHECK,因此不在该列添加数据库 CHECK。
## 配置分层
配置分三层,下层覆盖上层:
| 层 | 文件 | 是否进 Git | 放什么 |
|---|---|---|---|
| 1 默认值 | `server/config/settings.yml` | **是** | 应用配置和非机密运维参数(`extend.syb` 的 `baseurl` / `pagesize` / `maxmatches` / `ocrurl`) |
| 2 部署本地值 | `config.yaml`(仓库根) | **否**(`.gitignore`) | 数据库、端口、顺云宝凭据 |
| 3 覆盖值 | `GOAUTO_*` 环境变量 | — | 服务、容器和 CI 用;优先级最高 |
`[必须]` **凭据只允许出现在第 2、3 层。** `settings.yml` 被 Git 跟踪,任何时候都不能往里写账号密码。
服务端自己读第 2 层(`config/local.go` 的 `ApplyLocalConfig`),查找顺序为 `GOAUTO_CONFIG` 指定的路径 → 当前目录 `./config.yaml` → 可执行文件同级目录。**找不到不是错误**:容器场景只用环境变量,本来就没有这个文件。回调注册在 `ApplyEnvironment` 之前,环境变量因此始终有最后决定权。
`[必须]` 第 2 层的标量按 YAML 原始类型读取后统一转字符串。纯数字密码不加引号会被 YAML 读成整数,这里不能因此启动失败——但仍应加引号,否则前导零会丢。
开发启动脚本把 `config.yaml` 的路径通过 `GOAUTO_CONFIG` 传给服务端,不在 PowerShell 里重复解析 YAML。
## 前端伺服
`web/.env.production` 的 `VUE_APP_BASE_API` 为空,即生产构建发的是相对请求,**前端与接口必须同源**。服务端在 `dist/index.html` 存在时伺服构建产物并为 history 路由回退到 `index.html`(`app/admin/router/spa.go`)。
`[必须]` 回退只对非接口路径的 GET 生效。给写错的接口路径回 200 + HTML,客户端看到的会是 JSON 解析错误而不是 404。
`[必须]` `dist` 不存在时不注册 `NoRoute`。开发环境前端跑在 vite 独立端口上,装了回退会把每个真 404 变成一张 HTML 页。
## 已建立的工程入口
| 功能 | 当前目录 |
|---|---|
| 服务端基线 | `server/`(go-admin v2.3.0) |
| 最小闭环数据模型 | `server/app/goauto/models/` |
| 数据迁移与约束测试 | `server/app/goauto/migrations/` |
| go-admin 迁移注册 | `server/cmd/migrate/migration/version-local/1786700000000_goauto_schema.go` |
| 设备注册、认证、停用与吊销 | `server/app/goauto/device/` |
| PDD 商品与规则 | `server/app/goauto/product/`、`server/app/goauto/rule/` |
| PDD 商品档案增量迁移 | `server/cmd/migrate/migration/version-local/1786700500000_pdd_product_archive.go` |
| 虾皮商品档案与规格映射 | `server/app/goauto/shopeeproduct/`、`web/src/views/goauto/shopee-products/`;Admin 按确认原型提供颜色下拉人工映射、确定性尺码批量预览、失效提示和单一保存入口,SYB 快捷入口可突出目标颜色;不修改两端原始规格 |
| 虾皮商品档案增量迁移 | `server/cmd/migrate/migration/version-local/1786700600000_shopee_product_archive.go` |
| SYB 商品明细导入、解析与虾皮档案合并 | `server/app/goauto/sybimport/`(解析、幂等落库、同步编排、导入端点、管理端 API 和 Admin 页面均已实现) |
| 顺云宝(SYB)ERP HTTP 客户端与登录会话 | `server/app/goauto/sybclient/`(登录、OCR 验证码、会话缓存、列表与明细读取;见 [SYB-ERP-Interface-Contract](SYB-ERP-Interface-Contract)) |
| SYB 商品明细增量迁移 | `server/cmd/migrate/migration/version-local/1786700700000_syb_product_import.go` |
| SYB 店铺准入、发现与过滤 | `server/app/goauto/sybshop/`、`server/app/goauto/sybimport/`;迁移 `server/cmd/migrate/migration/version-local/1786700900000_syb_shop.go` |
| SYB 后台导入任务、进度、单任务互斥与启动恢复 | `server/app/goauto/sybimport/sync_run.go`、`sync_run_handler.go`;表 `syb_sync_run`,迁移 `server/cmd/migrate/migration/version-local/1786701000000_syb_sync_run.go` |
| SYB 每小时自动同步 | `server/app/goauto/sybimport/import_handler.go` 提供手动/定时共用的 `StartImport` 服务;`scheduled_job.go` 通过 go-admin 定时任务调度,固定按 Asia/Shanghai 取今天和昨天;任务注册迁移为 `server/cmd/migrate/migration/version-local/1786701600000_syb_hourly_sync_job.go` |
| 采购任务数据与类型化规则契约 | `server/app/goauto/models/purchase.go`、`server/app/goauto/purchasecontract/`;迁移 `server/cmd/migrate/migration/version-local/1786701100000_purchase_contract.go` |
| 采购任务单条/批量预检与创建、租约、attempt 幂等、Admin 只读查询和人工处置状态机 | `server/app/goauto/purchase/`;`batch-preview` 通过批量预加载 SYB、蝦皮、PDD 与最新任务执行快速只读预检,不访问 AI;`batch-create` 仍按最新数据逐项完整复核并可在必要时调用 AI;既有追加迁移为 `server/cmd/migrate/migration/version-local/1786701200000_purchase_state_machine.go` |
| 采购规格标准化匹配与 AI Provider 设置 | `server/app/goauto/aimatching/`;服务端先做繁简、空白/全半角/大小写和公斤/斤的唯一确定性匹配,再按需调用单一 OpenAI-compatible Provider;`1786701300000_ai_matching_setting.go` 创建设置表,`1786701400000_ai_matching_setting_plain_api_key.go` 将原加密列迁移为内部明文 `api_key`,仅管理员读取 |
| 任务领取、结果、同任务 attempt 重采与删除 | `server/app/goauto/task/`;`collection_task.attempt_number` 保存当前次数,`collection_task_attempt` 归档旧终态执行,增量迁移为 `server/cmd/migrate/migration/version-local/1787983500000_collection_task_attempt.go` |
| 管理端基线 | `web/`(go-admin-ui v3.0.0) |
| 管理端闭环页面 | `web/src/views/goauto/` |
| 备货采购服务端路径 | `purchase_task.task_type` 与迁移 `1787885300000_stock_purchase.go`;`POST /api/admin/v1/purchase-tasks/stock` 由 `server/app/goauto/purchase/service.go` 校验 PDD 当前可选规格并固化 `direct_select`,复用既有任务状态机和设备/账号互斥;重试、替换和 SYB 回填显式排除 `stock` |
| Admin 采购任务列表、详情与人工处理 | `web/src/views/goauto/purchase-tasks/`、`web/src/api/goauto/purchase-tasks.js`;创建入口不在本模块 |
| Admin AI 规格匹配设置 | `web/src/views/goauto/ai-matching-settings/`、`web/src/api/goauto/ai-matching-settings.js`;管理员可查看、维护和测试 Provider(包括内部明文 API Key);采购员菜单硬排除,仍只可通过受控 API 查看启用状态 |
| GoAuto 系统菜单与采购员权限基线 | `server/app/goauto/access/` 统一声明 12 个模块、路由元数据和 Admin API 权限矩阵;迁移 `1787885400000_goauto_menus.go` 幂等维护 `sys_menu`、`sys_menu_api_rule`、采购员默认菜单绑定和 Casbin 固定白名单。`web/src/router/index.js` 只保留公共路由,`web/src/store/modules/permission.js` 在登录时根据 `GET /api/v1/menurole` 返回结果注册业务路由与侧栏;退出、凭据异常和切换角色会清除旧动态路由,未授权深链接回退工作台。菜单可见性与 API 授权彼此独立 |
| Admin 失败采购任务批量重试 | `POST /api/admin/v1/purchase-tasks/batch-retry`;服务端 `server/app/goauto/purchase/retry.go` 负责资格判定、逐项幂等创建与部分成功结果,Admin 页面只允许选择服务端标记可重试的行;不修改 Android Agent |
| SYB 店铺管理页面与接口封装 | `web/src/views/goauto/syb-shops/`、`web/src/api/goauto/syb-shops.js`;确认原型快照 `prototypes/49/v1/index.html` |
| SYB 异步导入、商品筛选、当前页采购选择/确认/逐条结果与同步记录页面 | `web/src/views/goauto/syb-products/`、`web/src/views/goauto/syb-sync-runs/`、`web/src/api/goauto/syb-products.js`、`web/src/api/goauto/purchase-tasks.js`;商品列表按店铺名称包含匹配,并支持最多 100 个多行订单号精确筛选;确认原型见 #44 设计证据,导入原型快照为 `prototypes/50/v2/index.html` |
| Android Agent 基线 | `android/app/src/main/java/cn/ilapage/goauto/agent/` |
| Android 无障碍规则执行 | `android/app/src/main/java/cn/ilapage/goauto/agent/automation/` |
| PDD 安全规格入口与评论页误触恢复 | `automation/PddProductDetailCollector.kt`:明确选择语义、底部购买文字、评论上下文排除、一次返回重试和结构化错误;不保存控件树或截图 |
| Android 采购规则解释器与正式地址/订单动作 | `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseRuleContract.kt`、`PurchaseRehearsalExecutor.kt`、`PurchaseLiveAutomation.kt` |
| Android 采购任务、attempt 与 Outbox 持久化 | `android/app/src/main/java/cn/ilapage/goauto/agent/persistence/`;调度入口 `service/AgentForegroundService.kt` |
| 三端统一验证 | `scripts/verify.ps1` |
以上入口均已落地;新增 API、表或执行动作时必须同步更新本代码地图与共享契约。
## cmautobuy 商品离线导入
`server/cmd/import-cmautobuy-products/` 是 #70 的独立单向导入入口,转换逻辑位于 `server/app/goauto/cmautobuyimport/`。它不注册 HTTP 路由、不参与服务启动,也不是数据库 schema migration。
导入器从 cmautobuy MySQL 只读一致性快照读取有效 `pdd_products`、`shopee_products` 和 `shopee_skus`,再写入 GoAuto 的 `pdd_product`、`shopee_product`。旧 PDD 组合 SKU 聚合为 GoAuto 通用维度;同一颜色出现多个不同价格时不猜测价格。旧组合 `spec_mappings`、SYB、任务、订单和其他域数据不进入导入范围。
默认模式是 dry-run;显式 `--apply` 才会在目标 MySQL 单事务覆盖同业务键商品。来源与目标配置都来自未跟踪 YAML,连接串和密码不输出。来源蝦皮的字符串 `pdd_goods_id` 必须通过目标 PDD `goods_id` 换算为数字 `pdd_product_id`,提交前重新校验 JSON、唯一键和关联完整性。
## Android Agent 0.3 设备端外壳(#88)
- Android 入口仍为单 Activity,但从动态单页升级为 AndroidX Fragment + Material `BottomNavigationView` 的四 Tab 外壳:状态、采集、采购、设置;首次启动默认进入状态页,并持久保存最近一次有效 Tab。
- `android/app/src/main/java/cn/ilapage/goauto/agent/ui/` 保存状态页、设置页、任务占位页、无障碍真实就绪检测和共享 UI 组件;采集/采购历史列表与详情由 #90 接入。
- 状态页同时读取系统启用的 AccessibilityService 组件和 `GoAutoAccessibilityService.instance` 绑定事实,区分“未开启”“已开启等待连接”“已开启并就绪”,并显示服务连接、当前任务和最近心跳。
- 设置页维护服务器地址和设备名称,测试连接只访问 `GET /api/v1/health`;保存后沿用当前 installId / Device Token 重新连接。执行任务或测试连接期间表单与按钮禁用。
- 设置页只显示 Token 是否配置,不返回或显示明文;不包含 PDD 账号、采购写操作或支付入口。
- `AgentStateStore` 保存界面所需的当前任务类型/编号摘要;任务结束后由执行服务清除。它不保存控件树、截图、完整规则或凭据。
## Android Agent 任务结束冷却与返回(#89)
- `AgentForegroundService` 在采集结果已提交,或采购结果与 Outbox 已在同一事务安全保存后,建立 5 秒任务结束冷却;不新增第二套队列轮询器。
- 冷却到期仍由现有任务轮询按“采购优先、再采集”依次确认两个队列都为空。任何新任务、网络/服务端异常或本地未解除的不可逆采购边界都会取消返回。
- `IdleReturnCoordinator` 只保存冷却时间、自动化前台包名与无障碍前台切换序号,不保存控件树或截图。用户切换到其他 App 后,即使又返回 PDD,也不会被强制拉回 Agent。
- 返回请求只允许从 Agent 本次自动化停留的 PDD 或受支持浏览器页面发起,并由无障碍服务打开 `MainActivity`:现有 Activity 保持当前 Tab,重建时恢复最近一次有效 Tab;Android 后台 Activity 启动限制导致失败时只提示手动打开,不重复强拉。
- 任务执行和冷却分别使用带超时的亮屏锁;`MainActivity` 仅在运行态标记要求时设置 `FLAG_KEEP_SCREEN_ON`。任务、新任务接管、取消、返回或服务销毁后统一释放,不绕过设备 PIN、图案或密码。
## Agent 任务历史(#90)
- Android 的采集任务与采购任务 Tab 由 `TaskHistoryFragment` 承载,复用搜索、状态筛选、分页、加载/空/失败状态和只读详情能力。
- `AgentApiClient` 调用服务端当前设备历史接口;任务编号规范化由客户端先处理,服务端再次校验。
- 服务端采集与采购模块各自使用 `agent_history.go` 负责 Device Token 设备隔离、最近 30 天过滤、分页和最小化 DTO。
- `purchase_task.actual_unit_price_cent` 是可空字段,只记录 Agent 实际观察到的采购单价;旧任务不回填。
## Agent 受控重新采集(#91、#155)
- 服务端 `task/lifecycle_service.go` 的同一 Reset 事务同时服务管理端和设备端;设备端入口使用显式 Device Token 模式,不能用空 Token 退化为管理端路径。
- Reset 复用原 `collection_task.id`:事务先归档旧终态到 `collection_task_attempt`,递增 `attempt_number`,按来源刷新最新规则快照,再清理主表当前结果/错误/租约并恢复 `pending`。归档以 task + attempt 唯一,reset requestId 也唯一,阻止并发或重放重复递增。
- Admin 来源读取原规则当前存活内容;Agent 当前页来源读取当前手动默认规则。当前页未识别任务可以在下一 attempt 首次绑定,已识别任务仍由 `IdentifyCurrentPage` 的 goods_id 冲突保护限制为原商品。
- `POST /api/agent/v1/collection-tasks/{taskId}/reset` 返回 `taskId`、`attemptNumber`、状态和重放标记;设备归属/在线/忙碌、规则兼容、商品冲突和采购占用在事务内检查。
- Android `TaskHistoryFragment` 对所有失败来源显示“重新采集”;当前页来源复用无障碍、近期 PDD 前台、本地互斥和冷却检查,成功后调用 `AgentForegroundService.start`,执行仍由 `next/claim/start` 完成。Admin 详情抽屉与 Agent 详情都展示 attempt 历史摘要;Agent 接口不返回历史规则快照。
## Agent 受控采购重试(#95)
- 服务端 `server/app/goauto/purchase/retry.go` 的 `AgentRetry` 使用 Device Token 校验任务归属,并复用 Admin `BatchRetry` 的当前档案、不可逆边界、设备能力/忙碌和幂等创建逻辑;旧失败任务不修改,新任务固定原设备。
- `GET /api/agent/v1/purchase-tasks` 与详情通过 `retryable` / `retryDisabledReason` 返回服务端资格结论;`POST /api/agent/v1/purchase-tasks/{taskId}/retry` 只接受 `requestId`,不接受客户端指定设备、规格或规则。
- Android `TaskHistoryFragment` 在采购失败列表和详情按服务端资格显示单任务“重试采购”,二次确认后调用新接口;成功后唤醒既有 `AgentForegroundService` 队列,实际执行仍只走 `next → claim → start`。Agent 不实现批量重试、取消订单、修改既有订单或支付。
## Android 系统辅助功能按钮(#97)
- Android 8.0 及以上的 `GoAutoAccessibilityService` 请求系统 `flagRequestAccessibilityButton`,服务连接后向 `AccessibilityButtonController` 注册单一回调,销毁时注销;服务重连不会叠加回调。
- 单击由系统分配给 GoAuto 的辅助功能按钮时,只调用 `openAgentPreservingTab()`,以前台 `MainActivity` 打开 Agent 并保持最近一次有效 Tab;不领取、创建、重置或重试任务,不打开 PDD,也不改变 5 秒空闲返回规则。
- 不支持或未分配系统辅助功能按钮的 ROM 保持安全降级;项目不创建自定义悬浮窗。
## Android Agent 状态页手动检查(#98)
- `AgentStatusFragment` 使用 Android 原生下拉刷新容器;只有状态页顶部下拉会请求一次立即检查,采集/采购历史页的下拉只刷新记录,两者语义不同。
- `AgentForegroundService.checkNow` 只向既有单线程同步与调度入口提交一个可合并请求;不建立第二套轮询器,也不改变 15 秒自动轮询、采购优先、设备互斥或服务端租约。
- 手动请求在已有同步运行时保留一个待处理标记,当前同步结束后再执行一次;重复手势不会并发请求。
- 结果通过仅限当前应用包的广播返回状态页,区分无任务、采集任务、采购任务、设备忙、配置/认证和网络错误。状态页无障碍信息只读,唯一系统设置入口保留在设置 Tab。
- 手动检查不创建、重置或重试任务,不绕过无障碍与身份校验,不打开 PDD,不修改地址、不创建订单、不支付。
## Agent 任务记录刷新与摘要缓存(#99)
- Android 的采集/采购历史页使用单个 `SwipeRefreshLayout` 包裹既有滚动页,只在列表顶部下拉刷新当前查询;请求中合并重复手势,加载失败页仍保留可见重试入口。
- 设置页保存 1/3/7/15/30 天记录范围(默认 7 天),用当前 Device Token 分页读取采集与采购摘要。两类读取独立结算,支持完整、空、部分和失败反馈。
- `TaskHistoryCache` 使用独立 SharedPreferences 保存当前同步范围的列表摘要,网络读取失败时可显示最近同步摘要;Token 继续只保存在 Android Keystore 保护的设备身份存储中,任务详情不离线镜像。
- 服务端两个 Agent 历史列表查询增加 `days=1..30`,并继续在数据库查询中强制设备隔离、分页上限和 30 天最大窗口;详情读取仍保持原 30 天边界。
- 该链路只读,不接入任务领取、执行、重置、重试、PDD 导航、地址修改、创建订单或支付。
## Agent 当前页面临时采集(#101)
- 服务端入口位于 `server/app/goauto/task/current_page.go`:`CreateCurrentPage` 原子完成设备校验、任务创建、规则快照和租约;`IdentifyCurrentPage` 对 Agent 上报的白名单链接重新校验并最终裁决 goods_id,负责 PDD 商品复用/创建和任务身份绑定;服务端保留受限正文解析作为短链兜底。路由为 `/api/agent/v1/current-page-collection-tasks` 及其 `/{taskId}/identify`。
- 默认规则设置位于 `server/app/goauto/rule/agent_manual_setting.go`,使用单行表 `agent_manual_collection_setting`。迁移 `1787790000000_agent_current_page_collection.go` 增加任务来源、识别幂等字段、可空 PDD 外键和默认规则表,并对旧任务回填 `admin`。
- `collection_task.source` 目前只允许 `admin` / `agent_current_page`。当前页面任务创建时由设备运行槽防并发,识别商品后再参与 PDD 商品活动槽;结构化结果仍写入既有任务及规格子表,不新增临时任务表。失败后通过 #155 的同任务 attempt 机制重新进入队列,不创建第二条临时任务。
- Android 入口仍由 `TaskHistoryFragment` 承载;点击确认时先启动 `AgentForegroundService`,不先把 Agent 退到后台。服务先用 `TaskExecutionMutex` 预占本地串行槽,服务端创建成功后把预占转为真实 taskId;前台已是 PDD 时不执行返回,前台仍是 Agent 且近期见过 PDD 时只尝试一次返回,否则使用不清理任务栈、不带深链参数的 PDD 启动 Intent 恢复现有任务,并在有界等待确认 PDD 前台后才识别身份和调用既有 `PddProductDetailCollector`。
- `CurrentPageIdentityRunner` 负责页面证据、唯一分享/复制入口和面板清理;`ClipboardRelayActivity` 使用独立、不可导出的短生命周期任务在前台读取新鲜剪贴板,完成后移除中转任务并露出原 PDD 页面。`PddShareLinkExpander` 仅在手机侧用无 Cookie、无项目凭据的移动端 GET 有界展开白名单短链,逐跳校验并最多读取 64KB 正文;Agent 不裁决 goods_id。原始剪贴板、链接与响应正文不进入日志或缓存。
- v2 规则新增可选 `currentPageIdentity`(分享/复制别名和三个有界超时),旧 v2 规则由 Android 使用安全默认值;新设备能力为 `collector.pdd.current-page-share.v1`。
- 该路径复用既有结果/失败接口、PDD 最新档案写回、任务结束返回和采集间隔,不调用浏览器导航,不进入任何采购、地址、创建订单或支付代码。
## SYB 档口入库码(#119~#122)
- 数据模型与迁移位于 `server/app/goauto/models/syb_inner_code.go`、`server/app/goauto/migrations/` 和 `server/cmd/migrate/migration/version-local/1786701500000_syb_inner_code.go`;Excel 业务记录、逐件入库码、匹配计划、回写批次、检查点、租约和幂等请求分表保存。
- `server/app/goauto/sybinnercode/` 负责 Excel 解析、物理删除、异步只读匹配、回写预览、全局串行回写、逐件回读确认、结果不明确后的只读复核和启动恢复。
- `server/app/goauto/sybclient/` 提供 SYB 读取与三个入库码写接口;匹配器只依赖只读接口,不能取得写能力。真实写入只由用户确认后的回写批次触发。
- Admin API 注册在 `server/app/goauto/sybinnercode/router.go`,页面位于 `web/src/views/goauto/syb-inner-codes/`,菜单入口为“档口入库码”。
- 服务启动时,未开始的 `queued` 记录释放回可回写,已经进入远端动作的 `applying` 记录转为 `needs_check`,不自动重放写请求。
## PDD 商品替换数据模型(#129)
- `server/app/goauto/models/replacement.go` 定义审计主表 `pdd_product_replacement` 与分项表 `pdd_product_replacement_item`;版本迁移为 `1787885000000_pdd_product_replacement.go`,只新增表,不修改既有表和数据。
- 主表用 `origin_type + origin_task_id` 区分失败采集任务和失败采购任务来源,以 `target_collection_task_id` 保存替代商品采集证据,以 `created_by_device_id` 保存发起设备。当前设备模型没有操作人绑定,因此该字段只能追溯到设备,不能追溯到采购员账号。
- 主表的可空 `active_slot` 与 `source_product_id` 组成唯一索引,使同一源商品跨 SQLite、MySQL 和 PostgreSQL 同时最多存在一条 `active` 替换记录;历史记录使用 `superseded`,不软删除、不提供删除接口。
- 分项表按 `replacement_id + shopee_product_id` 唯一保存本次实际影响集合,并独立记录 `matching / matched / manual_required`、匹配来源、置信度和持久 worker 的尝试/错误信息。某条采购任务能否续做只能读取对应虾皮商品的分项状态,不能读取主表总体进度。
- `server/app/goauto/replacement/` 提供内部登记、幂等冲突校验、来源/采集证据校验、目标有效性、活动记录唯一性和环检测,以及只读审计查询;管理端只读路由为 `GET /api/admin/v1/pdd-product-replacements` 与 `GET /api/admin/v1/pdd-product-replacements/{replacementId}`。Agent 没有直接写入该领域的 HTTP 权限。
## PDD 商品替换生效与规格匹配(#131)
- `replacement.Service.RegisterAndActivate` 在同一数据库事务内锁定受影响采购任务与虾皮商品:虾皮商品改指替代 PDD、清除旧规格映射,待执行/待探测采购任务取消并释放租约与运行守卫;执行中和终态任务的 PDD 外键及全部快照保持原样,旧 PDD 仅置为 `disabled`。
- `pdd_product_replacement_item` 是持久匹配工作项;`pdd_product_replacement_worker_lease` 为多实例全局租约。事务提交后异步唤醒,API 服务启动时恢复遗留 `matching`,最多有限重试,失败转 `manual_required`。
- 匹配复用 `aimatching.Service.Resolve`。确定性 `exact_match` 可直接确认且不要求置信度;`ai_match` 只有在开关启用、置信度达到 `ai_matching_setting.auto_confirm_min_confidence`(默认 0.9)、候选/角色/原因/输入版本全部通过校验时才确认。
- 人工维护虾皮规格映射后,同一事务同步分项状态与主表 `completed` / `completed_partial` 聚合;worker 只更新仍为 `matching` 且商品关联、规格 JSON 未变化的行,不覆盖人工结果。
- 纠错使用 `CorrectAndActivate`:原记录转 `superseded`,新建原始失效商品到新目标的记录,只迁移原 `replacement_item` 冻结的虾皮商品集合,不影响共享中间目标的其他商品。
- 历史采购执行身份继续以 `PDDGoodsIDSnapshot` / `PDDURLSnapshot` 为准;当前采购查询未发现按可变 `pdd_product_id` 聚合历史数据的实现,因此本工单无需改写统计 SQL。
## 创建采购任务异步规格匹配(#148)
- `purchase_spec_match_work_item` 是采购独立持久工作项;模型在 `server/app/goauto/models/purchase.go`,迁移由 `migrations.MigratedModels` 统一创建。
- `server/app/goauto/purchase/match_worker.go` 负责原子领取、租约、30 秒/2 分钟/10 分钟退避、启动恢复、严格自动确认和输入指纹重校验;只写采购任务快照,不写虾皮长期映射。
- `service.go` 在需要 provider 时把 `pending` 任务与工作项同事务创建并立即返回;`lifecycle.go` 在 `Next`、`Claim`、`Start` 三个入口检查工作项,防止未完成匹配的任务执行。
- `match_admin.go`、`admin_query.go` 和 `/purchase-tasks/{taskId}/matching*` 提供状态、重新入队和人工候选选择;Admin 页面在列表和详情展示匹配状态与恢复操作。
## PDD 颜色图片采集与存储(#133)
```text
PddProductDetailCollector
-> 颜色卡片子树内锁定同 content-desc ImageView bounds
-> GoAutoAccessibilityService.takeScreenshot(仅内存)
-> 按 bounds 裁剪并压缩 JPEG
-> 先 POST /tasks/{taskId}/result
-> 再逐张 POST /tasks/{taskId}/color-images
-> task.UploadColorImage 校验任务/设备/颜色/JPEG/上限
-> pdd_product_color_image(PDD 商品 + 颜色唯一,保留最新)
-> static/uploadfile/goauto-color/*.jpg
-> product.Service.Detail 返回 colorImages
-> Admin PDD 商品详情 64×64 缩略图、无图占位与点击预览
```
- 整屏 Bitmap 不进入文件系统、网络、数据库或诊断;截图回调完成后在内存中裁剪,用后回收。
- `pdd_product_color_image` 保存图片路径、类型、字节数、宽高、来源任务与设备;来源任务继续关联其 `rule_snapshot`。同商品同颜色唯一,更新成功后删除被替换的受控目录旧文件。
- 上传处理沿用既有 `/static/uploadfile` 静态能力,但使用独立 Agent 受控接口和 `goauto-color` 子目录,不复用公共上传入口。
- 图片旁路独立于结构化结果状态机。Android 先安全提交结果,再上传图片;任何图片异常只写本地脱敏诊断,不触发失败提交或任务状态回滚。
## Admin GoAuto 分组导航与权限(#142)
- `server/app/goauto/access/modules.go` 是 12 个 GoAuto 页面模块及两个一级菜单组的代码事实来源;本地迁移 `1787885600000_goauto_menu_groups.go` 创建结构性父菜单,并把既有页面菜单直接迁入父组。
- 「采集采购」依次包含:SYB 商品、SYB 同步记录、档口入库码、虾皮商品、PDD 商品、采集任务、采购管理;「采采管理」依次包含:SYB 店铺、采集规则、采购规则、设备列表、AI 规格匹配。
- 迁移保留页面菜单 ID、路由、组件和 API 关联,移除旧的 12 个一级模块根菜单;导航固定为“分组 → 页面”两级,不增加第三级。
- Admin 角色继续通过角色菜单查询取得全部 GoAuto 页面;采购员获得两个父组和除采购规则、AI 规格匹配外的 10 个页面,AI 页面及入口均不可见。
- Web 继续通过 `/api/v1/menurole` 动态生成路由;直接访问组内页面时展开对应父组并高亮当前页面。
## GoAuto 采购员 API 权限启动对账(#156)
- `server/app/goauto/access/purchaser.go` 的 `AdminAPIs` 是 GoAuto 管理接口与采购员授权矩阵的唯一代码事实源;`PurchaserAPIs()` 只筛选其中明确标记为采购员可用的条目。
- API 服务在注册路由和监听端口之前调用 `access.ReconcilePurchaserPermissions`:补齐 `sys_api` 缺失项,并在单一事务内只删除、重建 `casbin_rule` 中 `ptype=p, v0=purchaser` 的策略。其他角色、自建策略和菜单绑定不在对账范围。
- 对账每次服务启动执行且幂等;代码新增采购员接口后无需补丁迁移,重启即可补齐;代码减权后旧采购员策略会被清除。
- 任一数据库对账失败时,API 启动直接返回错误,不注册路由、不监听端口,避免权限矩阵未对齐时继续提供服务。既有版本化迁移保留其历史语义,但不再是运行时权限同步的唯一入口。
## 采购规则与 Agent 发布组件
- `server/app/goauto/purchaserule/` 提供管理员采购规则 CRUD、当前规则切换和运行时加载;迁移 `1787983600000_purchase_rules.go` 新增两张表、原字节播种默认规则,并把“采购规则”放入“采采管理”。
- `server/app/goauto/purchasecontract/` 使用万分比定点值校验和计算 `priceGuard`,默认 0.2 / 1.5 与旧整数公式一致。
- `server/app/goauto/apprelease/` 负责私有 APK 上传、Manifest 解析、哈希、当前版本和双认证下载;迁移 `1787983700000_agent_app_release.go` 仅追加版本表与单例设置表。存储根可由 `GOAUTO_AGENT_RELEASE_DIR` 指定,缺省为服务端工作目录下 `var/goauto-agent-releases`。
- Android `update/` 组件负责启动静默检查、手动检查、私有下载、完整性校验、任务忙碌门禁、FileProvider 和系统安装确认;不参与任务轮询、心跳或前台服务生命周期。
## PDD 商品反向关联与订单继续采购(#161)
- `server/app/goauto/product/related.go` 通过 `shopee_product.pdd_product_id` 与 `syb_product.shopee_product_id` 批量反查;PDD 详情只返回虾皮商品和规格映射摘要,不嵌入订单行。
- `GET /api/admin/v1/pdd-products/{productId}/related-syb-products` 返回统一扁平分页,参数为 `page`、`pageSize`(默认 20、最大 100)、`scope=actionable|all`(默认 actionable)和可选 `shopeeProductId`。管理员和采购员可读,其他角色由 Casbin 拒绝。
- `purchase.Service.ProcessStages` 是 SYB 页和 PDD 关联订单共用的只读阶段入口;批量加载、不调用 AI。当前采购规则缺失或无效时仍返回基础关联、采集和已有任务事实,依赖规则才能创建的行明确标记不可采购。
- Web 的 PDD 商品详情把“创建备货采购”保留在顶部,把“关联订单继续采购”放在关联商品区块;订单行使用统一分页,并复用既有 `batch-preview` / `batch` 创建路径。
## 蝦皮规格自动匹配批处理(#195)
- 服务端在 `server/app/goauto/shopeeproduct/auto_match_batch.go` 复用单商品“一键匹配颜色和尺码”原子服务,定时任务与 Admin 手动执行共用同一批处理入口。
- `shopee_spec_auto_match_run` 保存触发来源、稳定 `requestId`、运行摘要和可空唯一 `active_slot`;活动槽与租约保证多实例、定时和手动同时触发时全局最多一个运行批次。
- `shopee_spec_auto_match_work_item` 按蝦皮商品唯一保存输入指纹、尝试次数、下次尝试时间和逐商品租约。已完成或低置信度/无结果的相同输入不重复调用 AI;规格、关联或 AI 设置更新时间变化后才允许重新处理。
- 迁移 `1788290000000_shopee_spec_auto_match.go` 幂等创建两张表并写入调用目标 `GoAutoShopeeSpecAutoMatch`。系统任务默认关闭,默认 Cron 为每小时第 15 分钟、每批最多 20 个商品。
- Admin 蝦皮商品列表通过异步手动接口启动同一批次并轮询最近运行摘要;该入口仅管理员可用,不创建采购任务、订单,不调用 Android Agent,也不执行付款。
## SYB 异常规格 AI 定时解析(#198)
- `server/app/goauto/sybimport/ai_parse_batch.go` 以 SYB 明细为单位执行“确定性重解析 → 封闭候选 AI 解析”,与 #195 的“蝦皮规格 → PDD 规格”任务保持独立。
- `syb_spec_ai_parse_run` 保存全局批次、活动槽、租约与结构化计数;`syb_spec_ai_parse_work_item` 按 `syb_product_id` 唯一保存输入指纹、尝试次数、冷却和逐行租约。
- `syb_product` 的 `ai_confirmed`、置信度、限长理由、确认时间和隐藏输入指纹记录 AI 确认事实;`parse_status` 仍只记录确定性解析器结果,`manually_confirmed` 仍只代表人工决定。
- Provider 只接收单条 `productSpec` 和关联蝦皮商品的颜色/尺码候选;返回值必须逐字属于对应候选。采购可信门禁接受仍有效的 AI 确认,但候选消失后立即 fail-closed。
## 定时任务持久化执行历史(#199)
- `server/app/jobs/execution_log.go` 为 Exec 与 HTTP 两类调度入口统一记录一次执行生命周期;`server/app/jobs/models/sys_job_execution_log.go` 对应表 `sys_job_execution_log`,保存执行标识、任务/调用目标快照、定时触发类型、开始/结束时间、耗时、`running` / `succeeded` / `failed` / `interrupted` 状态及脱敏错误码和摘要。
- 服务启动时按当前“每个数据库一个调度器实例”的拓扑,把上次进程遗留的 `running` 记录安全收敛为 `interrupted`;任务删除后历史仍保留并可只读查询。
- `GET /api/v1/sysjob/:id/execution-logs` 由 `server/app/jobs/service/execution_log.go`、`apis/execution_log.go` 和 `router/sys_job.go` 提供任务级分页、状态与开始时间过滤;沿用隐藏菜单 `JobLog` 的角色菜单绑定和精确 GET 权限。Web 入口为 `web/src/views/schedule/index.vue` 的单选“日志”按钮,详情页为 `web/src/views/schedule/log.vue`。
- 执行历史明确不保存任务参数、AI Provider 地址或密钥、第三方原始响应和业务原始载荷;当前不提供删除、保留期限自动化、WebSocket 实时流或立即执行动作。
- 追加迁移为 `server/cmd/migrate/migration/version-local/1788357000000_sys_job_execution_log.go`:创建执行历史表、登记只读 API、关联 `JobLog` 菜单,并只给迁移前已绑定该菜单的角色补充精确 Casbin 权限。
## 客户端密钥访问架构(#237)
代码基线 `71f7751`(含用户确认的默认 HTTP/HTTPS 兼容),2026-09-07 完成 Server/Web 代码与隔离测试;本机迁移、管理员菜单写入与 Server/Web 启动已于 2026-09-07 授权完成,真实 HTTP 管理列表和模块目录加载通过;线上仍未部署。
- `server/app/goauto/clientkey` 管理独立密钥、授权及审计;`clientapi/routes.go` 的显式 Inventory 将 `/api/client/v1` 映射到既有业务处理器,绝不转发任意 Admin 路由。
- `clientapi/gateway.go` 默认接受 HTTP 与 HTTPS,无协议开关或转发协议头门禁,按 Bearer 密钥、模块及能力顺序校验,每次请求读取数据库,无授权缓存。`common/clientprincipal` 传递独立客户端身份,不生成或伪装管理员 JWT。
- `client_api_key` 保存名称、随机 256 位密钥的 SHA-256 摘要、前缀、授权 JSON、启用状态、版本、创建/修改人和最后使用时间;完整密钥仅创建响应一次返回。`client_api_key_audit` 保存管理变更和客户端请求元数据,不保存请求正文、查询参数、响应正文或凭据。
- 编辑与停用采用启用状态和 version 条件更新,并与变更审计同事务提交;冲突返回 409。业务执行前先持久化请求审计意图,失败则不执行业务。完成后更新状态;更新失败或进程中断可能留下 status=0,表示结果待核对,不能据此自动重放。
- 部分既有业务操作人字段使用该密钥最近授权管理员的 ID 兼容现有外键;实际调用方以独立审计的 key_id 为准,不能把业务字段当成人工操作证据。
- Admin 页面 `web/src/views/goauto/client-keys/index.vue` 复用创建/编辑授权弹窗;菜单位于“采采管理”,仅管理员可见。新追加迁移 `1788798000000_client_api_key.go` 创建两表及管理员菜单,不改 Android。
## 采购规格面板预滑动兼容(#238)
代码基线 `58a6c1c`,Android 0.9.60 / versionCode 73(构建完成不等同于已安装/发布)。`PurchaseRehearsalExecutor.applyPostAction` 对 `openSpecPanel.swipeAfter` 只兼容解析、不执行机械预滑动,`waitAfterMs` 保留;首趟继续原 `probeSpecs` 遍历,第二趟继续原 `selectSpec` 精确查找与容器内有界滚动。其他动作的后置滑动仍沿用既有执行语义,失败不会被统一忽略。
`GoAutoAccessibilityService.swipePurchase` 的失败分类由 `PurchaseSwipeFailureReason` 枚举提供;共享 `swipeNode` 仅增加可选分类回调,不改变手势目标、轨迹、1500ms 回调等待或其他调用者行为。`GoAutoPurchasePanel` 日志经 `AgentForegroundService` 关联 task、attempt、device 与规则快照哈希,新增预滑动跳过/必需滑动失败标量;不记录节点文字、坐标、原始控件树、截图或凭据。
Server/Web、数据库和任务快照不变;旧 APK 仍有预滑动行为,必须更新 Agent 才生效。相关验证在 `PurchaseRehearsalExecutorTest`,Android 全量测试与 APK 构建入口不变。
## SYB 逐页保存与部分成功(#239)
实现绑定 c6a962d;代码已实现不代表当前线上已部署。每页完整明细在外部请求结束后按页事务保存;页回滚不累计明细/新增/覆盖数,已提交页保留。日期局部读取失败继续下一日期,全局数据库/进度/会话/取消故障停止。当天漂移不在一次运行内重扫;后续运行重新扫描并幂等补齐。
同步状态增加 `partial_success`(部分成功,15 字符,复用现有 varchar(16),无需迁移)。有错误且 created+updated>0 为部分成功;有错误无已提交明细为 failed;完整且无错误为 succeeded(包括无符合店铺的数据)。中断仍为 interrupted,不把中断追认为成功。所有终态沿用活动槽释放规则。
`orderCount` 是已验证页的原始列表读取数量;`detailCount`、`created`、`updated` 为已提交明细及其新增/覆盖数量;`daysProcessed` 是完整通过的日期数,不是已尝试日期数。失败日期/页码/阶段写入现有脱敏限长 errorMessage。部分成功不刷新店铺的完整同步统计。
Web 唯一展示位置为“采集采购 → SYB 同步记录”:列表状态、状态筛选及详情支持部分成功,详情保留已保存数量、错误原因与重新同步补齐提示。定时任务日志只表示异步任务受理,不等于最终业务同步成功。
入口为 `SyncWithShopSnapshot → loadDailyList → importSyncPage`;`server/app/goauto/sybimport/sync_page.go` 封装页事务和提交后计数;`import_handler.go` 判定终态,`sync_run.go` 保存及筛选,Web 复用 `web/src/views/goauto/syb-sync-runs/index.vue`。
## Android 连续临时采集收尾(#250)
当前实现绑定 `37e8b56`(Agent 0.9.72 / 85),用户确认 v5 按次数返回后切回 Agent。仅 Android,无 Admin/API/权限/数据库迁移;取代 v1~v4 首页导航。
- `AgentSettingsFragment` 在采集间隔后复用卡片和 SwitchMaterial;仅开启显示“返回次数”按钮,Material 单选框选择 1~5 次(默认 3),立即保存或取消。成功无常驻反馈,错误/忙碌显示原因。任务忙碌禁用,弹窗选定时再检查。
- `AgentSettingsStore` 在私有 `goauto_agent_settings` 保存 `continuous_collection`(false)与 `continuous_collection_return_count`(3);次数越界或读取错误回落 3,写入失败恢复旧值,无网络加载。
- `requestCurrentPageCollection` 保留原范围判断;`executeTask` 在采集开始固定次数。`executeTaskWhileAwake` 提交结果、结束图片上传后返回 resultStatus;`ContinuousCollectionPolicy.finish` 仅对完整确认成功调用新 `PddCollectionReturnNavigator`。失败/部分成功不导航。冷却间隔不变,连续模式不启动旧 idle-return。
- 删除 `PddCollectionHomeNavigator` 和专属首页/搜索层判据。新导航器仅使用 capture/back、当前包名、openAgentPreservingTab;1~5 次 Back,每次后 500ms,前台离开 PDD 终止剩余 Back;保留风险场景停止。最后打开 Agent 并等待 500ms 验证包名,不验证 PDD 首页。仍在原任务互斥与唤醒锁范围内完成,结束后原 finally 释放。
- `GoAutoCollector` 日志关联 taskId、attemptNumber、deviceId、规则快照 SHA-256;记录 continuousReturn、configuredBacks、固定 reason 和 actions。原因含 count_completed、left_pdd、back_failed、unsafe_page、agent_launch_failed、agent_not_confirmed、navigation_exception、invalid_count;actions 为 Back 尝试数,不代表最终页面。无节点文字、树、截图或个人数据落盘;不扩大采购诊断存储。
- `ContinuousCollectionTest` 覆盖范围/终态、1~5 次、无变化仍按次数、前台变化、风险、动作/启动失败、包名确认和异常;`ContinuousCollectionSettingsTest` 覆盖开关与次数持久化、默认/越界/写入失败。`:app:testDebugUnitTest :app:assembleDebug` 376 项通过。旧首页专属测试随旧行为替换,新版未安装,UI 大字体/横屏、多设备和完整采集真机链路尚未验证。
## SYB 存疑规格候选解析(#251)
实现绑定 `64b4205`,仅 Server `app/goauto/sybimport`;无 API 字段、数据库迁移、权限、并发模式或 UI 变化。
- `parse_candidates.go: ParseWithCandidates` 先运行原 `Parse`,仅 uncertain 时解码并校验已存 SpecsJSON;复用 `closedShopeeCandidates` 提取颜色/尺码封闭候选,拒绝未知有值角色/重复角色。完整候选组合与原文归一比较唯一时确认,其他情况返回原 ParseResult。
- `ApplyDetail` 在加载既有蝦皮商品后、写入规格前调用;`Reparse` 在保留人工/AI 确认保护后加载关联候选调用。新增 ParseResult 私有 matchedCandidates 标记,使 `mergeParsedSpec` 对已经存在的候选直接返回,避免按维度名重复合并或改变映射。
- #198 `ProcessSpecAIParseRun → Reparse` 自然接入确定性优化,仍需 AI 的记录沿用原后续步骤,不新增异步任务/Provider 调用/接口。
- 验证 `go test ./app/goauto/sybimport ./app/goauto/aimatching ./app/goauto/shopeeproduct ./app/goauto/purchase` 和 `go build ./...` 通过;新增候选正反/格式/组合色/单维度、歧义/空值、同步重解析一致、人工保护及无需调用 AI 测试。AI 原有置信度/失败重试测试改用仍需语义理解的合成输入,继续验证原分支。
- 本轮只使用本地测试数据与 httptest AI,未触发真实 SYB 同步、外部 AI、采购、数据库迁移或服务重启;线上及历史数据效果尚待授权验证。
## 蝦皮详情匹配等待与回读(#254)
实现绑定 `9088e6b`(2026-09-10,分支 fix/254-match-loading);已通过合成数据测试,尚未合并 main 或发布线上。
- Server `shopeeproduct.Service.DetailWithAutoMatchBudget` 为详情 GET 附加派生的整次匹配预算,不返回 Provider、Key 等配置。`AutoMatchMappings` 读取 AI 设置 T,使用 2T+10 秒的单个父 context;颜色、尺码、保存及结果回读共用剩余时间。初始配置读取也受有界 context 约束,读取耗时从后续预算扣除。
- 批量定时匹配调用内部 `autoMatchMappings` 并继续使用原 MaxProviderTimeout=600 秒,不随本次详情入口调整;候选建议与其他 AI 功能不改。
- Web `ShopeeProductDetailDrawer` 在提交前以普通 GET 刷新商品与预算,仅 auto-match POST 使用 (2T+20) 秒超时。准备失败/缺少预算时不发 POST;旧 Server 必须先更新,不能只发布新版 Web。
- 加载状态同时绑定商品加载代次和操作序号。关闭/切换立即结束该页面等待状态;同商品重开读取详情,旧响应/旧 finally 不得污染新操作。匹配期间禁止在当前抽屉编辑映射,原人工草稿放弃确认保留。
- POST 未正常返回时只回读详情及关联 PDD,不自动重发 POST;回读失败结束转圈并保留刷新入口。GET 沿用单请求10秒超时,完整结果回读最多两次串行 GET,不是无限等待。
- 验证:Go shopeeproduct/aimatching/clientapi 测试与 go build;Web `node --test tests/unit/shopee-match-lifecycle.test.cjs`,Playwright shopee-match-lifecycle 与 shopee-pdd-picker,受影响 ESLint 与 build:prod。
## SYB 行内一键关联最近采集(#253)
实现绑定 `b1594dc`,2026-09-10;分支 feat/253-one-click-link,包含 #254 依赖。已完成合成数据测试及构建,未合并 main、未部署,不代表当前线上已具备此能力。
- Web `syb-products/index.vue` 在“去关联”旁增加“一键关联+下拉箭头”,共享 `quick-link.js` 页面级状态。`quick-link-device-preference.js` 按当前浏览器来源、API环境、用户ID保存设备ID,独立于批量采购偏好;不存密钥或商品数据。首次主动作选机继续原行,箭头选机只保存偏好。
- Server `shopeeproduct/latest_collection.go` 复用 LinkPDD 的可选 Admin 模式。短事务核对 SYB→Shopee、未关联/规格版本、设备状态,选择并锁定最新 completed/completed_partial 的 agent_current_page 任务及其 PDD;仅完整采集、active且有可用颜色或尺码时更新,WHERE pdd_product_id IS NULL 不覆盖其他操作者的关联。响应保留该事务产生的规格版本及来源task/device/PDD,不能用后续GET替换冻结上下文再自动匹配。
- 不新增表、迁移、路由或权限对账。原手动关联/替换请求不变;共享 Handler 明确拒绝 Client API 使用此跨模块模式,不能借 Shopee-only write grant 读取 SYB/设备上下文。来源任务原规则快照不改,taskId 可回溯既有采集证据。
- 关联后读取现有采购准备;需要人工确认或不具备AI资格时停止自动链路并保留关联,其他情况调用原 AutoMatchMappings,沿用 #254 动态预算及原事务保存/上下文冲突保护。最后刷新实际处理阶段,不调用创建采购/订单接口。
- 运行期间冻结行、商品、手机;前端重复主动作禁用,切换偏好仍只影响未来操作。卸载或 keep-alive deactivated 令旧操作失效,禁止旧回调继续下一步;不声称已经取消服务端在途写入。异常仅有限GET回读和刷新,不自动重发POST。
- 验证命令:Server `go test -p 1 ./...`、`go build ./...`;Web `node --test tests/unit/syb-quick-link.test.cjs tests/unit/shopee-match-lifecycle.test.cjs`、`pnpm exec playwright test tests/e2e/syb-quick-link.spec.ts tests/e2e/shopee-match-lifecycle.spec.ts --workers=1`、受影响文件 ESLint、`pnpm build:prod`。SQLite 单连接并发测试不代替生产 MySQL 行锁验收;真实AI、生产数据、跨设备人工闭环待验证。
## SYB 行内一键替换(#258)
实现绑定 `2b01974`;部署与验收见 #258 发布证据。本节补充 #253,不改变原未关联入口。
- `syb-products/quick-link.js` 复用分体按钮、设备偏好和匹配/回读流程;已关联且允许处理阶段时同位置显示一键替换。先只读预览,固定任务、目标商品及规格版本;不同商品一次确认,同商品不重绑。
- `shopeeproduct/latest_collection.go` 在原 latestCollection 增加可选 replacement 模式。预览和提交均短事务锁定 SYB、Shopee、原PDD、设备、来源采集与目标PDD,核对旧关联及上下文。提交按预览 taskId 读取,不重新挑选后来采集的商品。
- Admin 路由注入 `purchase.ValidateQuickReplacement`,在事务内复用采购准备数据集及处理阶段计算,不调用 AI。缺少校验函数拒绝替换;客户端密钥不注入且仍拒绝 latestCollection。无新路由、权限、表或迁移。
- 关联完成后才执行现有 AI 匹配,不在数据库事务中等待 Provider;同一 SYB 正式采购创建与替换共用 SYB 行锁。历史采购快照不变。SQLite 合成测试覆盖冲突与串行并发请求,不能替代生产 MySQL 多连接锁验收。
## 辅助信息缺失的采集关联(#259)
实现绑定 `e467706`,仅 Server;部署/验收见 #259。替代 #253/#258 中“部分完成一律拒绝”的限制,不改变 completed 原有路径。
- completed_partial 的 missing 必须是非空合法列表且仅包含 salesText、shopName、reviewCount,才进入辅助信息兼容检查;title、规格/价格缺失、遍历截断、SKU数量上限及未知原因仍拒绝。
- `shopeeproduct/partial_collection.go` 核对本次 task 的维度和值、可售SKU的完整标记/规格引用/非负价格、本次颜色价格,以及当前档案可选值由本次有效SKU覆盖,不能借旧档案合并残留的规格或价格通过。真实单维度允许,不虚构另一维度;本次明确报告缺少规格仍拒绝。
- 一键关联、替换预览及确认提交共用验证。失败沿用409 LATEST_COLLECTION_UNAVAILABLE及现有提示,不回退更早采集、不换手机。状态 completed_partial 和 missing 原样保留;辅助字段缺失仍可在任务详情查看。
- 无接口字段、数据库迁移、Android/Web或权限变更;普通人工关联与采购/AI资格不变。不将“允许关联”视为“已匹配或已采购”。
## 采购单标题面板与地址就绪等待(#260、#261)
实现绑定 #260 `3c1ffa4`(执行器回归测试 `7de4318`)、#261 `5ea07f1`,Android 0.9.74 / versionCode87,基于0.9.73。安装及真实采购验收以工单证据为准;不代表 main 的 Android 已整合这些分支。
- 采购仅在安全规格入口点击返回SUCCESS后,记录本次页面包名/Activity作为内存上下文;开始新执行或页面身份改变时清除。`PddScreenParser` 的新增 purchaseEntryContext 默认关闭,仅该采购执行器传入;普通解析和采集原判定保留。
- 当前仍为同一PDD Activity、商品ID非空、没有页面风险、没有选中摘要,且唯一有标题规格滚动区、一个维度有值,同时具有关闭、数量、支付展示区域与下单操作证据时,单标题可识别为NORMAL_SCROLLABLE。只是允许进入既有精确规格探测/选择,不执行下单或付款,不全局认可UNKNOWN,不增加入口重复点击或滑动。
- 地址入口点击原有1000ms保留;地址列表等待增加唯一修改目标要求。点击修改和保存由500ms改为1000ms,其他通用动作间隔不变,无新配置项。
- 修改后不再只见“详细地址”文字就立即写入。最多50次、每次间隔200ms读取(不含读取耗时),检查PDD页面风险;唯一可见启用有内容的详细地址EditText,连续两次路径、边界、内容及Activity相同才继续原输入流程。始终零个/多个或不稳定时失败,不取第一个候选。
- 输入内容生成、输入后回读、保存后退出及返回规格面板、最终复核和单次下单边界不变。新增本机脱敏诊断 addressEditorCandidates / addressEditorPolls 两个整数,与task/attempt/device/ruleHash关联;不记录地址内容、原始树或整屏截图。
- 仅Android;无Server/Web、共享API字段、数据库迁移或权限变化。真实采购、地址写入及订单创建不作为自动安装验证,不执行付款。
### v2 商品入口补点与地址返回等待(#260、#261)
2026-09-10 实现绑定 #260 `713111d`、#261 `d0a82f3`,Android 0.9.75 / versionCode88;真实采购效果以工单验收为准,非 main 合并声明。
- 入口恢复不再把全页面支付方式展示文案单独视为支付页。当前包名/Activity、商品标题和安全入口必须与点击前一致,入口唯一且可用;已打开面板、关闭控件、数量、选中摘要、订单操作、风险/评价页面仍拒绝恢复。支付 Activity 或明确支付、订单、地址文本继续阻止补点。仅允许既有一次重新定位后的规格入口中心手势,随后仍验证面板,不循环、不扩展到地址保存或下单。
- 保存地址后的确认读取、必要时一次返回后的确认读取,增加最多50次、间隔200ms的有界就绪检查,页面捕获耗时另计。连续两次同一Activity满足原地址回读与确认面板条件后继续;完整信息不要求滚动区域。已明确到地址列表时保留原返回路径,不等待列表出现下单按钮;返回后增加1000ms缓冲。
- 就绪检查不修改原地址回读和结构下单入口规则;超时后只对既有识别的面板、唯一可用滚动区域执行原有有限滚动。零/多个区域仍失败,不凭地址入口或未知底部节点授权下单。最终精确规格、数量、价格、地址与单次下单边界不变。
- 私有采购诊断新增布尔 savedAddressMatched、savedPanelMatched;整数 savedLegacySubmitCandidates、savedAddressCards、savedAddressTargets、savedScrollCandidates、savedConfirmationPolls。只记录标量并绑定task/attempt/device/ruleHash,不保存地址、原始树或整屏图。
- CG109 v1现场读取未达到空闲状态,不能确认原失败具体缺哪项证据;v2覆盖迟到确认节点及可观测性,不将合成测试通过冒充真机修复验收。无Server/Web/API/迁移/权限变化,不需要服务器重启。
## 探测到正式采购的单次面板交接(#262)
2026-09-10 实现绑定 `2ea32fd`,Android0.9.76/code89;基于#260/#261,未声明合并main或真实采购验收通过。
- AgentForegroundService通过PurchaseProbeHandoff只在成功spec_probe且结束后新快照仍识别PDD规格面板时保留一次性内存交接。仅taskId/goodsId/ruleHash/deviceId、Activity、前台revision和单调时钟时间,不保留控件节点、地址或选中状态,不落盘;有效期180秒。
- 正式阶段消费时检查同任务/商品/规则/设备、前台revision及时间,消费后即清除。失配/超时/无任务或采集任务调度/结果异常/进程重启均不能沿用;普通手动再试不会继承已消费交接。现有服务端租约、互斥和匹配等待策略不变。
- 有交接时,执行器恢复限定的purchaseEntryContext,最多10次、间隔200ms新读取(捕获耗时另计);连续两次相同PDD Activity且具备既有商品/面板证据才跳过openProduct。空树可有限等待,其他应用/Activity改变/页面风险立即拒绝复用。没有交接时保留原一次判断路径;不把UNKNOWN当成功,不跳过正式精确规格/数量/价格/地址或下单验证。
- 私有诊断新增handoffPresent、productReused布尔值,reusePolls整数及reuseReason固定枚举legacy_evidence/page_problem/page_changed/handoff_ready/evidence_timeout。仍绑定task/attempt/device/ruleHash,无敏感文本。
- 无Server/Web、共享API或数据库变化,不需重启服务器。只有合成测试验证;原CG107/108日志证实两阶段,但未记录旧版重开判断,不把上下文丢失假设宣称为唯一现场根因。
## 人工订单回填金额扩展(#306)
实现绑定 8c01329(2026-09-18),分支 feat/306-order-paid-amount;待验收、未部署。复用 #241/#242 链路,不改普通采购提交和扫描器导航。
- Android automation/OrderBackfillPolicy.kt:BackfillDetailReader 在订单内收集唯一“实付”金额,使用 BigDecimal 精确转换为人民币分;BackfillItem 增加可选 pddOrderAmountCent。
- Android network/OrderBackfillUpload.kt 与 persistence/TaskHistoryCache.kt:上传可选金额,读取并缓存 Server 确认金额和独立警告;不把成功警告改为任务失败。
- Server models/purchase.go:purchase_task.pdd_order_amount_cent 为 nullable BIGINT、非负;与 actual_unit_price_cent 独立。追加迁移 1789800100000_purchase_order_amount.go 使用版本内的局部表模型,仅扩展 purchase_task;历史值 NULL,不回填猜测金额。
- Server purchase/order_backfill.go:原有逐条事务/设备与订单号保护内补齐空金额;相同值幂等,异值保留并警告;已 order_created 同号任务也能补写。purchase/admin_query.go 输出金额。
- Web views/goauto/purchase-tasks/index.vue:复用现有订单 state-card 显示 CNY 实付价格或“尚未获取”;列表结构不变。
- 本次不修改 SYB 客户端或 #305,金额不进入 SYB 请求。
验证覆盖 Android 解析/载荷/回执缓存、Server 幂等/冲突/旧请求和局部 SQLite 迁移、Web 金额/空值展示;真机端到端、实际 MySQL 迁移和发布须另行授权,不能以本地测试替代。
## SYB 单号回填持久队列(#305)
实现绑定 e89de1a(2026-09-18,未部署),仅 Server/Web;Android 复用现有普通结果与人工订单回填。
- purchase/lifecycle.go、purchase/order_backfill.go → ensureOrderWriteback:与订单事实同事务建立 pending,purchase_task_id 唯一;外部失败不撤销已提交的订单事实。
- models/purchase_order_writeback.go:purchase_order_writeback 保存不可变 stock/detail/单号快照、状态、attempt_count、write_started、租约、脱敏错误和完成时间;purchase_order_writeback_command 保存 requestId/输入摘要/逐项接受结果;purchase_order_writeback_lease 保存全局单例租约。追加迁移 1789800200000,不改旧物流字段,不批量排入历史订单。
- purchase/order_writeback.go:批量命令事务/幂等/条件校验和 Admin 状态投影;order_writeback_handler.go/router.go 暴露 Admin 补偿;access/purchaser.go 纳入原采购权限启动对账目录。
- purchase/order_writeback_worker.go:由 cmd/api/server.go 每数据库启动一个3秒轮询 worker;数据库2分钟单例租约使跨进程及不同任务的相同SYB明细写入串行。写前核对当前任务/关联快照,持久化写入起始标记,并验证租约所有者/剩余时间;不持有数据库事务跨网络请求。失联running恢复只回读,unknown不自动调度;其他同目标未确认写入阻止新写。
- sybclient/order_number.go:一次 POST updateDetailPurchaseCode,不跟随重定向、不使用读接口重试策略。会话仅从现有 SessionStore 恢复,不在 worker 自动登录/OCR;外部原始错误不写入任务原因。
- Web purchase-tasks API/页面:共享勾选、按动作筛选、独立状态列/详情及逐项接受结果;付款和物流流程保持原样。
- 验证入口:go test ./app/goauto/purchase ./app/goauto/sybclient ./app/goauto/access ./app/goauto/migrations ./cmd/migrate/migration/version-local;Web tests/e2e/purchase-order-writeback.spec.ts。测试只使用隔离SQLite和fake/httptest,不代表MySQL多实例或真实SYB验收。
## Chrome PDD 订单回填扩展(#316)
- `chrome-extension/` 是独立 Manifest V3 交付单元:popup 只负责配置、启动/停止、状态轮询和逐项结果;content script 只在 `mobile.yangkeduo.com` 的隔离世界中按可见 DOM 串行读取;service worker 负责持久运行状态、聚合冲突和分批提交。
- 列表遍历限定虚拟列表订单卡片的商品行,排除店铺头和支付、确认收货、退款、售后、取消、更多等动作区;详情只展开收货/订单信息。未看到明确列表末尾证据时必须标记未完整扫描,不得宣称全量。
- 客户端密钥与冻结的 Admin Origin 只保存在扩展可信上下文;content script 无权读取。请求禁用 Cookie 与重定向,批次最多 50 条,稳定 UUID 可在 service worker 重启或超时后重放;已确认批次不重复计数。
- 服务端 `POST /api/client/v1/purchase-tasks/order-backfill` 复用 Agent 回填事务校验,但使用独立 client principal,不伪造 Device Token 或 deviceId。Agent 路径仍严格保持设备归属校验及失败后按设备范围回读。
## 下单结果读取及应付总额链路(#325)
分支 fix/325-order-result,2026-09-19,尚未发布及真实下单验收。PurchaseLiveAutomation.readOrderResult 采用25次支付页返回后采样、首次待付款页独立30次采样/最多4次按需滑动;原过渡60次与总90次采样上限保留有界终止。parsePayableAmount只从本次只读标签集合提取明确应付总额。
数据链路:PurchaseOrderEvidence.pddOrderAmountCent → PurchaseExecutionOutcome → service/PurchaseResultPayload.kt → purchase.ResultRequest → lifecycle.go 的 order_created 分支 → 既有 purchase_task.pdd_order_amount_cent。重启只读核单也沿用同一可选字段。复用已有结果Outbox、幂等哈希和订单号唯一性;不新增表、配置、任务或SYB金额字段。未修改我的订单回填、Chrome扩展和Web,不能将旧入口的实付含义描述为本次已改成应付。
## Admin 订单资料补全与一致回填资格(#326)
实现3a2472d(2026-09-19),Server/Web,未合并发布;无迁移、无Android/插件改动。
- purchase/types.go ManualRequest 增加可选 pddOrderAmountCent;manual.go ResolveUnknown 复用既有行锁事务和模型单号保护,扩展为未知结果确认或已创建订单补缺。仅此操作启用新增重放内容校验和事务内 ensureOrderWriteback,其他人工付款/取消/授权流程不新增入队副作用。
- order_writeback.go 的任务资格去掉 order_created 限制;validOrderWritebackTarget 统一有效 stock/detail 边界。OrderWritebackViews 一次批量读取关联而非逐行查询,RequestOrderWriteback 和 worker 执行前再次检查。队列结构、租约、外部写入协议不变。
- web/src/views/goauto/purchase-tasks/index.vue 沿用原详情/弹窗/resolve-unknown API,新增缺字段补录入口、整数分转换、完整性展示及失败保留输入,不增加数据库状态或新的权限资源。旧未知结果请求不带金额继续兼容。
- 回归:server go test -p 1 ./app/goauto/purchase ./app/goauto/clientapi ./app/goauto/sybclient;Web 生产构建及 tests/e2e/purchase-order-writeback.spec.ts 的6项隔离浏览器测试。真实SYB接口用fake/httptest替代,未执行付款、采购或线上写入。
+1 -1
@@ -324,7 +324,7 @@
- 一个 PDD 商品可能被多个虾皮商品共用,因此规格匹配按受影响的每个虾皮商品分项记录。主表只表达总体进度;Agent 展示和“继续采购”资格必须读取当前任务对应虾皮商品的分项状态。
- 选错替代商品时,连续执行 B→C 不等于撤销 A→B,因为 B 可能还关联其他虾皮商品。正确纠错语义是把原 A→B 记录置为 `superseded`,再建立 A→C,并只处理原记录分项中冻结的影响集合。
- 创建请求按 `create_request_id` 幂等;重放时源商品、替代商品、来源类型、来源任务、采集证据和发起设备必须一致,否则返回幂等冲突,不能静默覆盖。
- `created_by_device_id` 只代表 Agent 设备。当前系统没有设备到采购员账号的绑定,多人多机场景若需要个人责任追踪,必须另建工单实现设备绑定操作员。
- `created_by_device_id` 只代表 Agent 设备。设备可由管理员绑定到一个采购员账号;一个采购员可拥有多台设备,一台设备最多归属一个采购员,也允许暂不归属。SYB 商品一键关联/替换按当前采购员选择的归属设备读取该设备最新临时采集,不使用其他账号或其他设备的全局最新记录。
## PDD 商品替换生效与规格匹配(#131)
-87
@@ -1,87 +0,0 @@
# 常见修改指南
## 风险分级
| 等级 | 常见修改 | 处理方式 |
|---|---|---|
| 低风险 | 文档措辞、格式化、不改变行为的测试、单文件且只恢复已有明确行为的 Bug | 范围明确、容易回退且不触及强制建单项时可直接提交;有疑问就建单 |
| 中风险 | API、配置、依赖、跨模块逻辑、数据结构、新组件 | 由 Agent 实现,维护者检查差异并执行验证 |
| 高风险 | 权限、安全、并发、迁移、地址修改、创建订单、支付、删除数据、不可逆操作 | 停止修改,由 Agent 分析并等待人工确认 |
风险由影响范围决定,不按代码行数判断。新页面、重大交互和导航变化还必须先通过设计与原型门禁。
## 增加设备字段
先检查设备表、注册响应、心跳、管理端列表和 API 文档。跨三端字段必须在同一工单修改,并运行服务端、Web、Android 和契约验证。
停止条件:字段涉及设备身份、Token、吊销、在线判定或任务互斥。
## 修改采集字段
依次检查规则输出、Android 结果 DTO、服务端校验、结果表和管理端详情。不能只改数据库或前端;长期字段同步更新业务规则和 API 契约。
停止条件:无法确定完整与部分结果的合并语义,或会删除已有结果。
## 新增或修改规则
规则创建后立即可用。编辑或删除规则不修改已有任务的 `rule_snapshot`;规则软删除后不能创建新任务,已有任务仍可执行。已支持选择器、别名、等待和有限滑动优先放规则;新增通用动作或复杂算法才升级 Agent。
停止条件:规则需要创建订单、支付、任意脚本、OCR/VLM 或点击不唯一候选。
## 新增数据库模型或字段
`[必须]` **两步都要做,缺一不可:**
1. 把模型加进 `server/app/goauto/models/schema.go`,并登记到 `migrations.MigratedModels()`;
2. 在 `server/cmd/migrate/migration/version-local/` 下**新建一个版本文件**(时间戳递增,照抄同目录已有文件的写法)。
只做第 1 步对**全新数据库**有效,对**已有数据库无效**:旧版本号已经记在 `sys_migration` 里,`Migrate()` 不会再次执行,表就是不会出现。而单元测试每次都用全新数据库,所以照样全绿——这个缺口只会在真实环境里暴露成一句 `Error 1146: Table ... doesn't exist`(见 [#48](https://git.ilapage.cn/OPC/goauto/issues/48))。
`[必须]` 模型必须显式声明 `TableName()` 返回单数表名。漏写时 gorm 会静默使用复数,迁移照样成功。
迁移命令跑完会调用 `migrations.VerifyTables` 核对所有表是否都在,缺表时直接以非零码退出并报出表名。
停止条件:修改或删除已有列、需要数据回填、涉及唯一键语义变化。
## 增加错误码
错误码必须包含稳定代码、用户可读消息、是否可重试和建议处理。同步更新 Android、服务端、管理端和 `docs/08-agent-api-contract.md`。
## 修改任务状态
任务状态影响数据库、领取租约、Android 本地状态和 UI 筛选,属于跨端高风险修改,必须有迁移、并发和重复提交测试。
## 修改任务重置
重置只允许用于终态任务,并在一个事务中删除旧规格/SKU、清空结果与错误、恢复为 `pending`。URL、goods_id、规则和设备快照保持不变。
## 修改界面
1. 先判断是否只是纯显示文案;不确定时建单。
2. 小范围布局使用标注截图或低保真图。
3. 新组件记录正常、空、加载、错误、禁用和权限状态。
4. 新页面或重大流程使用 QuantUX 或其他可审阅原型,记录可访问链接、App ID、版本/revision 或确认日期、审核版本识别方式、草稿/确认状态和覆盖范围。
5. 完整原型默认在线审核;只有用户明确要求或项目规则要求时才导出版本化本地 HTML,且不得覆盖已确认快照。
6. 用户确认原型后才实施生产页面,并用浏览器验证主流程和异常状态。
停止条件:原型未确认,或界面文字涉及支付、安全、权限、金额和不可逆操作。
## 更新文档
GoAuto 的长期文档采用 Wiki-first,但同步由长期事实变化触发,不由任务完成触发。无长期影响时只在工单说明原因并跳过本节;有影响时只执行一轮:
1. 在单元工单列出受影响页面。
2. 修改对应 Gitea Wiki 页面,不写入密码、Token、个人数据或生产数据。
3. 通过 API 或页面回读确认正文和 revision。
4. 运行 `python dev_scripts/harness.py sync` 导出核心镜像。
5. 运行 `python dev_scripts/harness.py sync --check` 和 `git diff --check`,再审查差异。
完成第 5 步后,本任务的文档闭环结束;验收时内容未变化不重复同步。标准任务不创建 Wiki 任务归档;既有 `archive` / `export` 只在用户明确要求专项历史快照时使用。
部署命令或常驻服务运维方式变化时,更新项目自己的 `Deployment-and-Operations` Wiki 页面;可从 `Deployment-Template` 复制章节结构,但必须按已验证的 GoAuto 环境改写,不得保留占位生产参数。当前真实部署拓扑未确认时,在工单记录限制,不创建虚假的部署说明。
停止条件:需要删除/重命名 Wiki 页面、修改映射、改变事实来源边界,或映射镜像存在未提交修改;这些必须在工单中单独确认,不得强制覆盖。
## 验收 Agent 修改
至少确认:解决哪个工单目标、修改入口和调用路径、行为变化、测试结果、未验证内容、文档影响、提交哈希和回退方式。只看到“测试通过”不足以验收。
-97
@@ -1,97 +0,0 @@
# 当前 MVP 交付工单索引
## 层级
- [Epic #1:PDD 商品采集最小闭环](https://git.ilapage.cn/OPC/goauto/issues/1)
- [MVP #2:PDD 商品到 Android 采集结果的最小闭环](https://git.ilapage.cn/OPC/goauto/issues/2)
## 活动 Task
| 顺序 | 工单 | 交付项 | 依赖 |
|---|---|---|---|
| T01 | [#3](https://git.ilapage.cn/OPC/goauto/issues/3) | 三端工程骨架与验证基线 | 无 |
| T02 | [#4](https://git.ilapage.cn/OPC/goauto/issues/4) | 最小闭环数据库模型 | T01 |
| T03 | [#5](https://git.ilapage.cn/OPC/goauto/issues/5) | 设备自注册、认证与吊销 | T01、T02 |
| T04 | [#6](https://git.ilapage.cn/OPC/goauto/issues/6) | 心跳与设备在线空闲状态 | T02、T03 |
| T05 | [#7](https://git.ilapage.cn/OPC/goauto/issues/7) | Android 自动注册、心跳与保活 | T01、T03、T04 |
| T06 | [#8](https://git.ilapage.cn/OPC/goauto/issues/8) | 指定/空闲领取、租约与串行 | T02~T04 |
| T07 | [#9](https://git.ilapage.cn/OPC/goauto/issues/9) | 自注册设备列表与任务设备选择 | T03、T04 |
| T09 | [#11](https://git.ilapage.cn/OPC/goauto/issues/11) | PDD 商品与 URL 管理 | T01、T02 |
| T10 | [#12](https://git.ilapage.cn/OPC/goauto/issues/12) | 创建即生效的规则管理 | T01、T02 |
| T11 | [#13](https://git.ilapage.cn/OPC/goauto/issues/13) | Android 规则快照解释器 | T05、T06、T10 |
| T12 | [#14](https://git.ilapage.cn/OPC/goauto/issues/14) | 浏览器到 PDD 跳转与异常 | T11 |
| T13 | [#15](https://git.ilapage.cn/OPC/goauto/issues/15) | 商品字段、规格与颜色价格 | T11、T12 |
| T14 | [#16](https://git.ilapage.cn/OPC/goauto/issues/16) | 单个 PDD 商品任务列表与创建 | T06、T07、T09、T10 |
| T15 | [#17](https://git.ilapage.cn/OPC/goauto/issues/17) | 任务结果持久化与详情 | T02、T06、T13、T14 |
| T16 | [#18](https://git.ilapage.cn/OPC/goauto/issues/18) | 任务重置、失败删除与并发约束 | T02、T06、T15 |
| T17 | [#19](https://git.ilapage.cn/OPC/goauto/issues/19) | 一加最小闭环验收 | T05、T06、T09~T16 |
| T18 | [#20](https://git.ilapage.cn/OPC/goauto/issues/20) | 本地 config.yaml 数据库启动配置 | T01 |
| T19 | [#21](https://git.ilapage.cn/OPC/goauto/issues/21) | PDD 商品详情颜色价格与尺码采集规则 | T11、T12、T13、T17 |
| T20 | [#22](https://git.ilapage.cn/OPC/goauto/issues/22) | v2 规则契约、阶段钩子与能力协商 | T19 |
| T21 | [#23](https://git.ilapage.cn/OPC/goauto/issues/23) | Android PDD 商品详情采集器 | T20 |
| T22 | [#24](https://git.ilapage.cn/OPC/goauto/issues/24) | 内置规则模板与管理端校验 | T20、T21 |
| T23 | [#25](https://git.ilapage.cn/OPC/goauto/issues/25) | v2 一加真机验收 | T20~T22 |
| T24 | [#26](https://git.ilapage.cn/OPC/goauto/issues/26) | 修复 PDD 规格面板蛇形遍历与尺码续页(2026-08-17 已验收) | T20~T23 |
| T25 | [#27](https://git.ilapage.cn/OPC/goauto/issues/27) | PDD 假售罄页面一次性下拉恢复 | T20~T24 |
| T26 | [#28](https://git.ilapage.cn/OPC/goauto/issues/28) | PDD 商品详情进入失败后一次性重开浏览器恢复 | T20~T25 |
| T27 | [#29](https://git.ilapage.cn/OPC/goauto/issues/29) | 修复相似商品区域导致假售罄恢复不触发 | T25、T26 |
| T28 | [#30](https://git.ilapage.cn/OPC/goauto/issues/30) | 启动端口统一由 config.yaml 配置 | T18 |
## 后续商品与采购设计工单
以下工单保持商品域与采购域解耦。#31 PDD 商品档案、#45 批量采集入口和 #32 采购原型均已验收;#32 原型已确认,可作为采购实施依据,后续采购单元继续遵守各自门禁,不得把采购能力混入采集功能。
| 顺序 | 工单 | 交付项 | 主要依赖 / 门禁 |
|---|---|---|---|
| T29 | [#31](https://git.ilapage.cn/OPC/goauto/issues/31) | PDD 商品采购档案与规格 JSON 管理(2026-08-17 已验收) | 已完成 |
| T30 | [#32](https://git.ilapage.cn/OPC/goauto/issues/32) | 采购闭环数据关系与交互原型(2026-08-18 已验收) | 已完成;原型已确认,是全部采购代码依据 |
| T31 | [#33](https://git.ilapage.cn/OPC/goauto/issues/33) | 采购任务数据模型与共享 API 契约(2026-08-20 已验收) | 已完成 |
| T32 | [#34](https://git.ilapage.cn/OPC/goauto/issues/34) | 服务端采购任务、租约、幂等与状态机(2026-08-20 已验收) | 已完成;未执行真实下单 |
| T33 | [#35](https://git.ilapage.cn/OPC/goauto/issues/35) | Admin 采购任务与人工处理页面(2026-08-21 已验收) | 已完成;不含任务创建和支付 |
| T34 | [#36](https://git.ilapage.cn/OPC/goauto/issues/36) | Android 地址后缀、不可逆门禁与创建订单 | #33、#34、#42;真机前再次人工确认 |
| T35 | [#37](https://git.ilapage.cn/OPC/goauto/issues/37) | 服务端物流调度与货运宝自动回填 | 采购任务与有效订单能力 |
| T36 | [#38](https://git.ilapage.cn/OPC/goauto/issues/38) | Android PDD 订单物流采集规则 | 采购订单关联契约 |
| T37 | [#39](https://git.ilapage.cn/OPC/goauto/issues/39) | 采购闭环真机端到端验收 | #33~#38、#42 |
| T38 | [#40](https://git.ilapage.cn/OPC/goauto/issues/40) | 虾皮商品档案、PDD 关联与规格映射(2026-08-20 已验收) | 已完成 |
| T39 | [#41](https://git.ilapage.cn/OPC/goauto/issues/41) | SYB 货运单商品导入与虾皮信息提取(2026-08-20 已验收) | 已完成 |
| T40 | [#42](https://git.ilapage.cn/OPC/goauto/issues/42) | Android 采购演练规则与持久执行基线(2026-08-20 已验收) | 已完成;只演练,不改地址、不创建订单 |
| T43 | [#45](https://git.ilapage.cn/OPC/goauto/issues/45) | Admin PDD 商品列表多选与批量采集任务创建(2026-08-18 已验收) | 已完成 |
| T46 | [#48](https://git.ilapage.cn/OPC/goauto/issues/48) | SYB ERP 客户端移植与真实导入 | #41;已实施,等待验收 |
| T47 | [#49](https://git.ilapage.cn/OPC/goauto/issues/49) | 店铺管理与 SYB 同步店铺过滤(2026-08-20 已验收) | 已完成 |
| T48 | [#50](https://git.ilapage.cn/OPC/goauto/issues/50) | SYB 导入后台任务与进度展示(2026-08-20 已验收) | 已完成 |
| T49 | [#51](https://git.ilapage.cn/OPC/goauto/issues/51) | 优化 SYB 商品列表工具栏与列宽(2026-08-20 已验收) | 已完成 |
| T50 | [#52](https://git.ilapage.cn/OPC/goauto/issues/52) | 修复 SYB 同步预创建失败无法定位(2026-08-20 已验收) | 已完成 |
| T51 | [#53](https://git.ilapage.cn/OPC/goauto/issues/53) | 补齐采购规则参数化动作契约(2026-08-20 已验收) | 已完成;#42 阻塞已解除 |
| T52 | [#54](https://git.ilapage.cn/OPC/goauto/issues/54) | 统一采集与采购任务的设备心跳、忙碌和离线处理(2026-08-20 已验收) | 已完成 |
| T53 | [#55](https://git.ilapage.cn/OPC/goauto/issues/55) | 补齐采购任务 Admin 列表与详情查询接口(2026-08-21 已验收) | 已完成;解除 #35 查询接口阻塞 |
| T56 | [#69](https://git.ilapage.cn/OPC/goauto/issues/69) | 蝦皮与 PDD 颜色人工映射及尺码批量匹配 | #31、#40、#44、#46/#62;v2 原型已确认,Stage B 已实施待验收 |
推荐依赖顺序:#31、#40、#41 完成商品域 → #33、#34 建立采购契约和服务端状态机 → #53 补齐参数化动作契约 → #42 完成不下单演练 → #35 管理端人工处理 → #36 高风险真实订单动作 → #37、#38 物流闭环 → #39 真机总验收。
## 延期
- [#10:只读实时屏幕](https://git.ilapage.cn/OPC/goauto/issues/10) 已关闭,未实施,不属于当前 MVP。
## 推荐执行批次
1. T01 → T02:工程与数据基线。
2. T03/T09/T10 → T04:设备身份、PDD 商品和规则。
3. T05/T06/T07:Android 连接与任务领取。
4. T11 → T12 → T13:Android 采集执行链。
5. T14 → T15 → T16:任务创建、详情、重置与删除。
6. T17:一加/ColorOS 真机验收。
任何实现工作必须先把对应活动 Task 置为进行中,并在工单中记录验证证据。
## 独立商品数据导入
| 顺序 | 工单 | 交付项 | 主要依赖 / 门禁 |
|---|---|---|---|
| T57 | [#70](https://git.ilapage.cn/OPC/goauto/issues/70) | 从 cmautobuy 线上库导入 PDD 与蝦皮商品 | #31、#40/#41;正式 apply 已完成并通过数据库完整性与幂等核验;2026-08-24 已验收 |
## 本机进程托管
| 顺序 | 工单 | 交付项 | 主要依赖 / 门禁 |
|---|---|---|---|
| T58 | [#71](https://git.ilapage.cn/OPC/goauto/issues/71) | 使用 Supervisor 托管 GoAuto Admin API 与 Admin UI | 已实施;两个实例 RUNNING、HTTP 与日志验证通过,待用户验收 |
-108
@@ -1,108 +0,0 @@
# 部署文档模板
> 本页是内部维护模板,不描述 GoAuto 当前真实生产部署。真实部署拓扑、域名、运行账号、目录、端口、凭据来源和回滚方式经过确认后,复制本页建立 `Deployment-and-Operations`;删除说明文字和占位值后再交付。不得把本地 Windows Supervisor 配置或 `server/docker-compose.yml` 直接宣称为生产方案。
## 本页用途
让维护者能够从已确认的干净环境完成首次部署、日常运维、健康检查、升级和回滚。每条命令必须写明执行位置、权限前提和预期结果。
## 安全边界
- 只记录配置项名称和安全来源,不记录密码、Token、Cookie、私钥、证书内容、PDD 账号、生产数据库地址、个人数据或生产数据。
- 发布、权限、数据库迁移、删除数据和不可逆操作必须有独立工单、备份与人工确认。
- GoAuto 永不执行付款;部署步骤不得引入支付能力或绕过现有高风险门禁。
- 对外服务必须使用 HTTPS;内部监听、反向代理、证书与防火墙边界按真实环境填写。
## 服务概览
| 项目 | 已确认内容 |
|---|---|
| 环境名称与用途 | `<填写>` |
| 目标提交或版本 | `<填写>` |
| 代码部署目录 | `<填写>` |
| 运行账号 | `<填写>` |
| Admin API 托管方式 | `<填写>` |
| Admin UI 托管方式 | `<填写>` |
| 内部监听地址 | `<填写>` |
| 对外域名与路径 | `<填写>` |
| MySQL/外部依赖 | `<填写名称与来源,不写敏感值>` |
| 日志位置 | `<填写>` |
## 环境要求
逐项写明操作系统、Go、Node/pnpm、MySQL、反向代理、进程托管工具和 Android 构建环境的版本要求、检查命令与预期结果。只保留部署端实际需要的组件。
## 首次部署
### 1. 准备账号与目录
记录最小权限运行账号、代码/配置/日志目录、属主和权限检查。服务账号不得复用个人高权限账号。
### 2. 取得代码并绑定版本
记录仓库来源、目标分支/标签/完整提交哈希及校验命令。部署记录必须保存实际提交哈希,不使用“最新版”。
### 3. 安装依赖与构建
分别记录服务端、管理端和需要交付的 Android 构建命令;每一步写明预期产物与失败停止条件。
### 4. 落位配置
列出 `GOAUTO_CONFIG`、数据库 DSN、端口、HTTPS、对象存储、SYB、AI Provider 等实际启用配置的安全来源和文件权限。不得把敏感值写入 Git、Wiki、工单或日志。
### 5. 数据库备份与迁移
> 数据库迁移属于高风险操作。必须先确认备份可恢复、迁移版本、影响范围和回退条件,再由独立授权执行。
记录迁移前检查、备份、迁移命令、表结构/版本验证和失败停止方式;不得通过修改已执行迁移版本号强制重跑。
## 进程托管与反向代理
按已确认的 Windows Supervisor、Linux supervisor/systemd、容器或其他真实方式填写:
- 进程名称、工作目录、启动/停止/重启命令;
- 自动重启、优雅停止、日志轮转和依赖启动顺序;
- nginx 或其他接入层配置、WebSocket/超时/静态资源规则;
- 配置语法检查和无中断重载命令;
- HTTPS 证书来源与续期检查,不记录证书内容或私钥。
## 配置与凭据来源
| 配置项 | 用途 | 安全来源 | 是否敏感 | 更新后动作 |
|---|---|---|---|---|
| `<名称>` | `<用途>` | `<密码库条目或受控配置路径>` | 是 / 否 | `<重启/重载/无需动作>` |
## 日常运维
用表格记录状态、启动、停止、重启、日志、配置重载、磁盘/数据库检查的命令与预期结果。不得把高风险动作包装成无确认的一键命令。
## 健康检查
每次部署、重启和回滚后至少验证:
- 进程状态稳定;
- Admin API 健康检查返回预期状态;
- Admin UI 能加载且 API 地址正确;
- WebSocket/Agent 连接按授权环境验证;
- 日志没有新增异常堆栈或敏感信息;
- 数据库迁移版本与目标提交相符。
任何一项不符合都不视为部署成功。
## 升级与回滚
### 升级
记录升级前提交、备份、目标提交、依赖安装、构建、迁移、进程重启和全量健康检查顺序。
### 回滚
记录可安全回滚的代码版本、产物、配置和数据条件。已执行的数据迁移不能假定可由切回代码自动撤销;不向后兼容时停止并使用已验证的备份恢复流程。
### 备份与恢复
记录备份对象、频率、位置、保留期、加密/访问权限、恢复步骤和最近一次恢复演练结果。
## 已知限制
明确记录尚未验证的真实回滚、多机部署、故障转移、容量、高并发、证书续期和灾难恢复能力;不得留空或用模板占位值宣称已经支持。
-136
@@ -1,136 +0,0 @@
# 部署与运维
## 当前线上拓扑
- 外部入口:`http://185.216.248.75:9527`,Nginx 同时承载 Admin 静态资源并反向代理 GoAuto API。
- GoAuto 服务监听:`127.0.0.1:8010`。
- 常驻服务:systemd `goauto.service`。
- 工作目录:`/home/goauto/current`,指向 `/home/goauto/releases/<发布标识>`。
- 服务配置:`/home/goauto/current/config/settings.yml`;敏感环境变量由 `/etc/goauto/goauto.env` 提供,不写入 Git、Wiki、工单或日志。
- Agent APK 私有目录:相对工作目录的 `var/goauto-agent-releases`;下载必须通过已认证接口。
## Agent HTTP 例外
服务端生产模式默认要求 Agent 使用 HTTPS。当前线上入口只有 HTTP,因此经 #181 用户明确确认,在 `/etc/goauto/goauto.env` 设置:
```text
GOAUTO_ALLOW_INSECURE_AGENT_HTTP=true
```
该开关只放宽全部 `/api/agent/v1/**`,包括注册、心跳、采集、采购、Agent 版本检查与下载;不放宽 Admin、PDD、SYB、OCR 或其他第三方地址的安全约束。HTTP 会明文传输 Device Token、任务内容、设备状态和执行结果。后续启用 HTTPS 后应删除该变量或设为 `false`。
`GOAUTO_TRUST_FORWARDED_PROTO=true` 只表示信任受控反向代理传入的 `X-Forwarded-Proto: https`,不能把实际 HTTP 请求视为加密链路,也不能替代上述显式 HTTP 开关。
## 发布与验证
1. 在本地完成服务端测试/构建和 Android 单测/APK 构建,记录提交、versionCode、SHA-256 与大小。
2. 创建新的 `/home/goauto/releases/<发布标识>`,复制服务端二进制、Web 静态资源和非敏感配置;保留旧发布目录用于回滚。
3. 原子切换 `/home/goauto/current` 后重启 `goauto.service`,确认 `systemctl is-active goauto.service` 为 `active`,并从外部入口验证 API。
4. 上传 APK 到 Admin Agent 版本并按需设为当前;服务端解析 Manifest,校验 versionCode 唯一性并保存 SHA-256。
5. 真机安装前确认设备没有运行中的任务。Android 系统安装确认仍由人工完成,Agent 不静默安装。
发布、重启、设置当前 Agent 版本和真机安装都属于高风险操作,必须有对应工单和人工确认。不得在部署过程中执行付款、创建订单或清理生产数据。
### API 长请求超时约束
Admin 蝦皮规格 AI 匹配会同步等待外部 Provider:Provider 配置允许 3~600 秒,旧版详情复合一键匹配共享600秒总预算,旧Admin客户端等待610秒;#254未部署前仍适用,部署后详情入口改用下节动态预算。因此服务端 `settings.application.writetimeout` 必须至少为 620 秒,受版本管理的标准配置固定为 `620`;启动时低于该值必须明确失败,禁止以 2 秒等短写超时运行,否则连接会在领域错误返回前被截断并表现为代理 502。反向代理的读/发送超时也必须大于服务端窗口。
Provider 故障日志只允许记录调用关联 ID、操作类型、耗时、上游 HTTP 状态码或网络错误分类;不得记录 API Key、Authorization、Provider URL、模型输入、候选规格、商品原始内容或响应正文。Provider 失败时接口返回 HTTP 503 与 `AI_MATCHING_UNAVAILABLE`,供 Admin 显示安全中文提示。
## 回滚
服务异常时把 `/home/goauto/current` 切回上一已验证发布目录并重启 `goauto.service`,随后复核服务状态和 Agent 接口。不要删除当前或历史 APK/发布目录来代替回滚;数据库变化如需回退必须单独评估。
## 蝦皮规格自动匹配任务运维(#195)
- 迁移 `1788290000000_shopee_spec_auto_match.go` 创建运行与工作状态表,并幂等写入 `sys_job.invoke_target=GoAutoShopeeSpecAutoMatch`。首次部署状态为关闭,迁移重跑不会覆盖管理员后来修改的 Cron、参数或启停状态。
- 默认 Cron 为 `0 15 * * * *`(每小时第 15 分钟),默认 `batchLimit` 为 20,合法范围 1~100。上线后先保持关闭,在 Admin 蝦皮商品列表手动执行小批次并核对运行摘要和映射,再由管理员在定时任务页明确启用。
- 定时和手动触发共享数据库唯一活动槽。看到“已有自动匹配正在运行”属于正常互斥结果;运行租约超过 30 分钟会在下次触发时安全释放并把旧运行标记失败。处理每个商品前会续租。
- Provider 临时失败的商品至少 60 分钟后重试,最多 3 次;低置信度或无结果在输入指纹不变时不会重复调用。排错优先查看 `shopee_spec_auto_match_run` 的结构化计数与脱敏错误,以及 `shopee_spec_auto_match_work_item` 的状态、尝试次数、下次尝试时间和租约;不得输出或复制 API Key、Provider 原始响应、商品原始 JSON 或个人数据。
- 紧急回退只需关闭系统任务并停止使用 Admin 手动入口;保留运行记录和既有已确认映射用于审计,不删除数据,也不回滚 #194。
## SYB 异常规格 AI 定时任务(#198)
- 追加迁移 `1788354164329_syb_spec_ai_parse.go` 创建 AI 确认字段、`syb_spec_ai_parse_run`、`syb_spec_ai_parse_work_item`,并幂等写入 `sys_job.invoke_target=GoAutoSYBSpecAIParse`。
- 系统任务默认关闭,默认 Cron 为每小时第 5 分钟、参数 `{"batchLimit":20}`;迁移重跑不得覆盖管理员后续修改的 Cron、参数或启停状态。建议先运行该任务,再由默认第 15 分钟的 #195 完成蝦皮到 PDD 的规格匹配。
- 多实例通过可空唯一活动槽和租约保证全局单批运行;逐行工作项按输入指纹去重。Provider 临时失败至少 60 分钟后重试且最多 3 次,低置信度/无结果在输入不变时不重复调用。
- 上线顺序为:备份数据库、执行追加迁移、发布服务并保持任务关闭、核验表/字段/任务种子,再由管理员决定是否启用。排错只查看运行计数、工作状态和脱敏错误,不输出 API Key、Provider 原始响应、完整 RawJSON 或订单数据。
## 定时任务执行历史运维(#199)
- 发布 #199 时先备份数据库,再执行追加迁移 `1788357000000_sys_job_execution_log.go`,随后发布服务与 Web。迁移创建 `sys_job_execution_log`、登记 `GET /api/v1/sysjob/:id/execution-logs`,并只为原本绑定隐藏菜单 `JobLog` 的角色写入精确读取权限;菜单缺失时迁移明确失败,不扩大角色授权。
- 服务启动会将上次进程遗留的 `running` 执行记录标记为 `interrupted`。该恢复依赖当前线上每个数据库只运行一个调度器实例;扩展为多调度器前必须另建工单引入实例租约,不能直接复用此判断。
- 排错从 Admin 定时任务页单选任务后进入“日志”,按状态和开始时间查询。记录只含稳定错误码和脱敏摘要;需要定位细节时查看受控服务日志,不得把任务参数、Provider 配置/响应、密钥或业务原始数据复制进执行历史。
- 当前没有执行历史删除接口和自动保留策略;删除定时任务不删除历史。数据库容量治理需要另建工单评估。#198 的 `GoAutoSYBSpecAIParse` 在 #199 发布和迁移后仍保持关闭,启用必须由管理员另行确认。
## 客户端密钥部署与验证(#237)
实现基线 `71f7751`;2026-09-07 已按用户授权完成本机 MySQL 迁移、管理员菜单写入和 Server/Web 构建重启;管理页面 HTTP 列表与模块加载已验证。2026-09-08 经用户授权,线上已发布 Server/Web 基线 `d403f3b`,执行迁移 `1788798000000` 并重启 GoAuto/Nginx;迁移记录、两张密钥表、仅管理员菜单关联已回读。HTTP 页面与健康检查通过;未登录管理请求 HTTP 200、业务码 401。真实密钥创建及客户端业务读写闭环尚未验证。
- 发布前须单独授权追加迁移 `server/cmd/migrate/migration/version-local/1788798000000_client_api_key.go`,按既有迁移流程创建 client_api_key、client_api_key_audit 和“采采管理/客户端密钥”管理员菜单。前置父菜单必须存在;不应以赋予普通用户管理员角色代替迁移或权限核验。
- 用户于 2026-09-07 明确确认默认兼容 HTTP/HTTPS、不设开关并接受明文风险;部署本版本并执行迁移后,现有 `http://185.216.248.75:9527` 可以使用客户端密钥管理及客户端 API。2026-09-08 线上部署及未登录拒绝检查已通过,真实密钥读写验收仍待进行。HTTP 会明文传输密钥和业务数据,仍建议使用 HTTPS。
- 客户端密钥功能不依赖 GOAUTO_TRUST_FORWARDED_PROTO、X-Forwarded-Proto 或 Agent HTTP 例外。既有其他路由的协议和代理配置保持不变;不伪造协议头,不新增明文放行开关。
- 代理、APM、应用日志均不得记录 Authorization、创建响应 secret 或原始业务载荷。应用对两类客户端密钥路由跳过旧请求/响应正文日志,使用专用元数据审计;实际代理日志脱敏仍须部署验收。
- 请求审计 status=0 可能表示在途、进程中断或结果审计更新失败;先按请求关联号核对业务结果,不自动重试采购、采集、同步等操作。停用阻止后续认证,不保证取消已开始的业务操作。
- 隔离验证:Server `go test ./app/goauto/clientkey ./app/goauto/clientapi ./cmd/migrate/migration/version-local`;Web `pnpm exec jest tests/unit/client-keys.spec.js --runInBand` 与 `pnpm run build:prod`。浏览器模拟入口 `/tests/fixtures/client-keys.html` 仅由本地 Vite 开发服务承载,使用内存模拟请求和无效示例密钥,不连接真实数据库,不证明线上鉴权已验收。
- 真实部署验收须另行验证实际 HTTP/HTTPS 入口、管理员创建/编辑/停用、普通用户拒绝、读写/独立动作隔离、停用后的后续请求拒绝及日志无密钥;任何真实业务执行继续按独立授权范围进行。
## Windows 本机运行目录与 #237 迁移验证(2026-09-07)
- Supervisor 实际配置 `D:/supervisor/programs/goauto.conf`;程序仅 `goauto-admin-api` 和 `goauto-admin-ui`。从已有干净工作区 `D:/OPC/goauto-worktrees/main-runtime` 的 main 分支运行;源码运行基线 `ac3c63e`,版本化启动模板更新提交 `9a10d82`。
- 原目录 `D:/OPC/goauto` 的用户改动保持原样;本机敏感配置继续读取 `D:/OPC/goauto/config.yaml`,不复制凭据到运行工作区或版本库。数据库 `127.0.0.1:3307/goauto`,API `http://127.0.0.1:8010`,Web `http://127.0.0.1:9527`。
- API 命令:`pwsh.exe -NoLogo -NoProfile -File "D:/OPC/goauto-worktrees/main-runtime/scripts/start-server.ps1" -ConfigPath "D:/OPC/goauto/config.yaml" -SkipMigration`;Web 同目录 `scripts/start-web.ps1` 与相同 ConfigPath。已验证无需 ExecutionPolicy Bypass。
- 真实迁移必须单独授权并先确认唯一待执行版本;常驻 API 加 `-SkipMigration`,日常重启不自动执行未来待审核迁移。#237 迁移 `1788798000000_client_api_key.go` 已执行,sys_migration 记录 `1788798000000`、两张密钥表及管理员菜单已回读;其他角色未新增菜单关联。任务启停状态未改。
- 范围明确的服务控制:`D:/supervisor/supervisord.exe -c D:/supervisor/supervisord.conf ctl status goauto-admin-api goauto-admin-ui`,启动/停止/重启将 status 分别替换为 start/stop/restart。不得为了 GoAuto 重启整个 Supervisor 或其他项目。配置内容变化后需重新读取配置;本次调用 `supervisor.reloadConfig`,再对上述两个程序定向 start,已通过进程命令行核对新目录。
- 日志:`D:/supervisor/logs/goauto-admin-api.log`、`D:/supervisor/logs/goauto-admin-ui.log`;对外分享只能保留脱敏结构摘要。迁移输出不得暴露凭据或业务原文。
- 管理员刷新页面后可进入 `http://127.0.0.1:9527/#/client-keys/index`;菜单缓存未更新时重新登录。已验证列表空态、创建弹窗加载 12 模块及 HTTP 风险提示;未实际创建密钥,真实客户端读写及停用闭环仍待范围明确的验证。本节为 2026-09-07 本机验证记录;线上已于 2026-09-08 另行授权发布,见上节。
## 蝦皮详情匹配等待与回读(#254)
实现绑定 `9088e6b`(2026-09-10,分支 fix/254-match-loading);已通过合成数据测试,尚未合并 main 或发布线上。
- 本次只完成代码构建与合成测试,未改线上配置、AI设置、常驻服务或Nginx;部署与重启需另行授权。
- 详情匹配新窗口是后端 2T+10 秒、Web POST 2T+20 秒。当前默认API最低写超时校验仍为620秒,这是历史最低值,不保证容纳所有动态预算;发布前读取实际 AI timeoutSeconds、application.writetimeout 和生效代理配置核对。
- 建议API写超时至少为 2T+30 秒,且仍满足原最低620秒;代理读/发送超时需留足对应窗口。T=60/180时原620秒足够容纳应用预算;T=600时至少需要1230秒API写超时及适当更长的代理窗口。不得仅设置较大的AI超时却忽略传输层更早截断。
- 上述是发布核对要求,不是本单已修改/验证线上代理的结论。超大T带来长连接占用,不能用关闭超时来代替正确预算。新增失败回读不代表必须延长任何线上请求。
- 必须配套发布 Server/Web;仅上新版Web连接旧Server时,因缺失派生预算会提示等待时间不可用并停止匹配。原Android不需升级。
## 本地 Supervisor 与局域网访问
本机开发/联调环境由 `D:\supervisor\programs\goauto.conf` 分别启动 GoAuto API 与 Admin Web;实例配置只负责调用项目脚本,端口读取 `D:\OPC\goauto\config.yaml`。
- API 由 `scripts/start-server.ps1` 启动并监听所有网卡的 `ports.server`。
- Admin Web 由 `scripts/start-web.ps1` 启动,监听 `0.0.0.0` 的 `ports.web`,因此同一局域网设备可通过 `http://<本机局域网IP>:<web端口>/` 访问。
- 浏览器使用同源相对 API;Vite 将 `/api` 与 `/static` 代理到本机 `127.0.0.1:<server端口>`,避免局域网客户端错误访问自身的 `127.0.0.1`。
- 修改脚本或配置后,只需重启 Supervisor 的 `goauto-admin-ui`;API 未变化时无需重启 `goauto-admin-api`。若局域网仍无法连接,检查 Windows 防火墙是否允许 `ports.web` 的 TCP 入站。
## SYB 订单号回填 worker 部署与排错(#305)
代码绑定 e89de1a(2026-09-18),基于 #306 的07a3817;本节是部署要求,不表示已发布或业务库已迁移。
1. 另行取得明确目标环境的数据库迁移、权限对账和发布授权。核对 #306 的1789800100000金额列前置;本单追加1789800200000_purchase_order_writeback.go,建立purchase_order_writeback、purchase_order_writeback_command、purchase_order_writeback_lease三表和单例行,不改任务/付款事实,不排入历史回填、不修改定时任务开关。
2. 配套构建并发布Server/Web,沿用现有启动权限对账为管理员/采购员登记POST /api/admin/v1/purchase-tasks/syb-order-writeback。不新增角色、菜单或支付门槛。旧常驻实例使用SkipMigration时,仅重启并不能替代授权迁移。
3. API启动后每数据库恢复一个3秒轮询的持久队列worker,独立于Admin定时任务列表;只处理已排入pending及租约过期running。正式采购结果、人工订单回填成功/同号回放会排队;历史补偿仅用户勾选发起。部署重启后已有pending会执行真实SYB写入,必须包含在发布授权范围内。
4. 全局租约2分钟;会话恢复预算25秒,每次外部读/写20秒(只读客户端重试亦受该context限制),写前再次核验租约。崩溃恢复只回读,不盲目重写;unknown不自动重试,等租约到期再人工核对/补偿。同目标其他未确认写入先解决,不能删除记录规避保护。
5. 排错先看采购管理的“SYB 单号回填”状态、脱敏原因、完成时间;需要结构诊断时查询记录的taskId/status/attempt_count/error_code/lease_expires_at,不输出单号、Cookie或完整原始响应。SYB会话失败应先恢复既有登录,再勾选失败任务补偿;按钮受理不等于SYB成功。conflict只核对,不强制覆盖。
6. 不执行真实支付;实付金额只存Admin,SYB请求cost固定0。停止新版API可停止新轮询,但不保证已发送远端请求取消;回滚保留追加表和未知状态,不删除记录、不回退采购事实。迁移/多实例MySQL/真实SYB端到端仍需授权验收。
### #305/#306 线上发布验证(2026-09-18)
用户授权合并main、迁移并发布线上;main已快进至7e257ca(包含#306及#305),Server/Web从该提交重新构建。此前线上RELEASE记录60c7526;新发布目录为/home/goauto/releases/20260918-7e257ca-305,current已原子切换,goauto.service重启、Nginx校验及reload成功。先前各节“未部署”是对应提交阶段记录,本节为后续发布事实。
- 迁移前51个版本已应用,唯一缺失为1789800100000和1789800200000;服务器内受限备份/home/goauto/backups/20260918-305/database.sql(目录0700、文件0600),不下载生产数据。执行后53版本齐全,purchase_task.pdd_order_amount_cent为nullable BIGINT,独立三表和单例租约行已建立,历史回填队列0条。
- 启动对账已登记新POST接口及purchaser权限,管理员沿用原授权;已登录采购列表返回orderWriteback,空批次请求返回422且不入队。未执行真实订单回填或付款,不将空队列健康验证宣称为SYB端到端验收。
- 保持sys_job原启停值不变(任务1/2为1,3/4/5为2),没有更改Cron或主动触发同步。发布前采集/采购运行数均0。
- 原20260916目录的static/var与20260914目录互相循环,不能继续通过旧current间接链接。新release分别直接引用已确认真实存在的/home/goauto/releases/20260907-29ba16e-236/static和/home/goauto/releases/20260903-2d6d244-r1/var;不删除或改写历史资源,不修改其他站点Nginx配置。后续清理旧release前必须检查这些被引用的数据目录。
- 公网入口http://185.216.248.75:9527首页和10项JS/CSS资源HTTP200,/api/v1/health HTTP200;未认证采购接口业务码401。二进制SHA256=4833d779843f0613455ea2b56cadff6efd88d2b96c13e00cb4423cac02e72cb7;发布后结构日志检查无panic/fatal/1146/1054。
- 回滚代码目标保留/home/goauto/releases/20260916-03647dd,追加数据库表/列保留;该旧目录原有static/var循环是已知问题,回退不能宣称资源链自动恢复。不要删除新队列记录或恢复旧数据覆盖后续业务;停止服务亦不保证已发送远端请求取消。
## Chrome 订单回填扩展交付与运行(#316)
- 扩展以 `chrome-extension/manifest.json` 为入口,不随 Server 自动部署。解压安装、授予 Admin Origin、创建或修改客户端密钥授权、真实 PDD 遍历及订单回填都必须按目标环境另行授权;发布 Server 不等于安装或启用扩展。
- 管理员为密钥显式勾选“采购管理 → 回填(writeback)”。建议 HTTPS;若使用项目既有客户端密钥 HTTP 例外,密钥和订单事实会明文传输,风险边界不扩大到 Cookie、JWT 或 Device Token。
- popup 关闭不停止运行;重新打开可查看持久状态。设置在运行时只读。停止会保留已确认批次并阻止后续请求;重启或超时仅以原批次 UUID 重放未确认批次。
- 排错只查看阶段、已读、可提交、已写入、冲突、未完整扫描原因、脱敏错误码和批次状态;不得记录客户端密钥、地址全文、订单号、Cookie、原始 HTML 或真实订单载荷。没有明确列表末尾证据时只能报告未完整扫描。
-188
@@ -1,188 +0,0 @@
# 开发工作流
## 语言与术语
- 用户可以使用中文、英文或合理的中英混合语言交流;默认使用中文分析、回复、编写工单和维护内部项目文档。
- 代码标识符、命令、参数、路径、文件名、API 名称、协议名、日志和错误原文保持原样;必要时补充简短中文解释。
- 用户明确要求某次回复或交付物使用其他语言时,按该次要求执行,不改写接口契约或影响搜索和执行的原文。
## 事实来源
| 信息 | 唯一事实来源 |
|---|---|
| Epic/MVP 目标、版本范围和子工单索引 | Gitea Epic/MVP |
| 单次任务需求、变化、方案、实现、测试、提交、阻塞和验收 | Gitea 单元工单 |
| 长期需求、架构、契约、业务规则、安全边界和操作说明 | Gitea Wiki |
| 源码、迁移、测试、版本绑定分析、本地原型和核心 Wiki 镜像 | Git |
| 可编辑交互设计 | QuantUX;App ID、版本、链接和确认状态记录在工单 |
核心页面通过 `wiki-docs.json` 显式映射,固定执行 Wiki → `docs/` 单向同步。日常 `sync` 与 `sync --check` 先比较页面 revision,revision 未变化时不重复下载正文;疑似镜像损坏或需要完整核对时显式使用 `sync --deep-check`。既有 Wiki 任务归档和 `docs/task/` 只作历史兼容;标准任务不创建或导出,只有用户明确要求专项快照时才使用 `archive` / `export`。
## 权威源与事实边界
发生冲突时按以下顺序裁决:
1. 可执行代码与自动化测试。
2. 状态为已批准的共享契约和决策。
3. 当前长期 Wiki。
4. 旧设计稿。
5. 注释、UI 文案和历史示例。
“当前实现”只能描述指定 commit 上可复核的行为;“目标契约”是尚待实施或验收的目标,不能用来宣称现有能力。能力状态使用“未发现实现 / 仅设计 / 部分实现 / 已实现某一端”等带限定的表达。
进行基线或差距审计时,记录 source commit、核验日期、证据路径、GAP-ID、审计范围、不在范围和证据边界。轻量日常工单不强制建立完整 SRS/SAD/ADR 文档集,但接口、状态机和跨端决策必须进入对应长期契约。
## Gitea 交互与工单最小读取
- 所有工单和 Wiki 的查询、创建、更新、评论、状态变更及关闭优先使用项目已配置的 Gitea MCP;MCP 不可用或不支持所需操作时才回退 Gitea API,并在当前工单记录原因。凭据只从环境或 MCP 安全配置读取。
- 首次接手任务时读取工单确认基线和完成当前判断所需的评论,不因节省上下文跳过范围、依赖、安全、验收或重要变化。
- 同一任务、同一会话且关键前提未变化时复用仍有效的工单事实,优先关注当前状态、最新评论和首个未完成步骤。
- 会话、代码、配置、依赖、凭据、远端状态或关键前提变化,或最新评论改变范围、方案、风险和验收时,重新读取必要历史;无法判断影响范围时读取完整工单。
- 连接器不支持评论分页或增量读取时可以读取完整工单,但不得为规避完整读取而建立本地缓存或第二事实来源。
- 正确性、安全规则和已确认范围优先于读取成本;边界不确定时补读必要证据。
## 新项目 Wiki 初始化门禁
本项目 Wiki 已于 2026-08-17 初始化并启用 Wiki-first,本节适用于从本仓库派生新项目的场景。
- 先创建 Gitea 远端仓库并启用工单和 Wiki,再配置 `wiki-docs.json`;不得把本地 `docs/` 当作线上 Wiki 已初始化的证据。
- 优先使用已配置的 Gitea MCP 查询和写入 Wiki;MCP 不可用或不支持所需操作时才回退到 Gitea API,并在初始化工单记录原因。凭据只从安全配置读取。
- `Home` 不存在时先创建并在线回读 revision,再逐页创建或更新其他核心映射页面。
- 每个核心页面写入后必须在线回读并取得 revision。页面缺失、回读失败或没有 revision 时停止初始化。
- 产品编码前运行 `python dev_scripts/harness.py sync --verify`;全部成功才表示初始化完成。
## 项目治理模式与不可裁剪底线
GoAuto 默认采用轻量治理。文案、注释、格式、局部样式或布局、预期行为明确的小 Bug,以及不改变接口、数据结构、权限和安全边界的单模块低风险调整可以直接实施,无需为了留痕补建工单。完整独立需求、新页面、跨模块功能,以及 API、数据结构、权限、安全、迁移或范围不明确的变化必须建单。
采购、创建订单、真实个人或生产数据、权限、安全、并发、数据库迁移、删除数据、发布和其他不可逆操作始终升级为高风险。无论任务采用何种最小门禁,凭据保护、永久禁止付款、个人与生产数据最小化、设备互斥、Agent 禁止 OCR/VLM、控件树与整屏截图禁存、工作区保护、真实测试和人工验收均不可裁剪。
### 明确授权后的执行
- 当前聊天中用户给出的明确指令,或 Gitea 工单中能够归属于有权人工的明确授权,可以作为执行依据,不要求把同一授权重复复制到工单后再次确认。
- 授权必须能识别操作、对象和范围;Agent 自动生成的工单、草稿、摘要或对用户意图的转述不能单独构成人工授权。
- 获得有效授权后,只核对准确目标、授权范围和当前状态等最小必要前提,不得仅因操作不可逆而重复询问或拒绝。
- 授权不自动覆盖相邻对象或后续任务;环境、对象、范围或影响发生实质变化时重新确认。平台自身强制的审批、安全策略或权限限制继续有效。
## 工单与设计证据双门禁
正式实施前先判断是否需要工单,再判断需要什么设计证据。工单不能替代原型确认,原型也不能替代技术方案、安全检查和单元工单。
### 工单豁免
直接提交必须同时满足:范围明确、容易回退、不涉及接口/数据库/状态/权限/安全/并发/重大 UI 等必须建单项,并完成受影响范围的最小验证。可包括:
- 错别字、注释、文档措辞、格式化和导入排序;
- 单文件内部变量改名、类型标注、文档字符串或不改变产品行为的测试;
- 已确认无人使用的死代码;
- 单文件低风险且只恢复已有明确行为的缺陷;
- 不改变业务含义、流程、权限、状态、接口、数据、布局和可访问性的纯显示文案。
有任何不确定就建立单元工单。
### 最低设计证据
| 修改类型 | 最低证据 | 正式实施门禁 |
|---|---|---|
| 纯显示文案且满足豁免 | 无原型 | 最小界面检查 |
| 现有界面小范围样式或布局 | 标注截图、低保真图或明确复用规范 | 工单确认后实施 |
| 新组件 | 正常、空、加载、失败、禁用和权限边界 | 工单确认后实施 |
| 新页面、独立功能、重大交互或导航 | QuantUX 或其他可审阅原型 | 用户确认文字需求、原型和覆盖范围后实施 |
| 后端、接口、数据或定时任务 | 架构、API、数据、状态或流程设计 | 用户确认技术方案后实施 |
| 恢复既有行为的 Bug | 原设计、截图、复现步骤或已有验收证据 | 确认是恢复而不是改需求 |
### 线上原型审核与按需导出
- 完整原型默认通过 QuantUX 或其他设计工具的可访问线上链接审核;工单记录链接、App ID、版本/revision 或确认日期、审核版本识别方式、确认人、确认时间和覆盖范围。
- 线上链接无法访问或不能区分审核版本时停止审核,等待用户确认等效方案。
- 只有用户明确要求 `导出原型 #N`、`导出全部原型`,或项目专用规则明确要求离线证据时,才导出到 `prototypes/<工单号>/<版本>/index.html`;导出不自动提交。
- 已确认的本地快照不得原位覆盖;结构、流程、状态、权限、异常处理或验收结果变化时建立新版本并重新确认。
- 导出后检查入口、主要交互、相对资源完整性,并删除凭据、账号、个人信息和生产数据。
- 设计工具无法完成用户明确要求的 HTML 导出时,记录限制并停止该导出;只要线上原型可访问且版本明确,线上审核不因此阻塞。纯文案、小范围 UI、非 UI 和恢复已有行为的 Bug 不强制完整原型或 HTML。
存量 `quantux-*.html` 平铺快照建立于规则前,保持原样;新目录规则自 #47 生效。
## 任务层级与状态
```text
[Epic] 长期产品目标
└── [MVP] 一个可交付版本
├── 单元任务 #N
└── 单元任务 #N+1
```
单元任务是唯一正式实施单位。状态为:
```text
待确认 → 待实施 → 进行中 → 待验收 → 已完成
└→ 阻塞
```
真实依赖未满足且不能并行时保持待实施;开始后遇到无法解除的问题才标记阻塞。
## 单元任务闭环
1. 只读确认当前代码事实、目标、非目标、依赖、设计证据、风险、回退、验证和长期文档影响。
2. 需要建单时创建单元工单并记录脱敏需求摘要;实施开始时检查分支和工作区并标记进行中。
3. 严格按工单范围实施;根因、范围、主要方案、风险或阻塞变化时先更新工单并按需重新确认。
4. 执行与风险相称的格式、单元、契约、集成、浏览器、真机或高风险验证;记录未验证内容。
5. 只有长期事实变化时更新 Wiki、在线回读 revision、运行一次 `sync` 和一次 `sync --check`;无长期影响时在工单说明原因并跳过。
6. 提交并推送当前工单文件,在工单集中回写最终差异、测试、未验证内容、提交哈希和 Wiki revision,保持待验收。
7. 用户明确验收后记录时间和结论、关闭单元工单并更新 Epic/MVP;没有新的长期事实变化时不重复同步 Wiki。
高风险数据库迁移、设备认证、并发租约、地址修改、创建订单、权限、删除、发布和不可逆动作必须单独建单并再次等待人工确认。任何自动支付需求直接拒绝。
## 标准任务节奏
默认只更新工单三次:
1. **开始实施**:确认依赖、范围、工作区和设计证据。
2. **待验收**:集中记录最终差异、测试、未验证项、提交和长期文档影响。
3. **验收关闭**:记录用户验收结论,关闭任务并更新父工单。
只有根因、范围、主要方案、风险或阻塞发生重要变化时才增加过程记录。不重复抄写完整聊天、Agent 内部推理、已存在的测试证据或提交信息。
## 需求记录与流转
- 聊天用于分析和确认,不是长期事实来源。
- 单元工单记录来源、提出时间和表达目的所需的少量关键原话或脱敏摘要,不保存凭据、个人数据或生产数据。
- 长期稳定需求进入产品需求或对应主题 Wiki;共享接口只进入 API 契约。
- 工单必须区分当前代码事实、目标契约和假设;不得把目标写成已有能力。
- 标准任务不创建 Wiki 任务归档。已有任务归档不删除、不补齐;用户明确要求专项历史快照时才按需创建或导出。
## 自然语言快捷指令
| 指令 | 执行动作 | 停止位置 |
|---|---|---|
| `只分析` | 只读检查并给出方案 | 等待确认,不建单、不修改 |
| `建工单` | 根据已确认方案创建单元任务 | 工单创建后停止 |
| `执行工单 #N` | 检查依赖,实施、测试、提交、推送并回写证据;仅在明确要求时创建任务快照 | 工单待验收 |
| `建工单并做` | 依次建单和执行 | 工单待验收 |
| `继续工单 #N` | 优先核对当前状态、最新评论、Git 和必要 Wiki 证据,从首个未完成步骤继续 | 到当前停止条件 |
| `检查工单 #N` | 只读检查范围、验收、测试和证据 | 输出报告,不自动修复 |
| `同步文档` | 读取 Wiki,导出核心镜像并检查一致性 | 不修改 Wiki、不处理任务快照、不自动提交 |
| `导出原型 #N` | 用户明确要求时导出指定工单已确认的原型版本 | 写入版本目录,不扩展范围、不自动提交 |
| `导出全部原型` | 用户明确要求时导出当前项目明确范围内的全部已确认原型 | 不扩展范围、不自动提交 |
| `导出任务归档` | 用户明确要求时按 revision 导出历史任务快照 | 只写 `docs/task/` |
| `导出全部任务归档` | 用户明确要求时全量导出历史任务快照 | 只写 `docs/task/` |
| `#N 验收通过` | 记录验收、关闭任务并同步父工单 | 无新长期事实时不再同步 Wiki |
快捷指令不能绕过方案确认、依赖、安全、设计证据、工单范围、必要验证或人工验收。
## 效率与范围控制
- 默认严格按已确认范围实施,不顺手修复相邻问题。
- 完成必要安全和前置检查后,优先执行能产生真实反馈的最小命令。
- 采用“执行 → 查看首个可行动错误 → 最小修复 → 继续”的闭环。
- 同一任务、同一环境已经验证的事实不重复检查;环境或关键前提变化后再验证。
- Skill 和平台规则是否需要重新读取,按当前 Agent 平台和任务触发规则执行,不自行跳过。
- 不新增与验收无关的文档、脚本、框架、重构或扩展性设计。
- 完成工单范围、必要验证、按影响触发的文档闭环和证据回写后立即停止。
## 文档影响
每个单元工单必须二选一:说明“无长期文档影响”的原因;或列出要更新的 Wiki 页面。
启动、测试、部署、排错命令,模块入口、目录职责、主要调用路径,配置、API、数据结构、状态、业务规则、安全边界或日志位置变化时必须更新长期 Wiki。部署命令或常驻服务运维方式变化时,更新项目自己的 `Deployment-and-Operations` 页面;当前真实部署拓扑尚未确认时不得用模板占位值冒充事实。普通内部重构只有在入口、行为、配置和验证方式都不变时才可记为无影响。
Wiki 同步由长期事实变化触发,不由任务完成触发。有影响时只执行一轮:修改 Wiki → 在线回读 revision → `sync` → `sync --check` → 提交镜像;验收时内容未变化不重复执行。不得直接修改映射镜像后反向覆盖 Wiki。
-66
@@ -1,66 +0,0 @@
# GoAuto 文档中心
GoAuto 使用 Gitea 工单记录单次任务的完整过程,使用 Gitea Wiki 维护长期事实,使用 Git 保存源码、迁移、测试、版本绑定资料和 Wiki 的本地镜像。标准任务不再重复建立 Wiki 任务归档。
## 建议阅读顺序
1. [项目档案](https://git.ilapage.cn/OPC/goauto/wiki/Project-Profile):项目目标、建设基线、交付单元和当前阶段。
2. [产品需求总览](https://git.ilapage.cn/OPC/goauto/wiki/Product-Requirements-Overview):长期需求状态、工单、原型和当前采集闭环。
3. [架构与代码地图](https://git.ilapage.cn/OPC/goauto/wiki/Architecture-and-Code-Map):请求怎样跨服务端、Web 和 Android 流动。
4. [业务规则与术语](https://git.ilapage.cn/OPC/goauto/wiki/Business-Rules-and-Glossary):重要状态和不能破坏的规则。
5. [本地开发与验证](https://git.ilapage.cn/OPC/goauto/wiki/Local-Development-and-Verification):怎样启动、测试和真机验证。
6. [常见修改指南](https://git.ilapage.cn/OPC/goauto/wiki/Common-Changes):常见修改入口、风险和停止条件。
7. [故障排查](https://git.ilapage.cn/OPC/goauto/wiki/Troubleshooting):出现错误时按什么顺序检查。
8. [开发工作流](https://git.ilapage.cn/OPC/goauto/wiki/Development-Workflow):完整建单、设计门禁、实施和验收流程。
9. [部署与运维](https://git.ilapage.cn/OPC/goauto/wiki/Deployment-and-Operations):当前线上拓扑、Agent HTTP 例外、发布验证和回滚。
专题资料:
- [Android Agent API 契约](https://git.ilapage.cn/OPC/goauto/wiki/Android-Agent-API-Contract)
- [工单与依赖索引](https://git.ilapage.cn/OPC/goauto/wiki/Delivery-Issues)
- [一加真机验收](https://git.ilapage.cn/OPC/goauto/wiki/OnePlus-Real-Device-Acceptance)
- [PDD 商品详情规则迁移分析](https://git.ilapage.cn/OPC/goauto/wiki/PDD-Detail-Rule-Migration-Analysis)
## 事实来源
| 信息 | 唯一事实来源 |
|---|---|
| 单次任务需求、讨论、方案变化、实现、测试、提交、阻塞和验收 | Gitea 单元工单 |
| 长期产品需求、架构、业务规则、开发规范、契约和操作说明 | Gitea Wiki |
| 跨服务端与 Android 的共享接口 | Wiki 的 Android-Agent-API-Contract 页面 |
| 源码、迁移、版本绑定分析、本地 HTML 原型和规则 JSON | Git 仓库 |
| 核心长期文档的离线副本 | Git 仓库 `docs/` 中的 Wiki 只读镜像 |
| 外部交互原型 | QuantUX 链接、App ID 和对应工单记录 |
本地映射 Markdown 不是编辑入口。只有长期事实变化时才执行:修改 Wiki → 读取确认 → 导出核心 `docs/` → 检查一致性 → 提交镜像。无长期文档影响的任务完全跳过 Wiki 同步。既有任务归档和 `docs/task/` 只作历史兼容;仅在用户明确要求专项快照时创建或导出。
## 权威源与事实边界
文档或界面与实现冲突时,依次以可执行代码与自动化测试、已批准的共享契约和决策、当前长期 Wiki、旧设计稿、注释/UI 文案/历史示例为准。
“当前实现”必须能在指定提交上复核;“目标契约”表示尚待实施或验收的目标,不能用来宣称现有能力。基线或差距审计需记录 commit、核验日期、证据路径、范围、不在范围和证据不能证明的内容。
## 五分钟检查
```powershell
git status --short --branch
python dev_scripts/harness.py check --strict
python -m unittest discover -s tests -v
.\scripts\verify.ps1 -Component all
```
预期结果是工作区范围明确、治理结构和 Wiki 工具单元测试通过,并且受影响的交付单元验证通过;只有本次修改了长期 Wiki 时才另跑 `sync --check`。具体环境、分组件命令和真机范围见[本地开发与验证](https://git.ilapage.cn/OPC/goauto/wiki/Local-Development-and-Verification)。
## 同步与归档
```powershell
# 仅当长期事实变化时:单向导出并检查核心文档
python dev_scripts/harness.py sync
python dev_scripts/harness.py sync --check
# 仅当用户明确要求专项历史快照时
python dev_scripts/harness.py archive 43 "升级 DevHarness 文档基线"
python dev_scripts/harness.py export
```
凭据只通过 `GITEA_TOKEN` 环境变量提供,不写入配置、日志、工单、Wiki 或文档。
-344
@@ -1,344 +0,0 @@
# 本地开发与验证
T01 已建立可执行的三端骨架。建议从仓库根目录运行统一脚本:
```powershell
.\scripts\verify.ps1 -Component all
```
`Component` 也可以是 `server`、`web` 或 `android`。
## Windows PowerShell 与 UTF-8
### Shell 选择
- 优先使用当前已配置的 PowerShell;可选择时优先 PowerShell 7 `pwsh.exe`。只有命令明确依赖 Windows PowerShell 5.1 时才使用 `powershell.exe`。
- 不得仅为设置编码重复启动一层 PowerShell;嵌套进程会增加启动时间、转义复杂度和错误定位成本。
- 代码发现优先使用项目配置的代码图工具;检索字符串、配置和非代码文件,或图工具不足时使用 `rg`。
### PowerShell 语法与外部命令
- Windows 命令不得默认套用 Bash 语法;复杂正则优先先赋给变量或使用 `rg -e`,避免在多层引号中继续嵌套。
- 多行 Python 或 JSON 正文使用单引号 PowerShell here-string,避免 `$()`、反引号和变量被 PowerShell 提前展开:
```powershell
$script = @'
print("保持原文")
'@
$script | python -
```
- `foreach`、`if` 等语句块应保留在同一个 PowerShell 解析上下文中;需要收集表达式结果时使用数组表达式:
```powershell
$items = @(foreach ($path in $paths) {
if (Test-Path -LiteralPath $path) { Get-Item -LiteralPath $path }
})
```
- `rg` 使用真实目录配合 `-g/--glob`,不要把 Bash 风格通配路径作为目录参数:
```powershell
rg -n -g '*.md' 'sync --check' docs
```
### 文件编码与控制台输出
文件解码和控制台输出是两个边界。读取 UTF-8 文本时,在命令支持的情况下显式指定字面路径与编码:
```powershell
Get-Content -LiteralPath "path\to\file.md" -Encoding utf8
```
仓库文件仍使用项目规定的编辑工具修改,不为指定编码改用 shell 拼接、重定向或临时文件。PowerShell 5.1 与 PowerShell 7 对无 BOM UTF-8 的默认处理不同,不能只凭控制台显示判断文件编码。
只有出现真实乱码,或已知宿主/外部程序不是 UTF-8 时,才在当前进程设置:
```powershell
$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
```
Python 中文输出已经出现乱码时,可只对当前进程设置:
```powershell
$env:PYTHONUTF8 = "1"
$env:PYTHONIOENCODING = "utf-8"
python dev_scripts/harness.py check --strict
```
同一会话已经生效且环境未变化时不重复设置;乱码仍存在时先区分文件解码、控制台、管道和外部程序,处理首个真实原因。
### ExecutionPolicy 边界
- `Get-Content`、`rg`、Git、Python 和普通 PowerShell cmdlet 不需要 `-ExecutionPolicy Bypass`。
- 不得默认添加 Bypass,也不得把它写入统一命令包装。
- 只有可信 `.ps1` 确实被执行策略阻止、任务范围允许且没有更小替代方案时,才对该次进程使用,并在工单记录脚本、阻止信息和原因。
- Bypass 只解决执行策略阻止,不解决编码、权限或脚本自身错误。
## 通用检查
```powershell
git status --short --branch
git diff --check
```
## 服务端验证
```powershell
Set-Location server
go test ./...
go build ./...
```
服务端固定为 go-admin v2.3.0,模块要求 Go 1.26.5;本机可由 `GOTOOLCHAIN=auto` 获取匹配工具链。对象存储用例带有 `integration` 标签,默认验证不会访问云端凭据。
默认运行目标是 MySQL。开发者在仓库根目录的 `config.yaml` 中维护本机连接参数和启动端口;该文件已被 Git 忽略,不得提交。可从不含凭据的 `config.example.yaml` 复制。启动脚本读取配置后,仅通过当前进程环境注入数据库连接、服务端端口和前端 API 地址,不把账号密码写回 `server/config/settings.yml`、日志、工单或文档。
如需启用 AI 规格匹配,管理员在“AI 规格匹配”页面配置单一 OpenAI-compatible Provider 的 Base URL、模型、超时和 API Key;超时默认 15 秒,可设置为 3~600 秒。根据 #62 已确认的内部部署例外,API Key 明文保存在专用设置表,并只在管理员设置页面回显,不能出现在代码、日志、工单、Wiki、任务快照、采购员接口或 Android 接口。Base URL 支持公网或内网的 HTTP/HTTPS 地址,不作局域网限制;HTTP 不加密传输中的 API Key,生产环境建议 HTTPS。
已经执行过旧版 GoAuto 表结构的数据库,会由 `1786700300000_collection_execution.go` 增量补齐任务幂等、结果、软删除和颜色价格字段;不要通过修改已执行迁移的版本号强制重跑。
Windows 本地 MySQL 8.4 可以从仓库根目录双击或执行:
```powershell
.\start-server.bat
```
首次使用先编辑根目录 `config.yaml`:`database` 节点配置 `host`、`port`、`user`、`password` 和 `name`,`ports.server` 与 `ports.web` 配置 API 和管理端开发服务器端口。两个启动端口必须位于 1~65535 且不能相同。随后脚本直接读取该文件,创建数据库、执行迁移并启动服务,不再交互询问密码。迁移已经执行过时,可使用 `.\start-server.bat -SkipMigration`;只验证服务端配置可使用 `.\start-server.bat -ValidateConfigOnly`,只验证前端端口配置可使用 `.\start-web.bat -ValidateConfigOnly`。
管理端前端可单独双击 `start-web.bat`;也可双击 `start-all.bat` 同时打开服务端和前端两个窗口。访问地址分别为 `http://127.0.0.1:<ports.web>` 和 `http://127.0.0.1:<ports.server>`,默认仍是 9527 和 8000。前端开发 API 地址会自动跟随 `ports.server`。修改服务端端口后,还需同步修改 Android Agent 保存的服务地址。API 根路径显示 go-admin 默认欢迎页属于正常现象。
只验证最小闭环迁移和数据库约束:
```powershell
Set-Location server
go test ./app/goauto/...
```
该命令同时覆盖设备注册的首次签发、requestId 重放、错误 Token、防接管、停用/吊销、默认 HTTPS、显式 HTTP 例外、限流、心跳任务一致性和超时离线失败测试。
AI 规格匹配的隔离验证:
```powershell
Set-Location server
go test ./app/goauto/aimatching ./app/goauto/purchase
```
该测试覆盖确定性标准化、歧义拒绝、Provider 回退的候选原文校验、内部 API Key 的管理员读取/采购员隔离以及 HTTP/HTTPS Base URL 校验;不会调用真实 Provider 或创建订单。
如需用临时 SQLite 做隔离联调,服务端命令必须显式带上 SQLite 构建标签:
```powershell
Set-Location server
go run -tags sqlite3 . migrate -c config/settings.sqlite.yml
go run -tags sqlite3 . server -c config/settings.sqlite.yml
```
SQLite 只用于测试;正式运行和最终迁移目标仍为 MySQL 8.4。真实 MySQL 连接串继续通过 `GOAUTO_DB_DSN` 注入。
## SYB 定时同步
迁移 `1786701600000_syb_hourly_sync_job.go` 会幂等写入 go-admin 的 `sys_job`,调用目标为 `GoAutoSYBHourlySync`,默认 Cron 为 `0 5 * * * *`、状态为启用。已有同调用目标的任务不会被迁移覆盖;管理员可在 go-admin“定时任务”中调整 Cron、启停状态和参数。
默认参数为 `{"lookbackDays":2,"timezone":"Asia/Shanghai"}`。当前实现允许回看 1~7 天;默认 2 天即当天和前一天。修改后需要让调度器重新加载任务(通常重启 Admin API)。
本地验证不要为了检查迁移而启动 Admin API:迁移本身不会访问 SYB,但启用状态的定时任务会在服务启动并到达下一次调度时间后访问已配置的 SYB。可只执行迁移并再次执行确认幂等:
```powershell
Set-Location server
go run . migrate -c config/settings.yml
go run . migrate -c config/settings.yml
```
## Web 验证
```powershell
Set-Location web
pnpm install --frozen-lockfile
pnpm run lint
pnpm run build:prod
```
上游现存 lint 警告和构建体积提示不会阻断验证,但新增代码不得增加错误。
## Android 验证
```powershell
Set-Location android
.\gradlew.bat test
.\gradlew.bat assembleDebug
```
Android 骨架使用 Kotlin 1.9.22、AGP 8.2.0、Java 17 和 SDK 34。
真机联调或经管理员确认的 HTTP 部署可在构建时设置 `GOAUTO_SERVER_URL`。Debug 和 Release 都接受 HTTP 或 HTTPS Origin;服务端生产模式默认仍拒绝 HTTP,仅显式设置 `GOAUTO_ALLOW_INSECURE_AGENT_HTTP=true` 后允许。HTTP 会明文传输 Device Token、任务内容和执行结果。
## 原型验证
- 直接打开 `prototypes/server-admin.html` 和 `prototypes/android-agent.html`。
- 检查 375、768、1024、1440 像素宽度。
- 用键盘完成导航,检查明显焦点、表单标签和删除/重置确认。
- 开启 `prefers-reduced-motion` 后不应依赖动画表达状态。
## 必须真机验证的范围
- 一加/ColorOS 的无障碍绑定、后台运行和安装保护。
- 任务执行期间 Agent 只持有最长 5 分钟的屏幕唤醒锁,并在任务结束时释放;不解锁安全锁屏,也不常驻保持屏幕。
- 浏览器打开 PDD、两层确认、商品详情识别和规格遍历。
- 网络断开、登录失效、验证码、风控和规则删除后的快照执行。
- 一台设备串行任务和 20 台设备连接稳定性。
## cmautobuy 商品导入
从 `server/` 运行 #70 独立命令。两个配置文件都必须是未被 Git 跟踪的本地文件;密码不接受命令行参数。
```powershell
# 默认只读预检,不写两个数据库
go run ./cmd/import-cmautobuy-products --source-config "D:\chengma\cmautobuy\admin\config.yaml" --target-config "D:\OPC\goauto\config.yaml"
# 仅在检查 dry-run 精确数量、备份目标商品表并再次人工确认后运行
go run ./cmd/import-cmautobuy-products --source-config "D:\chengma\cmautobuy\admin\config.yaml" --target-config "D:\OPC\goauto\config.yaml" --apply
```
也可分别用 `GOAUTO_CMAUTOBUY_CONFIG` 和 `GOAUTO_CONFIG` 指定路径。来源配置支持 `disabled` 或 `verify_ca`;相对 CA 路径按来源配置文件目录解析。导入报告不得包含 DSN、密码或原始业务响应。
MySQL 8.4 写入路径的隔离测试只允许连接名称以 `_test` 结尾的数据库:
```powershell
$env:GOAUTO_IMPORT_MYSQL_TEST_DSN="<仅由安全环境注入的 _test DSN>"
go test ./app/goauto/cmautobuyimport -run TestRunMySQL84DryRunAndApply -count=1
Remove-Item Env:GOAUTO_IMPORT_MYSQL_TEST_DSN
```
## Windows Supervisor 托管
本机安装的 Go Supervisor 位于 `D:\supervisor`。GoAuto 的版本化配置源为 `scripts/supervisor/goauto.conf`,运行副本为 `D:\supervisor\programs\goauto.conf`。两个实例都读取仓库根目录已忽略的 `config.yaml`,Supervisor 配置不得保存数据库密码:
- `goauto-admin-api`:调用 `scripts/start-server.ps1`,执行迁移后启动 Admin API。
- `goauto-admin-ui`:调用 `scripts/start-web.ps1`,先等待 `ports.server` 对应的 `/api/v1/health` 返回 200,再启动 Admin UI;脚本会解析 Node.js 并直接运行项目的 Vite CLI,避免 Supervisor 子进程缺少 Node PATH。API 在 60 秒内未就绪时,脚本明确失败并由 Supervisor 按重启策略处理。
- 日志:`D:\supervisor\logs\goauto-admin-api.log`、`D:\supervisor\logs\goauto-admin-ui.log`。
- Supervisor 管理界面:`http://127.0.0.1:9009`。
部署或修改配置后执行:
```powershell
Copy-Item -LiteralPath .\scripts\supervisor\goauto.conf -Destination D:\supervisor\programs\goauto.conf
D:\supervisor\supervisord.exe /c D:\supervisor\supervisord.conf ctl reload
D:\supervisor\supervisord.exe /c D:\supervisor\supervisord.conf ctl status
```
单独控制实例:
```powershell
D:\supervisor\supervisord.exe /c D:\supervisor\supervisord.conf ctl restart goauto-admin-api
D:\supervisor\supervisord.exe /c D:\supervisor\supervisord.conf ctl restart goauto-admin-ui
```
Supervisor 托管期间不要再运行 `start-all.bat` 或重复启动对应单端脚本,否则会因 8010/9527 被占用而失败。两个 GoAuto 实例同时重启时,Admin UI 会等待 API HTTP 就绪后再监听 Web 端口;SYB 商品页对一次短暂网络断开做单次有界重试,不对认证、权限或业务错误重试。
### Android Agent 0.3 四 Tab 真机检查
#88 起 Debug APK 版本为 0.3.0。除单元测试和构建外,在设备空闲且没有采集/采购任务时安装:
```powershell
Set-Location android
.\gradlew.bat testDebugUnitTest assembleDebug
adb install -r app\build\outputs\apk\debug\app-debug.apk
adb shell am start -n cn.ilapage.goauto.agent/.MainActivity
```
真机至少检查:
- 底部状态、采集、采购、设置四项都有图标与文字,默认状态页,触控目标不少于 48dp。
- 状态页分别验证无障碍未开启、系统已开启但服务未绑定、服务已绑定就绪;返回系统设置后自动刷新。
- 设置页服务器地址、设备名称、测试连接和保存重连反馈可读;测试连接只访问 `GET /api/v1/health`。
- 任务执行中服务器地址、设备名称、测试和保存均禁用;Token 只显示“已配置/未配置”。
- 采集/采购 Tab 在 #90 前只显示明确占位,不应出现采购写操作、PDD 凭据或支付入口。
- Debug 和 Release 都应验证 HTTP/HTTPS Origin;使用 HTTP 时必须确认服务端已显式开启 `GOAUTO_ALLOW_INSECURE_AGENT_HTTP=true`,并记录明文传输风险。安装前必须确认设备空闲,避免重启 Agent 中断任务。
### Android Agent 0.6.0 采购失败重试检查
- 无真机授权时只运行 `cd android && .\\gradlew.bat testDebugUnitTest assembleDebug`,以及服务端采购包测试;不要调用采购重试接口,因为成功后新任务会进入正式采购队列。
- 获得独立授权后,使用一条确认未进入不可逆边界、无 PDD 订单号的当前设备失败任务检查列表和详情入口、二次确认、旧任务保留、新任务编号/地址后缀、固定原设备与幂等反馈。
- 取消确认不得发请求;成功后只能由既有队列执行,验证停止在创建待付款订单并读取订单号,永久禁止支付。
### Android Agent 0.3.1 空闲返回与亮屏检查
#89 起 Debug APK 版本为 0.3.1(versionCode 4)。先确认设备空闲,再覆盖安装;安装后系统可能关闭无障碍服务,需要人工重新开启。
真机验证应至少覆盖:
- 分别在采集、采购和设置 Tab 离开 Agent,完成一个不会支付的采集或演练任务,确认结果已提交后约 5 秒返回 Agent,并保持离开前 Tab;记录 ROM 是否限制后台 Activity 启动。
- 在最近 Tab 不是状态页时重建 MainActivity 或 Agent 进程,确认恢复最近有效 Tab;无历史或无效值回退状态页,系统辅助功能按钮同样恢复最近一次有效 Tab。
- 冷却 5 秒内创建任一类型新任务,确认 Agent 取消返回并继续按采购优先、采集其次执行。
- 冷却期间断网,确认不会把网络失败当成空队列,也不会返回。
- 冷却期间人工切换到其他 App,确认 Agent 不会强拉回;即使再切回 PDD,前台切换序号变化也会取消本次返回。
- 使用 `adb shell dumpsys power` 或屏幕实际状态确认任务执行与冷却期间亮屏;返回、取消或服务停止后不再持有 `cn.ilapage.goauto.agent:collection-task` / `:idle-return` WakeLock。
- 该流程不自动解锁安全锁屏,不点击创建订单或支付。
### Android 系统辅助功能按钮检查(#97)
- 覆盖安装可能被部分 ROM 视为无障碍服务配置变化并自动关闭服务;不得用 ADB 强行开启,必须由用户在系统设置中重新开启“采集采购助手”。
- Android 8.0 及以上且系统已把辅助功能按钮分配给 GoAuto 时,单击图标应只把 Agent 切到前台并显示“状态”Tab;返回键行为保持系统默认。
- 验收时先确认设备无活动任务,再检查点击不会打开 PDD、领取/重试任务、修改地址、创建订单或支付。不支持该系统按钮的 ROM 只验证无崩溃和现有 Agent 入口可用。
### Android Agent 0.7.0 状态页下拉检查(#98)
- 先确认设备没有活动任务,再覆盖安装 Debug APK;覆盖安装后若系统关闭无障碍服务,必须由用户在系统设置中重新开启“采集采购助手”。
- 状态 Tab 顶部下拉应显示检查中,并分别验证:暂无任务、领取采集、领取采购、设备忙、无障碍未开启和网络失败。检查期间重复下拉不得产生并发请求。
- 使用服务端日志或任务状态确认手动动作复用了既有 `next → claim → start` 调度;自动轮询仍为 15 秒、采购优先和单设备串行。
- 状态页不再提供无障碍设置按钮或重复设备信息卡;设备身份只显示简短名称与编号,唯一无障碍入口位于设置 Tab。
- 无授权任务时只验证空队列、忙碌、无障碍和网络反馈,不创建正式采购任务或订单,永久禁止支付。
### Android Agent 0.8.0 任务记录刷新与同步检查(#99)
- 运行 `cd android && .\\gradlew.bat testDebugUnitTest assembleDebug assembleRelease`,服务端运行 `go test ./app/goauto/task ./app/goauto/purchase`;确认 `days` 省略时仍为 30,1/3/7/15/30 天有效,0、负数和大于 30 均拒绝。
- 真机覆盖安装前确认设备无活动任务。采集、采购 Tab 在列表顶部下拉应只刷新当前筛选、编号和当前页;重复下拉不得并发,成功、无变化和失败均有文字反馈,失败页仍有“重新加载”。
- 设置页默认 7 天,可切换 1/3/7/15/30 天;分别检查完整成功、范围内无记录、单类失败和全部失败,按钮在同步中禁用并在结束后恢复。
- 断网后打开有缓存的同范围列表,应显示最近同步摘要;任务详情仍需联网读取。检查应用数据中不存在 Device Token 明文、完整规则、PDD URL、地址、控件树或截图。
- 只读刷新和同步无需创建正式任务;验收不得借此触发重新采集、采购重试、PDD、创建订单或支付。
### Android Agent 采集任务间隔检查(#102)
- 运行 `cd android && .\\gradlew.bat testDebugUnitTest assembleDebug assembleRelease`;单元测试至少覆盖两个输入的 0/15/600 秒边界、起始值大于结束值、`0~0`、固定范围、包含上下边界的可控随机、倒计时向上取整、过期清理、系统时间回拨截断和亮屏策略。随机源必须可注入,禁止概率性测试。
- 真机覆盖安装前确认设备无活动任务。设置页检查“采集任务执行间隔”默认 `15~15` 秒、两个 0~600 整数输入、左值不得大于右值、保存反馈,以及修改设置不改变已经开始的倒计时。
- 分别以成功、部分完成和失败的采集任务确认:结果被服务端接收后进入“在线 · 采集间隔中”;状态页下拉和采集记录“重新采集”均显示剩余秒数且不能绕过;间隔结束后现有调度器继续领取下一条采集任务。
- 间隔期间创建采购任务,确认采购仍优先执行;采购结束后若原间隔未到期,采集继续等待。心跳、采购 Outbox、记录刷新与同步不受影响。
- 将范围设为非固定值并完成一次采集,记录状态页显示的实际剩余秒数;间隔期间重启 Agent 前台服务,确认按同一抽取结果恢复且没有重新随机。使用 `adb shell dumpsys power` 确认 `:collection-cooldown` WakeLock 有界持有并在到期或服务停止后释放。
- 此项验证不要求创建正式采购订单;没有单独授权时不得点击创建订单,永久禁止支付。
### Android Agent 0.9.6 当前页面临时采集检查(#101/#107/#108/#109)
- 自动化验证运行 `cd android && .\gradlew.bat testDebugUnitTest assembleDebug`,服务端运行 `cd server && go test ./...`;覆盖本地串行预占转移、分享 URL 白名单、唯一分享/复制入口、默认规则、创建/识别幂等、身份冲突和结果写回。
- 本地 MySQL 8.4 必须先在明确授权后运行 `cd server && go run . migrate -c config/settings.yml`,确认迁移版本 `1787790000000` 已应用;重复执行应报告 0 个新增迁移。迁移保留旧任务并回填来源 `admin`。
- 真机覆盖安装前确认设备空闲、Agent 0.9.6 已上报 `collector.pdd.current-page-share.v1`、无障碍已人工开启,服务端已有 Agent 手动采集默认规则。
- 在 PDD 人工打开已授权的测试商品详情页,分别经 PDD 直接切换、桌面图标和最近任务进入 Agent,点击“采集”并确认;验证前台服务先启动,PDD 现有任务被拉回前台且仍停在原商品详情页,没有清栈、重置首页或启动浏览器,再依次完成分享、复制链接、goods_id 识别和常规采集。另验证 PDD 未安装、启动 Intent 缺失、前台切换超时和前台服务启动被拒绝时均有可见反馈。
- 分别验证含 goods_id 的直链,以及白名单内无 goods_id 的 `p.pinduoduo.com` 短链和 `mobile.yangkeduo.com/goods2.html?ps=...`:直链不应触发 Agent 额外网络展开;无 goods_id 的白名单链接应优先由手机侧在 4 跳、每次连接/读取 5 秒、64KB 正文上限内展开,并覆盖 302/307。正文只含 `refer_goods_id` 时不得误判为商品身份。另覆盖链接后紧跟中文、多个链接冲突、白名单外跳转、超时和服务端兜底;诊断、日志和数据库不得出现链接原文、goods_id、剪贴板或响应正文。成功/部分成功应显示来源“Agent 当前页面”,相同 goods_id 不产生重复商品,任务详情与 PDD 最新档案一致。
- 断开网络、离开详情页、制造重复分享入口或剪贴板不可用时,应得到普通人可理解的失败原因并释放设备槽;原始分享文案、剪贴板、控件树和截图不得出现在数据库或日志。
- 验证期间不得自动搜索或选择相似商品,不得修改虾皮关联,不创建采购任务、不修改地址、不创建订单,永久禁止支付。
### Android Agent 0.9.28 应用内更新检查(#143/#144)
- 自动验证:`cd server && go test ./app/goauto/apprelease ./app/goauto/access ./app/goauto/migrations`;`cd android && .\gradlew.bat testDebugUnitTest assembleDebug`。服务端 APK 解析测试在 debug APK 已构建时读取真实 Manifest,当前期望 `versionCode=41`、`versionName=0.9.28`。
- 新增迁移 `1787983700000_agent_app_release.go` 只创建 `agent_app_release` 和 `agent_app_release_setting`。未获得明确本机/正式库迁移授权时只做测试库验证。
- APK 文件目录使用 `GOAUTO_AGENT_RELEASE_DIR`;缺省为服务端工作目录下 `var/goauto-agent-releases`,不得映射成公开静态目录。部署需让 Admin/API 进程对该目录具有创建、写入、读取和删除临时失败文件的权限。
- 真机发布验证前确认设备空闲,再由管理员上传一个签名一致、versionCode 更高的 APK并显式设为当前。依次验证启动静默提示、设置页手动检查、下载进度/取消、断网、哈希不一致删除、未知来源引导和系统确认安装。任务执行中全部更新动作必须被阻止,心跳与前台服务继续运行。
- 上传或设为当前属于发布动作,安装会改变设备应用版本;没有用户独立授权时不得执行。安装后系统可能关闭无障碍服务,只能由用户在系统设置重新开启。
## Chrome 订单回填扩展验证(#316)
从仓库根目录执行:
```powershell
cd chrome-extension
npm install
npm test
node --check service-worker.js
node --check content.js
node --check popup.js
```
自动化测试只使用合成 DOM 与 Chrome API mock,覆盖列表商品行负向选择、双展开与延迟字段、解析/冲突、启动互斥、停止、错误终态、扫描延迟增长、详情恢复、提交恢复、冻结配置、超时和可重试项。真实 PDD 浏览器点击、扩展安装和真实 Admin 上传不属于自动化验证,必须取得独立授权并在结果中明确区分。
-89
@@ -1,89 +0,0 @@
# 一加真机验收记录
> 验收结论:2026-08-15 用户已确认 T06~T17 通过验收;其中 T08 为已延期、未实施且不属于当前 MVP 的只读实时屏幕工单。
## 验收范围
T17 只验证 PDD 商品采集最小闭环:Agent 注册与心跳、任务串行领取、浏览器进入 PDD、规则快照执行、结构化结果或明确失败、管理端详情与重置。禁止购买和支付,不保存控件树或截图。
## 2026-08-15 一加 PKG110(Android 16)
已通过:
- ADB 设备在线,PDD App 已安装。
- Debug Agent 安装成功并保持前台服务。
- Agent 使用局域网地址访问服务端,设备自动注册成功,之后每 15 秒持续心跳。
- 手机到 `config.yaml` 的 `ports.server` 端口连通(默认 8000)。
- Android 单元测试与 Debug APK 构建通过。
- 人工启用“GoAuto 采集服务”后,系统显示服务已启用且已绑定;应用升级或被强制停止后,一加系统可能撤销该授权,必须重新人工启用。
- 指定设备任务成功经历 `pending`、`running`、`completed_partial`,完成任务领取、PDD 链接打开、规则快照执行和结构化结果提交。
- 未指定设备任务由在线空闲的一加设备原子领取,领取后设备快照固定为设备 2,并成功提交 `completed_partial`。
- 浏览器页面中的“安全验证”文案不再被误判为 PDD 验证码;登录、验证码和风控识别只在 PDD 包内生效。
- 真实商品页可访问节点能够识别商品标题、销量、店铺入口;进入店铺页能够识别店铺名;规格弹层能够识别颜色和尺码。
- 当前安全基线规则只验证到达 PDD 商品详情页,因此缺少标题、店铺、销量、评价、颜色和尺码时按约定提交 `completed_partial`,没有猜测数据。
- 同一任务重置后,旧错误和结果摘要已清空并可重新领取;URL、goods_id、规则和设备快照保持不变。
- 设备心跳中断后,运行中任务按约定以 `DEVICE_OFFLINE` 失败且没有自动重试或换机;重新启用设备后必须人工新建或重置任务。
- 全程未点击提交订单或支付控件,未保存控件树或截图。
- 最终版在任务执行期间使用有时限的屏幕唤醒锁,防止设备休眠中断心跳;任务终止后立即释放。
待验证:
- 用正式字段规则逐项采集标题、店铺、销量、评价、颜色、尺码和各颜色价格,并在管理端详情复核规格与 SKU 展开结果。
- 当前只有一台受支持的一加设备,多设备防双领已通过服务端并发测试,尚无第二台一加真机可做补充复核。
## 2026-08-15 最终闭环证据
- 任务 16(指定设备):`pending → running → completed_partial`;最终前台包名为 `com.xunmeng.pinduoduo`,Activity 为 `com.xunmeng.pinduoduo.activity.NewPageActivity`。
- 任务 17(未指定设备):由设备 2 原子领取并提交 `completed_partial`。
- 两条任务均保存 URL、goods_id、规则和设备关联,只保存结构化缺失字段清单;规格、颜色价格和 SKU 子表没有伪造记录。
- 本轮未执行购买或支付,也未持久化原始控件树或截图。
## 2026-08-15 T23 v2 真机复核
已验证:
- 一加 PKG110 / Android 16 的设备 2 上报 `rule.schema.v2`、`action.swipe.v1` 和 `collector.pdd.product-detail.v1`,心跳在线空闲。
- 正式规则 2 和任务 20 固化 schema v2 规则、商品 `719834019024`、URL 和设备快照;任务经历 `pending → running → completed_partial`。
- 浏览器点击返回值不可靠时,以离开浏览器后的最终 PDD 包名和 `NewPageActivity` 为页面事实,不把已成功跳转误报为动作失败。
- 规格入口使用右下方“价格 + 购买/拼单语义 + 最近可点击容器”强证据,只用于打开规格面板。
- 2026-08-21 三星 SM-G9700、PDD 7.95.0、商品 `172119352488` 的任务 42 暴露规格入口误触评价页:评价卡片包含规格词和数字,被旧兜底误判;评价页又与详情页共用 `NewPageActivity`。#59 收紧为明确选择语义或底部真实购买文字,排除评价上下文,并增加一次安全返回重试与分级错误。安装新 APK 并通过正式接口重置任务后,真机日志记录 `spec-entry source=bottom_purchase`、标签“快要抢光 免拼购买”、点击结果 `SUCCESS`,未进入商品评价页;规格面板采到 2 个颜色和 8 个尺码,任务以 `completed_partial` 提交,仅缺少颜色 `9031#长裤` 的价格。该缺失属于既有价格采集问题,不阻塞本次误触修复验收。
- #60 针对上述颜色价格缺失增加点击生效证据、300ms 刷新保护、连续稳定读取和分级诊断。三星覆盖安装新 APK 后再次重置任务 42,结果为 `completed`、缺失清单为空:颜色 `8913#长裤` 与 `9031#长裤` 均取得 3112 分价格,8 个尺码完整;两个颜色的日志均显示 `target_state` 证据和连续 2 次稳定价格读取。
- 2026-08-21 三星 SM-G9700 的采购任务 5(CG-5)使用不含 `openSpecPanel.textAliases` 的新规则快照完成第一趟 `spec_probe`:回传 2 个颜色、8 个尺码和两个颜色的 3112 分价格,attempt 结果为 `spec_probe_completed`。任务转入 `spec_probe_pending` 等待服务端规格决策;未修改地址,未创建订单,未提交支付。
- 图片型颜色卡片优先点击 `TextView` 颜色文字,排除 `ImageView`、“打开大图”和“查看大图”;真机不再进入 `SkuPhotoBrowseActivity`。
- 任务 20 采到 4 个颜色,四个颜色价格均为 2690 分,并生成 4 个可用、完整的单维 SKU;标题和“已拼299件”已提交。
- 管理端任务详情 API 返回 1 个规格维度、4 个颜色价格、4 个 SKU 和缺失清单,且没有原始控件树或截图字段。
- 当前商品只暴露出颜色维度,结果按规则以 `completed_partial` 提交,缺失为 `size`、`shopName`、`reviewCount`,没有猜测数据。
- 已验证规格标题、图片入口、窗口类名和浏览器返回值异常都会明确停止或采用受约束恢复,不点击相似候选。
二维规格复核:
- 商品 `972800403573`、任务 22 在同一台一加设备完成 `pending → running → completed_partial`,最终 Activity 为 PDD `NewPageActivity`。
- 采到颜色“灰色、黑色”和尺码“M码80-120斤、+同色螺纹袜”,两个颜色价格均为 492 分;按当前业务规则展开为 4 个可用且完整的二维 SKU,同一颜色价格正确复制到各尺码。
- 首次真机结果暴露出 `#一次选多款#` 是规格面板功能入口而非尺码。Agent 增加排除规则和单元测试后重置同一任务,旧结果被清空,复采结果由 6 个错误 SKU 收敛为 4 个正确 SKU。
- 标题和“已拼1件”已提交;页面未暴露店铺名和评价数量,因此按约定保存 `completed_partial` 和 `missing=[shopName, reviewCount]`,没有猜测字段。
- T23 所需的浏览器跳转、颜色文字点击、逐颜色价格、全部可见尺码和颜色 × 尺码展开均已有真机证据,实施验证完成,等待用户验收。
## 2026-08-15 T24 长规格列表复核
- 商品 `236231603269` 的任务 23 初次结果只有颜色并标记 `missing:size`;真机面板实际存在尺码,确认属于 Agent 遍历提前结束,而非商品无尺码。
- 修正后颜色容器与纵向规格面板通过当前规格节点的祖先关系分别锁定;规格面板先稳定归顶,颜色按视觉行交替方向蛇形遍历。
- 已确认 Android 坐标手势原来在 450ms 完成前被读取,导致旧视口连续两次被误判为列表边缘。现在优先调用目标容器的无障碍滚动动作;坐标手势回退等待完成回调。
- 最终重置复采状态为 `completed`、缺失清单为空:14 个颜色、14 个颜色价格、8 个尺码和 112 个完整可用 SKU;同一颜色价格按业务规则复制到各尺码。
- 尺码包含 `S` 至 `4XL` 和页面实际提供的定制选项;Agent 全程只读尺码,没有点击尺码、订单或支付控件。
- 标题候选改为与销量节点存在空间关联的文本,不再把页面任意最长文本当作商品标题;旧结果已由重置事务清除。
- 全程只持久化结构化结果,未保存或上传原始控件树和截图。
- 2026-08-17 三星 SM-G9700 的任务 35 复采验证了选中颜色节点消失并重排的兼容修复:3 个颜色、3 个价格和 1 个尺码均已保存,`colorRow:1/2` 误报消失。评价数量仍未暴露为受支持的无障碍文本,因此按约定保留 `completed_partial` 和 `missing=[reviewCount]`,用户确认暂不处理。
- T24 于 2026-08-17 经用户明确验收通过。
## 支持范围
当前 MVP 只维护一加/ColorOS 真机兼容,不包含华为 ROM。实机过程中发现的精确 Activity 页面证据和最近可点击父容器规则属于通用安全能力,继续保留。
## 通过标准
- 每台设备同一时刻最多运行一个任务,指定任务不能被其它设备领取。
- 页面控件唯一匹配;未命中或多命中必须失败,不猜测点击。
- 登录、验证码、人机验证、风控和无效链接返回稳定错误码且不自动重试。
- 字段不完整仍保存结果并标记 `completed_partial`。
- 任务详情可查看规格与 SKU;重置后旧结果和错误全部清空。
- 全程不触发购买/支付,不保存原始控件树或截图。
-121
@@ -1,121 +0,0 @@
# PDD 商品详情采集规则迁移分析
## 结论
从 `D:\chengma\cmautobuy\client` 迁移这套能力是合理的,但不能把其中的文字、resource-id 或坐标直接拼成 GoAuto `schemaVersion: 1` 规则。源项目的可靠性来自一套有状态采集算法,而 GoAuto v1 目前只支持唯一节点上的单次 `wait`、`click`、`input`、`back` 和 `extract`。
推荐新增 `schemaVersion: 2`、可扩展的类型化动作注册表和高层采集动作:服务端规则管理页面证据、规格别名、阶段钩子、超时和遍历上限;Android Agent 提供经过测试的动作实现,并由策略层按 `ruleType` 授权。规则草案见 [pdd-product-detail-v2.proposed.json](rules/pdd-product-detail-v2.proposed.json)。该文件目前是设计输入,v2 执行器完成前不能导入管理端。
## 源项目实际做法
正式采集链位于 `client/src/pdd_collect_service.py`,可复用的是以下行为:
1. 校验 URL 域名和 `goods_id` 一致性。
2. 打开链接后持续分类页面,区分商品页、首页、登录、验证码、网络错误、风控和支付页。
3. 读取商品摘要;有限纵向滚动补采店铺和评价。
4. 用多项强证据确认规格面板,避免把推荐商品或全屏散落文字误判为规格。
5. 颜色列表先归左,再按行蛇形遍历;每次点击后丢弃旧树并重新读取节点。
6. 只有确认颜色已选中,且价格连续两次一致,才建立颜色与价格的关联。
7. 颜色不可选或价格缺失时保留缺失信息,继续采集其它颜色和尺码。
8. 尺码只读不点击;横向或纵向滑动到连续两次视口稳定,并校验“尺码(N)”中的数量。
9. 按颜色价格展开颜色与尺码组合,并限制最大 SKU 数量。
源项目正式采集使用 `device.open_url()` 发起深链,不是先控制浏览器。`tools/test_pdd_home_deeplink.py` 只是用固定通用 URL 做只读诊断。因此浏览器“打开拼多多APP”和系统“打开”的步骤应继续采用 GoAuto 已通过一加真机验证的导航链,不能声称它来自源项目正式采集规则。
## 不能直接迁移的内容
| 源项目能力 | GoAuto v1 现状 | 直接迁移结果 |
|---|---|---|
| 同一容器提取多个规格值 | 每步必须唯一匹配 | 多个颜色或尺码会报 `RULE_AMBIGUOUS` |
| 横向、纵向滚动及边界判断 | 没有滚动动作 | 只能读取当前视口 |
| 点击后重新获取树 | 线性步骤不表达节点失效 | 容易点击旧节点或错绑价格 |
| 选中状态、可用状态和 bounds | `UiNodeRef` 只有文本 | 不能确认点击是否生效 |
| 价格连续两次稳定 | 只读取一次 | 可能记录颜色切换前的旧价格 |
| 颜色与价格成对累计 | `extract` 只产生独立字符串列表 | 两个数组可能错位 |
| 规格面板容器和相对位置 | selector 只支持节点自身属性 | 推荐列表可能被误判为规格 |
| 完整性计数、总时长和 SKU 上限 | 仅有单步超时 | 无法证明遍历完整,也缺少资源上限 |
因此,给 v1 增加一批 `extractAll` 步骤仍不能解决关联、滚动和状态确认问题。
## 推荐架构
```text
任务规则快照(服务端)
├─ navigation:浏览器和系统确认层的精确允许动作
├─ pageEvidence:PDD 包名、Activity 和商品页证据
└─ collector:pddProductDetailV1 + 别名、超时、次数和 SKU 上限
│
▼
Android 类型化状态机
页面分类 → 规格入口 → 面板强校验 → 逐颜色确认并采价 → 只读尺码 → 组装结果
│
▼
结构化结果(不含原始控件树和截图)
```
### 为什么采用可扩展动作注册表,而不是任意脚本 DSL
Agent 不限制为“只能采集”,而是注册带版本的类型化能力,例如 `swipe.v1`、`pddProductDetail.v1` 和未来的 `pddCreateOrder.v1`。规则可以组合 Agent 已声明支持的动作,因此增加一次已支持的滑动不需要升级 APK;只有出现新动作类型或新页面算法时才升级 Agent。
把任意脚本、任意坐标和不受约束的循环开放给服务端仍不可取:它们无法静态校验,可能绕过任务类型边界,也很难证明不会误触付款。类型化动作注册表保留扩展性,同时让服务端校验参数,让 Android 策略层做最终授权。
### 服务端可配置内容
- 浏览器包名和精确的“打开拼多多APP”/系统“打开”步骤。
- PDD 商品详情精确包名、Activity 和根节点证据。
- 颜色、尺码标题的精确别名列表。
- 页面、选中、价格稳定超时。
- 最大商品页纵向滑动、规格横向/纵向滑动和最大 SKU 数量。
- 固定阶段的安全钩子;当前支持语义目标、方向、次数和动作后等待。
例如规格面板打开后向上滑动两次,只需更新规则,不需要修改 Agent:
```json
{
"hooks": {
"afterSpecPanelOpen": [
{
"action": "swipe",
"target": "specPanel",
"direction": "up",
"count": 2,
"settleMs": 350
}
]
}
}
```
### Android 固定内容
- 动作注册表与参数边界;规则只能引用 Agent 声明支持的能力,不能提交可执行代码。
- 规格入口默认使用 `safeBottomSpecEntryV1` 策略,不接受规则提供任意坐标。
- 价格只能使用固定人民币解析器,金额输出为整数分。
- 每次颜色点击后必须重新获取内存控件树;不能复用旧节点。
- 若页面暴露 `selected`/`checked` 或“已选”摘要,必须据此确认颜色已选中。
- 价格必须连续两次读取一致才算该颜色的价格。
- 尺码只读,不点击;禁止任何提交订单和支付动作。
- 每个循环都有次数和总时长上限。
- `collection` 规则不能调用创建订单动作;未来 `purchase` 规则可以调用专用的 `pddCreateOrderV1`,但付款相关动作在最底层始终拒绝。
## 相对源项目的优化
1. **不复制 XML 解析实现。** GoAuto 直接把 `AccessibilityNodeInfo` 投影为仅存在内存的不可变树模型,避免序列化、落盘和再次解析 XML。
2. **保留浏览器链。** 源项目直接深链,GoAuto 则继续验证浏览器提示、系统确认和最终 PDD Activity,符合当前设备部署方式。
3. **能力协商。** 设备注册/心跳应上报 `rule.schema.v2`、`action.swipe.v1` 和 `collector.pdd.product-detail.v1`;服务端不能把 v2 任务发给旧 APK。只比较 `agentVersion` 不够可靠。
4. **第三维不静默合并。** 若出现颜色、尺码之外的维度,保存已确认的数据并提交 `completed_partial`,在 `missing` 标记不支持维度;不伪造完整 SKU。源项目当前直接失败,和 GoAuto“数据不齐仍提交”的规则不完全一致。
5. **缺价不伪造 SKU。** 颜色仍进入维度,缺少稳定价格时记录 `price:<颜色>`;不为该颜色生成带虚构价格的 SKU。
6. **规则能力版本独立。** `schemaVersion` 管契约形状,`collectorId` 管 Android 算法能力,便于以后修复页面适配而不破坏已有任务快照。
7. **采集与采购分权。** Agent 平台可以扩展到创建订单,但规则必须声明类型;采集规则无权创建订单,采购规则可以在独立高风险流程中创建订单,任何规则都无权付款。
## 建议实施顺序
1. 定义 v2 规则契约、设备能力协商和服务端校验。
2. 建立纯 Kotlin 内存节点树与可单测的 PDD 采集状态机。
3. 接入无障碍服务的滚动、节点刷新、选中与价格稳定读取。
4. 在管理端提供内置规则模板,避免管理员手写大段 JSON。
5. 用一加真机验证浏览器到商品页、多个颜色价格、全部尺码和部分结果。
## 当前状态
T19~T22 已验收:v2 契约、能力协商、Android 商品详情采集器、服务端内置模板和管理端安全编辑表单已经接入。T23 已在一加真机跑通浏览器跳转、规格面板强识别、颜色文字安全点击、逐颜色稳定价格、全部可见尺码读取和颜色 × 尺码 SKU 展开。T24 进一步迁移并强化了源项目的逐行蛇形算法:用规格节点祖先锁定横纵容器、以文字和 bounds 判断视口、等待手势完成、尺码标题滚出后按容器结构续页。任务 23 使用商品 `236231603269` 得到 14 个颜色、8 个尺码、14 个颜色价格和 112 个完整可用 SKU,状态为 `completed`。T23、T24 实施验证完成,等待用户验收。
-89
@@ -1,89 +0,0 @@
# 产品需求总览与当前 MVP
## 本页用途
本页统一导航 GoAuto 的长期需求、状态、工单、原型和验收入口,同时保留当前采集 MVP 的稳定范围。它不复制完整工单、原型内容或聊天记录。
## 事实来源边界
| 信息 | 唯一事实来源 |
|---|---|
| 项目目标、用户、规模和建设基线 | [项目档案](https://git.ilapage.cn/OPC/goauto/wiki/Project-Profile) |
| 长期业务规则和安全边界 | [业务规则与术语](https://git.ilapage.cn/OPC/goauto/wiki/Business-Rules-and-Glossary) |
| 单次实现范围、变化和验收 | Gitea 单元工单 |
| Android 与服务端共享接口 | [Agent API 契约](https://git.ilapage.cn/OPC/goauto/wiki/Android-Agent-API-Contract) |
| 外部原型 | QuantUX 应用和对应工单评论 |
| 真机验证结果 | [一加真机验收](https://git.ilapage.cn/OPC/goauto/wiki/OnePlus-Real-Device-Acceptance)和对应工单 |
## 长期需求索引
| 需求领域 | 用户与场景 | 状态 | 工单 | 原型 / 验收入口 |
|---|---|---|---|---|
| PDD 商品采集闭环 | 管理员维护商品、规则和设备,由 Android 采集结构化结果 | 已交付;T23~T28 增强项部分待验收 | [Epic #1](https://git.ilapage.cn/OPC/goauto/issues/1)、[MVP #2](https://git.ilapage.cn/OPC/goauto/issues/2)、[#3~#30 索引](https://git.ilapage.cn/OPC/goauto/wiki/Delivery-Issues) | [真机验收](https://git.ilapage.cn/OPC/goauto/wiki/OnePlus-Real-Device-Acceptance) |
| PDD 正式商品档案 | 采购人员长期复用商品资料,可由采集或人工覆盖维护 | 原型草稿,待用户确认 | [#31](https://git.ilapage.cn/OPC/goauto/issues/31) | [QuantUX App `6a81db5d191a826306a7edd6`](http://124.222.27.183:8082/#/apps/6a81db5d191a826306a7edd6.html) |
| Shopee、SYB 与 PDD 商品关系 | 从 SYB 明细提取 Shopee 商品,人工关联 PDD 商品和规格 | #40、#41 Stage B 服务端与 Admin 页面均已完成;#41 尚缺 SYB 接口拉取客户端(无凭据,明确记录待补) | [#40](https://git.ilapage.cn/OPC/goauto/issues/40)、[#41](https://git.ilapage.cn/OPC/goauto/issues/41) | [#40 QuantUX App `6a83b708191a826306a7eeb1`](http://124.222.27.183:8082/#/apps/6a83b708191a826306a7eeb1.html);[#41 QuantUX App `6a83d7b4191a826306a7eebd`](http://124.222.27.183:8082/#/apps/6a83d7b4191a826306a7eebd.html) |
| PDD 采购闭环 | 采购人员派发任务,Agent 选规格、改地址并创建待付款订单,人工支付后回填物流 | #33、#34 数据契约与服务端状态机已验收;下一步 #42 Android 无副作用采购演练 | [#32](https://git.ilapage.cn/OPC/goauto/issues/32)、[#33~#39](https://git.ilapage.cn/OPC/goauto/wiki/Delivery-Issues)、[#42](https://git.ilapage.cn/OPC/goauto/issues/42) | [QuantUX App `6a827440191a826306a7eddd`](http://124.222.27.183:8082/#/apps/6a827440191a826306a7eddd.html) |
| SYB 档口入库码 | 采购员导入标签入库码 Excel,系统自动匹配 SYB 商品,人工确认后逐件回写 | #119~#122 已实现,待用户验收;不包含可选 #123 | [Epic #117](https://git.ilapage.cn/OPC/goauto/issues/117)、[#119](https://git.ilapage.cn/OPC/goauto/issues/119)~[#122](https://git.ilapage.cn/OPC/goauto/issues/122) | [已确认原型快照](https://git.ilapage.cn/OPC/goauto/src/branch/main/prototypes/118/v1/index.html) |
| 开发治理与需求追溯 | 负责人和 Agent 需要可复现模板基线、双门禁和需求索引 | 本次文档升级待验收 | [#43](https://git.ilapage.cn/OPC/goauto/issues/43) | 无 UI 原型;DevHarness 目标提交见项目档案 |
原型状态只有“草稿、已确认、已废弃”。#31 原型已确认(2026-08-17),#32 原型已确认(2026-08-18),均可作为实现依据;#40 原型 2026-08-19 按 #46 修订后重新确认,#41 原型 2026-08-19 首次确认,两者均可作为实现依据,Stage B 代码尚未开始。
## 当前采集 MVP 用户闭环
1. 管理员添加一个 PDD URL。
2. 服务端提取 `goods_id`;重复时提示商品已存在。
3. 管理员创建一条采集规则,创建成功后立即可用。
4. 管理员从 PDD 商品创建采集任务,选择规则,默认指定设备,也可不指定。
5. 指定设备领取自己的任务;空闲设备可原子领取未指定任务。
6. Android 使用任务内的 URL 和规则快照进入 PDD 并采集结构化数据。
7. Android 把完整、部分或失败结果写回该任务。
8. 管理员在任务详情查看结果,必要时重置终态任务重新采集。
## 当前采集 MVP 包含
- PDD 商品新增、列表和 URL 编辑;URL 规范化、`goods_id` 提取和唯一性校验。
- 规则新增、编辑、列表和软删除;创建即生效。
- 单商品创建任务,可指定设备或留空。
- 设备注册、令牌、心跳、在线状态与单设备串行。
- 指定任务领取与未指定任务的空闲设备原子领取。
- 任务内 URL、goods_id 和规则快照。
- `pending`、`running`、`completed`、`completed_partial`、`failed` 状态。
- 标题、店铺、销量、评价、规格维度、规格值和 SKU 结果。
- 终态任务重置、失败任务删除和任务详情。
- 登录失效、验证码、风控、人机验证、控件缺失和离线错误。
## 当前采集 MVP 不包含
- SYB、货运单和 Shopee 商品。
- 采购、修改地址、创建订单和支付。
- PDD 与其他平台商品关联。
- 批量创建任务、规则草稿/发布/版本历史、全局停机和实时屏幕。
- 独立采集结果主表、自动重试、自动换机和离线续跑。
- OCR/VLM、原始控件树、截图和价格历史。
后续工单存在不代表以上能力已经进入当前采集 MVP。采购能力必须使用独立任务类型、规则权限和高风险门禁,任何阶段都不允许自动支付。
## 数据约束
- `pdd_product.goods_id` 唯一。
- 同一 `pdd_product_id` 最多一个 `pending` 或 `running` 采集任务。
- 一台设备最多一个 `running` 任务。
- 规则软删除后不能创建新任务,但已有任务继续使用自身快照。
- 终态任务提交后结果冻结,除非管理员执行重置。
- 重置事务保留输入快照,删除旧结果子记录并清空结果字段。
## 当前 MVP 验收
- 一加/ColorOS 完成真实商品闭环;当前 MVP 不包含华为兼容。
- 重复 goods_id 显示明确冲突,不产生第二条商品。
- 规则创建后可直接选用;删除后不能创建新任务,旧任务仍可执行。
- 指定设备和未指定设备两种领取方式正确且没有双领。
- 同一商品不能创建第二个未完成任务。
- 每个颜色价格正确展开到该颜色的尺码 SKU;部分结果正常展示。
- 重置后原结果彻底清除,快照保持不变,并可重新采集。
- 离线和各类安全页面返回明确错误且不自动重试。
## 更新时机
新的长期需求、状态变化、主要工单、原型或验收入口变化时更新本页。普通内部重构、小缺陷和不改变长期能力的任务只保留在工单。
-71
@@ -1,71 +0,0 @@
# 项目档案
## 基本信息
| 项目 | 内容 |
|---|---|
| 项目名称 | GoAuto 移动采集与采购管理平台 |
| 一句话目标 | 管理 Android 手机,采集 PDD 商品资料,并在独立高风险阶段支持创建待付款采购订单 |
| 主要使用者 | 管理员、采购人员、开发维护者 |
| Gitea 仓库 | `OPC/goauto` |
| 默认分支 | `main` |
| 当前已实施范围 | PDD 商品与采集闭环、虾皮/SYB 商品档案与导入、采购任务服务、Admin 采购管理与人工处理、Android 采购演练,以及 #36 已实现但尚未真机授权验证的正式地址/待付款订单能力 |
| 后续设计范围 | 从 SYB 创建采购任务、创建待付款订单、物流采集与自动回填 |
| 预计规模 | 20 台 Android;每天约 100 个采集任务、200 个采购任务 |
## 项目治理模式
GoAuto 默认采用轻量治理:文案、注释、格式、局部样式或布局、预期行为明确的小 Bug,以及不改变接口、数据结构、权限和安全边界的单模块低风险调整可以直接实施,无需为了留痕补建工单。完整独立需求、新页面、跨模块功能,以及涉及 API、数据结构、权限、安全、迁移或范围不明确的变化必须建立单元工单。采购、创建订单、真实个人或生产数据、权限、安全、并发、迁移、删除、发布和不可逆操作始终升级为高风险,必须具备对象和范围明确的人工授权;已有有效授权时不机械重复确认,范围或环境发生实质变化时重新确认。永久禁止付款以及设备、隐私、AI 和真机安全红线不可裁剪。
## 建设基线
| 基线 | 来源与版本 | 许可证 / 使用方式 | GoAuto 适配 |
|---|---|---|---|
| DevHarness | `D:\OPC\dev_harness`,目标提交 `ecab899` | 开发流程与文档模板 | 2026-09-05 升级(#221):在既有单人工单事实源基础上,选择性加入轻量治理、明确授权边界、中文默认沟通、Windows PowerShell 安全规则,以及按 revision 增量同步与显式深度检查;继续保留 GoAuto 专用高风险与安全门禁 |
| 服务端 | `go-admin` v2.3.0 | 上游开源管理端基线;升级时复核许可证和安全公告 | 保留认证、菜单、配置和管理端基础能力,新增 GoAuto 业务模块 |
| 管理端 | `go-admin-ui` v3.0.0,`web/package.json` 标注 MIT | Vue 管理界面基线 | 保留应用外壳与通用组件,新增 GoAuto 页面 |
| Android | 原生 Kotlin Agent | 自研业务客户端 | 通过管理员明确配置的 HTTP 或 HTTPS Origin 直连服务端,不保留 Windows 桌面 Client/ADB 作为生产拓扑 |
升级必须比较当前记录的目标提交与新的明确提交,不能笼统复制“最新版”。本次上一基线为 `4bbacf4d7fb265984396bb5589c544105043fa0b`,目标为 `ecab899`,选择性适配其后的 7 个提交;更早基线适配见 #47 与 #114。模板内容一律按 GoAuto 事实改写:不复制 DevHarness 的项目事实、任务记录、占位部署参数或历史归档;`harness.py` 继续校验 GoAuto 实际核心页面与产品 README,GoAuto 更严格的付款、订单、设备、数据和真机门禁继续优先。
## 交付单元
| 目录 | 职责 | 技术栈 | 独立验证 |
|---|---|---|---|
| `server/` | go-admin API、任务调度、规则、设备连接、结果持久化 | Go 1.26.5、go-admin v2.3.0、MySQL 8.4 | Go 单元与构建验证 |
| `web/` | go-admin-ui 管理端 | Vue、go-admin-ui v3.0.0、pnpm | lint、生产构建、浏览器验收 |
| `android/` | Portal/Agent、注册保活、任务执行和结果提交 | Kotlin 1.9.22、Android SDK 34 | Android 单元测试、APK 构建与真机验收 |
| `prototypes/` | 用户或项目规则明确要求时导出的版本化 HTML 原型快照;既有快照保留 | HTML/CSS/JavaScript | 入口、交互、相对资源和敏感信息检查 |
| `docs/` | 核心 Wiki 的本地只读镜像、版本绑定分析和规则文件 | Markdown、JSON | Wiki 镜像一致性、链接、结构和差异检查 |
跨端共享契约以 [Android Agent API 契约](https://git.ilapage.cn/OPC/goauto/wiki/Android-Agent-API-Contract) 为唯一事实来源。各交付单元可以独立构建和验证,但共享字段、状态或能力变化必须在同一工单中验证所有受影响端。
## 文档事实来源
- Gitea 工单是单次任务需求、变化、实现、测试、提交和验收的唯一事实来源。
- Gitea Wiki 只保存长期产品需求、架构、业务规则、开发规范、共享契约、安全边界和操作说明。
- Git `docs/` 保存核心 Wiki 的只读镜像,以及与代码版本绑定且未映射到 Wiki 的分析和规则文件。
- QuantUX 保存可编辑交互原型;工单必须记录可访问链接、App ID、版本或确认日期和审核状态。本地 HTML 只在用户或项目规则明确要求时按版本导出。
- Gitea Wiki 已于 2026-08-17 完成核心页面迁移、显式映射和单向同步验证,长期文档采用 Wiki-first。
- 只有长期事实变化时才按修改 Wiki、读取 revision、导出本地镜像、检查一致性并提交的顺序更新;无长期影响的任务不运行 Wiki 同步。不得直接编辑镜像后反向覆盖 Wiki。
- 既有 Wiki 任务归档和 `docs/task/` 只作历史兼容;标准任务不创建,只有用户明确要求专项快照时才使用 `archive` / `export`。
## 环境与凭据
- 服务端正式环境默认必须使用 HTTPS。仅当管理员接受 Device Token、任务内容和执行结果明文传输风险,并显式设置 `GOAUTO_ALLOW_INSECURE_AGENT_HTTP=true` 时,Agent `/api/agent/v1/**` 可通过 HTTP;管理端和第三方服务不因此放宽。
- #237 客户端密钥例外(用户 2026-09-07 明确接受风险):提交 `71f7751` 起,管理员密钥管理及 `/api/client/v1` 默认兼容 HTTP/HTTPS,无开关;HTTP 明文传输密钥及业务数据,建议优先 HTTPS。实际迁移部署仍须单独授权;此例外不改变其他第三方服务的安全边界。
- 每台设备使用独立 Device Token;Token 只存安全配置,不进入仓库。
- PDD 账号密码、Cookie、验证码和用户个人数据不得进入日志。
- 根目录 `gitea.env` 是本机工单访问配置,已被 Git 忽略。
- 根目录 `config.yaml` 是本机数据库和端口配置,已被 Git 忽略;示例见 `config.example.yaml`。
- `demo/` 是旧 PoC 的本地归档,已被 Git 忽略,不是新产品代码入口。
## 当前阶段
当前采集 MVP 的 T01~T22 和 T24 已完成实现并由用户验收;T08 只读实时屏幕已延期。T23、T25~T28 的一加真机增强、假售罄恢复、浏览器重开恢复和端口配置已经实现,其中部分工单仍等待用户验收。T24 的规格遍历已在一加和三星真机复核;三星未识别评价数量的问题由用户确认暂不处理。
#31 PDD 商品档案原型、数据库、API、Admin 页面和采集结果写回已于 2026-08-17 通过用户验收;#45 PDD 商品列表批量采集入口已于 2026-08-18 通过用户验收。#32 采购闭环原型及后续采购工单仍按各自门禁推进。采购永不支付,真实地址修改和创建订单属于必须再次人工确认的高风险范围。
#40、#41、#49~#52 已于 2026-08-20 通过用户验收,虾皮/SYB 商品档案、店铺过滤、异步同步记录、列表布局和 MySQL 同步缺陷修复均已完成。
#33、#34、#42 已于 2026-08-20 通过用户验收,采购数据契约、服务端状态机和 Android 安全演练基线已经完成。#35、#55、#56 已于 2026-08-21 通过用户验收,Admin 采购管理、列表与详情查询以及 go-admin 成功响应兼容已经完成。#36 已在 Android Agent 0.2.0 实现正式地址后缀、不可逆门禁、一次性创建待付款订单和只读核单;自动化测试与 APK 构建已通过,但未安装到真机、未修改真实地址、未点击真实创建订单,等待用户独立授权验证。
-685
@@ -1,685 +0,0 @@
# 12 顺云宝(SYB)ERP 接口契约
- 文档状态:**从 HAR 抓包还原**,未经官方文档核对
- 上游来源:本文档移植自 cmautobuy 项目的 `docs/admin/08-顺运宝接口.md`,随 [#48](https://git.ilapage.cn/OPC/goauto/issues/48) 一并引入
- 本仓库抓包样本:`demo/shunyunbaoerp_*.har`
- 基址:`https://www.shunyunbaoerp.com`
> 顺云宝账号、密码、Cookie 和会话**不得写入代码、日志、工单和文档**。
> 本文档里的账号、单号、金额全部是脱敏或示例值。
> 凭据通过环境变量注入,见 §8。
> 上游文档使用 `[必须]` / `[建议]` / `[待定]` 标注,移植时原样保留。
> 「上游工单 #NN」指 cmautobuy 项目的工单编号,与本仓库工单无关。
---
## 1. 这份文档是怎么来的,可信度如何
全部结论来自抓包和上游的示例脚本,**不是官方文档**。
上游那份 962 行的示例脚本 **未收录进本仓库**;下文引用它只是说明某个取值的出处,本仓库不依赖该文件。
凡是只有一个样本支撑的判断,
下面都标了 `[待定]`,实现时要在真实数据上再确认一次。
已核对过的 4 份抓包:
| 文件 | 覆盖 |
|---|---|
| `demo/shunyunbaoerp_login.har` | 验证码、登录 |
| `demo/shunyunbaoerp_stock_list.har` | **按日期范围**列表查询 |
| `demo/shunyunbaoerp_stock_query.har` | 按单号查询 + 货运明细 |
| `demo/shunyunbaoerp_userinfo.har` | 个人信息(用来探测会话是否还有效) |
---
## 2. 统一响应信封
所有 `/am/**` 接口都是这个形状:
```json
{ "status": true, "msg": "获取成功", "data": <任意>, "code": null }
```
`[必须]` 判断成功**只看 `status === true`**,不要看 HTTP 状态码——
服务端在业务失败时也可能返回 200。
`[必须]` `data` 的类型随接口变:可能是对象、数组,也可能是**裸整数**
(`listTotal` 就返回 `"data": 1`)。不要假设它一定是对象。
`[必须]` 失败时把 `msg` 和 `code` 一起带进错误信息,否则排查时看不出原因。
---
## 3. 认证
### 3.1 认证靠 Cookie,不是 token
`[必须]` **登录响应里的 JWT 从来不参与后续请求。**
已核对示例脚本全文:`self.token` 只出现在「登录时赋值 / 存缓存 / 读缓存 /
算缓存有效期」四处,**从没被放进任何请求头**。认证完全靠
`requests.Session` 自动维护的 Cookie。
所以 Go 侧要持久化的是 **Cookie**,token 只用来算过期时间——
甚至可以不存 token,直接存算好的 `expires_at`。
### 3.2 登录流程
```text
① GET /api/p/code1?<毫秒时间戳> → image/jpeg,约 2.3KB,4 位字母数字
② POST /am/auth/login → {"username","password","code"}
→ {"status":true,"data":{"user":{...},"token":"<JWT>"}}
```
`[必须]` 三步必须用**同一个 HTTP 客户端**(同一个 Cookie Jar)——
验证码是和会话绑定的,换客户端拿到的验证码对不上。
`[必须]` 密码**明文提交**(走 HTTPS)。客户端不做哈希。
`[必须]` 时间戳参数是为了绕开缓存,每次取验证码都要换。
### 3.3 会话有效期正好 24 小时
实测 JWT 载荷:
```json
{"authLogin": false, "exp": 1785294742, "iat": 1785208342,
"jti": "<用户名>", "username": "<用户名>"}
```
`exp - iat = 86400` 秒,**整 24 小时**。
`[必须]` 缓存有效期取 `min(自定义上限, JWT 剩余时间)`,缓存不能活得比会话长。
### 3.4 没有滚动续期
`[必须]` 示例脚本里的 `_capture_refreshed_token()` 从响应头
`X-Requested-With` 读刷新后的 JWT,**这是死代码**。
实测 4 份 HAR 共 18 个接口响应,**带该响应头的:0 个**。
(`X-Requested-With` 本来就是**请求**头,脚本自己也设了 `XMLHttpRequest`。)
**不要把这段逻辑移植到 Go。** 会话就是 24 小时硬上限,到点重新登录。
### 3.5 怎么判断会话还活着
```text
GET /am/user/get?id=<登录响应里的 user.id>
```
`[必须]` **必须区分「明确未登录」和「网络故障」**:
| 情况 | 处理 |
|---|---|
| HTTP 401 / 403 | 判定未登录,清会话 |
| `msg` 含「未登录」「登录过期」,或 `code=-2` | 判定未登录,清会话 |
| 超时、5xx、响应格式错 | **抛错,不要判定未登录** |
理由:网络抖一下就判定登出的话,会触发重新登录,验证码弹个不停,
而且可能把本来有效的会话丢掉。示例脚本这一点做对了,照抄。
`[必须]` 还要核对返回的 `id` / `username` 与缓存的一致——
不一致说明串号了,同样清会话。
---
## 4. 货运单列表
两个接口配合,**payload 完全相同**:
```text
POST /am/stock/listTotal → data 是裸整数,总条数
POST /am/stock/list → data.list 是数组,data.total 是当前页条数
```
`[必须]` 不要把 `list.data.total` 当成筛选范围总数。实测 HAR 中范围总数为
3846 时,第一页返回 20 行且 `list.data.total = 20`;范围总数只能以
`listTotal.data` 为准。
### 4.1 请求体
```json
{
"history": 0,
"length": 20, // 每页条数
"start": 0, // 偏移
"pageTotal": 0,
"pageIndex": 1, // 从 1 开始
"store": false,
"columns": [ ... 72 个列定义 ... ],
"queries": [ ... 查询条件 ... ]
}
```
`[必须]` `columns` 是**要返回哪些列**的声明,72 项,每项形如:
```json
{"tableName":"t_stock","colName":"created","fieldName":"created",
"hasAlias":0,"tableAlias":"t"}
```
完整清单见示例脚本的 `COLUMN_SPECS`(第 51–124 行)。
`[建议]` 直接照搬,不要自己删减——服务端可能依赖这批列做联表。
### 4.2 两种查询条件
**按日期范围(同步用这个)** —— 出自 `demo/shunyunbaoerp_stock_list.har`:
```json
{"dvalue": "2026-07-25,2026-07-28", "tableName": "t_stock",
"colName": "created", "op": 0, "type": 3, "tableAlias": "t", "optType": 0}
```
`[必须]` `dvalue` 是 `起始日期,结束日期`,逗号分隔,`YYYY-MM-DD`。
`type: 3` 表示日期类型,`op: 0` 表示范围。
**按单号(查单条用这个)** —— 出自 `demo/shunyunbaoerp_stock_query.har`:
```json
{"dvalue": "<单号>", "tableName": "t_stock",
"colName": "allcode", "op": 6, "type": 0, "tableAlias": "t", "optType": 1}
```
`[必须]` `queries` 是**数组**,理论上可以组合多个条件。
示例脚本里那句 `if not order_number: raise ValueError` 是**脚本自己的限制**,
接口没有这个限制。
`[待定]` 空 `queries`(不加任何条件)能否拉全量未验证。同步用日期范围就够,
不需要冒这个险。
### 4.3 分页
`[必须]` 一个跨日范围要拆成逐日查询。先逐日调用 `listTotal` 做全范围容量
预检,确认合计不超限后,再按天以配置的 `length` 翻页调用 `list`。2026-08-29
对稳定历史日(753 张)只读验证:20 条在第 38 页超时;50 条 16 页用时 24.242 秒;
100 条 8 页用时 2 分 34.751 秒;200 条 4 页用时 22.183 秒。三种大页均满足总数、
页长、唯一 ID 和尾页完整性,生产采用保守的 50;明细仍按最多 100 个 ID 一批读取。
`[必须]` **必须有单次同步的条数上限**,超了报错而不是硬拉。
Admin 默认 `max_matches = 10000`,可以在配置中调整;上限针对整个日期范围的
逐日总数合计,不是每天各算一次。任何一天的列表或明细都不得在容量预检通过前
开始拉取,避免超限后已经产生部分写入。
`[必须]` 每一页 `list.data.total` 必须等于该页 `list` 数组长度。非最后一页
必须返回 `length` 条,最后一页必须返回预检总数对应的剩余条数。每天翻页结束后
再次调用 `listTotal`,前后总数必须一致;全部页去重后的货运单 ID 数还必须等于
预检总数。历史日期出现前后总数变化、短页、重复 ID 或唯一 ID 不足时立即失败,
不推进游标。
`[必须]` `listTotal`、`list` 和 `detail/listByStock` 属于语义只读查询,虽然
使用 POST,也只允许在超时、网络故障、响应读取失败、HTTP 5xx 或响应信封格式异常时
有限重试:单次 HTTP 总超时 60 秒,最多执行 3 次,重试前分别等待 1 秒、2 秒,等待须
响应 context 取消。HTTP 401/403、明确会话失效、业务失败、接口数据完整性错误和本地
校验失败不得重试;登录、验证码、单件码写入和任何回填请求也不得使用该机制。重试
耗尽后返回最后一次错误,外层保留日期、页码和已获取数量上下文,按最终已保存成果将同步标为失败或部分成功。
自 #239(实现提交 c6a962d,已于 2026-09-08 随 d403f3b 部署线上)起,改为按页验证和保存:每页先验证条数、合法 ID、页内/跨页重复,再按冻结店铺快照获取并验证全部明细;外部请求完成后才开启页事务。页内任一入库失败回滚整页,已提交的前页保留,计数在事务提交后累计。
当天和历史日期统一处理:分页/明细失败或最终总数漂移时停止该日期并继续后续日期,不在同次运行中重新扫描当天,以避免重复写入和计数。数据库故障、进度持久化失败、明确会话失效、上下文取消或总任务超时停止整个范围。下一次人工或定时同步重新扫描日期,按 (order_code, detail_id) 幂等覆盖并保留人工确认。跨页重复不去重后冒充完整数据;最终日期完整性校验仍覆盖所有店铺。此流程替代此前 #235 的当天三次整日快照重扫。
重复诊断只记录日期、首次/当前页码和行号、start、pageSize、expectedTotal、已获取唯一数量,不记录真实重复 ID、原始响应或个人数据。
### 4.4 统一日期范围同步与覆盖游标
GoAuto 同步记录页的立即同步允许管理员和采购员(purchaser)使用(#236),固定覆盖昨天和今天;其他已登录角色仅可查看记录。复用既有同步互斥、日期校验、启用店铺筛选及操作人审计,不授予店铺配置、凭据或定时任务管理权限。权限代码发布并完成启动对账后生效。
页面只有一个同步入口,操作员确认工具条上的开始日和结束日后发起。首次打开页面
固定默认昨天到今天,不因 `last_synced_at` 更早而自动扩大范围;需要补历史缺口时
由操作员明确选择日期,单次仍不得超过 31 天。
`[必须]` 首次成功同步以所选结束日建立覆盖游标。已有游标时,只有日期范围从
游标当天或更早开始、并且结束日在游标之后,全部成功后才推进游标。局部历史补拉
或跳过缺口的范围只 upsert 数据,不动游标。
例如覆盖游标为 `2026-08-01`,同步 `2026-08-01 ~ 2026-08-09` 可以推进到
`2026-08-09`;只同步 `2026-08-07 ~ 2026-08-08` 不能推进,因为中间有缺口。
`[必须]` 日期格式固定 `YYYY-MM-DD`,按 UTC+8 解释;两端必须同时填写,
开始不得晚于结束,结束不得晚于 UTC+8 下的今天;闭区间最多 31 天。中途失败
或超过 `max_matches` 时不推进游标。
`[必须]` 每批明细响应必须与请求的货运单 ID 一一对应。缺失、重复、出现未请求
ID,或某张货运单返回空商品明细,都视为不完整并停止当前日期,继续后续日期;已经写入的幂等数据
可以保留,但只有所有日期全部成功才推进游标。
`[必须]` 登录和验证码只是同步前置步骤。日期范围经过自动 OCR 降级、手工
输入验证码和 303 跳转时必须原样保留。
---
## 5. 货运单字段
`data.list[]` 每行 **77 个字段**。业务上要紧的:
| 字段 | 示例 | 说明 |
|---|---|---|
| `id` | `75104587` | **货运单主键**,取明细要用它 |
| `code` | `260728TB95MJTQ` | 单号(界面上搜的就是这个) |
| `created` | `2026-07-28 10:37:59` | 创建时间,日期范围筛的就是它 |
| `status` | `13` | 数字状态码 |
| `orderStatus` | `待出货` | 中文状态 |
| `purchaseStatus` | `0` | 采购状态 |
| `shopName` | `<店铺名>` | 蝦皮店铺 |
| `productName` | `純棉上衣` | **只有一个商品名**,一单多商品时不完整 |
| `orderQty` / `detailQty` | `2` / `2` | 商品件数 |
| `isCancel` | `0` | 是否取消 |
| `expCompany` | `蝦皮店到店` | 物流方式 |
| `receiver` / `receiverTel` / `receiverAddr` | — | **收件人信息,属个人数据** |
`[必须]` **收件人姓名、电话、地址属于个人信息**,落库要考虑是否必要。
不做采购决策用不到它们,`[建议]` 不入库,或只存脱敏后的。
### 5.1 金额单位不统一 —— 最容易算错钱的地方
`[必须]` 实测同一张单、同一行里,两个金额字段**单位不一样**:
| 字段 | `/am/stock/list` | `detail/listByStock` | 关系 |
|---|---|---|---|
| `amtOrder` | `61200` | `612.0` | list 是**分**,×100 |
| `escrowAmount` | `505` | `505.0` | list **不是分**,未 ×100 |
`61200 / 100 = 612` 对得上,`505 / 100 = 5.05` **对不上**。
`[必须]` **不要假设「列表接口的金额都是分」。** 逐个字段确认,
并且在代码里为每个金额字段写清它的单位。
`[建议]` 金额一律以 **`detail/listByStock` 的值为准**,那边是统一的元/TWD。
需要存整数分时自己 ×100,不要用列表接口的原值。
`[待定]` 只有一个样本。实现前**必须再取几张单核对**,尤其是
`escrowAmount` 不是整数元的情况。
### 5.2 金额合计对不上是正常的
```text
明细单价合计 239.0 + 439.0 = 678.0
amtOrder 612.0
```
差 66,应该是优惠。`[必须]` **不要用「明细合计 == amtOrder」做校验**,
会误报。
### 5.3 `created` 是 UTC+8,不是 UTC —— 日期范围查询最容易算错的地方
`[必须]` 实测 `demo/shunyunbaoerp_stock_query.har`:
```text
HAR 记录的抓包时刻 startedDateTime 2026-07-28T03:31:45Z (= 11:31:45 UTC+8)
同一次请求响应里的 created 2026-07-28 10:37:59
```
`10:37:59` 作为 **UTC+8** 讲得通(比抓包时刻早 54 分钟,正常)。
若把它当成 **UTC**,换算成 UTC+8 就是 18:37,比抓包时刻**晚 7 小时**——
订单创建于尚未发生的未来,不成立。所以 `created` 是 UTC+8,不是 UTC。
`[必须]` §4.2「按日期范围」的 `dvalue` 筛的就是这个 `created`,
所以**换算"今天是哪一天"也必须用 UTC+8**,不能用 UTC 或本机系统时区
(本机系统时区不一定是 UTC+8,取决于部署环境)。用 UTC 算的话,
在 UTC+8 的 00:00–08:00 这段时间会把"今天"算成昨天,当天早晨创建的单
这一轮同步拉不到——虽然下一轮的起始日期仍是"上次同步日",范围会覆盖
回来、不会永久丢单,但操作员当场点同步会以为同步坏了。
`[必须]` 代码里固定用 `time.FixedZone("UTC+8", 8*60*60)`,不要用
`time.LoadLocation("Asia/Shanghai")`——那个要读系统 tzdata,Windows 上
默认没有,打包成 exe 后会在运行时报错。
`[待定]` 只有一个样本(一次抓包)支撑这个结论,且没有拿到顺运宝官方
文档确认。以后如果日期范围附近出现"该有的单没同步到",先来这里核对
这条结论是否仍然成立。
---
## 6. 货运明细
```text
POST /am/stock/detail/listByStock?hist=0
{"ids": [75104587, ...]}
→ data.list[]
```
`[必须]` 一次最多传 **100 个 id**(示例脚本的分批大小),超了分批。
### 6.1 一单多商品是嵌套,不是多行
返回的每一行对应**一张货运单**,商品在嵌套的 `details[]` 里:
```json
{
"id": 75104587, // 与 stock.id 相同,可直接对应
"code": "260728TB95MJTQ",
"shopName": "<店铺名>",
"productName": "純棉上衣",
"amtOrder": 612.0,
"details": [
{
"id": 145306175,
"productId": 50209124255,
"productTitle": "蕾絲花邊拼接背心女 上衣 背心 無袖打底衫 …",
"detailProductName": "打底衫",
"productSpec": "白色,L【建議50-60公斤】",
"productQty": 1,
"productPrice": 239.0,
"productThumb": 190639637,
"shopId": 999611342,
"pruchaseId": 38884195
},
{ ... 第二个商品 ... }
]
}
```
`[必须]` 外层 `id` **就是** `stock.id`,可以直接按它把列表和明细对起来。
`[必须]` **一张货运单可以有多个商品**(示例这张就有 2 个)。
落库时必须一对多拆开,不能只取 `productName`——那个字段只有一个商品名。
### 6.2 `productId` 是蝦皮商品 ID,不是規格 ID
`[必须]` 这一条推翻了之前「货运单的规格 SKU 能直接对上蝦皮商品規格ID」的假设。
| | 位数 | 例 |
|---|---|---|
| 顺运宝 `productId` | 11 | `50209124255` |
| 蝦皮 `商品ID` | 11 | 实测导入的 5195 个**全是 11 位** |
| 蝦皮 `商品規格ID` | 12 | 实测 5742/6092 是 12 位 |
顺运宝给的是**商品级 ID + 规格原文**,没有規格 ID。
### 6.3 `productSpec` 的角色顺序不是固定的
```text
蝦皮目录 `spec_raw` 黑色,M【建議40-50公斤】
顺运宝 productSpec 白色,L【建議50-60公斤】
均碼,黑色
```
2026-09-04 的真实数据确认:`productSpec` 至少存在 `颜色,尺码` 与
`尺码,颜色` 两种顺序,不能再固定把逗号前当颜色、逗号后当尺码。
`[必须]` 先按最后一个 ASCII 逗号拆成两段并剥离 `【...】` 备注。只有两侧
恰好一侧带有明确尺码信号(如均码、字母尺码或明确尺码单位)时,才把该侧判为
尺码、另一侧判为颜色。两侧都像尺码时标记存疑;不得使用模糊颜色词库、AI 或
跨维度候选猜测角色。两侧都没有明确尺码信号时,暂按既有 `颜色,尺码` 契约兼容,
并继续执行原有复杂分隔符和空值校验。
`[必须]` 修正规格角色时保留原始 `raw_json`,不得覆盖人工确认值。已错误写入
虾皮档案的导入规格只能在确认无其他 SYB 明细引用且没有规格映射时移除;人工规格、
映射及历史采购任务保持不变。
`[必须]` 规格身份继续复用 `spec.SpecKey()`:只折叠空白,不改写用于 PDD
精确点击的原始候选标签。第三方目录脚本提交 `spec_raw` 和明确解析结果;角色或
规格比对不明确时保持存疑,不得跨颜色/尺码猜测。
### 6.4 由此推导出的匹配路径
```text
productId ──→ shopee_products.goods_id 商品级,直接相等
productSpec ──→ SpecKey() ──→ 稳定规格身份键
──→ 在该商品目录和人工映射中查找
```
`[必须]` 比对不上时**不要猜**,标成待人工匹配。蝦皮报表只含有销售成绩的
SKU(平均每商品 1.17 个),**查无此 SKU 是常态**,不是异常。
---
## 7. 其他接口
| 接口 | 用途 | 备注 |
|---|---|---|
| `POST /am/store/listByUsed` | 仓库列表 | 请求体为空;返回 `[{"name":"京发仓","id":129}, …]` |
| `POST /am/wallet/showTip` | 登录后弹提示 | 同步不需要 |
| `GET /am/menu/curr` | 当前菜单 | 同步不需要 |
| `POST /am/notice/myList` | 通知列表 | 同步不需要 |
| `GET /am/user/checkNeedAgreement` | 是否需同意协议 | 同步不需要 |
`[建议]` 登录后**只调 `/am/user/get` 验证会话**,其余几个是网页自己的初始化请求,
Go 侧不用跟着调。
### 7.1 档口入库码逐件写入(上游工单 #234、#250、#278)
> 本节已由 GoAuto #119~#122 实现。匹配阶段保持纯只读;只有采购员在 Admin 回写确认弹窗二次确认后,后台回写执行器才可调用写接口。#37 的采购物流回填仍是独立范围。
档口入库码最终写入货运明细的 `innerExpCode`(页面名称“快递单号”)。接口来自
已验证的上游流程和 HAR 响应样本:
```text
GET /am/stock/detail/deleteInnerCode?detailId={detailID}
POST /am/stock/detail/createDetail
POST /am/stock/detail/updateDetailCode?t=0&id={stockID}&detailId={detailID}&code={单件innerCode}
```
`[必须]` `t` 固定为 `0`。顺运宝把该参数绑成 Java `Integer`(上限 `2147483647`)。
`#250` 改成当前毫秒时间戳后,2026-08-19 全部回写被拒:
`Can not parse the parameter "1787108752485" to Integer value`。不得再传毫秒,
也不得改成 Unix 秒——秒目前能进 Integer,2038 年仍会溢出。`#234` 的 `t=0`
已通过参数绑定;`#250` 保留的 POST 方法不要改回 GET。
`createDetail` 使用 JSON 请求体:`id=null`、稳定占位标题、`productSpec=null`、
`productQty=1`、`productPrice=0`、`stockId`;成功响应的 `data` 是新 `detailId`。
- 回写前必须同时核对目标码数量、Excel 合并源行数和原商品 `productQty`;任一不一致时
零写入。一个远端明细只写一个单件码,原明细为空时承载第一个缺失码,其余缺失码创建
零价占位明细。
- 同一货运单任意明细已有目标码时直接复用,不删除、不搬移。稳定占位标题可用于识别
“已创建但检查点未保存”的明细,防止崩溃后重复创建。
- 规划时的旧值为空时不调用删除;旧值非空且仍与规划快照一致时,只有删除明确成功才继续。
- 删除和写入请求都只允许发送一次,不使用自动重试。超时、5xx、响应读取失败或成功响应
无法解析时,结果可能已经在远端生效,必须标为“需核对”。
- 每件动作前后把检查点写入本地 `syb_inner_code_checkpoint`;匹配快照保存在 `syb_inner_code_plan.remote_items_json`。每个单件码写入后重新读取整张货运单,
只有它唯一出现在预期明细中才继续。全部目标码确认后才标为已回写。
- “重新核对”只调用 `listByStock`,绝不能再次调用上述两个写接口。
- 页面提交数量不限制为 20 条。所选记录先在 MySQL 事务中进入 `queued`,HTTP 请求
随即返回;后台取得数据库全局租约后逐条执行上述远端门禁。Admin 重启不会自动继续
未开始的真实远端写入:`queued` 恢复为可回写,`applying` 转为需核对。
---
## 8. 实现时的固定约束
这几条不是抓包结论,是本项目的决定,写在这里避免每次重新讨论:
> 以下「店铺准入」已由 GoAuto #49 采纳并实现;店铺规范化还包括全角/半角统一和忽略大小写。
`[必须]` **同步先校验原始全量,再做店铺准入。** 顺序固定为:按日期查询原始总数
并执行单次容量熔断 → 逐页核对页长和唯一 ID,整日结束复核总数 → 按
`shopName` 去除首尾空白后与启用店铺精确匹配 → 只为接受的货运单请求明细和入库。
店铺过滤只决定明细获取和入库,不能减少原始列表完整性校验范围;已保存不代表整日完整。
`[必须]` **店铺准入之后再按 `variationSku` 过滤商品(#269)。** 顺序固定为:店铺准入 →
结构过滤 → 关键词过滤 → 入库。两类规则都存在 `syb_product_filter`,由 `kind` 区分:
- `kind=char`:`-` 和 `#` 两条,命中任意一条即跳过(**OR,不是 AND**)。这两个字符是
「档口-供应商#货号」编码格式的判据。2026-09-11 核验线上 13010 行明细:含 `#` 8498 行、
含 `-` 6392 行、两者都含 6386 行、任一 8504 行(65%)。用 AND 只命中 6386 行,会漏掉
`DD#004`、`300斤牛奶絲圓領#A057` 这类只含 `#` 的 2118 行。
- `kind=keyword`:关键词清单,匹配 `variationSku`,**不匹配 `productTitle`**。同一次核验
中六条初始关键词在 `productTitle` 上命中为 0,且全部已被结构过滤覆盖,净增为 0;保留它
是为档口改用不含 `#` 的编码时兜底。
`[必须]` 空 `variationSku` **不过滤**。核验时 2724 行(21%)为空,分布在正常启用店铺且持续
产生,属于尚未填写供应商编码的正常商品,不是档口货。
`[必须]` 两类命中数分别统计并分别回写到每条规则自己的 `last_hit_count`,不得合并成单一
「过滤总数」,也不得按 `kind` 写同一个汇总值。前者会使结构判据失效不可观测(关键词净增为 0
时无法区分「没漏网」和「规则没生效」);后者会让停用确认对 `#` 和 `-` 提示同一个数字。
`[必须]` `kind=char` 不可新增、不可删除,只能停用,且停用需要二次确认并记录操作人与时间。
判据本身需要变更(例如档口改用 `/` 或 `@`)属于范围变化,应另建工单评估。
`[必须]` 同步开始时只读取一次启用店铺,整次运行使用同一个快照。列表允许但明细
响应中的 `shopName` 变为空或非允许店铺时再次拦截。没有启用店铺时在会话/OCR/
验证码等任何顺运宝请求之前停止,并且不推进覆盖游标。该过滤只影响后续入库,
不清理历史货运单。
`[必须]` **会话缓存存 GoAuto 自己的 MySQL,不引入 Redis。** 上游示例脚本用 Redis 是因为
它是反复启动的一次性脚本,进程间要传会话;GoAuto 服务端是常驻进程,没有这个需求,
持久化只为重启后免登录,复用现有数据库即可。表见 `syb_session`。
`[决定已变更]` ~~不引入 OCR 服务。~~ 这条判断在上游工单 #47 里被推翻了,
原文和推翻理由都留在这里,方便后来人知道这个决定变过、为什么变:
> 原判断(上游工单 #46):会话 24 小时,一天登录一次。为省一次手工输验证码
> 而依赖 `127.0.0.1:8000` 不划算——多一个必须先启动的东西,而且 OCR
> 会失败(示例脚本自己写了 5 次重试),失败了照样要人工。界面上显示
> 验证码图片、操作员输一次即可。
`[必须]` **这条判断的前提是"本机服务 `127.0.0.1:8000`",托管服务不适用。**
上游工单 #47 里用户提供了托管地址 `https://ocr.ilapage.cn/ocr`:没有要启动的
东西,就是一次 HTTP 调用,"多一个必须先启动的东西"这条理由不成立了。
而"每天第一次同步都要人在场"这个代价是实打实的——会话 24 小时过期,
意味着做不了无人值守的定时同步。于是上游工单 #47 引入了 OCR 自动识别,
失败或服务不可达时**降级**到原有的手工输入弹窗(那条兜底路径没有变),
不是"失败了照样要人工"变成了"失败了才要人工"。详见下面「验证码自动识别」一节。
### 凭据与配置
`[必须]` **凭据只通过环境变量注入,不进任何被 Git 跟踪的文件。**
GoAuto 已有的做法见 `server/config/extend.go` 的 `ApplyEnvironment()`:
非机密项写在 `server/config/settings.yml` 的 `extend.syb` 下,机密项走环境变量。
| 项 | 来源 | 说明 |
|---|---|---|
| `GOAUTO_SYB_USERNAME` | 环境变量 | 顺云宝账号 |
| `GOAUTO_SYB_PASSWORD` | 环境变量 | 顺云宝密码 |
| `extend.syb.base_url` | settings.yml | 默认 `https://www.shunyunbaoerp.com` |
| `extend.syb.page_size` | settings.yml | 列表每页条数;2026-08-29 只读实验验证 50/100/200 完整,生产默认采用保守值 50 |
| `extend.syb.max_matches` | settings.yml | 单次同步货运单数上限,超过即停止 |
| `extend.syb.ocr_url` | settings.yml | 验证码识别服务;留空 = 禁用,只走手工输入 |
| `extend.syb.ocr_max_attempts` | settings.yml | OCR 重试次数,默认 5 |
`[必须]` **密码在任何日志里都要打码。** 日志可能被贴进工单排查问题。
`[必须]` 用环境变量而不是 YAML,顺带避开了上游踩过的坑:YAML 里纯数字密码
不加引号会被解析成整数(`0012345` → `12345`,前导零丢失),反序列化到
`string` 字段直接报错。环境变量永远是字符串,没有这个问题。
---
## 8.1 验证码自动识别(上游工单 #47)
### 响应格式(已实测)
```
POST https://ocr.ilapage.cn/ocr
Content-Type: multipart/form-data,字段名 file
→ 200 {"code":200,"message":"Success","data":"kycv"}
```
实测耗时约 1.4 秒。
`[必须]` **识别失败也是 `code:200`,这是最容易写错的地方。**
实测用一张无文字的图片探测,返回的是 `{"code":200,"message":"Success","data":""}`——
不是错误码,是 `code:200` 加空 `data`。判断这一次调用真正"识别出了点什么",
必须同时满足:HTTP 200、`code == 200`、**`data` 非空**。只看 `code` 会把
"没识别出来"当成功,拿空字符串去登录。
`[必须]` 顺运宝验证码固定 **4 位字母数字**(08 §3.2 实测)。识别结果过滤
空格标点后长度不是 4,说明识别错了,**不要拿去登录**——直接换一张图重试。
拿明知不对的验证码去登录白费一次尝试,而且频繁的错误登录可能触发对方风控。
### 重试与降级
```text
点同步 → 会话过期
├─ ocr_url 已配置
│ └─ 循环 ocr_max_attempts 次:取新验证码图 → OCR 识别 →
│ 校验(非空 && 4位字母数字) → 登录
│ ├─ 登录成功 ─────────────────→ 直接同步,无人值守
│ └─ 次数用完仍失败 ────────────┐
└─ ocr_url 未配置 / 请求本身失败 ────────┴─→ 弹手工输入框(#46 已有的兜底路径)
```
`[必须]` **OCR 不可达要降级,不是报错。** 外部服务挂了不该让整个同步功能
不可用——手工路径一直在,走它就是了。
`[必须]` 每次重试都要**重新取一张验证码图**。同一张图再识别一次结果一样,
纯属浪费;而且验证码可能已经被上一次失败的登录作废。
`[必须]` OCR 请求本身失败(连不上、超时、返回非法 JSON、`code != 200`)判定为
"服务不可用",**立即降级,不占用重试次数**——重试对"服务本身连不上"这种情况
没有意义。只有"HTTP 调用成功但识别结果不合格(空/长度不对)"才占用一次重试。
`[必须]` 调 OCR **不带顺运宝的 Cookie**,用独立的 `http.Client`(独立的
Cookie Jar、独立的超时),避免把顺运宝会话泄漏给另一个服务;也不共用
顺运宝请求的超时,OCR 慢不该拖垮整个登录流程(`[建议]` 10 秒)。
`[必须]` **验证码图片全程在内存里传字节,不写文件。** 参考实现
上游示例脚本 把图片存成 `captcha.jpg` 是命令行脚本
的做法,Admin 是常驻进程,写文件只会在 `data/` 里堆垃圾。
`[必须]` **验证码图片会被发送到 `ocr_url` 配置的外部服务。**
当前 `https://ocr.ilapage.cn/ocr` 是用户自己的服务,不算交给第三方;
把 `ocr_url` 换成别人运营的服务前,必须重新评估这一点。
`[必须]` GoAuto 的永久规则原本禁止一切 OCR/VLM。该规则是为 **Android Agent 端采集**
写的,#48 把它按端重新划定:Agent 端维持禁止,**服务端 SYB 登录验证码是唯一例外**。
不得以本节为由把 OCR 引入 Agent 端或任何 PDD 相关流程。
见 [Business-Rules-and-Glossary](Business-Rules-and-Glossary) 的「自动化边界」。
### 配置
```yaml
# server/config/settings.yml
settings:
application:
extend:
syb:
# 验证码自动识别服务地址。留空则只用手工输入弹窗,不报错。
ocr_url: https://ocr.ilapage.cn/ocr
ocr_max_attempts: 5
```
`[必须]` `ocr_url` 留空 = 禁用,直接走手工输入弹窗,**不报错**。
---
## 9. 已知未验证的部分
实现前应逐条确认,都只有单一样本支撑:
- [ ] `escrowAmount` 等金额字段在列表接口里的**单位**(见 §5.1)
- [ ] 空 `queries` 能否拉全量(同步用日期范围,可不验)
- [ ] 日期范围跨度很大时服务端是否限流或超时
- [ ] `status` 数字码与 `orderStatus` 中文的完整对应表
- [ ] 会话失效时服务端返回的**确切**形态(HTTP 码 / `code` / `msg` 文案)
- [ ] 同一账号多处登录是否互踢
- [ ] 验证码错误、密码错误分别返回什么,能否区分
- [ ] `created` 是 UTC+8 这一条(见 §5.3)只有一次抓包支撑,
没有官方文档确认,也没有跨夏令时/时区配置的验证
`[必须]` 最后两条影响错误提示的准确性:分不清「密码错」和「验证码错」的话,
操作员会一直重输密码。
---
## 10. 相关文档
- 架构与代码地图:[Architecture-and-Code-Map](Architecture-and-Code-Map)
- 业务规则与术语:[Business-Rules-and-Glossary](Business-Rules-and-Glossary)
- 客户端实现:`server/app/goauto/sybclient/`
- 落库与解析:`server/app/goauto/sybimport/`
- 相关工单:[#48 客户端移植](https://git.ilapage.cn/OPC/goauto/issues/48)、[#41 SYB 货运单商品导入](https://git.ilapage.cn/OPC/goauto/issues/41)、[#37 物流调度与货运宝回填](https://git.ilapage.cn/OPC/goauto/issues/37)
## SYB 逐页保存与部分成功(#239)
实现绑定 c6a962d;2026-09-08 已随 d403f3b 部署线上,未手动触发真实同步验收;#240 上游尾页超时尚未修复。每页完整明细在外部请求结束后按页事务保存;页回滚不累计明细/新增/覆盖数,已提交页保留。日期局部读取失败继续下一日期,全局数据库/进度/会话/取消故障停止。当天漂移不在一次运行内重扫;后续运行重新扫描并幂等补齐。
同步状态增加 `partial_success`(部分成功,15 字符,复用现有 varchar(16),无需迁移)。有错误且 created+updated>0 为部分成功;有错误无已提交明细为 failed;完整且无错误为 succeeded(包括无符合店铺的数据)。中断仍为 interrupted,不把中断追认为成功。所有终态沿用活动槽释放规则。
`orderCount` 是已验证页的原始列表读取数量;`detailCount`、`created`、`updated` 为已提交明细及其新增/覆盖数量;`daysProcessed` 是完整通过的日期数,不是已尝试日期数。失败日期/页码/阶段写入现有脱敏限长 errorMessage。部分成功不刷新店铺的完整同步统计。
Web 唯一展示位置为“采集采购 → SYB 同步记录”:列表状态、状态筛选及详情支持部分成功,详情保留已保存数量、错误原因与重新同步补齐提示。定时任务日志只表示异步任务受理,不等于最终业务同步成功。
## PDD 采购单号写入(#305)
实现绑定 e89de1a(2026-09-18,未部署)。协议依据用户本地 update_syb_pdd_order_number.har 单次样本;HAR/真实标识/Cookie不进入源码、Wiki或工单。本节不宣称远端幂等或原子比较写入。
POST /am/stock/detail/updateDetailPurchaseCode,无JSON body;query:id=<stock.id>、detailId=<details[].id>、code=<PDD单号>、type=pdd、created=""、cost=0。ID必须为正整数,单号非空、无首尾空白/控制换行、最多100字符。cost是抓包确认的固定参数,不是实际金额;不得传Agent实付价格。
样本成功信封 HTTP200、status=true;随后 listByStock?hist=0 对应明细 purchaseCode 等于提交单号、purchasePlatform=pdd、purchaseStatus=1,purchaseTime由SYB生成。更新存在采购状态/时间副作用。
客户端每次处理最多一次写请求,并拒绝HTTP重定向重发;超时/连接中断/5xx/损坏响应作为结果未知。无论写响应成功或未知,都须按 stockId + detailId 唯一回读目标;相同单号+pdd才确认为成功,不同已有单号或其他非空平台记冲突且不覆盖。写前为空才写;目标缺失/重复/字段类型异常不得猜测选择。未知且未回读确认时持久化unknown,人工补偿前再次回读,不使用自动写重试。
GoAuto自身通过持久租约串行,但SYB没有CAS证据,无法原子隔离系统外客户端;不能保证任意迟到请求已终止。真实接口写入需另行明确授权;当前验证仅fake/httptest。
-57
@@ -1,57 +0,0 @@
# 26 修复 PDD 规格面板蛇形遍历与尺码续页
- 类型:缺陷
- 所属 Epic:#1
- 所属 MVP / 版本:#2 采集最小闭环
- 状态:已完成
- 日期:2026-08-15~2026-08-17
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/26
- Wiki 页面:Task-26-PDD-spec-traversal
- Wiki revision:以页面当前 revision 为准
## 背景与目标
商品 `236231603269` 初次采集只得到颜色,没有得到页面实际存在的尺码。后续三星任务 35 又暴露出选中颜色后节点消失、剩余选项重排时错误报告 `colorRow:1/2`。目标是稳定完成颜色蛇形遍历、逐颜色价格采集和尺码续页,不点击尺码、订单或支付控件。
## 最终方案
- 从当前规格节点祖先锁定横向颜色容器和纵向规格面板。
- 规格面板有界归顶,使用文字与 bounds 视口签名判断边缘。
- 颜色按视觉行交替方向遍历;每次点击后重新读取无障碍树并等待价格稳定。
- 尺码只读,首次识别后保存容器结构,标题滚出后继续分页读取。
- 选中颜色从可点击节点中消失并引起重新排版时,只结束当前行扫描,不再把行消失误报为数据缺失;颜色为空、价格缺失和点击失败仍明确报告。
## 修改文件
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddProductDetailCollector.kt`:规格容器、蛇形遍历、尺码续页和颜色行重排处理。
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/AndroidUiDriver.kt`:规格容器滚动与手势完成等待。
- `android/app/src/test/java/cn/ilapage/goauto/agent/PddProductDetailCollectorTest.kt`:长规格、续页和选中项消失重排回归测试。
- `docs/` 与 `android/README.md`:算法和真机验证证据。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 商品 236231603269 采集实际尺码 | 通过:14 个颜色、14 个价格、8 个尺码、112 个 SKU |
| 颜色文字安全点击并逐项读取价格 | 通过 |
| 尺码只读,不触发订单或支付 | 通过 |
| 三星颜色选中后重排不再误报 `colorRow:*` | 通过:任务 35 复采只剩 `reviewCount` |
| 只保存结构化结果 | 通过 |
| 用户验收 | 2026-08-17 明确通过 |
## 测试
- 执行命令:`android/gradlew.bat test assembleDebug`
- 结果:通过,Debug APK 构建成功。
- 一加任务 23:`completed`,14 个颜色、8 个尺码、112 个完整 SKU。
- 三星任务 35:3 个颜色、3 个价格、1 个尺码;颜色行误报消失,`reviewCount` 按用户决定暂不处理。
- **未验证部分**:三星页面评价数量兼容不属于 #26,保留 `completed_partial`。
## 遗留问题
- 三星任务 35 无障碍数据未提供当前解析器支持的评价数量,用户已确认暂不处理。
## 相关提交
- `72d6145` fix: complete PDD spec panel traversal (#26)
- `1bf676e` fix(#26): ignore reflowed completed color rows
-65
@@ -1,65 +0,0 @@
# 31 PDD 商品档案与规格 JSON 管理
- 类型:需求
- 所属 Epic:#1
- 所属 MVP / 版本:采集闭环后的商品档案阶段
- 状态:已完成
- 日期:2026-08-17
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/31
- Wiki 页面:Task-31-PDD-product-archive
- Wiki revision:以页面当前 revision 为准
## 背景与目标
PDD 商品需要作为采购人员长期复用的独立档案,采集只是维护最新资料的一种方式。商品最简创建只输入包含 `goods_id` 的 PDD URL,并允许人工完整维护或由采集结果自动更新标题、店铺、销量、评价和规格。
## 最终方案
- 先完成并由用户确认 QuantUX 原型,再实施数据库、API 和 Admin 页面。
- `pdd_product` 保存标题、店铺、数字销量/评价、`pending/active/disabled` 状态及最新 `specs_json`,不建立规格、SKU 或历史子表。
- 规格使用 `color/size/other` 通用多维结构;只有颜色值保存整数分价格,规格值保存是否可选择。
- 人工编辑全量覆盖;`completed` 采集全量覆盖;`completed_partial` 只合并明确采集到的数据;`failed` 不修改商品。
- URL 创建继续按 `goods_id` 唯一,采集任务保存自己的 URL、goods_id 和规则快照。
- PDD 商品列表多选和批量创建采集任务从本工单拆到独立 #45,避免商品档案与批量任务耦合。
## 修改文件
- `server/app/goauto/models/schema.go`、`server/app/goauto/migrations/`:商品档案字段与 MySQL 8.4 增量迁移。
- `server/app/goauto/product/`:商品创建、查询、完整覆盖编辑和规格 JSON 校验。
- `server/app/goauto/task/result_service.go`:任务完成事务内更新商品最新档案。
- `web/src/views/goauto/pdd-products/`、`web/src/api/goauto/pdd-products.js`:列表、添加、详情和编辑页面。
- `web/tests/e2e/pdd-product-module.spec.ts`:页面和请求回归测试。
- `prototypes/quantux-pdd-product-6a81db5d191a826306a7edd6.html`:已确认 QuantUX 离线原型。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| URL 最简创建并唯一校验 goods_id | 通过 |
| 人工完整维护基础资料与通用规格 JSON | 通过 |
| 完整、部分和失败采集按规则更新商品 | 通过 |
| 颜色价格使用整数分,尺码不单独定价 | 通过 |
| 不引入规格、SKU 或历史子表 | 通过 |
| Admin 列表、添加、详情、编辑符合确认原型 | 通过 |
| 三星任务 35 部分结果写回商品档案 | 通过:3 个颜色价格和 1 个尺码已写入 |
| 用户验收 | 2026-08-17 明确通过 |
## 测试
- `scripts/verify.ps1 -Component server`:通过。
- `scripts/verify.ps1 -Component web`:通过,0 lint error;上游既有 warnings 不属于本工单。
- `pnpm exec playwright test tests/e2e/pdd-product-module.spec.ts`:1 passed。
- MySQL 8.4 增量迁移 `1786700500000`:通过。
- 三星任务 35:`completed_partial`;标题、店铺、销量、3 个颜色价格和 1 个尺码自动更新到 PDD 商品。评价数量未识别,用户决定暂不处理。
- **未验证部分**:#45 的列表多选和批量采集任务尚未实施,不属于本工单代码范围。
## 遗留问题
- PDD 商品列表多选和批量创建采集任务由 #45 实施。
- 三星评价数量兼容暂不处理,部分结果不会猜测或填写为 0。
## 相关提交
- `a7a4536` feat(#31): implement PDD product archive module
- `34b5e0c` QuantUX PDD 商品档案离线原型
- `7df4546` 批量采集入口原型快照
-70
@@ -1,70 +0,0 @@
# 32 采购闭环数据关系与交互原型
- 类型:需求 / 高风险采购流程设计
- 所属 Epic:无(采购 MVP 建立后补充)
- 所属 MVP / 版本:PDD 自动采购 MVP(不属于当前采集 MVP)
- 状态:已完成(Stage A 原型验收通过)
- 日期:2026-08-18
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/32
- Wiki 页面:Task-32-procurement-closed-loop-prototype
- Wiki revision:以页面当前 revision 为准
## 背景与目标
货运宝 ERP 货运单包含多个 Shopee 商品采购明细。系统需要关联 Shopee 商品与 PDD 商品、派发 Android 采购任务、在 PDD App 选择规格数量并修改固定收货地址后创建订单但不支付,再采集快递单号自动回填货运宝。本工单只交付 Stage A 的 QuantUX 可点击原型和评审结论,用来在写任何采购代码之前确认数据关系与人工决策入口。
## 最终方案
- 确认数据关系:`syb_products N → 1 shopee_products N → 1 pdd_products`,且 `syb_products → N purchase_tasks`;`syb_products.effective_purchase_task_id` 指向回填成功的有效任务。
- 确认执行边界:Android Agent 只创建订单,永不支付;支付由采购人员在 PDD 中人工完成。
- 确认地址后缀规则 `_cg{采购任务ID}`,只清理末尾 `(?:-|_)cg\d+$`,禁止按分隔符整体切分;地址修改或复核失败时任务明确失败,不点击创建订单。
- 确认防重复下单:创建订单前持久化 `order_submit_started`;无法确认结果时进入 `order_result_unknown` 交人工处理;服务端以 `task_id + task_attempt_id` 幂等。
- 确认重新采购、有效订单人工选择、快递单号覆盖回填的人工决策入口。
- 原型交付 12 个页面、770 个控件、31 条流程线,把采购任务、人工支付、PDD 订单同步和 SYB 快递单号回填拆成独立流程;采购任务统一从 SYB 商品列表多选后批量创建。
- 独立商品域管理页原型不在本工单:#40 和 #41 各自建立 Stage A 原型门禁,#32 原型只覆盖采购流程内嵌的未映射跳转入口。
## 修改文件
- `prototypes/quantux-procurement-flow-6a827440191a826306a7eddd.html`:QuantUX 离线原型导出(本工单唯一交付物)。
- `prototypes/README.md`:原型清单与审核状态。
- Wiki `Product-Requirements-Overview` / `Delivery-Issues`:需求索引与交付顺序状态。
- 本工单不修改数据库、服务端、Web、Android 和共享 API。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 原型走通货运宝明细 → PDD 订单 → 人工支付 → 有效订单选择 → 快递采集 → 自动回填 | 通过 |
| 同一 Shopee 商品多规格明细、多个 Shopee 商品共用一个 PDD 商品的关系表达清楚 | 通过 |
| 地址后缀固定为 `_cg{采购任务ID}`,并展示修改失败禁止创建订单 | 通过 |
| 重新采购、覆盖回填和结果未知场景有清晰人工决策入口 | 通过 |
| 页面不提供任何自动支付入口 | 通过 |
| 用户完成原型人工审核 | 2026-08-18 明确通过 |
## 测试
- QuantUX MCP 结构校验:12 个页面、770 个控件、31 条流程线,导出验证 `PASS`,无运行时错误或不支持控件。
- 浏览器人工检查采购人员主流程、危险操作提示和异常状态:通过。
- **未验证部分**:Stage A 不进行真实 PDD 下单、支付、物流采集和货运宝回填测试;PDD 订单详情能否稳定读取订单号与快递单号,需在 #38 实施前真机验证。
## 遗留问题
- 采购实施按 #33、#34、#35、#36、#37、#38、#39、#42 拆分推进,每个高风险单元实施和真机前需再次人工确认。
- 商品域独立管理页原型由 #40、#41 各自的 Stage A 交付。
- 地址后缀不承诺一定阻止 PDD 或卖家合并发货;订单与任务的正式关联以 PDD 订单号为准。
## 后续变更
> 本工单已于 2026-08-18 验收关闭,正文与原型保持归档原样,不再修改。以下规则已被后续决策取代:
| 本单原规则 | 取代者 | 变更内容 |
|---|---|---|
| 「Shopee 与 PDD 规格名称不一致,映射由 `shopee_products` 维护;**Agent 不得猜测缺失映射**」 | [#46 T44 采购规格 AI 匹配与实时规格回传(决策变更)](https://git.ilapage.cn/OPC/goauto/issues/46) | 映射缺失、商品无规格数据或目标规格定位不到时,允许由**服务端** AI 匹配接口决策后下发精确规格;Agent 本地仍不得自行猜测或点击相近候选。AI 无结果时明确失败。匹配结果须落库留痕并标记 `spec_source`。 |
变更依据:采购商品单价较低(实测 NT$239~559),用户于 2026-08-18 明确接受个别匹配错误的风险,以换取人工参与的显著减少。金额护栏复用 `#42` 的订单总价上限机制。
本单其余已确认规则(禁止支付、不使用 OCR/VLM、不保存控件树与截图、地址后缀 `_cg{task.id}`、防重复下单、`task_id + task_attempt_id` 幂等、设备与 PDD 账号级互斥)均**未变更**,继续有效。
## 相关提交
- 原型快照与文档状态更新提交见本工单回写记录。
-66
@@ -1,66 +0,0 @@
# 33 采购任务数据模型与共享 API 契约
- 类型:需求
- 所属总体设计:#32
- 所属 MVP / 版本:采购闭环
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/33
- Wiki 页面:Task-33-purchase-task-contract
## 背景与目标
在 PDD、虾皮和 SYB 三个独立商品域之外建立采购任务事实,固定演练/正式模式、不可变业务快照、任务尝试幂等、设备与可选账号串行、订单结果未知和后续物流回填边界,同时保证系统永远不支付。
## 最终方案
- 新增 `purchase_task`:消费三个商品域外键并固化身份、规格、数量、价格边界、币种、URL、goods_id、规则、设备和可选账号快照。
- 新增 `purchase_task_attempt`:使用稳定 `attempt_id`、任务内序号、阶段和结果幂等键记录每次 Agent 执行,不保存控件树或截图。
- 新增可选 `pdd_account` 调度引用,只保存人工名称和状态,不保存凭据;Android 无法识别账号时允许为空。
- 使用可空 guard 复合唯一索引限制同一 SYB 明细最多一个活动任务、同一设备最多一个执行中任务,以及已知账号最多一个执行中任务;终态历史完整保留。
- 正式 `live` 任务必须关联 SYB;无副作用 `rehearsal` 可不关联。MySQL 8.4 不允许外键列同时参与跨字段 CHECK,因此该条件由模型钩子和后续 #34 创建服务校验。
- 采购规则仅接受白名单类型化动作。演练拒绝改地址、创建订单和核单;任何模式拒绝支付与未知动作;正式高风险动作要求独立版本化能力。
- 地址后缀固定为 `_cg{task.id}`;价格使用参考/最低/最高单价整数分与币种;PDD 商品实际单价越界时明确失败。
- Wiki-first 更新共享 API、业务规则、架构、项目档案与交付索引;HTTP 路由、状态机服务、Admin 页面和 Android 执行仍属于后续工单。
## 修改文件
- `server/app/goauto/models/purchase.go`:采购任务、尝试和可选 PDD 账号引用模型及 guard 派生。
- `server/app/goauto/purchasecontract/`:采购规则类型化动作、模式和能力校验。
- `server/app/goauto/migrations/`:迁移清单与采购约束回归测试。
- `server/cmd/migrate/migration/version-local/1786701100000_purchase_contract.go`:追加型版本迁移。
- `docs/00`、`02`、`03`、`08`、`09`:Wiki revision `f9245c8` 的只读镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 追加迁移在既有商品表环境执行并可幂等复跑 | 通过;MySQL 8.4 版本只登记一次 |
| 演练与正式规则能力隔离,所有模式禁止支付 | 通过 |
| 商品域只通过外键与快照被消费 | 通过 |
| 同一 SYB 可保留多个历史任务且最多一个活动任务 | 通过 |
| 商品档案修改不改变已创建任务快照 | 通过 |
| 契约包含能力、幂等、租约、结果未知和回填状态 | 通过 |
| 三个商品表没有采购订单或物流字段 | 通过;MySQL 查询为 0 |
## 测试
- `go test ./app/goauto/purchasecontract ./app/goauto/migrations`:通过。
- `go test ./...`:通过。
- `powershell -ExecutionPolicy Bypass -File scripts/verify.ps1 -Component server`:全量测试与构建通过。
- `python dev_scripts/harness.py check --strict`:通过。
- 本地 MySQL 8.4:迁移 `1786701100000` 成功且幂等复跑通过;`pdd_account`、`purchase_task`、`purchase_task_attempt` 均存在,采购任务为 0 条,三个 guard 唯一索引正确。
- **未验证部分**:#33 不实现 HTTP 状态机、Admin 页面、Android 执行、真实地址修改、创建订单、人工支付或物流回填;没有执行任何 PDD 下单或支付动作。
## 遗留问题
- Gitea 迁移后的 Wiki REST API 返回 404;本次按已经验证的 Wiki Git 仓库完成 Wiki-first 更新和归档,核心镜像内容已逐页一致性比较。该环境问题不影响 #33 产品契约。
- #34 必须在创建服务事务中回写 `_cg{task.id}`,并再次强制校验正式任务的 SYB 引用、规则能力和状态转换。
## 相关提交
- `978f42806a9d649236a1476c5a831d2b02dfc16a` feat(#33): define purchase task data and API contract
## 用户验收
- 2026-08-20:用户明确回复“通过验收 #33”。
-66
@@ -1,66 +0,0 @@
# 34 服务端采购任务、租约、幂等与状态机
- 类型:需求
- 所属总体设计:#32
- 所属 MVP / 版本:采购闭环
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/34
- Wiki 页面:Task-34-purchase-task-state-machine
## 背景与目标
在 #33 数据与规则契约上实现服务端采购任务状态机,让后续 Admin 和 Android 能通过稳定接口创建、领取、执行和提交采购任务,同时隔离演练与正式能力、阻止设备/已知账号并发、避免重复订单事实,并保证系统不执行支付。
## 最终方案
- 新增 `server/app/goauto/purchase/`,实现从单条 SYB 明细创建正式任务,以及从 PDD 商品创建无副作用演练任务。创建时固化商品、规格、数量、价格、币种、规则、设备、可选账号和 `_cg{taskId}` 地址后缀。
- 正式任务校验 SYB → 虾皮 → PDD 关联和已确认规格映射;映射不完整、PDD 档案待采集或无规格时要求 `purchase.spec-probe.v1` 并进入两趟执行。
- 创建、下一任务、领取和开始四个边界均校验版本化能力。领取通过数据库 guard 同时占用设备和可选 PDD 账号租约,并检查该设备没有采集任务。
- `start` 创建唯一 attempt;规格探测第一趟提交后释放租约,服务端对同一 attempt 固化一次 AI/人工决策,第二趟使用新 attempt 派发。无匹配结果明确失败。
- 正式任务必须先提交 `order-submit-started` 才能提交订单号;`order_result_unknown` 不自动重试,只能人工解除。
- attempt 最终结果保存请求 ID 和内容哈希;相同内容重放返回原事实,不同内容拒绝覆盖。
- 一次性重新采购授权在新任务创建事务中消耗,旧任务和旧订单保留;人工支付复核、取消、结果未知解除和回填候选选择均有请求审计。后选的已支付订单覆盖同一 SYB 明细的旧回填候选。
- 演练规则在服务端拒绝改地址、创建订单和核单;任何规则拒绝支付。Admin 页面、Android 页面动作、物流和回填不在本工单。
## 修改文件
- `server/app/goauto/purchase/`:创建、租约、状态转换、结果幂等、人工处置、HTTP 路由和回归测试。
- `server/app/goauto/models/purchase.go`:状态审计、请求幂等、规格决策和取消字段;演练任务的虾皮关联改为可空。
- `server/cmd/migrate/migration/version-local/1786701200000_purchase_state_machine.go`:既有数据库追加迁移。
- `server/app/admin/router/init_router.go`:注册采购任务路由。
- `docs/00`、`02`、`03`、`08`、`09`:Wiki revision `5da7f225` 的只读镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 演练规则不能获得改地址、创建订单和支付能力 | 通过;规则契约回归覆盖 |
| 创建、next、claim、start 均执行能力校验 | 通过;不兼容 next 会跳过,其他边界明确拒绝 |
| 同一设备或已知 PDD 账号只能有一个领取/执行租约 | 通过;查询校验加数据库唯一 guard |
| 同一 attempt 相同结果重放不重复写,不同结果不能覆盖 | 通过 |
| 成功任务默认不能重建,一次授权只产生一条新任务并自动消耗 | 通过 |
| 结果未知不自动重派且必须人工解除 | 通过 |
| 状态事务化并保存审计字段,不记录敏感信息 | 通过 |
## 测试
- `go test ./...`:服务端全量通过。
- `go vet ./app/goauto/purchase ./app/goauto/models ./app/admin/router`:通过。
- `powershell -ExecutionPolicy Bypass -File scripts/verify.ps1 -Component server`:全量测试与构建通过。
- `python dev_scripts/harness.py check --strict`:通过。
- 本地 MySQL 8.4:迁移 `1786701200000` 成功登记;`purchase_task` 的状态、规格决策和订单提交幂等字段存在。
- **未验证部分**:未实现或验证 Admin 页面、Android 规则执行、真实修改地址、创建 PDD 订单、人工支付、物流采集和 SYB 回填;没有执行任何真实下单或支付动作。
## 遗留问题
- Gitea Wiki REST API 仍返回 404;本次通过可正常读写的 Wiki Git 仓库完成 Wiki-first 更新,并逐页比对核心 `docs/` 镜像。
- Admin 列表、详情和批量创建交互由 #35 实现;Android 演练和正式动作分别由 #42、#36 实现。
## 相关提交
- `65ae41fd6fdd9a0ee31b3c7a3fab1d0fec58db6e` feat(#34): implement purchase task state machine
## 用户验收
- 2026-08-20:用户明确回复“#34通过验收”。
@@ -1,79 +0,0 @@
# 35 Admin 采购任务与人工处理页面
- 类型:需求
- 设计依据:#32 采购闭环交互原型
- 所属 MVP / 版本:采购 MVP
- 状态:已完成(2026-08-21 用户验收通过)
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/35
- Wiki 页面:Task-35-Admin-采购任务与人工处理页面
- 设计证据:已确认的 #32 QuantUX 采购闭环原型;本工单不改变页面结构与主要流程
## 背景与目标
在 #33、#34、#42、#54 已建立采购数据、状态机、Android 演练和设备离线边界后,为管理员和采购员提供管理已有采购任务的页面。任务创建仍属于 SYB 商品模块的独立工单,页面不得提供支付或远程点击能力。
实施前发现 Admin 缺少查询接口和 go-admin-ui 成功响应包络,分别由 #55(`182ef39`)和 #56(`9ee03ec`)补齐后继续本工单。
## 最终方案
- 新增“采购管理”导航和采购任务 API 封装,支持按任务编号、状态、模式、SYB 商品 ID、PDD 订单号筛选。
- 列表展示 SYB/虾皮/PDD 快照、精确执行规格、数量、设备、任务状态、订单号和支付复核状态,不提供创建入口。
- 详情展示不可变商品与规格快照、允许价格区间、地址后缀、attempt 执行记录,以及订单、支付、物流和回填相互独立的状态。
- 支持人工标记已支付/未支付、选择回填候选、标记已取消、处理订单结果未知,以及输入原因并二次确认一次性重新采购授权。
- 订单结果未知时明确禁止重复采购,先人工核对后才能解除;该状态不显示重新采购授权。
- 支付复核只记录人工结果,系统无支付按钮;PDD 订单同步、物流采集与 SYB 自动回填仍由后续独立工单实现。
- 从详情跳转虾皮规格映射时携带 `productId`,虾皮商品页面自动打开对应详情。
UI 按既有 go-admin-ui 信息密集型后台样式实现,并补齐加载、空数据、失败重试、禁用边界和高风险确认反馈。
## 修改文件
- `web/src/views/goauto/purchase-tasks/index.vue`:采购任务列表、详情和人工处理交互。
- `web/src/api/goauto/purchase-tasks.js`:采购 Admin 查询和人工处置接口封装。
- `web/src/router/index.js`:新增“采购管理”路由与侧边栏入口。
- `web/src/views/goauto/shopee-products/index.vue`:支持通过 `productId` 查询参数打开规格映射详情。
- `web/tests/e2e/purchase-task-module.spec.ts`:关键页面与人工处理 Playwright 测试。
- Wiki `Project-Profile`、`Architecture-and-Code-Map`、`Business-Rules-and-Glossary`、`Delivery-Issues`:同步已实施范围、代码入口和安全边界。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 采购任务列表支持主要条件筛选并显示可读状态 | 通过 |
| 详情展示任务快照、attempt、订单、支付、物流与回填状态 | 通过 |
| 支持已支付/未支付、回填候选、取消、未知结果和重新采购授权 | 通过 |
| 订单结果未知时禁止重复采购并要求人工核对 | 通过 |
| 不提供采购任务创建入口、支付按钮或远程点击 | 通过 |
| 与虾皮规格映射页面形成可用跳转 | 通过 |
| 正常、空、加载、失败、禁用和权限边界有清晰反馈 | 通过;权限仍由服务端管理员/采购员门禁兜底 |
## 用户验收
- 验收人:用户
- 验收时间:2026-08-21
- 结论:#35 通过验收。
## 测试
- 执行命令:`pnpm lint`
- 结果:通过,0 error;保留上游已有 30 个 warning。
- 执行命令:`pnpm build:prod`
- 结果:通过;保留上游既有 CSS 兼容和大 chunk 警告。
- 执行命令:`pnpm exec playwright test tests/e2e/purchase-task-module.spec.ts`
- 结果:2 passed,覆盖无创建/支付入口、任务详情、支付复核、未知订单人工解除。
- 执行命令:`.\scripts\verify.ps1 -Component web`
- 结果:通过(依赖、lint、生产构建)。
- 执行命令:`python dev_scripts/harness.py sync --verify`
- 结果:通过,核心 Wiki 镜像与线上 revision 一致。
- 额外回归:旧 `sidebar-navigation.spec.ts` 依赖 `127.0.0.1:8001` 真实后端,因该服务未启动返回 `ECONNREFUSED`;本工单自身 Playwright 用例不受影响。
- **未验证部分**:未使用真实采购数据调用人工处置接口;未执行真机采购、创建订单、支付、物流采集或 SYB 回填,这些不属于 #35 范围。
## 遗留问题
- 从 SYB 商品批量创建采购任务由 #44 实现;真实创建待付款订单由高风险 #36 实现,开始前必须再次取得人工确认。
## 相关提交
- `c0c88bb` feat(#35): add purchase task admin UI
- `35dd3e0` docs(#35): clarify unknown-order guard
@@ -1,51 +0,0 @@
# 36 Android地址后缀不可逆门禁与创建订单
- 类型:需求 / 缺陷 / 重构
- 所属 Epic:#
- 所属 MVP / 版本:#
- 状态:待验收 / 已完成
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/36
- Wiki 页面:Task-36-Android地址后缀不可逆门禁与创建订单
- Wiki revision:见本地镜像头
## 背景与目标
<!-- 原来有什么问题,这次达到什么结果。 -->
## 最终方案
<!-- 说明实际实现。与建单方案不同之处必须写清原因。 -->
## 修改文件
- `<文件>`:<改动说明>
## 验收结果
| 验收标准 | 结果 |
|---|---|
| | 通过 / 未通过 |
## 测试
- 执行命令:`<命令>`
- 结果:
- **未验证部分**:<!-- 必填;没有就写“无”。 -->
## 遗留问题
<!-- 没有就删除本节。 -->
## 相关提交
- `<提交哈希>` <提交说明>
## 验收结论
- 状态:已验收
- 验收日期:2026-08-22
- 用户确认:#36 验收通过
- 真机证据:CG-12 已完成正式采购闭环,状态为 `order_created`,订单号和下单时间已保存
- 实现提交:`6a0dfed`、`f0cc0a5`;关联恢复缺陷 #66:`0ceb248`
- 安全边界:地址修改和创建待付款订单已验证,未执行支付
-43
@@ -1,43 +0,0 @@
# 40 虾皮商品档案、PDD 关联与规格映射
- 类型:需求
- 所属 MVP / 版本:采购前置数据准备
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/40
- Wiki 页面:Task-40-Shopee-product-archive
## 背景与目标
建立独立虾皮商品档案,保存参考售价、规格、PDD 商品关联和人工确认的规格映射,为后续采购任务提供稳定输入。
## 最终方案
- 新增 `shopee_product` 档案、软删除、PDD 多对一关联和通用规格 JSON。
- 参考售价以整数分保存,币种读取系统配置,缺省为 `TWD`。
- 支持导入值、人工值、精确匹配、AI 建议及人工确认状态。
- 提供管理 API 和 Admin 列表、详情、创建、编辑、关联与映射页面。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 数据模型、迁移和唯一性规则 | 通过 |
| PDD 关联与规格映射 | 通过 |
| Admin 页面和接口 | 通过 |
| 系统默认币种与 SYB 导入联动 | 通过 |
## 测试
- Go 单元测试、服务端构建、Web lint/构建通过。
- 已在本地 MySQL 和实际 SYB 导入结果中验证档案写入。
- **未验证部分**:采购任务消费该映射由后续采购工单验证。
用户于 2026-08-20 明确验收通过。
## 相关提交
- `f9cb194` 数据模型、规格映射与迁移
- `658ffd5` 管理 API
- `40d2862` Admin 页面
- `136f5c9` 页面布局收敛
-43
@@ -1,43 +0,0 @@
# 41 SYB 货运单商品导入与虾皮信息提取
- 类型:需求
- 所属 MVP / 版本:采购前置数据准备
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/41
- Wiki 页面:Task-41-SYB-product-import
## 背景与目标
把 SYB 货运单商品明细幂等保存,解析目标颜色、尺码和数量,并提取、关联可复用的虾皮商品档案。
## 最终方案
- `syb_product` 以货运单号和来源明细 ID 幂等覆盖,保留原始 JSON 和结构化字段。
- `productSpec` 按受控规则解析,成功、存疑和失败明确区分,不猜测。
- 成功解析值合并到虾皮商品档案;既有人工数据和映射不被导入覆盖。
- 提供列表、详情、重新解析和人工修正 API 及 Admin 页面。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| SYB 明细幂等落库 | 通过 |
| 颜色尺码解析和人工修正 | 通过 |
| 虾皮商品提取与关联 | 通过 |
| Admin 页面与真实导入 | 通过 |
## 测试
- Go 单元测试、服务端构建、Web lint/构建通过。
- 本地 MySQL 实际导入验证成功:1413 条 SYB 明细关联 823 个虾皮商品。
- **未验证部分**:采购任务创建不属于本工单。
用户于 2026-08-20 明确验收通过。
## 相关提交
- `d676298` 模型、解析器和虾皮合并
- `3b27d19` 管理 API
- `03a5656` Admin 页面
- `f53e6ba` 长期文档同步
@@ -1,61 +0,0 @@
# 42 Android 采购演练规则与持久执行基线
- 类型:需求 / Android 采购演练
- 所属总体设计:#32
- 状态:已完成,用户于 2026-08-20 明确验收
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/42
- Wiki 页面:Task-42-Android-采购演练规则与持久执行基线
## 背景与目标
在不修改收货地址、不创建订单、不支付的前提下,让 Android Agent 通过服务端规则快照演练打开商品、精确选择颜色尺码、设置数量、校验价格并安全停止,同时建立断网和重启后的结果可靠上报基线。
## 最终方案
- Android 增加采购演练规则解析器,只接受已声明的类型化动作和受限参数;不支持的能力在打开 PDD 前失败。
- 演练执行器精确匹配商品、规格和数量,校验实际单价范围;找不到规格时提交规格探测结果,不在本地猜测相近候选。
- 地址修改、创建/提交订单和支付相关动作均被规则解析器拒绝。
- 本地 SQLite 保存 task、attempt 和 Outbox;最终结果和待上传记录在同一事务保存。
- 断网或重启后只重发已有 requestId 和结果,不再次操作 PDD。
- Agent 心跳如实上报采购任务 ID;跨域设备状态由 #54 服务端实现统一处理。
## 修改文件
- android/.../automation/PurchaseRuleContract.kt:采购规则、能力与安全校验。
- android/.../automation/PurchaseRehearsalExecutor.kt:安全演练动作执行。
- android/.../persistence/PurchaseTaskStore.kt:本地 task、attempt 和 Outbox 事务持久化。
- android/.../persistence/PurchaseOutboxUploader.kt:只重传已有结果。
- android/.../network/AgentApiClient.kt:采购任务 Agent API。
- android/.../service/AgentForegroundService.kt:采购任务领取、执行、恢复和心跳调度。
- android/.../PurchaseRehearsalExecutorTest.kt:安全动作、精确匹配、价格和 Outbox 测试。
- Wiki 核心页面:项目档案、代码地图、业务规则、Agent API 契约和交付索引。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 规则参数可调整且旧任务使用自身快照 | 通过 |
| 精确规格、数量和价格校验后安全停止 | 通过 |
| 不支持能力在操作 PDD 前失败 | 通过 |
| 缺少规格走探测,不选择相近候选 | 通过 |
| 结果与 Outbox 事务保存,重传不重复操作 | 通过 |
| 不修改地址、不创建订单、不支付 | 通过 |
| 用户验收 | 2026-08-20 明确通过 |
## 测试
- 执行命令:android/gradlew.bat testDebugUnitTest assembleDebug
- 结果:BUILD SUCCESSFUL。
- 执行命令:python dev_scripts/harness.py sync --verify
- 结果:Wiki 镜像同步与 DevHarness 严格校验通过。
- 未验证部分:本次验收归档没有重新执行真机动作;用户已明确验收 #42。本实现不包含真实下单。
## 相关提交
- 888cc8b feat(#42): add persistent purchase rehearsal execution
- dae9cb6 docs(#42): record user acceptance
## 遗留边界
- #54 已完成实现但仍是独立待验收工单,本次不代替用户验收 #54。
- Admin 采购页面属于 #35;地址修改和创建待付款订单属于后续高风险 #36。
@@ -1,53 +0,0 @@
# 43 升级 GoAuto 的 DevHarness 文档基线
- 类型:文档 / DevHarness 基线升级
- 所属 Epic:无(开发治理)
- 状态:已完成(已被 #47 取代)
- 日期:2026-08-17 实施,2026-08-19 补归档并关闭
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/43
- Wiki 页面:Task-43-升级-DevHarness-文档基线
## 背景与目标
GoAuto 文档基于 `D:\OPC\dev_harness` 创建,模板更新后需要增量合并新版开发规则、工单模板与长期文档结构,同时保留 GoAuto 专用安全与业务规则。目标基线为 `b1f500128d6eb100985792d4a715db8b6b5ae203`。
## 最终方案
按当时确认的方案增量合并:`AGENTS.md` 吸收工单与设计证据双门禁、自然语言快捷指令、范围控制与需求记录边界;升级 `.gitea/issue_template/task.md`;`docs/00-project-profile.md` 记录 DevHarness 来源与目标基线;`docs/01-workflow.md`、`docs/README.md`、`docs/05-common-changes.md`、`docs/07-mvp-requirements.md`、`docs/09-delivery-issues.md` 同步升级。GoAuto 永久规则(禁止支付、不使用 OCR/VLM、不保存控件树与截图、采集 MVP 边界等)全部保留。
## 修改文件
- `AGENTS.md`、`CLAUDE.md`、`.gitea/issue_template/task.md`
- `docs/00-project-profile.md`、`docs/01-workflow.md`、`docs/README.md`
- `docs/05-common-changes.md`、`docs/07-mvp-requirements.md`、`docs/09-delivery-issues.md`
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 明确记录 DevHarness 来源、目标完整提交和升级日期 | 通过 |
| GoAuto 专用安全、采集和禁止支付规则未被模板覆盖 | 通过(`AGENTS.md` 三条核心永久规则均在) |
| 文档明确 Git `docs/` 仍为事实来源,Wiki-first 仍需独立工单 | **已废止**:Wiki 于 2026-08-17 由独立工作完成初始化并启用 Wiki-first,该前提被现实推翻 |
| 产品需求入口可找到采集 MVP、#31/#32 原型和 #33~#42 工单 | 通过 |
| 工单模板包含设计证据和交付文档影响字段 | 通过 |
| 文档没有 DevHarness 占位值、任务归档或无效 Wiki 命令 | 通过(残留 0 处) |
| Markdown/链接/工作区检查通过 | 通过(`git diff --check` 无错误,工作区干净) |
## 测试
- `git diff --check`:通过。
- 占位值与旧命令残留扫描:0 处。
- `python dev_scripts/harness.py check --strict`:通过(以升级后的 #47 版本校验)。
- **未验证部分**:本工单实施于 2026-08-17,当时未留下逐条验证记录;以上结果为 2026-08-19 关闭前对当前仓库状态的补充核对,不代表当时的即时验证。
## 遗留问题
本工单正文中「不启用或创建 Gitea Wiki」「以 Git `docs/` 为当前长期文档事实来源」的表述已过时,正文保持归档原样不修改。当前事实以 `Project-Profile` 与 `AGENTS.md` 为准。
## 后续变更
本工单确立的 `b1f500128d6eb100985792d4a715db8b6b5ae203` 基线已由 [#47 T45 升级 DevHarness 文档基线至 bfdf648](https://git.ilapage.cn/OPC/goauto/issues/47) 取代,升级区间 12 个提交。后续基线以 #47 记录为准。
## 相关提交
2026-08-17 的文档升级提交,具体哈希见当时的 Git 历史;本次仅补充归档与关闭,未产生新的代码或文档变更。
@@ -1,59 +0,0 @@
# 43 升级 GoAuto DevHarness 文档基线
- 类型:文档 / 工作流升级
- 所属 Epic:无
- 所属 MVP / 版本:DevHarness 基线升级
- 状态:待验收
- 日期:2026-08-17
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/43
- Wiki 页面:Task-43-升级-GoAuto-DevHarness-文档基线
- Wiki revision:以页面当前 revision 为准
## 背景与目标
GoAuto 的 Harness 文档基于 `D:\OPC\dev_harness` 建立。目标是增量采用 DevHarness 提交 `b1f500128d6eb100985792d4a715db8b6b5ae203` 的流程能力,并按用户确认把长期文档切换为 Wiki-first,同时保留 GoAuto 专用业务、安全和验收规则。
## 最终方案
- 初始化并验证 GoAuto Wiki,API 读写、Wiki Git clone/pull/push 均通过。
- 把 13 份现有核心文档和任务归档模板迁移为 14 个 Wiki 页面。
- 使用 `wiki-docs.json` 显式映射 Wiki 页面和本地 Markdown。
- 引入 Wiki 到 `docs/` 单向同步、任务归档创建和按需导出工具。
- 映射镜像存在未提交修改时拒绝覆盖;页面删除、重命名和映射变化不自动传播。
- `docs/rules/*.json` 等版本绑定资料继续保留在 Git,不迁移到 Wiki。
- 用户授权后删除误生成的乱码归档页,并把本地 `main` 的 12 个待推送提交快进推送到远端。
## 修改文件
- `AGENTS.md`、`CLAUDE.md`、`.gitea/issue_template/task.md`:采用新版门禁、快捷指令和 Wiki-first 规则。
- `wiki-docs.json`:登记 14 个核心页面映射。
- `dev_scripts/`:Wiki 同步、归档创建和按需导出工具。
- `tests/test_wiki_docs.py`:同步与归档安全边界测试。
- `docs/`:从 Wiki 生成的核心只读镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| Wiki 首页与独立 Wiki Git 仓库可正常读写 | 通过 |
| 14 个核心页面全部存在并可回读 | 通过 |
| 14 个本地镜像正文、页面和 revision 一致 | 通过 |
| 镜像同步只允许 Wiki 到 docs,脏镜像拒绝覆盖 | 通过 |
| 未复制 DevHarness 项目事实或历史任务归档 | 通过 |
| GoAuto 禁止支付等专用红线继续保留 | 通过 |
| 乱码归档页已按用户授权删除,正确归档保留 | 通过 |
| 本地 12 个提交已推送,远端 main 与本地一致 | 通过 |
## 测试
- `python -m unittest discover -s tests -v`:16 项通过。
- `python dev_scripts/sync_wiki_docs.py --check`:14 个映射全部一致。
- `git diff --check`:通过。
- Wiki API 与 Wiki Git:创建、更新、clone、pull、标准 push 均通过。
- `git rev-parse main` 与 `git rev-parse origin/main`:均为 `10966c5c25ec2b01288503b34d2bd617f737a8cc`。
- **未验证部分**:无。
## 相关提交
- `a4ac398` docs: prepare Wiki-first migration (#43)
- `10966c5` docs: generate Wiki mirrors (#43)
@@ -1,42 +0,0 @@
# 44 Admin SYB商品列表规格匹配入口与批量采购创建
- 类型:需求 / 缺陷 / 重构
- 所属 Epic:#
- 所属 MVP / 版本:#
- 状态:待验收 / 已完成
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/44
- Wiki 页面:Task-44-Admin-SYB商品列表规格匹配入口与批量采购创建
- Wiki revision:见本地镜像头
## 背景与目标
<!-- 原来有什么问题,这次达到什么结果。 -->
## 最终方案
<!-- 说明实际实现。与建单方案不同之处必须写清原因。 -->
## 修改文件
- `<文件>`:<改动说明>
## 验收结果
| 验收标准 | 结果 |
|---|---|
| | 通过 / 未通过 |
## 测试
- 执行命令:`<命令>`
- 结果:
- **未验证部分**:<!-- 必填;没有就写“无”。 -->
## 遗留问题
<!-- 没有就删除本节。 -->
## 相关提交
- `<提交哈希>` <提交说明>
@@ -1,83 +0,0 @@
# 47 升级 DevHarness 文档基线至 bfdf648
- 类型:文档 / 工具 / DevHarness 基线升级
- 所属 Epic:无(开发治理)
- 状态:已完成
- 日期:2026-08-19
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/47
- Wiki 页面:Task-47-升级-DevHarness-文档基线至-bfdf648
## 背景与目标
GoAuto 文档基线停留在 DevHarness `b1f500128d6eb100985792d4a715db8b6b5ae203`(2026-08-17)。模板已更新至 `bfdf648962d11a8024f62768380d8571e1f45f68`,区间 12 个提交、18 个文件、+500/−345,包含四个主题:新项目 Wiki 初始化门禁(#20)、本地 HTML 原型快照(#21)、项目档案改按需读取(#22)、命令入口合并(#23),另有一次行尾统一。
## 最终方案
### A. dev_scripts 入口合并
新增 `dev_scripts/harness.py`,提供 `check` / `sync` / `archive` / `export` 四个子命令,`sync` 支持 `--check` 与 `--verify`。删除 `export_task_archives.py`、`new_task_archive.py`、`sync_wiki_docs.py`,保留 `wiki_docs.py`(与模板逐字一致)。
**两处按 GoAuto 事实改写,未照抄模板:**
1. `CORE_PAGE_PATHS` 与 `CORE_DOCUMENT_REQUIREMENTS` 改为 GoAuto 实际的 14 个映射页与真实章节名。模板期望 `docs/09-product-requirements-overview.md` 等命名,GoAuto 实际为 `07-mvp-requirements.md`、`08-agent-api-contract.md`、`09-delivery-issues.md`,另有 `03`、`06`、`10`、`11` 四份专题文档。只登记结构性稳定标题,具体故障现象与单条业务规则不纳入校验,避免文档正常更新即触发失败。
2. `check_repository_readme()` 改为校验产品 README 结构(`## 阅读入口` / `## 当前状态` / `## 验证`)。模板此处校验「新项目快速开始」的建仓步骤,面向从模板派生新仓库;GoAuto 是已建成产品仓库,写入建仓步骤会误导读者。新项目 Wiki 初始化门禁改由 `AGENTS.md` 与 `Development-Workflow` 承载,两处仍由 check 强制校验,规则未削弱。偏离原因写入函数 docstring。
### B. `.gitattributes`
统一文本文件以 LF 存储。实测解决两个既有问题,且**无需大规模行尾重写提交**——索引中本已为 LF,`.gitattributes` 生效后 git 在比对前归一化工作区文件。
### C. 根规则文件
`AGENTS.md` 按模板编号结构重写,GoAuto 全部永久规则逐条保留;新增常用命令表、项目档案按需读取的五种情形、新项目 Wiki 初始化门禁、本地 HTML 审核快照门禁、`### 效率与范围控制` 四个子节、`### 需求记录与流转`。`CLAUDE.md` 改为显式三模型路由并保留 GoAuto 专用提醒。`.gitea/issue_template/task.md` 增加四个设计证据字段与一项文档影响。
### D. Wiki 长期文档(Wiki-first)
`Project-Profile` 基线更新为 `bfdf648`;`Development-Workflow` 新增两节;`Troubleshooting` 新增「排查顺序」;`Home`、`Common-Changes` 命令引用统一。
### E. 原型快照目录结构(用户选定方案二)
新规则要求 `prototypes/<工单号>/<版本>/index.html` 且已确认快照不得原位覆盖。用户确认**只对新增原型生效,存量不迁移**,已在 `AGENTS.md` 与 `Development-Workflow` 中以「存量偏离(#47 确认)」注明。
## 修改文件
- `AGENTS.md`:按模板编号结构重写,保留 GoAuto 永久规则
- `CLAUDE.md`:显式三模型路由 + GoAuto 专用提醒
- `.gitattributes`:新增,统一 LF
- `.gitea/issue_template/task.md`:设计证据与文档影响字段
- `dev_scripts/harness.py`:新增,按 GoAuto 事实改写两处校验
- `dev_scripts/export_task_archives.py`、`new_task_archive.py`、`sync_wiki_docs.py`:删除
- `docs/00-project-profile.md`、`01-workflow.md`、`05-common-changes.md`、`06-troubleshooting.md`、`README.md`:Wiki 镜像同步
## 验收结果
| 验收标准 | 结果 |
|---|---|
| `harness.py` 四个子命令可用,`sync --verify` 通过 | 通过 |
| 三个旧入口脚本已删除,无残留引用 | 通过 |
| `CORE_DOCUMENT_REQUIREMENTS` 与 GoAuto 实际文档一致,`check --strict` 通过 | 通过 |
| `.gitattributes` 生效,`gitea.env` 不再需要 `tr -d '\r'` | 通过 |
| `AGENTS.md` 含常用命令表、按需读取说明、两项新门禁,永久规则未被覆盖 | 通过 |
| Wiki 基线为 `bfdf648`,新增章节齐备,镜像一致性通过 | 通过 |
| 全仓库无旧命令引用 | 通过(残留 0 处) |
| Git 差异只含本工单文件 | 通过 |
| 用户验收 | 2026-08-19 明确通过 |
## 测试
- `python dev_scripts/harness.py check --strict`:通过(升级过程中由 45 项问题逐步收敛至 0)。
- `python dev_scripts/harness.py sync --verify`:通过。
- `python dev_scripts/harness.py archive`:实际用于创建 #43 与 #47 归档页,功能验证通过。
- `.gitattributes` 效果:`git status --porcelain` 由数百个文件降为 0;`gitea.env` 直接读取 token 调用 Gitea API 成功。
- 旧命令引用扫描:0 处。
- **未验证部分**:`harness.py export` 子命令未实际执行(任务归档默认只存 Wiki,用户未要求导出);`.gitea/issue_template/epic.md` 与 `mvp.md` 未随本次升级改动。
## 遗留问题
- `harness.py export` 未实测。
- 存量原型快照按方案二保持平铺结构,与新规则的版本目录并存。
## 相关提交
- `b343063` docs(#47): upgrade DevHarness baseline to bfdf648
- `f8d0011` docs(#47): mirror harness command entry changes
- `0d623d8` docs(#47): unify harness command references
@@ -1,67 +0,0 @@
# 49 店铺管理与 SYB 同步店铺过滤
- 类型:需求
- 所属 Epic:SYB 商品数据域
- 所属 MVP / 版本:采购前置数据准备
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/49
- Wiki 页面:Task-49-店铺管理与-SYB-同步店铺过滤
- Wiki revision:见本地镜像头
## 背景与目标
SYB 账号能看到多个店铺的货运单,原导入会把全部店铺写入 GoAuto。本工单增加可管理的店铺准入表和 Admin 页面,在不削弱原始列表完整性校验的前提下,只读取和落库启用店铺的明细。
## 最终方案
- 新增 `syb_shop` 软删除模型、版本迁移、全半角/空白/大小写规范化以及 CRUD、启停和分页查询。
- 新增“从 SYB 发现店铺”,只返回真实列表中去重后的店铺和已存在状态,不自动新增。
- 同步先校验原始全量列表,再使用本次任务固定的启用店铺快照过滤;明细店铺为空或不允许时再次拦截并计入跳过数。
- 导入入口在读取凭据和连接 SYB 之前检查启用店铺;Admin 导入弹窗也做一次可读的前置提示。
- Admin 管理员可维护店铺,其他角色显示只读状态。写权限仍由 go-admin/Casbin 配置。
- QuantUX App ID `6a866e98191a826306a7ef78`,本地确认快照为 `prototypes/49/v1/index.html`。
## 修改文件
- `server/app/goauto/sybshop/`、`server/cmd/migrate/migration/version-local/1786700900000_syb_shop.go`:模型、迁移、服务和 API。
- `server/app/goauto/sybimport/`:导入前置拒绝、完整性校验后的过滤和明细二次校验。
- `web/src/views/goauto/syb-shops/`、`web/src/api/goauto/syb-shops.js`:店铺管理页面与接口。
- `web/src/views/goauto/syb-products/index.vue`:无启用店铺时阻止打开导入。
- `web/tests/e2e/syb-shop-module.spec.ts`:管理员、只读角色和无店铺导入门禁。
- `prototypes/49/v1/index.html`:QuantUX 本地 HTML 审核快照。
- 核心 Wiki 与 `docs/` 镜像:业务规则、API、架构、项目档案和交付索引。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 原型包含列表、发现、新增/改名/删除、无启用店铺和只读状态 | 通过 |
| 店铺名规范化且软删除 | 通过 |
| 完整性校验后过滤,明细再次校验 | 通过 |
| 无启用店铺时不建立 SYB 连接 | 通过 |
| Admin 页面及角色边界 | 通过 |
| 真实 MySQL 迁移与启用店铺过滤导入 | 通过 |
## 测试
- 执行命令:`go test ./app/goauto/sybshop ./app/goauto/sybimport`
- 执行命令:`powershell -ExecutionPolicy Bypass -File scripts/verify.ps1 -Component server`
- 执行命令:`powershell -ExecutionPolicy Bypass -File scripts/verify.ps1 -Component web`
- 执行命令:`pnpm exec playwright test tests/e2e/syb-shop-module.spec.ts`
- 结果:服务端全量测试与构建通过;Web lint 0 错误(30 个既有警告)且生产构建通过;新增 3 条浏览器流程通过;QuantUX HTML 导出检查通过。
- **未验证部分**:生产角色授权和历史错误数据清理未验证;没有 Android 改动。
用户于 2026-08-20 明确验收通过。
## 遗留问题
- 既有错误导入数据的清理为不可逆操作,必须先查询并展示影响行数、确认精确 SQL,再单独执行。
- `last_seen_at` 的同步记录展示留给 #50。
## 相关提交
- `cda37b4` syb_shop 模型、迁移和规范化
- `7bd8166` 店铺服务、CRUD 和启用名称查询
- `1b67495` 店铺 API、SYB 发现和同步过滤
- `6c7a729` Admin 页面、导入前置门禁、测试、原型与文档
-36
@@ -1,36 +0,0 @@
# 50 SYB 同步记录与导入异步化
- 类型:需求
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/50
- Wiki 页面:Task-50-SYB-async-sync
## 最终方案
- 导入请求创建持久化后台任务并立即返回,页面关闭不影响执行。
- `syb_sync_run` 保存进度、结果、店铺统计和安全错误摘要。
- 数据库执行槽与进程内锁共同保证单任务运行;启动时恢复中断状态。
- Admin 提供同步记录列表和详情。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 后台执行、进度与终态 | 通过 |
| 单任务互斥和启动中断恢复 | 通过 |
| 同步记录页面 | 通过 |
| 本地 MySQL 真实 SYB 导入 | 通过 |
## 测试
- 服务端全量测试与构建、Web lint/构建通过。
- 本地真实同步成功完成并能刷新出 SYB 商品。
- **未验证部分**:多服务实例竞争未做部署级压测。
用户于 2026-08-20 明确验收通过。
## 相关提交
- `39d38e1` 已确认原型
- `badb78f` 异步同步实现
-30
@@ -1,30 +0,0 @@
# 51 优化 SYB 商品列表工具栏与列宽
- 类型:界面优化
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/51
- Wiki 页面:Task-51-SYB-list-layout
## 最终方案
移除店铺管理按钮,显示已选择数量;把重新解析移至重置右侧;压缩指定列并把空间交给虾皮商品列。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 工具栏位置和选中计数 | 通过 |
| 列宽调整 | 通过 |
| 页面构建与浏览器回归 | 通过 |
## 测试
- Web 生产构建、lint 和 Playwright 布局测试通过。
- **未验证部分**:无。
用户于 2026-08-20 明确验收通过。
## 相关提交
- `d392ed5` SYB 商品列表布局优化
-38
@@ -1,38 +0,0 @@
# 52 修复 SYB 同步预创建失败无法定位
- 类型:缺陷
- 状态:已完成
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/52
- Wiki 页面:Task-52-SYB-sync-diagnostics
## 背景与目标
SYB 同步只返回“服务端处理失败”,无法区分店铺预检和同步记录创建失败。
## 最终方案
- 增加稳定阶段错误码和脱敏、截断的服务端诊断日志。
- 定位到 GORM 字段名与既有 MySQL `created_count`、`updated_count` 不一致。
- 使用显式列映射做最小修复,不修改数据库结构。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 错误阶段可定位且响应不泄露底层信息 | 通过 |
| MySQL 列映射正确 | 通过 |
| 真实 SYB 导入恢复 | 通过 |
## 测试
- SYB 导入包测试、迁移测试和服务端全量验证通过。
- 用户重启服务后真实同步成功。
- **未验证部分**:无。
用户于 2026-08-20 明确验收通过。
## 相关提交
- `32edb91` 预创建失败阶段诊断
- `903280f` MySQL 计数字段映射修复
@@ -1,61 +0,0 @@
# 53 补齐采购规则参数化动作契约
- 类型:缺陷修复 / 共享 API 契约
- 父级 Epic:#32 采购闭环数据关系与交互原型
- 关联工单:#42 Android 采购演练规则与持久执行基线
- 状态:2026-08-20 用户验收通过
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/53
- Wiki 页面:Task-53-补齐采购规则参数化动作契约
## 需求与目标
#42 要求按钮同义词、动作后等待和有限滑动可以只修改规则而无需重新发布 APK;原采购规则契约只有 `actions[].type`,严格 JSON 校验会拒绝这些参数。本单补齐受限、类型化、可审计的参数契约,同时保持演练模式不改地址、不创建订单且任何模式永不支付。
## 交付方案
- 安全 action 支持可选 `textAliases` 和 `waitAfterMs`;只有 `openProduct`、`openSpecPanel`、`selectSpec`、`probeSpecs` 支持 `swipeAfter`。
- 文字候选、等待、方向、次数、持续时间和间隔均有明确上限;未知字段、空/重复候选、越界值和未授权 action/参数组合拒绝。
- 地址、下单和支付相关文字不能借安全 action 的文字候选绕过能力门禁。
- 旧的仅含 `actions[].type` 的规则继续有效;任务创建仍原样保存完整规则快照,后续规则更新不改变旧任务。
- 正式高风险 action 暂不接受本单参数,留待其独立契约和人工确认。
## 修改文件
- `server/app/goauto/purchasecontract/contract.go`:新增参数结构、边界、动作矩阵和安全校验。
- `server/app/goauto/purchasecontract/contract_test.go`:覆盖合法规则、旧规则、未知字段、别名安全、数值边界和动作矩阵。
- `server/app/goauto/purchase/service_test.go`:验证参数化演练规则可创建任务且快照原样保存。
- `docs/08-agent-api-contract.md`:从 Wiki 导出的参数契约与安全边界镜像。
- `docs/09-delivery-issues.md`:从 Wiki 导出的 #53 与 #42 依赖索引镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 规则 A/B 可分别保存按钮候选、有限等待和有限滑动 | 用户验收通过 |
| 未知字段、越界参数、空/重复/危险候选明确拒绝 | 通过 |
| 旧 type-only 规则兼容 | 通过 |
| 演练规则继续拒绝地址、创建订单、核单和支付动作 | 通过 |
| 参数化规则原样固化到任务快照 | 通过 |
| Wiki-first 更新、回读和本地镜像一致 | 通过 |
## 验证
- 执行命令:`go test ./...`(目录 `server/`)
- 执行命令:`python dev_scripts/harness.py check --strict`
- 执行命令:`python dev_scripts/harness.py sync --check`
- 结果:全部通过。
- **未验证范围**:未修改 Android,未构建 APK,未执行真机或 PDD 操作;Android 消费本契约由 #42 实现和验收。
## 遗留问题
- #42 在 #53 用户验收后继续实施 Android 解释器、持久任务和 Outbox。
- 正式地址修改与创建订单 action 的参数化不属于本单,必须遵守后续高风险工单门禁。
## 关联提交
- `afeb2723ea0acfb03f2ab31b7b12f491602f4a9b` fix(#53): parameterize purchase rule actions
## 用户验收
- 2026-08-20:用户明确回复“验收 #53,继续 #42”。#53 验收通过,解除 #42 的参数契约阻塞。
@@ -1,57 +0,0 @@
# 54 统一采集与采购任务设备状态
- 类型:缺陷修复
- 所属 Epic:采购闭环
- 所属 MVP / 版本:采购 MVP
- 状态:已完成,用户于 2026-08-20 明确验收
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/54
- Wiki 页面:Task-54-统一采集与采购任务设备状态
## 背景与目标
采购任务已具备服务端领取和运行状态,但设备心跳、设备列表和离线扫描仍只识别采集任务,导致 Android 正确上报采购任务时被判定为任务不一致。本次统一设备级当前任务事实,解除 #42 服务端联调阻塞。
## 最终方案
- 统一查询采集 running 与采购 running / order_submit_started,供心跳和设备列表复用。
- 同一设备异常存在跨任务域同时运行时返回服务端一致性错误,不静默选择任务。
- 设备离线时,普通运行采购任务和对应 attempt 以 DEVICE_OFFLINE 失败并释放租约、设备槽和账号槽。
- 已进入 order_submit_started 的任务转为 order_result_unknown,保留人工核对边界,禁止自动重派或再次点击创建订单。
- 保持 currentTaskId 现有接口字段不变;本单不执行 PDD、创建订单或支付动作。
## 修改文件
- server/app/goauto/device/current_task.go:统一设备当前任务查询与冲突检测。
- server/app/goauto/device/heartbeat.go:心跳兼容采购任务并补齐采购离线处理。
- server/app/goauto/device/*_test.go:覆盖采购心跳、列表、离线及跨域冲突。
- docs/03、docs/08、docs/09:Wiki-first 同步业务规则、契约和交付索引。
## 用户验收
用户于 2026-08-20 明确表示 #54 通过验收。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 采购任务 ID 心跳成功;遗漏任务 ID 被拒绝 | 通过(自动测试) |
| 设备列表显示采购任务忙碌状态与任务 ID | 通过(自动测试) |
| 运行采购任务离线后任务和 attempt 失败并清理运行资源 | 通过(自动测试) |
| order_submit_started 离线转为结果未知且禁止自动重派 | 通过(自动测试) |
| 跨任务域同时运行返回一致性错误 | 通过(自动测试) |
| 采集任务原有行为保持通过 | 通过(完整服务端回归) |
## 测试
- 执行命令:go test ./app/goauto/device
- 结果:通过。
- 执行命令:go test ./...
- 结果:通过。
- 执行命令:python dev_scripts/harness.py sync --verify
- 结果:Wiki 镜像同步与 DevHarness 严格校验通过。
- **未验证部分**:未做 Android 真机采购演练;该项属于 #42,且本单没有执行任何 PDD 或订单动作。
## 相关提交
- b0f33bf fix(#54): unify device state across task domains
@@ -1,45 +0,0 @@
# 55 补齐采购任务 Admin 列表与详情查询接口
- 类型:服务端接口缺口
- 状态:已完成(2026-08-21 用户验收通过)
- 日期:2026-08-20
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/55
## 背景与目标
#35 需要展示真实采购任务列表和详情,但 #34 只有创建与状态写接口。本单补齐只读查询,不触发 Agent 或订单动作。
## 最终方案
- 增加采购任务分页列表,可按任务 ID、状态、模式、SYB 商品 ID 和 PDD 订单号筛选。
- 增加任务详情与按序 attempt 历史。
- 使用明确 DTO 返回页面所需快照、设备、订单、支付、物流、回填和错误信息。
- 不返回规则原文、Token、凭据、完整地址、控件树或截图。
- 沿用管理员和采购员角色门禁,无数据库迁移。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 分页与筛选 | 自动测试通过 |
| 详情与 attempt 历史 | 自动测试通过 |
| 非授权角色拒绝 | 自动测试通过 |
| 敏感字段不返回 | 自动测试通过 |
| 完整服务端回归 | 通过 |
## 用户验收
- 验收人:用户
- 验收时间:2026-08-21
- 结论:#55 通过验收。
## 测试
- go test ./app/goauto/purchase:通过。
- go test ./...:通过。
- python dev_scripts/harness.py sync --verify:通过。
- 未验证部分:尚未由用户验收;未执行 PDD、订单或支付动作。
## 相关提交
- 182ef39 feat(#55): add purchase admin query APIs
@@ -1,26 +0,0 @@
# 56 采购 Admin 接口成功响应兼容
- 类型:缺陷修复
- 状态:已完成(2026-08-21 用户验收通过)
- 日期:2026-08-20
- 工单:https://git.ilapage.cn/OPC/goauto/issues/56
## 结果
采购域全部 Admin 成功响应统一包含 code: 200、data 和按需 replayed,兼容 go-admin-ui 响应拦截器。Agent 接口、业务状态机和数据库未改变。
## 用户验收
- 验收人:用户
- 验收时间:2026-08-21
- 结论:#56 通过验收。
## 验证
- go test ./app/goauto/purchase:通过。
- go test ./...:通过。
- 未执行 PDD、订单或支付动作。
## 提交
- 9ee03ec fix(#56): add go-admin success envelope
@@ -1,57 +0,0 @@
# 57 优化采购管理筛选栏布局
- 类型:小范围 UI 调整
- 所属功能:#35
- 状态:已完成(2026-08-21 用户验收通过)
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/57
- Wiki 页面:Task-57-优化采购管理筛选栏布局
## 背景与目标
按用户精确标注压缩采购管理筛选栏,删除重复页头说明和模式筛选,使常用筛选项在桌面端保持一行并默认只查询正式采购任务。
## 最终方案
- 删除页面内“采购管理”标题和说明文案,侧边栏导航名称保持不变。
- “任务编号”改为“任务”,任务、SYB 商品、PDD 订单号输入宽度分别设为 72px、100px、120px。
- 删除模式选择器,但查询模型固定 `executionMode=live`,重置后仍只查询正式采购任务。
- 筛选顺序调整为任务、状态、SYB 商品、PDD 订单号、查询、重置;桌面端不换行,768px 以下允许自然换行。
- 保留可见字段标签、Enter 查询、查询与重置行为。
UI/UX 检查采用已有数据密集型后台规范,未引入新颜色、字体、组件或交互流程。
## 修改文件
- `web/src/views/goauto/purchase-tasks/index.vue`:调整筛选栏结构、宽度、默认模式与响应式样式。
- `web/tests/e2e/purchase-task-module.spec.ts`:验证正式模式参数、控件宽度、顺序和同一行布局。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 删除页面标题和原说明文案 | 通过 |
| 删除模式并默认正式采购 | 通过 |
| 输入框按紧凑宽度显示 | 通过 |
| PDD 订单号、查询、重置位于 SYB 商品之后并保持同一行 | 通过 |
| 原查询、重置和详情人工处理行为不变 | 通过 |
## 用户验收
- 验收人:用户
- 验收时间:2026-08-21
- 结论:#57 通过验收。
## 测试
- 执行命令:`pnpm lint`
- 结果:通过,0 error;保留上游既有 30 个 warning。
- 执行命令:`pnpm build:prod`
- 结果:通过;保留上游既有 CSS 兼容和 chunk 体积警告。
- 执行命令:`pnpm exec playwright test tests/e2e/purchase-task-module.spec.ts`
- 结果:2 passed。
- **未验证部分**:未进行用户浏览器人工视觉验收;窄屏换行通过 CSS 边界实现,未纳入本次桌面布局 Playwright 断言。
## 相关提交
- `3875776` fix(#57): compact purchase task filters
@@ -1,58 +0,0 @@
# 58 采购管理失败任务批量重试
- 类型:需求
- 所属 MVP / 版本:采购闭环真机验收 #39
- 状态:待验收
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/58
- 原型:`prototypes/58/v1/index.html`,用户于 2026-08-21 确认
## 背景与目标
采购失败后,原流程需要进入详情授权,再返回 SYB 商品列表重新创建,步骤繁琐。目标是在采购管理当前页勾选多个可安全重试的失败任务,一次确认后分别创建新的待执行任务。
## 最终方案
- Admin 列表由服务端返回 `retryable` 和不可重试原因,页面只允许勾选可重试行;表头全选不跨页,批次最多 100 条。
- `POST /api/admin/v1/purchase-tasks/batch-retry` 接收批次 `requestId` 与原任务 ID,逐项校验、逐项创建,允许部分成功;批次重放不会重复创建。
- 重试保留原失败任务和 attempt,使用当前 SYB/PDD 档案、当前规格映射、当前价格保护及最新内置规则创建新 `pending` 任务,并生成新任务编号和地址后缀。
- 只有没有不可逆或订单事实、仍是同一 SYB 最新记录的正式失败任务可重试。沿用原设备;设备离线、停用、忙碌或能力不足时明确失败,不自动换机。原任务未指定设备时仍允许空闲设备领取。
- 失败任务不再使用一次性重新采购授权;该授权只保留给已经创建订单且满足条件的任务。
- 不新增数据库表或迁移,不修改 Android Agent,不执行下单或支付。
## 修改文件
- `server/app/goauto/purchase/retry.go`、`retry_test.go`:批量重试、资格判定、幂等与边界测试。
- `server/app/goauto/purchase/admin_query.go`、`handler.go`、`router.go`、`types.go`、`manual.go` 及相关测试:Admin 接口、列表资格和授权边界。
- `web/src/views/goauto/purchase-tasks/index.vue`、`web/src/api/goauto/purchase-tasks.js`:当前页多选、确认、加载与部分成功结果。
- Wiki `Business-Rules-and-Glossary`、`Architecture-and-Code-Map` 及对应只读镜像:长期规则和代码入口。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 原型先确认再编码 | 通过 |
| 失败任务当前页多选、禁用原因和一次确认 | 已实现,待用户页面验收 |
| 新建待执行任务并保留旧失败任务 | 自动化测试通过 |
| 最新资料/规则、新编号与地址后缀 | 自动化测试通过 |
| 离线/忙碌设备、旧记录、订单事实等安全拒绝 | 自动化测试通过 |
| 幂等重放与部分成功 | 自动化测试通过 |
| 不包含支付、数据库迁移或 Android 改动 | 通过 |
## 测试
- `go test ./app/goauto/purchase`:通过。
- `.\scripts\verify.ps1 -Component server`:通过(`go test ./...`、`go build`)。
- `.\scripts\verify.ps1 -Component web`:通过(Lint 0 error、30 条仓库既有 warning;生产构建通过,保留既有 CSS 与大包 warning)。
- `python dev_scripts/harness.py sync --verify`、`python dev_scripts/harness.py check --strict`、最终 `sync --check`:通过。
- `git diff --check`:通过。
- **未验证部分**:未触发真实采购、PDD 下单或支付;生产页面交互由用户验收。
## 遗留问题
- 无本工单范围内已知代码遗留;真实下单仍遵循独立高风险授权门禁。
## 相关提交
- `0d13ed4` `prototype(#58): add failed purchase batch retry flow`
- `01d8520` `feat(#58): batch retry failed purchase tasks`
@@ -1,59 +0,0 @@
# 59 修复 PDD 规格入口误触商品评论页
- 类型:缺陷
- 所属 Epic:采集 MVP #1
- 所属 MVP / 版本:PDD 商品详情采集最小闭环
- 状态:待验收
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/59
- Wiki 页面:Task-59-修复-PDD-规格入口误触商品评论页
- Wiki revision:在线页面 `last_commit.sha`
## 背景与目标
三星 SM-G9700 采集商品 `172119352488` 时,旧规格入口兜底把含规格词和数字的商品评价卡片误判为入口;评价页和商品详情页又共用 PDD `NewPageActivity`,最终任务报“规格面板没有出现强证据”。目标是排除评价上下文,只点击明确规格选择入口或安全的底部购买文字,并在首次误入评价页时安全返回重试一次。
## 最终方案
- 明确规格入口只接受“请选择/选择/已选”等选择语义与规格别名组合,不再接受宽泛的“规格词 + 数字”。
- 底部入口定位“单独购买、立即购买、发起拼单、免拼购买”等真实购买文字,沿可点击父链执行动作;订单和支付动作仍不进入本工单。
- 识别商品评价页;首次误入时返回、重新验证商品详情并只重试一次,重复误入明确失败。
- 区分评论误入、点击无页面变化和规格面板证据不匹配,记录入口来源、短标签、控件类型、坐标和结果,不保存原始控件树或截图。
## 修改文件
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddProductDetailCollector.kt`:收紧入口、增加评价页恢复和分级错误。
- `android/app/src/test/java/cn/ilapage/goauto/agent/PddProductDetailCollectorTest.kt`:补评价卡片、底部入口、恢复重试和无效点击测试。
- `android/README.md`:更新 Agent 行为说明。
- `docs/02-architecture-and-code-map.md`、`docs/03-business-rules-and-glossary.md`、`docs/08-agent-api-contract.md`、`docs/10-real-device-acceptance.md`:从 Wiki 同步架构、业务、契约和真机验收记录。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 评价卡片不再成为规格入口 | 通过,自动化测试覆盖 |
| 明确规格入口与安全底部按钮仍可打开规格面板 | 通过 |
| 首次误入评价页安全返回并只重试一次 | 通过,自动化测试覆盖 |
| 点击无效和面板证据异常返回分级错误 | 通过 |
| 不保存控件树/截图,不触发订单或支付 | 通过 |
| 三星真机复采任务 #42 | 通过:日志记录 `bottom_purchase` 点击“快要抢光 免拼购买”成功,未进入评价页,采到 2 个颜色和 8 个尺码 |
## 测试
- 执行命令:`android/gradlew.bat test assembleDebug`
- 执行命令:`scripts/verify.ps1 -Component android`
- 执行命令:`python dev_scripts/harness.py sync --verify`
- 执行命令:`python dev_scripts/harness.py check --strict`
- 执行命令:`git diff --check`
- 结果:28 个采集器相关测试、Debug APK 构建、Android 验证、Wiki 镜像与 DevHarness 严格检查均通过。
- 真机结果:三星 SM-G9700、Android 12、Agent 0.2.0、PDD 7.95.0;任务 #42 为 `completed_partial`,保存 2 个颜色、8 个尺码和颜色 `8913#长裤` 的 3112 分价格。
- **未验证部分**:未覆盖其他手机型号和其他 PDD 版本;颜色 `9031#长裤` 未取得稳定价格,属于相邻的价格采集问题,不在 #59 范围。
## 遗留问题
- 任务 #42 缺失 `price:9031#长裤`。现有证据只能确认颜色点击未报错,但不能区分选中状态未暴露还是价格文本未稳定解析;需另建价格采集缺陷处理。
## 相关提交
- `2977edc` `fix(#59): avoid PDD review page spec misclick`
- `37db89c` `docs(#59): record Samsung device verification`
@@ -1,54 +0,0 @@
# 60 修复可选颜色切换后价格缺失
- 类型:缺陷
- 所属 Epic:采集 MVP #1
- 所属 MVP / 版本:PDD 商品详情采集最小闭环
- 状态:待验收
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/60
- Wiki 页面:Task-60-修复可选颜色切换后价格缺失
- Wiki revision:在线页面 `last_commit.sha`
## 背景与目标
商品 `172119352488` 的两个颜色均可人工选择,但三星任务 #42 只保存 `8913#长裤` 的 3112 分价格,`9031#长裤` 记录为 `price:` 缺失。旧实现只返回价格或空值,没有逐颜色选中与价格采样证据,无法区分选择未确认、价格未出现和价格不稳定。目标是在不猜测价格的前提下容忍 PDD 异步刷新,并提供可定位的结构化诊断。
## 最终方案
- 精确点击颜色前保存最小规格面板基线;点击后只接受目标选中状态、已选摘要或规格面板选中/价格变化作为动作生效证据。
- 其他颜色残留的 `selected/checked` 不再阻塞当前颜色;相同价格合法,但界面完全无变化时不能沿用旧价格。
- 动作生效后等待 300ms 刷新保护,再按规则要求连续读取相同价格。
- 结构化轨迹记录短颜色标签、点击结果、证据类型、候选价格和稳定次数;失败区分 `selection_not_confirmed`、`price_not_found`、`price_not_stable`,不保存控件树或截图。
- 缺失时仍按既有业务规则提交 `completed_partial`,不猜测或补造价格。
## 修改文件
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddProductDetailCollector.kt`:增加动作生效证据、刷新保护、稳定价格和分级诊断。
- `android/app/src/test/java/cn/ilapage/goauto/agent/PddProductDetailCollectorTest.kt`:覆盖残留选中、延迟价格、同价无效点击和诊断原因。
- `android/README.md`:记录逐颜色价格边界。
- `docs/03-business-rules-and-glossary.md`、`docs/08-agent-api-contract.md`、`docs/10-real-device-acceptance.md`:由线上 Wiki 同步业务、契约和真机证据。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 其他颜色残留选中不阻塞当前价格 | 通过,单测覆盖 |
| 动作生效且价格稳定后才保存 | 通过 |
| 延迟更新和同价边界 | 通过,单测覆盖 |
| 失败原因可区分 | 通过,结构化日志与单测覆盖 |
| 不保存控件树/截图,不触发订单/支付 | 通过 |
| Android 测试、构建和组件验证 | 通过 |
| 三星任务 #42 两个颜色价格 | 通过,两个颜色均为 3112 分,任务 `completed`、缺失为空 |
## 测试
- `android/gradlew.bat test assembleDebug`:通过。
- `scripts/verify.ps1 -Component android`:通过。
- 采集器聚焦测试:通过;新增 4 个颜色价格边界测试。
- 三星 SM-G9700、Android 12、Agent 0.2.0、PDD 7.95.0:覆盖安装 APK 后经正式接口重置任务 #42,采到 2 个颜色价格、8 个尺码,缺失清单为空。
- `python dev_scripts/harness.py sync --check`、`python dev_scripts/harness.py check --strict`、`git diff --check`:通过。
- **未验证部分**:未覆盖其他手机型号与其他 PDD 版本;本次真机结果能证明修复后的稳定读取有效,但历史失败缺少旧版逐次采样日志,无法再还原当时每一次无障碍树刷新状态。
## 相关提交
- `36b11ec` `fix(#60): stabilize per-color price capture`
@@ -1,63 +0,0 @@
# 61 修复采购规格探测未识别安全入口
- 类型:缺陷修复
- 所属 Epic:无
- 所属 MVP / 版本:PDD 自动采购 MVP
- 状态:待用户验收
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/61
- Wiki 页面:Task-61-修复采购规格探测未识别安全入口
## 背景与目标
采购任务 CG-3、CG-4 在三星商品 `172119352488` 的第一趟规格探测中失败,错误为“没有找到商品规格入口”。旧默认规则把 `选择规格` 作为硬编码文字别名;Agent 一旦收到别名会改用页面原始文本扫描,绕过 PDD 语义解析。实际页面提供的是底部“快要抢光 免拼购买”安全入口,因此任务没有进入规格面板。
目标是在不扩大点击范围的前提下,使 Agent 能使用已确认的规格/购买语义入口开始规格探测;不得修改地址、创建订单或支付。
## 最终方案
1. 默认正式采购规则移除 `openSpecPanel.textAliases`,由 Agent 使用 PDD 页面解析器给出的语义安全入口。
2. 即使某个历史规则提供文字别名,Agent 也只会在已确认的规格入口和底部购买入口中按别名过滤;别名不能再把任意页面同名文字(例如商品评价)变成可点击入口。
3. 新增单元测试覆盖底部“免拼购买”入口,以及“商品评价”别名不能绕过安全候选。
与建单方案一致;没有引入 OCR/VLM、原始控件树或截图持久化。
## 修改文件
- `server/app/goauto/purchasecontract/default.go`:默认采购规则取消硬编码规格文字。
- `server/app/goauto/purchasecontract/default_test.go`:锁定默认规则必须使用语义安全入口。
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseRehearsalExecutor.kt`:别名改为安全候选的交集过滤。
- `android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseRehearsalExecutorTest.kt`:覆盖安全底部购买入口与评价页防误点。
- Wiki `Business-Rules-and-Glossary`、`Android-Agent-API-Contract`、`OnePlus-Real-Device-Acceptance`:记录行为边界与真机证据。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 默认规则不再把任意“选择规格”文字当作入口 | 通过(自动化测试) |
| 别名不能触发商品评价等非规格控件 | 通过(自动化测试) |
| 新规则可在三星进入规格面板并提交第一趟探测 | 通过:采购任务 5(CG-5)attempt 1 返回 `spec_probe_completed` |
| 不修改地址、不创建订单、不支付 | 通过:CG-5 没有订单号、下单时间或不可逆标记 |
| 用户最终验收 | 待确认 |
CG-5 回传了 2 个颜色、8 个尺码以及两个颜色的 3112 分价格,随后按既有两趟流程进入 `spec_probe_pending`。其后因 #46 尚未实施,服务端不会自动处理繁简/单位差异的规格匹配;这不是本缺陷的规格入口失败。
## 测试
- 执行命令:`go test ./app/goauto/purchasecontract/... ./app/goauto/purchase/...`
- 结果:通过。
- 执行命令:`.\\gradlew.bat testDebugUnitTest --tests cn.ilapage.goauto.agent.PurchaseRehearsalExecutorTest`
- 结果:通过。
- 执行命令:`.\\gradlew.bat test assembleDebug`
- 结果:通过。
- 真机:三星 SM-G9700 覆盖安装 Debug APK,启用无障碍服务后以新规则重试;第一趟规格探测成功。
- **未验证部分**:未修改真实地址、未创建真实待付款订单、未支付;这些不属于本工单且需要独立人工授权。#46 的自动规格匹配尚未实施。
## 遗留问题
- #46 的服务端“确定性规格标准化 → AI 兜底”尚未实施。当前 CG-5 因目标规格繁简/重量单位与 PDD 实际文字不同,保持在 `spec_probe_pending`,不会自动进入第二趟。
## 相关提交
- `aebe80f` `fix(#61): use safe purchase spec entry`
- `5a94f4f` `docs(#61): record safe purchase spec entry`
@@ -1,56 +0,0 @@
# 62 采购规格标准化匹配与 AI Provider 设置
- 类型:采购规格匹配能力与管理端设置
- 所属:[#46 采购规格 AI 匹配与实时规格回传](https://git.ilapage.cn/OPC/goauto/issues/46) 的独立实施单元
- 状态:用户已验收通过
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/62
- 设计证据:`prototypes/62/v2/index.html`、`prototypes/62/v3/index.html`(均已获用户确认)
## 背景与目标
采购任务需要把 SYB 目标颜色、尺码精确对应到 PDD 的实际可选规格。Android 不得猜测、转换或点击相近规格;当可用档案无法唯一确定时,服务端应先探测实时规格,再执行同一套决策。
本任务提供确定性标准化匹配和可选 AI Provider 兜底,并提供单一内部 Provider 的管理员设置页。用户确认内部部署例外:API Key 明文保存在专用表,管理员可以下次查看和替换;采购员与 Android 永远不接收该字段。
## 最终方案
- 服务端先做唯一确定性匹配:繁体转简体、空白/全半角/大小写统一及公斤/斤区间换算;候选不唯一时不自动选取。
- 仅在确定性匹配失败时调用已启用的 OpenAI-compatible Provider;Provider 只能在当前 PDD 可选原文候选中返回逐字相等的标签,否则视为无匹配。
- 创建采购任务和第一趟规格探测回传后均执行“确定性 → AI”。两者均无唯一结果时明确失败,不派发可下单的第二趟。
- Android 只收到服务端固化的精确 PDD 规格标签;Provider 地址、模型和 API Key 不进入任务快照、采购员接口或 Android 接口。
- Provider Base URL 支持公网或内网 HTTP/HTTPS。超时默认 15 秒,可设为 3~600 秒。管理员测试连接时,页面在操作栏直接显示测试中、成功或失败结果。
- 首版原型采用加密设想;用户在 v2 明确确认内部明文保存与管理员回显范围后,生产实现按 v2 调整。用户在 v3 确认后,将超时上限由 60 秒扩展至 600 秒并精简页面说明文字。
## 修改范围
- `server/app/goauto/aimatching/`:标准化器、候选约束、Provider 调用、管理员设置接口与权限测试。
- `server/app/goauto/purchase/`:创建任务和规格探测回传后的服务端匹配决策。
- `server/app/goauto/models/schema.go`、迁移目录:AI 单例设置表及从旧列 `api_key_encrypted` 到 `api_key` 的迁移。
- `web/src/views/goauto/ai-matching-settings/`:管理员设置页、600 秒表单边界与就地测试反馈。
- `docs/` 核心镜像:同步 Wiki 中的业务、架构、API 契约与本地验证说明。
## 验收结果
| 验收项 | 结果 |
|---|---|
| v2 内部明文 API Key 与权限边界原型 | 用户确认通过 |
| v3 超时 600 秒和测试结果位置原型 | 用户确认通过 |
| 600 秒保存、601 秒拒绝 | 自动化测试通过 |
| 确定性匹配、AI 候选精确约束、采购员隔离 | 自动化测试通过 |
| 本机 MySQL 迁移 | 用户确认后成功执行,新增 1 个迁移、跳过 16 个已应用迁移 |
## 测试
- `go test ./...`:通过。
- `pnpm lint`:通过;存在 30 条既有警告,无错误。
- `pnpm build:prod`:通过;存在既有 CSS 与包体积警告。
- `python dev_scripts/harness.py check --strict`、`sync --check`:通过。
- **未验证部分**:未配置真实 Provider,未发起外部 AI 连接;未执行 Android 动作、改地址、创建订单或支付。
## 相关提交
- `9adb29b` docs(#62): revise internal AI key policy prototype
- `6181d97` feat(#62): add AI specification matching settings
- `97a7131` docs(#62): add AI settings v3 review prototype
- `0e3be46` feat(#62): extend AI matching timeout
@@ -1,47 +0,0 @@
# 63 采购列表刷新不得触发 AI 规格匹配
- 类型:服务端缺陷修复
- 状态:用户已验收通过
- 日期:2026-08-21
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/63
- 设计证据:恢复既有后端行为,不改变 UI、接口字段、流程、权限或状态;复用 #57 已确认的采购管理批量重试交互,无需新增原型。
## 问题与原因
其他管理模块刷新正常,但采购管理刷新提示“服务器连接异常,请检查服务器!”。端口、数据库、CORS、前端 API 地址和旧进程均正常。
采购列表和详情为了展示失败任务是否允许重试,调用了完整的重试资格计算。该计算会读取当前 SYB、虾皮和 PDD 档案;规格未确认且 PDD 档案具有可用候选时,还会调用外部 AI Provider。测试库中的失败任务均处于规格未解决状态,而 Provider 超时长于 Web 请求超时,因此读取列表可能先被浏览器中止。
## 最终方案
- 把失败任务的基础安全资格与提交重试时的完整预检拆开。
- 采购列表和详情只检查任务状态、正式采购、不可逆边界、是否为同一 SYB 商品的最新任务,以及原设备在线、能力和忙碌状态。
- 列表和详情禁止调用 `previewOne`、确定性规格匹配或外部 AI Provider。
- 批量重试提交继续读取当前商品档案、规格映射、价格和设备状态;先执行确定性匹配,必要时才调用 AI,并逐项返回失败原因。
- 因此列表中的“可重试”表示基础安全条件允许;最终商品和规格条件仍以提交重试时的完整预检为准。
## 修改范围
- `server/app/goauto/purchase/admin_query.go`:列表和详情改用轻量重试资格。
- `server/app/goauto/purchase/retry.go`:拆分任务状态、设备和完整预检逻辑。
- `server/app/goauto/purchase/retry_test.go`:验证列表/详情不调用 Matcher,批量重试仍调用完整 Matcher。
## 验证结果
- 采购列表和详情使用计数 Matcher 时调用次数为 0。
- 同一失败任务提交批量重试时 Matcher 调用次数为 1,并按规格无匹配逐项失败。
- 既有“旧任务、设备离线、设备忙碌、不可逆任务不得重试”测试继续通过。
- `go test ./app/goauto/purchase -run 'TestAdmin|TestBatchRetry' -count=1`:通过。
- `go test ./...`:通过。
- `.\scripts\verify.ps1 -Component server`:测试和构建通过。
- `python dev_scripts/harness.py check --strict`:通过。
- `python dev_scripts/harness.py sync --check`:当前 Gitea Wiki API 返回 404,未能完成自动检查;核心 Wiki 镜像无改动,任务归档改用 Wiki Git 仓库写入和回读。
- 未调用真实 AI、未创建采购任务或订单、未执行付款、未修改数据库结构或测试数据。
## 相关提交
- `ec9f47f` fix(#63): keep AI matching out of purchase queries
## 用户验收
- 2026-08-21:用户重启服务端并刷新采购管理后,明确确认 #63 验收通过。
@@ -1,57 +0,0 @@
# 64 修复正式采购规格滚动定位与重复探测卡死
- 类型:缺陷
- 所属 Epic:采购 MVP
- 状态:待用户验收
- 日期:2026-08-22
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/64
- 设计证据:内部 Android 执行与服务端状态机缺陷,不改变页面或用户交互,无需 UI 原型。
## 背景与目标
正式任务 CG-7 的两次 `purchase` attempt 都能探测到目标颜色与尺码,但 Android 只检查规格面板当前可见节点,目标 `4XL 160-170斤` 位于下方时不会滚动查找,因此再次提交规格探测。服务端第一次已经固化规格决策,第二次探测清空映射后无法重复固化,任务遗留在 `spec_probe_pending`。
用户确认后已安全取消 CG-7。取消时没有不可逆时间、PDD 订单号或下单时间,未修改地址、未创建订单、未支付。
## 最终方案
- Android 在已经确认打开的规格面板内搜索服务端下发的精确原始标签:先做最多 3 次向下手势恢复顶部,再做最多 6 次向上手势查看下方内容;每次手势后重新读取控件并重新解析规格。
- 只接受同一规格角色、完整文字相等且可选的唯一节点;相近规格、重复节点、页面异常或面板关闭均不点击。
- 相邻两次规格证据不变时提前停止当前方向,所有手势都有固定上限。
- 服务端发现任务已经固化过一次规格决策后,若第二趟仍提交 `spec_probe_completed`,直接把当前 attempt 和任务标记为失败,错误码为 `PURCHASE_SPEC_NOT_MATCHED`。
- 重复探测失败时保留第一次映射和决策证据,释放任务租约及活动槽,不再进入 `spec_probe_pending`,也不再次派发。
- 首次探测、服务端确定性/AI 匹配、地址修改、创建订单和永不支付边界保持不变。
## 修改文件
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseRehearsalExecutor.kt`:增加规格面板内有限纵向精确搜索。
- `android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseRehearsalExecutorTest.kt`:覆盖下方尺码和搜索上限。
- `server/app/goauto/purchase/lifecycle.go`:重复规格探测安全失败并保留既有决策。
- `server/app/goauto/purchase/service_test.go`:覆盖首次探测后第二次探测的失败收敛。
- Wiki `Business-Rules-and-Glossary`、`Android-Agent-API-Contract`:补充长期业务规则和 Agent 契约。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 下方精确尺码可经有限纵向搜索定位 | 自动化测试通过 |
| 不点击相近规格,搜索有固定上限 | 自动化测试通过 |
| 第二次探测明确失败并释放活动槽 | 服务端测试通过 |
| 保留第一次映射和规格决策证据 | 服务端测试通过 |
| 首次规格探测链路保持可用 | 既有及新增测试通过 |
| 不修改地址、不创建订单、不支付 | 本工单实施过程满足 |
## 测试
- `go test ./...`:通过。
- `android/gradlew.bat test assembleDebug`:通过,Debug APK 已生成。
- `scripts/verify.ps1 -Component server`:通过。
- `scripts/verify.ps1 -Component android`:通过。
- `python dev_scripts/harness.py check --strict`:通过。
- `python dev_scripts/harness.py sync --verify`:Gitea Wiki API 返回 404,自动同步不可用;改用 Wiki Git 仓库推送和回读核心页面,revision `e4ecce2abf9a5ac210522747ccadee110aec0aa1`,本地镜像正文已逐字比对一致。
- `git diff --check`:通过。
- **未验证部分**:尚未把新 APK 安装到三星,也未重新执行正式采购、修改真实地址或创建订单;这些高风险真机动作需要用户再次确认。
## 相关提交
- `22a024be641239d4aea8dceb42e077a5f36c82b7` fix(#64): bound purchase spec selection retries
-71
@@ -1,71 +0,0 @@
# 65 修复规格面板地址入口恢复与地址页稳定识别
- 类型:缺陷
- 所属 Epic:采购 MVP
- 状态:待用户验收
- 日期:2026-08-22
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/65
- 设计证据:恢复既有 Android 行为,不改变 Admin 页面或用户交互,无需 UI 原型。
## 背景与目标
正式任务 CG-8 已完成精确规格、数量、价格和摘要校验,但选好规格后收货地址位于规格/下单面板顶部的裁切区域。原实现按页面中面积最大的可滚动节点执行通用滑动,可能选错页面或评论滚动容器,最终以 `PURCHASE_ADDRESS_UPDATE_FAILED` 页面切换超时安全失败。
参考 `D:\chengma\cmautobuy\client` 的成熟做法,确认地址修改应在当前规格/下单面板内先恢复地址入口;不存在额外的“进入确认页”按钮。
## 最终方案
- 地址入口不可见时,只接受唯一、可见、启用、占据至少 60% 屏幕宽度和 20% 屏幕高度的纵向滚动面板。
- Android 驱动使用面板节点的类名和位置重新定位唯一真实无障碍节点,在该节点内有限向下滑动;不再退化为页面最大滚动区域。
- 每次滑动后重新读取页面,并用不含控件文字的结构签名检测无进展;达到次数上限或连续无变化后明确失败。
- 脱敏手机号入口的近乎完全重叠无障碍重复节点合并为一个目标;多个独立入口或多个候选面板均失败关闭。
- 地址面板、编辑页、输入回读和保存返回分别使用明确错误码;原有地址后缀、保存回读、唯一创建订单按钮和禁止支付边界保持不变。
- CG-9 真机验证确认面板定向下拉成功,但三星当前 PDD 页面中,脱敏手机号文字的最近可点击祖先覆盖几乎整个下单面板,导致祖先中心点击没有打开地址页。后续修复改为重新定位唯一脱敏手机号文字节点并在其中心执行一次精确手势点击,不再沿用宽泛可点击祖先。
- CG-10 真机验证已成功打开收货地址页,但“修改”文字节点不可点击、同尺寸父节点可点击。原帮助函数用无文字父节点替换文字锚点,导致新鲜定位失败。后续统一保留原始文字节点,父节点只用于证明可点击路径,执行时从重新定位的文字节点向上点击最近父节点;同一机制覆盖“修改”“保存”和“提交订单”。
- CG-11 真机验证已成功进入地址编辑页。页面同时存在收货人、手机号和详细地址 3 个输入框,原实现错误地要求页面只有一个非空输入框而安全失败。后续以“详细地址”标签为结构锚点,只选择与标签纵向相交且位于右侧的唯一输入框。
- CG-12 真机验证已完成地址修改、保存回读和最终商品/规格/数量/价格复核,并在 10:03:43 进入 `order_submit_started`。创建订单按钮只点击一次,随后设备停留在 Android 系统 `ChooserActivity`,Agent 未取得唯一 PDD 订单号和下单时间,任务进入 `order_result_unknown`。该系统选择器核单问题超出本地址缺陷范围,必须独立建单;在人工核对前禁止重试或再次点击创建订单。
- 不保存原始控件树、截图、完整地址或手机号。
## 修改文件
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/GoAutoAccessibilityService.kt`
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt`
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseRehearsalExecutor.kt`
- `android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt`
- `android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseRehearsalExecutorTest.kt`
- Wiki `Business-Rules-and-Glossary`、`Android-Agent-API-Contract`
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 地址被裁切时只在唯一规格/下单面板内下拉 | 自动化测试通过 |
| 多个候选面板时不滑动、不点击并明确失败 | 自动化测试通过 |
| 通用页面滚动不再用于地址入口恢复 | 自动化测试通过 |
| 地址后缀、保存回读、创建订单一次性和禁止支付保持有效 | 全量 Android 单元测试通过 |
| 最新 Debug APK 安装至三星 `SM_G9700` | `adb install -r` 返回 `Success` |
| CG-9 未进入地址页时安全失败 | `PURCHASE_ADDRESS_PANEL_TIMEOUT`;无订单号、无不可逆时间、未支付 |
| 不可点击手机号文字使用自身中心精确手势 | 自动化测试通过,最新 APK 已重新安装 |
| CG-10 进入地址页后“修改”安全失败 | `RULE_ACTION_FAILED`;无订单号、无不可逆时间、未支付 |
| 不可点击文字保留原始锚点并沿父链执行 | 自动化测试通过,最新 APK 已重新安装 |
| CG-11 进入编辑页后因多个输入框安全失败 | `PURCHASE_ADDRESS_UPDATE_FAILED`;无订单号、无不可逆时间、未支付 |
| 三输入框页面精确定位详细地址 | 自动化测试通过,最新 APK 已重新安装 |
| CG-12 完成地址修改并进入订单提交边界 | 通过;`irreversible_at=2026-08-22 10:03:43.027` |
| 创建订单后的唯一订单号与下单时间 | 未取得;任务为 `order_result_unknown`,无自动重试、无支付 |
## 测试
- `android/gradlew.bat testDebugUnitTest --tests cn.ilapage.goauto.agent.PurchaseLiveAutomationTest --tests cn.ilapage.goauto.agent.PurchaseRehearsalExecutorTest`:通过。
- `android/gradlew.bat testDebugUnitTest assembleDebug`:通过。
- `scripts/verify.ps1 -Component android`:通过。
- `python dev_scripts/harness.py check --strict`:通过。
- `python dev_scripts/harness.py sync --verify`:Gitea Wiki API 返回 404,自动同步不可用;改用 Wiki Git 仓库先推送长期页面,再按 revision `78583c8528b4157a90b1fe221fa39288480cb90a` 更新本地只读镜像。
- `git diff --check`:通过。
- **未验证部分**:地址恢复与修改链路已由 CG-12 真机通过。创建订单后停留在 Android 系统选择器,尚未人工确认 PDD 是否已生成待付款订单,也未取得订单号和下单时间;任务必须保持 `order_result_unknown`,禁止自动重试。系统选择器的安全返回与只读核单由独立缺陷处理。全程未支付。
## 相关提交
- `f5ac7f6` fix(#65): restore address entry within purchase panel
- `464a3b8` fix(#65): tap exact purchase address entry
- `3405439` fix(#65): preserve text anchors for purchase clicks
- `553dc46` fix(#65): locate detailed address editor by label
-64
@@ -1,64 +0,0 @@
# 66 修复创建订单后多微信选择器与只读核单恢复
- 类型:缺陷
- 所属 Epic:采购 MVP
- 状态:待用户验收
- 日期:2026-08-22
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/66
- 设计证据:恢复 Android 内部只读核单行为,无 Admin UI 变化,不需要原型。
## 背景与目标
CG-12 已完成地址修改和最终复核,并在 2026-08-22 10:03:43.027 进入 `order_submit_started`。创建订单按钮只点击一次后,三星进入 Android 多微信应用选择器,旧 Agent 没有识别系统选择器,无法回到 PDD 获取订单号和下单时间,任务进入 `order_result_unknown`。全程未支付。
用户确认正确流程是从多微信选择器返回 PDD,再离开 PDD 支付页,继续只读订单详情。参考 `D:\chengma\cmautobuy\client\src\pdd_u2_purchase_reconcile_adapter.py` 的白名单核单实现。
## 最终方案
- 只有前台包为 `android` / `com.android.intentresolver`、Activity 为已知 `ChooserActivity` / `ResolverActivity`、页面出现已知选择器标题且至少一个候选以“微信”开头时,才把页面认定为多微信选择器。
- 已知选择器最多按一次返回,不点击任何微信候选;一次返回后仍出现选择器则返回未知结果。
- 回到 PDD `com.xunmeng.pinduoduo.app_pay.core.PayActivity`,或当前页面出现明确支付动作文字时,最多再按一次返回;不点击支付控件。
- 返回 PDD 后继续累计只读页面证据,只有唯一订单号和合法下单时间同时成立才完成核单。
- 非白名单系统页面、返回失败、重复选择器/支付页、结果歧义或超时均返回未知结果;不重新创建任务、不再次点击创建订单、不取消订单、不支付。
- CG-12 保持 `order_result_unknown`,本工单实施期间未重试或自动解除。
- 用户于 2026-08-22 人工确认 CG-12 对应商品已出现在 PDD“待付款订单”,证明创建订单成功且不得再次下单;由于尚未取得订单号和下单时间,服务端仍保持 `order_result_unknown`。
## 修改文件
- `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PurchaseLiveAutomation.kt`
- `android/app/src/test/java/cn/ilapage/goauto/agent/PurchaseLiveAutomationTest.kt`
- Wiki `Business-Rules-and-Glossary`、`Android-Agent-API-Contract`
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 已知多微信选择器最多返回一次且不点击微信 | 自动化测试通过 |
| PDD 支付页最多返回一次且不点击支付 | 自动化测试通过 |
| 返回订单详情后读取唯一订单号和下单时间 | 自动化测试通过 |
| 非白名单选择器不自动返回 | 自动化测试通过 |
| 既有地址、创建订单一次性和禁止支付边界 | Android 全量测试通过 |
| 最新 APK 安装三星 | `adb install -r` 返回 `Success` |
| CG-12 是否实际创建待付款订单 | 用户人工确认存在;未支付 |
## 测试
- `android/gradlew.bat testDebugUnitTest --tests cn.ilapage.goauto.agent.PurchaseLiveAutomationTest`:通过。
- `android/gradlew.bat testDebugUnitTest assembleDebug`:通过。
- `scripts/verify.ps1 -Component android`:通过。
- `python dev_scripts/harness.py check --strict`:通过。
- `git diff --check`:通过。
- **未验证部分**:最新 APK 尚未通过新的正式采购任务验证自动选择器返回与订单号读取。CG-12 已人工确认存在待付款订单,但尚缺订单号和下单时间,不能解除 `order_result_unknown`;再次创建订单或继续不可逆核单必须获得用户明确授权。全程未支付。
## 相关提交
- `0ceb248` fix(#66): recover from Android payment chooser
## 验收结论
- 状态:已验收
- 验收日期:2026-08-22
- 用户确认:#66 验收通过
- 真机证据:CG-12 已出现在 PDD「待付款订单」中
- 实现提交:`0ceb2482338f8bce84bb7bf4d0d9a8cfa74b0c42`
- 安全边界:仅恢复多微信选择器与执行只读核单,未执行支付
@@ -1,51 +0,0 @@
# 67 SYB 与采购任务蝦皮订单号
- 类型:需求 / 缺陷 / 重构
- 所属 Epic:#
- 所属 MVP / 版本:#
- 状态:待验收 / 已完成
- 日期:2026-08-22
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/67
- Wiki 页面:Task-67-SYB-与采购任务蝦皮订单号
- Wiki revision:见本地镜像头
## 背景与目标
<!-- 原来有什么问题,这次达到什么结果。 -->
## 最终方案
<!-- 说明实际实现。与建单方案不同之处必须写清原因。 -->
## 修改文件
- `<文件>`:<改动说明>
## 验收结果
| 验收标准 | 结果 |
|---|---|
| | 通过 / 未通过 |
## 测试
- 执行命令:`<命令>`
- 结果:
- **未验证部分**:<!-- 必填;没有就写“无”。 -->
## 遗留问题
<!-- 没有就删除本节。 -->
## 相关提交
- `<提交哈希>` <提交说明>
## 验收结论
- 状态:已验收
- 验收日期:2026-08-22
- 用户确认:#67 验收通过
- 实现提交:`9490bddf8f3bf1884d45c2c58d6f45cd14735ab4`
- 数据迁移:版本 `1786701500000` 已应用,12 条关联历史采购任务全部回填,空值 0、错配 0
- 验证:服务端测试、Web lint/构建、Wiki 核心镜像检查通过;Android 无改动
@@ -1,42 +0,0 @@
# 68 调整 SYB 商品页顶部工具栏与采购入口
- 类型:需求 / 缺陷 / 重构
- 所属 Epic:#
- 所属 MVP / 版本:#
- 状态:待验收 / 已完成
- 日期:2026-08-22
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/68
- Wiki 页面:Task-68-调整-SYB-商品页顶部工具栏与采购入口
- Wiki revision:见本地镜像头
## 背景与目标
<!-- 原来有什么问题,这次达到什么结果。 -->
## 最终方案
<!-- 说明实际实现。与建单方案不同之处必须写清原因。 -->
## 修改文件
- `<文件>`:<改动说明>
## 验收结果
| 验收标准 | 结果 |
|---|---|
| | 通过 / 未通过 |
## 测试
- 执行命令:`<命令>`
- 结果:
- **未验证部分**:<!-- 必填;没有就写“无”。 -->
## 遗留问题
<!-- 没有就删除本节。 -->
## 相关提交
- `<提交哈希>` <提交说明>
@@ -1,61 +0,0 @@
# 69 蝦皮与 PDD 规格匹配
- 类型:需求
- 所属 Epic:采购闭环
- 所属 MVP / 版本:采购 MVP
- 状态:待验收
- 日期:2026-08-23
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/69
- Wiki 页面:Task-69-蝦皮与-PDD-规格匹配
- Wiki revision:以线上页面 revision 为准
- 设计证据:`prototypes/69/v2/index.html`,用户于 2026-08-23 确认
## 背景与目标
蝦皮与 PDD 的颜色名称可能不同,需要采购人员可视化建立明确对应关系;尺码应优先批量完成唯一确定性匹配,剩余项目再人工选择。映射操作不得修改两端原始规格,Android Agent 只接收服务端最终确定的 PDD 原始标签。
## 最终方案
- 继续使用 `shopee_product.specs_json` 保存每个蝦皮商品独立的规格映射,不新增数据库表或迁移。
- 颜色使用关联 PDD 商品当前可选颜色的可搜索下拉框;未使用颜色优先,已被其他蝦皮颜色使用的选项仍可选择并显示占用者,支持多对一。
- 新增只读计算接口批量预览尺码:只接受繁简、空白、大小写、全半角等统一后的唯一确定结果;没有唯一结果则保留待人工选择。
- 单一“保存修改”提交全部变更;自动尺码建议经采购人员显式保存后确认生效,离开未保存页面会提示。
- PDD 目标规格消失后页面显示“已失效”;服务端拒绝保存不存在的目标,采购预检和任务创建也拒绝使用失效映射。
- SYB “去匹配”快捷入口携带目标颜色,蝦皮详情打开后突出对应行。
- Android Agent 无改动,不增加本地猜测能力。
## 修改文件
- `server/app/goauto/shopeeproduct/`:确定性尺码预览、映射目标校验、Admin 路由及测试。
- `server/app/goauto/purchase/`:采购预检和创建的失效映射门禁及测试。
- `web/src/views/goauto/shopee-products/index.vue`:按 v2 原型实现颜色/尺码映射交互。
- `web/src/views/goauto/syb-products/index.vue`:快捷入口传递当前目标颜色。
- `web/src/api/goauto/shopee-products.js`:尺码预览接口封装。
- 核心 Wiki 与 `docs/` 镜像:架构、业务规则、共享 API 和工单索引。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 两端原始规格保持不变,映射独立保存与覆盖 | 通过(自动化与代码检查) |
| 尺码批量匹配只接受唯一确定结果 | 通过(Go 单元测试) |
| PDD 规格变化能识别并拦截失效项 | 通过(服务端单元测试与 Web 状态逻辑) |
| 采购优先使用已确认映射,Agent 不猜测规格 | 通过(采购服务测试;Agent 未修改) |
| Web 颜色分组、多对一和单一保存入口 | 待用户页面验收 |
## 测试
- `.\scripts\verify.ps1 -Component all`:Go 全量测试与构建、Web lint/生产构建、Android 单元测试和 debug APK 构建均通过;Web 仅有仓库既有 warning。
- `python dev_scripts/harness.py check --strict`:通过。
- `python dev_scripts/harness.py sync --verify`:在本次命令中临时使用当前 Gitea URL 后通过,四个核心镜像已更新。
- `git diff --check`:通过。
- **未验证部分**:当前浏览器运行时没有可用浏览器会话,未执行登录态页面点击走查;未进行真机采购或创建订单,本工单也不包含该高风险动作。
## 遗留问题
- 等待用户在本地管理端验收规格匹配页面。
- 本机全局 Gitea 环境变量仍指向旧入口,直接运行 harness 会失败;本次使用命令级当前地址覆盖完成同步。未在 #69 中修改本机凭据或环境配置。
## 相关提交
- `67d8f32` feat(#69): add Shopee PDD specification mapping
@@ -1,84 +0,0 @@
# 70 从 cmautobuy 线上库导入 PDD 与蝦皮商品
- 类型:需求
- 所属 Epic:独立商品数据导入
- 所属 MVP / 版本:采购数据准备
- 状态:已验收
- 日期:2026-08-23
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/70
- Wiki 页面:Task-70-从-cmautobuy-线上库导入-PDD-与蝦皮商品
- Wiki revision:以本页在线 revision 为准
## 背景与目标
把 cmautobuy 线上 MySQL 的有效 PDD 与蝦皮商品单向导入 GoAuto 本地 MySQL 8。目标同 goods_id 或存活 shopee_item_id 时按用户确认覆盖本机商品档案;导入不得带入任务、订单、SYB、设备、用户、规则或旧组合规格映射。
## 最终方案
新增独立命令 `server/cmd/import-cmautobuy-products` 和转换包 `server/app/goauto/cmautobuyimport`。命令默认 dry-run;来源在 MySQL 只读一致性事务中读取,显式 `--apply` 才在目标单事务写入。
PDD 组合 SKU 聚合为 GoAuto 通用维度。只有同颜色唯一有效价格才保存价格,多价不猜测并保持 pending。蝦皮 SKU 只聚合已解析颜色/尺码;来源 PDD goods_id 换算为目标数字外键。目标人工 disabled 保留,来源缺少的销量、评价和蝦皮售价按覆盖语义清空。旧组合规格映射不迁移。
用户于 2026-08-24 明确确认正式导入;执行前已备份本地两张目标商品表,随后在单事务中完成 apply。
## 修改文件
- `server/app/goauto/cmautobuyimport/`:配置、来源快照、规格转换、dry-run 计划、目标事务和测试。
- `server/cmd/import-cmautobuy-products/main.go`:离线命令入口与脱敏错误。
- `docs/02-architecture-and-code-map.md`、`docs/03-business-rules-and-glossary.md`、`docs/04-local-development-and-verification.md`、`docs/09-delivery-issues.md`:Wiki-first 镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 默认运行不写数据库 | 通过:真实线上/本地连接 dry-run 完成 |
| 同键覆盖与关联换算 | 通过:MySQL 8.4 隔离 _test 集成测试 |
| 同颜色多价格不猜测 | 通过:单元测试 |
| 蝦皮规格只聚合已解析字段 | 通过:单元测试 |
| 不导入范围外数据和旧规格映射 | 通过:命令仅查询三张来源表、写两张目标表 |
| 正式 apply | 通过:用户确认后完成备份、事务导入和导入后核验 |
## 真实 dry-run
- 来源 PDD:8459;预计新增 8454、覆盖 3、冲突 2。
- 来源蝦皮:25151;预计新增 24221、覆盖 925、冲突 5。
- 可建立蝦皮→PDD 关联:20796。
- 同颜色多价格:0;未解析蝦皮 SKU:0。
- 冲突:2 条 PDD URL/goods_id 无效或不一致;5 条蝦皮关联的来源 PDD 不存在、已删除或无法转换。
- 本次 dry-run 没有写入来源或目标数据库。
## 测试
- `go test ./app/goauto/cmautobuyimport ./cmd/import-cmautobuy-products`
- `go test ./app/goauto/...`
- `.\scripts\verify.ps1 -Component server`
- `python dev_scripts/harness.py check --strict`
- `python dev_scripts/harness.py sync --check`
- MySQL 8.4 隔离 `goauto_cmautobuy_import_test`:dry-run、覆盖、关联和提交前校验通过;测试库已删除。
- 正式表核验:PDD 8457 条、有效蝦皮商品 25208 条、有效蝦皮→PDD 关联 20796 条;重复业务键、非法规格 JSON、悬空关联均为 0。
- 导入后再次 dry-run:PDD 新增 0、覆盖 8457、冲突 2;蝦皮新增 0、覆盖 25146、冲突 5,确认来源有效键已完整落地且命令可幂等识别。
## 遗留问题
- 本机 `gitea.env` Token 已失效,`harness.py archive` 返回 401;本页按项目 MCP 优先规则通过 Gitea MCP 创建并回读。未修改或输出 Token。
- 2 条来源 PDD 与 5 条蝦皮关联冲突已在 apply 时跳过,未自动猜测或修复。
## 相关提交
- `166adc6` feat(#70): import cmautobuy product catalog
## 正式导入记录(2026-08-24)
- 用户明确确认后执行 `--apply`;来源库仍使用只读一致性事务,目标在单事务提交。
- 导入前目标:PDD 3 条、有效蝦皮商品 987 条。
- 本次写入:PDD 新增 8454、覆盖 3;蝦皮新增 24221、覆盖 925;建立关联 20796。
- 跳过 7 条已报告冲突:2 条 PDD URL/goods_id 无效或不一致,5 条蝦皮来源关联无法转换。
- 可恢复备份:`D:\\OPC\\goauto-backups\\cmautobuy-import-20260824-000239\\goauto-products-before.sql`;SHA-256 `C30C6C52FF2094032BE7F1E03F7B4673C4F1A9AB01DD189958C42156E1BF41D9`。
- 未导入或改动任务、订单、SYB、设备、用户、规则和旧规格映射。
## 用户验收
- 2026-08-24:用户明确确认 #70 通过验收。
- 正式导入结果、备份、完整性核验和幂等复跑均纳入本次验收范围。
-49
@@ -1,49 +0,0 @@
# 71 使用 Supervisor 托管 GoAuto Admin API 与 Admin UI
- 类型:本机开发环境
- 状态:待验收
- 日期:2026-08-24
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/71
- 实现提交:`ca0c81b`
## 需求与范围
在已安装的 `D:\supervisor` 中新增两个 GoAuto 实例:`goauto-admin-api` 和 `goauto-admin-ui`。不修改数据库、端口、业务接口、Android 服务地址或现有 Chorus 实例,不在 Supervisor 配置中保存密码。
## 实现
- 新增版本化配置 `scripts/supervisor/goauto.conf`,部署副本为 `D:\supervisor\programs\goauto.conf`。
- 两个实例启用自动启动、异常重启、启动重试、进程组停止和独立滚动日志。
- API 复用 `scripts/start-server.ps1`,从根目录 `config.yaml` 读取 MySQL 和 8010 端口。
- UI 复用 `scripts/start-web.ps1`,从同一配置读取 9527 和 API 地址。
- 由于 Supervisor Windows 子进程不能可靠地让 npm 的 `pnpm.cmd` / Vite 包装器解析 Node PATH,启动脚本会定位 `node.exe`,用它直接执行 pnpm CLI(仅依赖安装时)和项目 Vite CLI。
- 长期启动、控制和日志说明已写入 Local Development and Verification Wiki;Delivery Issues 已加入 T58。
## 运行结果
- `goauto-admin-api`:RUNNING。
- `goauto-admin-ui`:RUNNING,执行一次 Supervisor restart 后仍恢复 RUNNING。
- `http://127.0.0.1:8010/api/v1/health`:HTTP 200。
- `http://127.0.0.1:9527/`:HTTP 200。
- 三个既有 Chorus 实例保持 RUNNING。
- 仓库配置与部署配置 SHA-256 一致。
- 两个 GoAuto 日志未检出本机数据库密码。
## 验证
- PowerShell Parser:`scripts/start-web.ps1` 无语法错误。
- `scripts/start-web.ps1 -ValidateConfigOnly`:通过。
- `.\scripts\verify.ps1 -Component web`:通过;lint 0 error、30 个既有 warning,生产构建成功。
- `python dev_scripts/harness.py check --strict`:通过。
- `python dev_scripts/harness.py sync --check`:通过。
- Supervisor ctl status、端口监听、API/UI HTTP 和日志检查:通过。
## 文档与参考
- Local Development and Verification Wiki revision:`fbd334cc639f9053388af09319f34faf1dc4a5ab`。
- Delivery Issues Wiki revision:`0eed28dd5dc629ce66071833d5fefbdf24d567d3`。
- Go Supervisor 官方说明:https://github.com/ochinchina/supervisord
## 遗留
- 等待用户验收;用户明确验收前不关闭 #71。
-60
@@ -1,60 +0,0 @@
# #72 修复 Supervisor 重启后 SYB 页面短暂报错
- 类型:缺陷
- 所属 Epic:本地开发与运行稳定性
- 所属 MVP / 版本:T59
- 状态:待验收
- 日期:2026-08-24
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/72
- Wiki 页面:Task-72-Fix-Supervisor-SYB-startup-race
- Wiki revision:见页面 revision
## 背景与目标
Supervisor 同时重启 Admin API 和 Admin UI 时,Vite 比 API 的迁移、编译和 HTTP 监听更早就绪。SYB 商品页随即请求列表和采购准备预检,短暂连接失败被统一显示为“服务器连接异常”。本任务使 UI 等待 API 真正就绪,并让 SYB 页面对一次短暂网络断开做有界恢复。
## 最终方案
- `start-web.ps1` 在启动 Vite 前轮询 `config.yaml` 的 `ports.server` 对应 `/api/v1/health`。
- API 返回 HTTP 200 后才启动 UI;60 秒仍未就绪则明确失败,由 Supervisor 按既有策略重启。
- SYB 商品列表和采购准备预检第一次遇到 Axios `Network Error` 时,静默等待 1 秒并只重试一次。
- 第二次网络失败显示原有提示;认证、权限、业务错误及有响应的服务端错误不重试。
- 与建单方案一致,无数据库、接口、认证、Android 或业务规则变化。
## 修改文件
- `scripts/start-web.ps1`:增加 API HTTP 就绪轮询。
- `web/src/utils/request.js`:支持首次网络失败的静默处理。
- `web/src/api/goauto/syb-products.js`:允许传入请求控制选项。
- `web/src/api/goauto/purchase-tasks.js`:允许采购准备预检传入请求控制选项。
- `web/src/views/goauto/syb-products/index.vue`:增加一次性网络重试。
- `web/tests/e2e/syb-network-retry.spec.ts`:覆盖恢复和不重试边界。
- `docs/04-local-development-and-verification.md`:同步长期 Wiki 镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| UI 等待 API HTTP 就绪后再启动 | 通过 |
| 首次短暂网络失败自动恢复且不显示最终错误 | 通过 |
| 业务错误不重试 | 通过 |
| API/UI HTTP 200,GoAuto 与 Chorus 实例 RUNNING | 通过 |
| 用户页面验收 | 待验收 |
## 测试
- 执行命令:PowerShell Parser、`start-web.ps1 -ValidateConfigOnly`
- 结果:通过。
- 执行命令:`pnpm exec playwright test tests/e2e/syb-network-retry.spec.ts --workers=1`
- 结果:2 项通过。
- 执行命令:`.\scripts\verify.ps1 -Component web`
- 结果:通过;lint 0 error、30 个既有 warning,生产构建成功。
- 执行命令:Supervisor 同时重启两个 GoAuto 实例并检查日志、HTTP 和全部实例状态。
- 结果:UI 等待 API ready 后启动;8010/9527 返回 200;GoAuto 和 Chorus 均 RUNNING。
- 执行命令:`python dev_scripts/harness.py sync --check`、`python dev_scripts/harness.py check --strict`、`git diff --check`
- 结果:通过。
- **未验证部分**:用户登录后的 SYB 页面人工验收待用户完成。
## 相关提交
- `476ca64` fix(#72): wait for API and retry SYB load
@@ -1,65 +0,0 @@
# 73 优化 SYB 商品列表加载与采购准备预检
- 类型:缺陷 / 性能优化
- 所属 Epic:未指定
- 所属 MVP / 版本:采购 MVP
- 状态:待验收
- 日期:2026-08-24
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/73
- Wiki 页面:Task-73-优化-SYB-商品列表加载与采购准备预检
- Wiki revision:见页面修订记录
## 背景与目标
SYB 商品页原来必须同时等待商品列表与采购准备预检完成。预检又逐条查询关联数据,并可能在被动浏览时调用外部 AI Provider,因此页面明显慢于其他列表模块。
目标是让商品列表先显示、采购准备独立异步加载,把只读预检与创建任务时的完整规格决策分开,并消除只读批量预检的 N+1 查询。
## 最终方案
- Web 在 SYB 列表接口返回后立即关闭表格 loading,采购准备列继续显示既有“正在检查”状态。
- 采购准备请求失败时保留已经显示的商品,只在该列显示“采购准备检查失败,请点击刷新重试”,并抑制重复的全局错误提示。
- 使用加载序号隔离快速查询、翻页和刷新产生的并发响应,旧列表或旧预检不能覆盖新页面。
- Server 的 `batch-preview` 一次批量读取 SYB、蝦皮、PDD 和每条 SYB 最新采购任务,查询数不随当前页商品数量线性增长。
- 只读预检只运行已确认映射和本地确定性匹配,不调用 AI Matcher。
- `batch-create` 继续使用完整创建预检并按最新数据复核;无法确定性匹配时仍可按既有规则调用 AI,创建权限和安全边界未放宽。
- 无数据库迁移,不修改 Android Agent,不创建订单、不支付。
## 修改文件
- `server/app/goauto/purchase/batch.go`:拆分快速/完整预检,批量预加载关联数据。
- `server/app/goauto/purchase/batch_test.go`:固定四次批量查询和只读预检不调用 AI。
- `web/src/views/goauto/syb-products/index.vue`:列表先展示、预检独立加载、失败降级和并发响应隔离。
- `web/src/utils/request.js`:增加请求级可选错误提示抑制,仅供页面内可恢复状态使用。
- `web/tests/e2e/syb-network-retry.spec.ts`:覆盖延迟预检、失败保留列表及旧响应隔离。
- `docs/02-architecture-and-code-map.md`、`docs/03-business-rules-and-glossary.md`:线上 Wiki-first 更新后的本地镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 列表返回后立即显示,不等待预检 | 自动化通过,待人工验收 |
| 预检失败不清空列表 | 自动化通过,待人工验收 |
| 只读预检不调用 AI | 单元测试通过 |
| 批量预检使用固定批量查询 | 两条商品仍为四次查询,单元测试通过 |
| 创建阶段保留完整 AI 决策 | 现有创建路径与全套服务端回归通过 |
| 旧响应不覆盖新查询 | Playwright 通过 |
| 无数据库、Android、下单和支付影响 | 已核对 |
## 测试
- `go test ./app/goauto/...`:通过。
- `pnpm exec eslint src/views/goauto/syb-products/index.vue src/utils/request.js`:通过。
- `pnpm build:prod`:通过;只有项目既有 CSS/chunk 警告。
- `pnpm exec playwright test tests/e2e/syb-network-retry.spec.ts --reporter=line`:5 项通过。
- `python dev_scripts/harness.py sync --check`:通过。
- `python dev_scripts/harness.py check --strict`:通过。
- **未验证部分**:尚未由用户在本机真实 SYB 数据页面进行人工加载速度验收。
## 遗留问题
- 本机 `gitea.env` 的旧地址/Token 无法用于归档;核心 Wiki 同步通过临时覆盖当前地址完成,任务归档按 MCP 优先规则由当前 Gitea MCP 创建。未修改或输出本机 Token。
## 相关提交
- `634bd46` fix(#73): speed up SYB purchase preview
@@ -1,66 +0,0 @@
# #74 优化 SYB 商品筛选与列表列宽
- 状态:待用户验收
- 实施日期:2026-08-24
- 工单:https://git.ilapage.cn/OPC/goauto/issues/74
- 代码提交:`0fccbc9d`、`c706f1e`、`3a8ec20`
## 需求与范围
优化 SYB 商品页面的筛选区和列表布局:
- 在订单号左侧增加店铺搜索,按店铺名包含匹配。
- 订单号输入支持多行,每行一个订单号;清理空行、去重后最多 100 个,每个最长 64 字符。
- 多个订单号按精确匹配查询,不扩展为商品关键词搜索。
- 店铺、订单号和解析状态之间使用 AND 组合。
- 支持在订单号输入框按 `Ctrl+Enter` 查询。
- 删除独立的“蝦皮关联”列;未关联状态并入“蝦皮商品”列。
- 将释放出的宽度分配给“采购准备”和“操作”列。
不涉及数据库迁移、Android Agent 或采购流程变更。
## 实现结果
- Admin API 增加 `shopName` 与 `orderCodes` 查询参数。
- 服务端完成参数清理、数量与长度校验、店铺包含匹配、订单号精确集合匹配。
- Admin UI 增加店铺输入框和多行订单号输入框,完成筛选组合、重置和快捷查询。
- 列表移除独立关联列,并调整“采购准备”和“操作”列宽。
- 根据验收反馈删除列表底部范围提示,将“蝦皮商品”缩小为固定 260px;长标题单行省略,并支持悬停查看完整标题。
- 店铺筛选改为可输入的联想下拉框,异步加载现有 SYB 店铺并标识已停用店铺;部分名称和历史店铺名仍可直接输入。
- 店铺选项加载失败时给出简单提示,但不阻断商品列表和手动店铺查询。
- 补充服务层测试和 Playwright 页面/网络回归测试。
## 验证
- `go test ./app/goauto/...`:通过。
- `pnpm exec eslint src/views/goauto/syb-products/index.vue`:通过。
- 首轮 `pnpm exec playwright test tests/e2e/syb-product-layout.spec.ts tests/e2e/syb-network-retry.spec.ts --reporter=line`:6 项通过。
- 验收调整后 `pnpm exec playwright test tests/e2e/syb-product-layout.spec.ts --reporter=line`:1 项通过。
- 店铺联想调整后 `pnpm exec playwright test tests/e2e/syb-product-layout.spec.ts --reporter=line`:2 项通过。
- `pnpm build:prod`:通过;仅有既有 CSS 选择器和包体积警告。
- `python dev_scripts/harness.py check --strict`:通过。
- `python dev_scripts/harness.py sync --check`:通过。
- `git diff --check`:通过。
## 长期文档
Wiki-first 更新并回读:
- Architecture-and-Code-Map revision:`5f88de723d9ca9124383cd4abcfa5c27ddcd6d48`
- Business-Rules-and-Glossary revision:`5adb7b0f05c4562e23fe92fe7ff89625d697c2d4`
对应核心镜像已同步到 `docs/02-architecture-and-code-map.md` 和 `docs/03-business-rules-and-glossary.md`。
## 待验收
用户在真实 SYB 商品页面确认:
1. 店铺输入位于订单号左侧。
2. 多行订单号只按订单号精确查询。
3. 店铺、订单号和解析状态组合正确。
4. “蝦皮关联”列已移除,未关联提示清楚。
5. 底部范围提示已删除。
6. “蝦皮商品”列为 260px,长标题省略且悬停可查看完整内容。
7. 店铺筛选可联想选择现有及已停用店铺,也可输入部分名称或历史店铺名。
8. 店铺选项加载失败时仍可手动查询。
9. “采购准备”和“操作”列宽及筛选区布局符合使用习惯。
-56
@@ -1,56 +0,0 @@
# #75 SYB 每小时自动同步并精简商品页入口
## 状态
待用户验收。
## 目标与范围
- 服务端每小时自动同步一次 SYB,默认在每小时第 5 分钟触发。
- 按 Asia/Shanghai 计算日期范围,默认同步当天和前一天。
- 保留手动导入 API 和同步记录页面,但移除 SYB 商品页的“导入”“同步记录”按钮及导入弹窗。
- 商品页自动展示最近一次同步状态;同步运行中轮询,完成后刷新商品列表。
- 不修改 Android Agent,不涉及 PDD 下单或支付。
## 实现
- 抽取手动接口与定时任务共用的 `StartImport`,复用店铺准入、凭据检查、单任务互斥、同步记录和现有导入执行链路。
- 新增 go-admin DB-aware ExecJob 调用能力,注册 `GoAutoSYBHourlySync`。
- 新增幂等迁移 `1786701600000_syb_hourly_sync_job.go`,首次写入启用的 `sys_job`;重复迁移及管理员修改后的 Cron/状态不会被覆盖。
- 上一次同步仍在运行时,本次定时触发跳过;失败不立即重试,等待下一小时。
- 调度失败不再输出误导性的成功日志。
## 数据库迁移
用户已明确确认执行本机数据库迁移。迁移首次执行新增 1 个版本,第二次执行新增 0 个、跳过 19 个,确认幂等。验证期间没有启动 Admin API,因此没有触发真实 SYB 同步。
## 验证
- `go test ./...`:通过。
- `go build ./...`:通过。
- `pnpm run lint`:0 errors;仅上游既有 30 个 warnings。
- `pnpm run build:prod`:通过;仅既有 CSS/包体积提示。
- 相关 Playwright:8 passed。
- `python dev_scripts/harness.py sync --verify`:通过。
- `python dev_scripts/harness.py check --strict`:通过。
- `git diff --check`:通过。
## 长期文档
Wiki-first 已更新并回读:
- Architecture-and-Code-Map:`ce37cbe86bad24c38c801955e301ff8e90ec86b5`
- Business-Rules-and-Glossary:`bb1dea3110c827ee6516f74eb440417a0551f9b8`
- Local-Development-and-Verification:`465390216d69d28a642854fd946d2d818a834f9c`
对应核心镜像已同步。
## 提交
- `a2cc9a1 feat(#75): 增加 SYB 每小时自动同步`
## 待验收
- 重启 Admin API 后确认“定时任务”里存在且启用“SYB 每小时自动同步”。
- SYB 商品页确认顶部入口已精简、同步状态可显示。
- 到下一次每小时第 5 分钟后确认产生“系统定时同步”的同步记录并刷新商品。
@@ -1,48 +0,0 @@
# 78 在 SYB 同步记录页增加管理员立即同步
- 类型:需求
- 状态:待验收
- 日期:2026-08-24
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/78
- Wiki revision:以本页线上 revision 为准
## 背景与目标
定时同步之外,管理员需要在不返回 SYB 商品页的情况下立即发起同步。入口与执行结果统一放在“SYB 同步记录”页,采购员保持只读。
## 最终方案
- 复用既有管理员手动导入 API,不改服务端和数据库。
- 同步记录页仅管理员显示“立即同步”,固定同步浏览器本地日期的昨天至今天。
- 请求提交期间及当前列表存在运行中记录时禁用按钮;服务端互斥继续阻止与定时任务并发。
- 受理后清空筛选、刷新列表并沿用已有轮询;失败原因仍只在同步记录页展示。
- SYB 商品页不恢复入口或同步结果提示。
## 修改文件
- `web/src/views/goauto/syb-sync-runs/index.vue`:增加管理员手动同步入口及交互状态。
- `web/tests/e2e/syb-sync-run.spec.ts`:覆盖管理员、采购员、日期范围和运行中禁用。
- `docs/03-business-rules-and-glossary.md`:同步 Wiki-first 业务规则镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 管理员可触发昨天至今天的同步 | 自动化验证通过,待人工验收 |
| 提交及运行中不能重复点击 | 自动化验证通过 |
| 采购员无执行入口 | 自动化验证通过 |
| 商品页不恢复同步入口 | 保持既有实现 |
| 失败在同步记录中查看 | 保持既有实现 |
## 测试
- `pnpm exec eslint src/views/goauto/syb-sync-runs/index.vue`:通过。
- `pnpm exec playwright test tests/e2e/syb-sync-run.spec.ts`:3 项通过。
- `pnpm run build:prod`:通过;仅有既有 CSS 与大 chunk 警告。
- `python dev_scripts/harness.py sync --check`:通过。
- `python dev_scripts/harness.py check --strict`:通过。
- 未验证部分:未实际请求线上 SYB,同步数据结果需管理员人工触发验收。
## 相关提交
- `5c9b75fc53f454ff9eac55487a3a627f771ca63d` feat(#78): add manual SYB sync action
@@ -1,51 +0,0 @@
# 79 SYB 只读分页请求超时后有限重试
- 类型:缺陷
- 状态:待验收
- 日期:2026-08-24
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/79
- Wiki revision:以本页线上 revision 为准
## 背景与目标
SYB 同步在前 22 页成功后,第 23 页等待响应头超过 30 秒,导致 440/442 张时整次失败。目标是在不削弱完整性校验、不误判登录失效、也不重试写操作的前提下恢复瞬时只读请求故障。
## 最终方案
- 单次 SYB HTTP 总超时由 30 秒调整为 60 秒。
- 仅货运单总数、列表和明细三个语义只读接口最多执行 3 次。
- 可重试错误限定为网络/超时、响应读取失败、HTTP 5xx 和响应信封格式异常;分别等待 1 秒、2 秒。
- 401/403、明确会话失效、业务失败、接口数据完整性错误和本地校验失败不重试。
- 登录、验证码、单件码写入和回填请求不使用重试。
- 等待响应 context 取消;重试耗尽后保留外层日期、页码和已获取数量错误。
- 分页大小和既有完整性校验保持不变。
## 修改文件
- `server/app/goauto/sybclient/client.go`:增加显式只读重试边界和 60 秒请求超时。
- `server/app/goauto/sybclient/client_test.go`:覆盖 5xx/响应头超时恢复、上限、非重试错误和 context 取消。
- `docs/03-business-rules-and-glossary.md`:同步业务规则 Wiki 镜像。
- `docs/12-syb-erp-interface.md`:同步 SYB 接口契约 Wiki 镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 总数、列表、明细瞬时故障后恢复 | 单元测试通过 |
| 连续三次失败返回最后错误 | 单元测试通过 |
| 会话和业务失败不重试 | 单元测试通过 |
| context 取消立即停止 | 单元测试通过 |
| 写请求只发送一次 | 既有回归测试通过 |
| 真实 SYB 第 23 页恢复 | 待人工同步验收 |
## 测试
- `go test ./app/goauto/sybclient ./app/goauto/sybimport`:通过。
- `go test ./app/goauto/...`:通过。
- `go test ./...`:通过。
- `python dev_scripts/harness.py sync --verify`:通过。
- 未验证部分:没有访问真实 SYB;需用户重启服务后人工同步验证。
## 相关提交
- `af3dd55` fix(#79): retry transient SYB read failures
@@ -1,51 +0,0 @@
# 81 修复 SYB“查看 PDD 商品”跳转
- 类型:缺陷
- 状态:待验收
- 日期:2026-08-25
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/81
- Wiki revision:以本页线上 revision 为准
## 背景与目标
#80 验收时发现:SYB 商品的处理阶段显示“查看 PDD 商品”,点击后却进入蝦皮商品页面。目标是让该入口打开当前行关联的指定 PDD 商品详情。
## 最终方案
- SYB 页的 `open_pdd` 动作改为使用预检结果中的 `pddProductId`。
- 跳转到 `/pdd-products/index?productId=<PDD商品ID>`,不再调用蝦皮商品详情动作。
- PDD 商品页读取路由中的有效 `productId`,自动打开既有详情抽屉。
- PDD ID 缺失时显示“该明细尚未关联 PDD 商品”,不执行错误跳转。
- 服务端处理阶段、接口、数据库、Android 和业务规则均未改变。
## 修改文件
- `web/src/views/goauto/syb-products/index.vue`:增加 PDD 详情跳转并修正 `open_pdd` 动作。
- `web/src/views/goauto/pdd-products/index.vue`:支持通过路由 `productId` 自动打开详情。
- `web/tests/e2e/syb-product-layout.spec.ts`:覆盖 SYB 下一步跳转到正确 PDD ID。
- `web/tests/e2e/pdd-product-module.spec.ts`:覆盖带 `productId` 路由自动打开详情。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| “查看 PDD 商品”不再进入蝦皮商品模块 | 自动化测试通过 |
| 打开当前行对应的 PDD 商品 ID | 自动化测试通过 |
| 带 productId 的 PDD 路由自动打开详情 | 自动化测试通过 |
| Web lint 和生产构建 | 通过 |
| 用户页面人工验收 | 待验收 |
## 测试
- `pnpm exec playwright test tests/e2e/syb-product-layout.spec.ts tests/e2e/pdd-product-module.spec.ts --grep "PDD 待采集的下一步|带 productId" --reporter=line`:2/2 通过。
- `.\scripts\verify.ps1 -Component web`:通过;lint 0 error、30 条存量 warning,生产构建成功。
- `python dev_scripts/harness.py check --strict`:通过。
- 未验证部分:未在用户当前实际数据上进行页面人工点击验收;未执行真机采集、采购、创建订单或支付。
## 遗留问题
- 两个既有 PDD E2E 用例仍使用页面改版前的“PDD 商品”页标题和“批量创建采集任务”按钮文案,整文件运行会失败;与 #81 行为无关,本工单未扩大范围修复。
## 相关提交
- `66ea01c` fix(#81): open PDD detail from SYB stage
@@ -1,52 +0,0 @@
# 82 区分“去关联 PDD”与“去匹配”动作
- 类型:缺陷
- 状态:待验收
- 日期:2026-08-25
- Gitea 工单:https://git.ilapage.cn/OPC/goauto/issues/82
- Wiki revision:以本页线上 revision 为准
## 背景与目标
SYB 商品处于“未关联 PDD”时,页面提示先关联商品,但按钮错误显示“去匹配”。原因是未关联商品与颜色匹配失效共用了 `open_mapping`。本任务将商品关联和规格匹配拆成两个普通用户可以理解的下一步。
## 最终方案
- 新增管理端下一步动作 `open_pdd_link`。
- 未关联 PDD 或原关联商品不存在时返回 `open_pdd_link`,页面显示“去关联”。
- `open_mapping` 只用于已有关联但颜色匹配缺失或失效的场景,继续显示“去匹配”。
- 点击“去关联”打开当前蝦皮商品详情,并用 `action=link_pdd` 直接打开既有 PDD 商品选择器。
- 相关提示统一使用“PDD 商品”;数据模型、关联规则、采购状态和 Android 均未改变。
## 修改文件
- `server/app/goauto/purchase/process_stage.go`:拆分处理阶段的关联动作。
- `server/app/goauto/purchase/batch.go`:拆分采购预检的关联动作与提示。
- `server/app/goauto/purchase/process_stage_test.go`:覆盖全部阶段动作及旧关联目标不存在。
- `server/app/goauto/purchase/batch_test.go`:覆盖未关联 PDD 的预检动作。
- `web/src/views/goauto/syb-products/index.vue`:显示“去关联”并携带关联定位参数。
- `web/src/views/goauto/shopee-products/index.vue`:按路由动作自动打开 PDD 商品选择器。
- `web/tests/e2e/syb-product-layout.spec.ts`:覆盖“去关联”“去匹配”的区分及选择器打开。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 未关联 PDD 显示“去关联” | 自动化测试通过 |
| 去关联直接打开 PDD 商品选择器 | 自动化测试通过 |
| 颜色待匹配仍显示“去匹配” | 自动化测试通过 |
| PDD 不存在时要求重新关联 | 单元测试通过 |
| 用户页面人工验收 | 待验收 |
## 测试
- `go test ./app/goauto/purchase`:通过。
- `pnpm exec playwright test tests/e2e/syb-product-layout.spec.ts --grep "未关联 PDD 显示去关联" --reporter=line`:1/1 通过。
- `.\scripts\verify.ps1 -Component server`:通过。
- `.\scripts\verify.ps1 -Component web`:通过;lint 0 error、30 条存量 warning,生产构建成功。
- `python dev_scripts/harness.py check --strict`:通过。
- 未验证部分:未在用户当前实际数据上人工点击;未执行真机采集、采购、创建订单或支付。
## 相关提交
- `7b488e2` fix(#82): separate PDD linking from mapping
@@ -1,38 +0,0 @@
# Task #83:PDD 商品页增加单商品定位模式
- 工单:https://git.ilapage.cn/OPC/goauto/issues/83
- 状态:待用户验收
- 实施提交:`0991fd2`
- 交付单元:Web Admin
- 长期文档影响:无。仅调整既有页面从携带 `productId` 的链接进入时的展示与退出交互,不改变 API、数据库、业务状态、权限或部署方式。
## 需求与确认方案
从 SYB 商品的“查看 PDD 商品”进入 PDD 商品页时,页面进入单商品定位模式:
- 只显示 URL 中 `productId` 对应的 PDD 商品,并自动打开既有详情抽屉;
- 页面提示当前定位的内部商品编号和 `goods_id`;
- 不新增“查看全部 PDD 商品”按钮;
- 关闭详情抽屉仍保留单商品列表;
- 点击“查询”时清除 URL 的 `productId`,按当前筛选条件加载正常列表;
- 点击“重置”时清除 `productId` 和全部筛选条件,加载全部列表;
- 编号无效或商品不存在时显示明确空状态,不回退为全部商品,以免误认。
## 实现结果
- 单商品定位只调用既有详情接口,不先加载完整列表;
- 定位模式下隐藏批量选择列,并明确标识“当前定位商品”;
- 查询和重置均通过替换当前路由退出定位模式;
- 补充端到端测试,覆盖定位、自动打开详情、查询退出、重置退出及无效编号边界。
## 验证
- 专项 Playwright:2/2 通过;
- `git diff --check`:通过;
- `python dev_scripts/harness.py check --strict`:通过;
- `.\scripts\verify.ps1 -Component web`:通过(lint 0 error,保留既有 warnings;Vite 构建成功);
- 未执行人工浏览器验收,等待用户审核。
## 风险边界
未修改服务端、数据库、Android Agent、权限、安全或创建订单行为。
@@ -1,36 +0,0 @@
# Task 85:修复采购员账号登录并补齐最小权限
## 来源与结论
- 工单:#85
- 用户于 2026-08-25 明确确认验收通过。
- 采购员角色标识固定为 `purchaser`,不使用管理员旁路。
- 自动支付边界未改变。
## 已实施
- 新增并启用“采购员”角色。
- 登记 GoAuto Admin API,并按已确认权限矩阵授予采购员日常商品、采集和采购能力。
- 系统管理、设备停用与 Token 吊销、SYB 店铺写操作、手动同步、AI 配置写入与测试、采集规则写操作继续仅管理员可用。
- 用户新增和修改要求选择有效角色,避免再次产生无效角色用户。
- 修正本机测试采购员账号的登录名、昵称和角色,保留原密码散列;归档不记录明文凭据。
## 数据与迁移
- 本机 MySQL 已执行迁移版本 `1786701700000`。
- `purchaser` 获得确认权限矩阵中的 48 条策略。
- 运行库权限表为 `casbin_rule`。
## 验证证据
- `go test ./...`:通过。
- `pnpm lint`:0 error;仅仓库既有 warning。
- `pnpm build:prod`:通过;仅仓库既有 CSS/大包 warning。
- `python dev_scripts/harness.py check --strict`:通过。
- 核心 Wiki 镜像检查:通过。
- 业务规则 Wiki revision:`6bad462cbf91d592ba95fbe2003402d20a589cc3`。
- 实现提交:`ae9a9fe`,已推送 `origin/main`。
## 验收
用户已确认采购员登录和权限范围通过验收,#85 可以关闭。
-44
@@ -1,44 +0,0 @@
> 本模板只用于用户明确要求的专项历史快照或读取既有归档,不属于标准任务闭环。单次任务的唯一事实来源是 Gitea 工单;不要为了完成普通任务创建本页面,也不要自动导出到 `docs/task/`。
# <工单号> <标题>
- 类型:需求 / 缺陷 / 重构
- 所属 Epic:#
- 所属 MVP / 版本:#
- 状态:待验收 / 已完成
- 日期:YYYY-MM-DD
- Gitea 工单:<链接>
- Wiki 页面:<页面名>
- Wiki revision:<线上页面 revision>
## 背景与目标
<!-- 原来有什么问题,这次达到什么结果。 -->
## 最终方案
<!-- 说明实际实现。与建单方案不同之处必须写清原因。 -->
## 修改文件
- `<文件>`:<改动说明>
## 验收结果
| 验收标准 | 结果 |
|---|---|
| | 通过 / 未通过 |
## 测试
- 执行命令:`<命令>`
- 结果:
- **未验证部分**:<!-- 必填;没有就写“无”。 -->
## 遗留问题
<!-- 没有就删除本节。 -->
## 相关提交
- `<提交哈希>` <提交说明>
-98
@@ -1,98 +0,0 @@
# 故障排查
## 排查顺序
1. 先确认现象可复现,并记录设备、App 版本、任务号和规则快照。
2. 从服务端结构化任务日志与错误码入手,不先猜测 Android 端实现。
3. 再看设备心跳、租约与任务状态是否自洽。
4. 最后才进入 Android 侧控件与页面证据排查。
5. 涉及创建订单、权限、凭据或数据删除时立即停止并等待人工确认,不通过重试获取反馈。
以下按现象归类,均已实测确认。
## 设备显示离线
1. 检查服务器 API 地址和 HTTPS 证书。
2. 检查 Device Token 是否属于该设备且未吊销。
3. 检查 Android 网络、电池限制、自启动和 Portal 进程。
4. 对比服务端 `last_seen_at` 与最近心跳失败原因。
## 一加通过 ADB 安装一直不返回
ColorOS 开启“安装增强防护”时,电脑端未知来源 APK 可能不会直接失败,而是让 `adb install` 一直等待系统确认:
1. 保持手机解锁,查看系统弹出的 Mobile Agent 安装详情页。
2. 点右上角“更多” → “开始深度扫描”。
3. 查看扫描结论后点“继续安装”。
4. 更新同一签名 APK 通常可直接执行;首次安装仍以手机实际提示为准。
不要为方便联调长期关闭系统安装防护,也不要用脚本修改系统安全策略。若只是局域网 Debug 联调,确认安装的是 Debug APK;Release 仍只连接 HTTPS 服务端。
## Debug 局域网地址提示 Cleartext HTTP 不允许
- 确认使用包含 `src/debug/AndroidManifest.xml` 的最新 Debug APK,而不是 Release APK。
- 重新运行 `assembleDebug` 并覆盖安装;应用诊断页会显示具体错误。
- 正式部署不要放开明文 HTTP,应为服务端配置可信 HTTPS 证书。
## 无法打开 PDD
1. 检查 Portal 无障碍是否 Enabled 且 Bound。
2. 一加检查后台运行、电池优化和浏览器跳转权限。
3. 检查浏览器“打开拼多多APP”和系统“打开”是否被最新控件树唯一识别。
4. 动作后验证包名和 Activity,不只检查 RPC success。
## 控件树数据不全
- 在系统设置页与 PDD 页分别对照。
- 一加/ColorOS 若只剩根节点,先重启设备验证 UiAutomation 状态。
- 不通过放宽业务选择器掩盖系统抓树故障。
## 任务一直运行
1. 检查设备租约是否过期。
2. 检查 Android 本地是否仍持有执行锁。
3. 检查最后心跳、步骤名、任务规则快照和错误码。
4. 网络断开应由超时器把任务置为失败,不能静默回到待领取。
## 采集结果不完整
保存已有结果并标记 `completed_partial`;展示缺失维度和值。不要伪造缺失 SKU,也不要自动重试。
## 规则疑似失效
复制现有规则创建或编辑可用规则,用测试任务验证。删除旧规则只阻止创建新任务,已经创建的任务仍按自身快照执行。
## Android 颜色采集诊断
Agent 0.9.13 起,颜色发现阶段在本地 `goauto_diagnostics.db` 的 `agent_diagnostic` 表保存一条脱敏 `COLOR_DISCOVERY` 记录。数据库最多保留最近 50 条、最长 7 天;不得将数据库文件上传工单。
记录只包含颜色行数、各行值数量、可点击颜色数、不可点击候选数、首次颜色点击前的颜色/尺码选中数、是否存在“已选”摘要、横向滑动次数、终止原因和耗时,不保存规格文案、价格、摘要原文、坐标、链接、goods_id、控件树、XML 或截图。
只对 Debug APK 使用以下读取流程:
```powershell
adb shell run-as cn.ilapage.goauto.agent ls databases
adb exec-out run-as cn.ilapage.goauto.agent cat databases/goauto_diagnostics.db > agent-diagnostics.db
sqlite3 -readonly agent-diagnostics.db "SELECT task_id,reason,color_row_count,color_row_value_counts,clickable_color_count,non_clickable_color_candidate_count,initial_selected_color_count,initial_selected_size_count,selected_summary_present,horizontal_swipe_count,elapsed_ms,agent_version,created_at FROM agent_diagnostic WHERE stage='COLOR_DISCOVERY' ORDER BY id DESC LIMIT 1;"
```
读取时记录设备、Agent 版本、任务号和规则快照;工单只回写查询得到的脱敏聚合数值。读取完成后删除本地导出副本。正式 APK 若不允许 `run-as`,停止排查并确认安全的只读诊断出口,不通过放宽应用安全配置或上传完整数据库绕过。
## Android 采购规格入口本地诊断(#249)
实现绑定 `99faf5a`,Agent `0.9.67`(versionCode 80)。单元测试与 Debug 构建已通过,新增版本真机留存/读取尚待验证;本节不属于上文既有实测结论。
采购执行器启动后,在应用私有 `files/purchase_diagnostics/<taskId>_<attemptId>.jsonl` 保存白名单结构化入口诊断。元数据包含 taskId、attemptId、deviceId、agentVersion、phase、ruleHash(当前任务规则快照 SHA-256)、timestamp 和 elapsedMs。现有任务接口只有 attemptId,没有 attemptNumber;用 attemptId 对照服务端历史,不推算重试序号。
证据仅包含入口来源枚举、候选数、点击结果/原因枚举、等待轮数、面板结构布尔值/计数,以及受控恢复是否执行及固定拒绝原因。没有商品标题、规格原文、地址、手机号、Cookie、Token、链接、原始控件树或截图。entryWaitMillis 是该等待循环累计的计划等待量;elapsedMs 才是自本次采购执行器诊断起点计量的实际耗时,timestamp 是后台写入时间。
复用现有单线程诊断队列异步写入,写入/排队失败不改变采购结果;不新增点击、滑动、等待或采购门禁。最多保留最近 5 个 attempt 文件、每文件最后 128 条事件,超过 7 天的文件在服务启动或下次写入时清理。异常断电或存储故障仍可能丢失诊断,不能将日志缺失当成动作未执行;不上传 Admin,不增加服务端接口。
仅对允许 run-as 的 Debug APK,在设备已连接且选定准确序列号后只读提取:
```powershell
adb -s <device-serial> shell run-as cn.ilapage.goauto.agent ls files/purchase_diagnostics
adb -s <device-serial> exec-out run-as cn.ilapage.goauto.agent cat files/purchase_diagnostics/<taskId>_<attemptId>.jsonl
```
先列出文件,再读取本次任务/attempt 的准确文件名;不得扩大为导出所有应用数据或完整数据库。新版安装前的失败不会补生成日志;需要用户授权后重试才能产生新证据。Release 若禁止 run-as 则停止,不更改权限绕过;首版没有导出界面。该功能用于确定拒绝分支,并不代表已经修复对应采购故障。