7.1 KiB
7.1 KiB
255 Admin:实现采购运行时真机规格规则与 AI 解析
- 类型:需求
- 父级大工单:#96
- 所属 MVP / 版本:#253 采购运行时 AI 规格纠偏闭环
- 状态:已完成,待用户验收
- 日期:2026-08-17
- Gitea 工单:#255
背景与目标
Client 在真实采购时能够读取 PDD App 当前颜色下的真实可购买尺码,但数据库里的规格可能
来自蝦皮回填,和真机文字不同。原有 MatchSybSpecWithAI 只读取数据库候选,不能保证
返回真机可点击的规格。
本任务实现 #254 已冻结的一次性规格解析命令:Admin 核对任务和真机候选快照,优先用 确定性规则寻找唯一等价候选,规则无法唯一判断时再调用受控 AI,并把第一次结论幂等、 可审计地返回 Client。解析不得修改任务、PDD 主数据或真实采购安全门禁。
最终方案
- 新增
POST /api/v1/client/tasks/{task_id}/spec-resolution。Handler 限制请求体为 64 KiB, 读取原始字节用于请求哈希,并把 Service 错误转换为 Client 契约定义的稳定错误码。 - Service 逐项校验 schema、字段长度、1~100 条连续候选、候选原文和
color/size,再按 Client 契约的 UTF-8 长度前缀算法重算候选快照哈希和确定性幂等键。 - 在写候选观察前核对任务存在、类型为采购、版本、PDD 商品、领取时的完整动态规格, 并确认当前 Client 曾领取该任务。任务已取消、重派或结束不作为拒绝条件。
- 规则忽略大小写、空白和安全的展示分隔符,处理常见繁简差异,支持
kg / 公斤 / 千克 / 斤单值和正向区间等价。只有一个等价候选时返回 matched;多个 等价或多个重量区间同时重叠时直接 uncertain,不调用模型。加减号和范围符号保留, 避免把M+ / M-或不同重量范围折叠成同一规格。 - 抽出 SYB 批量 AI 和运行时 AI 共用的候选白名单核心,统一执行运行快照、严格响应结构、 候选编号、冲突/缺失维度和置信度阈值门禁。运行时模型只得到任务目标和请求内短候选, 不能生成规格、真实 PDD 选项键或点击坐标。
- 使用“短事务写 pending 候选观察 → 事务外规则/AI → 短事务完成一次决策并保存幂等响应”。
同一进程内相同幂等身份串行;数据库唯一键和只允许
pending完成一次的条件更新负责 最终收敛。中断留下的 pending 由相同请求接管,不另建记录。 pdd_products.skus_json.spec_source=shopee_backfill只追加到非敏感决策原因作为诊断, 不主动触发 AI,也不阻断本来可以由规则唯一确定的候选。沿用 v26 表,不新增字段或迁移。- 最终决策只写
purchase_spec_resolutions和idempotency_keys,另刷新 Client 活动时间; 不更新任务状态、版本、payload、订单总价上限、结果数据或 PDDskus_json。 - 不记录完整模型请求/响应、API Key、原始控件树、截图、订单、收货信息、Cookie 或 Token。
模型错误中的当前密钥原文会在进入审计原因前替换为
[REDACTED]。
和建单方案相比,最终实现进一步收紧了两个边界:多个重量候选同时重叠时不交给 AI 猜;
+/-/范围 符号不作为普通展示符号删除。由于 #254 的 v26 审计模型没有独立诊断来源列,
shopee_backfill 使用决策原因留痕,没有为诊断信息增加数据库迁移。
改了哪些
admin/handler/api/client_api.go- 注册一次性规格解析路由,增加请求大小限制和稳定 HTTP 错误映射。
admin/handler/api/client_api_test.go- 验证超过 64 KiB 的请求在进入数据库前返回
INVALID_BODY。
- 验证超过 64 KiB 的请求在进入数据库前返回
admin/main.go- 把既有 AI 密钥存储和端点策略注入 Client API。
admin/repository/task.go- 任务身份查询补充版本和原始 PDD 动态规格,供运行时核对。
admin/service/ai_specmatch_client.go- 增加 SYB 与运行时共用的模型候选白名单、阈值和冲突门禁核心。
admin/service/ai_specmatch.go- 原 SYB AI 编排改用共用核心,保留原有人工映射优先和保存前复查流程。
admin/service/purchase_spec_resolution.go- 实现请求/哈希校验、任务授权、规则、AI、诊断、并发门禁、短事务和幂等响应。
admin/service/purchase_spec_resolution_test.go- 覆盖规则边界、任务越权、AI 门禁、密钥脱敏、并发重放、主数据不变和诊断来源。
docs/admin/01-requirements.md- 记录采购运行时真机规格解析的产品和安全边界。
docs/admin/02-architecture.md- 记录 Client API、共用 AI 核心和两阶段短事务编排。
验收结果
| 验收标准 | 结果 |
|---|---|
| 未领取、非采购、版本、商品或任务规格不一致时拒绝且不写解析记录 | 通过 |
| 完全等价、繁简等价和唯一公斤/斤单值及区间由规则匹配,不调用模型 | 通过 |
| 多个等价、多个区间重叠或模型报告信息缺失时不自动选择 | 通过 |
| AI 只能选择当前候选;伪造编号、低置信度、冲突和错误不能返回 matched | 通过 |
| 同一幂等请求重试和并发得到一致响应、一个最终记录和一次有效模型调用 | 通过(隔离 SQLite 与 race 测试) |
shopee_backfill 只记录诊断来源,不触发或拒绝解析 |
通过 |
| 不修改任务状态、payload、价格上限、结果数据和 PDD 主规格 | 通过 |
| 日志、数据库和响应不保存 API Key、原始 XML、订单或个人数据 | 通过 |
| 原 SYB AI 共用白名单核心后保持人工优先和原门禁 | 通过(全量 Go 回归;MySQL 场景见未验证项) |
| Go 1.23.0 格式、build、test 和 vet | 通过 |
MySQL 8.4 _test 服务编排集成测试 |
未验证 |
测试
- 执行的命令:
$env:GOTOOLCHAIN='go1.23.0'; gofmt -l .$env:GOTOOLCHAIN='go1.23.0'; go test ./... -count=1$env:GOTOOLCHAIN='go1.23.0'; go vet ./...$env:GOTOOLCHAIN='go1.23.0'; go build ./...$env:GOTOOLCHAIN='go1.23.0'; go test -race ./service -run 'Test(MatchPurchaseSpec|ParsePurchaseSpec|ResolvePurchaseSpec)' -count=1
- 结果:Go 1.23.0 全量测试、vet、构建和本任务 race 测试通过。运行时服务编排测试默认在
临时隔离 SQLite 数据库执行;设置
CMAUTOBUY_MYSQL_TEST=1时会自动改用项目的隔离 MySQL 8_test数据库。 - 没验证到的部分:本机 MySQL 8.4 服务没有当前可用的授权测试账号,因此未执行
CMAUTOBUY_MYSQL_TEST=1的 #255 服务集成用例;没有用生产库替代测试库。依赖工单 #254 已验证 v26 MySQL 8.4 表、约束、唯一键和 Repository。本工单也未做 Client 真机联调, Client 的采集和续跑属于后续 #256~#258。
遗留问题
- 配置可创建临时数据库的本地 MySQL 8
_test账号后,应补跑:$env:CMAUTOBUY_MYSQL_TEST='1'; go test ./service -run 'TestResolvePurchaseSpec' -count=1 -v。
相关提交
de36cfd实现采购运行时规格规则与 AI 解析