# 报价提速与 API 稳定性 — 改动清单 > 版本:2026-06-30 > 目标:端到端 **15s 内 P50**(Direct 成功路径)、**并发不互相挡死**、**失败快速返回**(禁 Widget 回退)。 > 基准:当前 `probe:human-doc-system` 10/10 约 2.4min(~14s/单),候选 5~15s + 报价 5~20s + 队列/轮询开销。 --- ## 总览(按优先级) | 批次 | 项数 | 预期收益 | 风险 | 建议工期 | |------|------|----------|------|----------| | **P0 运维** | 5 | 稳定性 + 避免假慢 | 低 | 0.5 天 | | **P1 小改** | 4 | 候选 -3~8s,报价 -2~5s | 低 | 1~2 天 | | **P2 架构** | 2 | 整单 -3~8s(去队列+轮询) | 中 | 2~3 天 | | **P3 隔离** | 2 | 并发稳定性 | 中 | 1~2 天 | **推荐实施顺序:** P0 → P1-1 → P1-2 → P1-3 → P1-4 → P2-1 → P2-2 → P3(按需) --- ## P0 — 运维与配置(不改代码) | ID | 改动 | 操作 | 验收 | |----|------|------|------| | **P0-1** | 生产 Direct + 禁 Widget | `.env`:`RPA_QUOTE_MODE=direct`、`RPA_DISABLE_WIDGET_QUOTE_FALLBACK=true` | worker 日志出现 `node-direct 成功`,**无** `Widget fallback` | | **P0-2** | Nginx 超时对齐 | `proxy_read_timeout 420s`(方式 A submit) | submit 不在 60s 被 502,420s 内正常返回 | | **P0-3** | storageState 预热的 | worker 启动日志无 bootstrap 失败;`.rpa/mothership-storage.json` 存在且 `rpa_state` 卷挂载 | 冷启动首单不额外 +30~60s | | **P0-4** | orphan job 巡检 | 重启后看 `已回收 N 条 orphan`;Bull Board `/admin/queues` 无长期 active | 连续 2 单报价第二单不无故 +30s | | **P0-5** | 宿主传完整 placeId | 第二步候选对象含 `option_id`(ChIJ…) | worker 日志无重复 `searchLocation` | --- ## P1 — 小改快赢(低风险代码) ### P1-1 去掉报价重复 `fetchPlace` | 项 | 内容 | |----|------| | **根因** | `resolveSide` 已 `fetchPlace`,75~79 行又对同一 placeId 再 fetch 一次 | | **文件** | `lib/axel/quote-from-request.ts` | | **改法** | `resolveSide` 返回 `{ placeId, description, place?: AxelPlaceLocation }`;已知 placeId 路径复用 place 对象;search 路径只 fetch 一次 | | **并行** | `pickupSide` / `deliverySide` 改 `Promise.all` | | **测试** | 扩展 `__tests__/lib/axel/` 或 mock client,断言 `fetchPlace` 每侧最多 1 次 | | **验收** | 同址报价 worker 日志 place 请求减少;`probe:human-doc-system` 仍 10/10 | | **预期** | **-1~3s/单** | ### P1-2 候选 `verifyCandidates` 只校验 Top-K | 项 | 内容 | |----|------| | **根因** | 每条联想都 `fetchPlace`,N 条候选 = N 次 HTTP | | **文件** | `lib/axel/candidates.ts` | | **改法** | 新增 `AXEL_CANDIDATE_VERIFY_TOP_K`(默认 **3**);仅前 K 条 `verifyCandidate`,其余 `selectable: true`(或 `unknown` 由前端展示「未校验」— 推荐默认 true 保持 UX) | | **测试** | `__tests__/lib/axel/candidates.test.ts`:5 条候选 mock 断言 fetchPlace ≤ K | | **验收** | candidates API P50 下降;仍至少 1 条 `selectable:true` | | **预期** | **候选 -3~8s**(候选多时明显) | ### P1-3 宿主同步接口轮询间隔缩短 | 项 | 内容 | |----|------| | **根因** | `waitForQuoteDetail` 用 `POLL_INTERVAL_MS=2000`,完成后再等平均 +1s | | **文件** | `modules/host/wait-for-quote.ts`;可选 `lib/frontend/constants.ts` 拆 `HOST_POLL_INTERVAL_MS=500` | | **改法** | 仅 host submit 路径用 500ms;embed 前端可保持 2s 减 DB 压力 | | **测试** | `__tests__/modules/host/wait-for-quote.test.ts` mock 时间 | | **验收** | submit 在 worker 完成后 **<1s** 内返回 | | **预期** | **-0.5~1.5s/单**(方式 A) | ### P1-4 候选结果缓存(短 TTL) | 项 | 内容 | |----|------| | **根因** | 同街道重复联想仍打 MotherShip | | **文件** | `modules/address/mothership-candidates.ts` 或 `lib/axel/candidates.ts` + `modules/cache/redis-cache.ts` | | **改法** | key `addr-cand:{hash(pickup+delivery)}`,TTL **120s**,仅缓存成功结果 | | **测试** | 集成测试:第二次同址命中缓存 | | **验收** | 重复候选 <500ms;TTL 后失效 | | **预期** | **重复查询 -5~12s** | --- ## P2 — 架构提速(中风险,收益最大) ### P2-1 API 层 Direct 同步报价(跳过 BullMQ 快乐路径) | 项 | 内容 | |----|------| | **根因** | L2 未命中必 `enqueueForRpa`,node-direct 本可在 API 进程完成 | | **文件** | `modules/quote/orchestrator.ts`(主)、`workers/rpa/job-handler.ts`(复用 `completeQuoteSuccess` 逻辑抽共享函数)、`lib/rpa/env.ts`(`isInlineDirectQuoteEnabled()`) | | **改法** | 1) 新增 `tryInlineDirectQuote(cargo)`:`fetchAxelQuoteItems` → 写 L2 + `quote_record.done`;2) 成功则直接返回 `status:done`;3) 失败且 `RPA_DISABLE_WIDGET_QUOTE_FALLBACK=true` 则 `failed`;4) 失败且允许 fallback 则入队 Worker | | **开关** | `RPA_INLINE_DIRECT_QUOTE=true`(生产默认开,可关回滚) | | **约束** | 仍计 1 次 MotherShip 报价;须写 L2/L3、metrics、幂等一致 | | **测试** | 单测 mock axel;`probe:human-doc-system`;并发 2 单不串单 | | **验收** | 日志 `inline-direct 成功`;无 queue 等待;10/10 PASS | | **预期** | **整单 -3~8s**(去队列 + 部分轮询) | ### P2-2 方式 A submit 合并为「创建即等待」单事务感知 | 项 | 内容 | |----|------| | **根因** | P2-1 后 host submit 可 `submitQuote` 同步得到 `done`,无需 `waitForQuoteDetail` 长轮询 | | **文件** | `modules/host/public-orchestrator.ts` | | **改法** | `hostPublicSubmitAndWait`:若 `submitQuote` 返回 `done` 直接 `getQuoteDetail`;仅 `processing` 时轮询 | | **测试** | host submit 单测 | | **验收** | submit 在 inline 成功时 **0 次** DB 轮询 | | **预期** | 叠加 P2-1,方式 A 再 **-1~2s** | --- ## P3 — 并发与稳定性隔离 ### P3-1 Priority1 与 MotherShip 分队列 | 项 | 内容 | |----|------| | **根因** | `priority1-quote` 与 `quote-rpa` 同 Worker、`concurrency=1`,实验挡生产 | | **文件** | `lib/constants/rpa.ts`、`workers/rpa/index.ts`、`workers/rpa/queue.ts` | | **改法** | 新队列 `priority1-rpa` + 可选独立 worker 容器;或 Priority1 job 仅 dev 环境入队 | | **测试** | 入队 priority1 时 MotherShip quote 仍可并发消费(若 P3-2) | | **验收** | 生产 compose 仅 `rpa-worker` 消费 `quote-rpa` | | **预期** | **消除实验性挡队列** | ### P3-2 node-direct 专用 Worker 并发(可选) | 项 | 内容 | |----|------| | **根因** | `RPA_WORKER_CONCURRENCY=1` 为 Playwright 设计;HTTP-only 可并行 | | **文件** | `workers/rpa/job-handler.ts`、`workers/rpa/mothership.ts` | | **改法** | 仅当 job 走 `fetchAxelQuoteItems` 成功路径时无 session lock;浏览器 fallback 仍 concurrency=1;或拆 `rpa-http-worker` 进程 concurrency=3 | | **风险** | MotherShip 限流;storageState 并发读 | | **验收** | 3 并发 HTTP 报价无串扰;失败率不升 | | **预期** | **高峰 P95 -10~30s** | --- ## 观测与门禁(每批完成后必跑) ```bash # 功能回归 npm run test:unit npm run probe:human-doc-system # 耗时对比(改前改后各跑 1 次,记录总秒数) npm run probe:human-doc-system # 服务器 curl -w "candidates: %{time_total}s\n" -X POST .../api/host/quote/candidates ... # worker 日志关键字 docker logs 2>&1 | grep -E "node-direct|inline-direct|Widget fallback" ``` | 指标 | 改前参考 | P1 目标 | P2 目标 | |------|----------|---------|---------| | 候选 API P50 | 5~15s | 3~8s | 3~8s | | 报价(含队列)P50 | 10~25s | 8~18s | **5~12s** | | 方式 A submit P50 | 15~30s | 12~22s | **8~15s** | | `probe:human-doc-system` | 10/10 ~144s | 10/10 ≤120s | 10/10 ≤90s | --- ## 不建议做 | 项 | 原因 | |----|------| | 生产开启 Widget fallback | 6~12min/单,堵死 worker | | 仅增大超时 | 不解决排队与冗余 HTTP | | 盲目 `concurrency=4` 同进程 Playwright | session 争用、orphan job | | 跳过 placeId 校验 | 报价 400 / 地址不可选返工更慢 | --- ## implementation-plan 对应 Step | Step | 对应 ID | 状态 | |------|---------|------| | 12.1 | P1-1 去重 fetchPlace + 并行 resolve | 待执行 | | 12.2 | P1-2 候选 Top-K verify | 待执行 | | 12.3 | P1-3 host 轮询 500ms | 待执行 | | 12.4 | P1-4 候选 Redis 短缓存 | 待执行 | | 12.5 | P2-1 inline direct quote | 待执行 | | 12.6 | P2-2 host submit 免轮询 | 待执行 | | 12.7 | P3-1 Priority1 分队列 | 待执行 | | 12.8 | P3-2 HTTP worker 并发(可选) | 待执行 | 详细执行记录在 `cursor_project_rules/implementation-plan.mdc` Phase 12。