# 查价项目 > **新人入口文档**:5 分钟了解项目、跑起来、别踩红线。 > 权威细节以 PRD / 技术设计 / 工程任务拆解为准;本文是索引与速查。 | 项 | 值 | |----|-----| | 文档版本 | v1.2 | | 更新日期 | 2026-06-26 | | 产品名 | 美美与共报价中台(查价系统) | | 代码仓库 | `chajia` | --- ## AI 项目交接文档 > 供下一 AI 会话快速接手。执行日志:`cursor_project_rules/implementation-plan.mdc`(Step 10.x~11.x)。 ### 1. 项目核心身份 (Project Identity) **项目名称与一句话定义** **美美与共报价中台(chajia)** — 通过 Playwright RPA 抓取 MotherShip 公开报价页,一次询价返回多档 LTL 报价,经加价引擎输出给宿主系统内嵌查价组件;中台自身提供 admin 管理端(预警、RPA 运维、队列监控)。 **技术栈清单**(来源:`package.json` / `docker-compose.yml`) | 类别 | 技术 | 版本 | |------|------|------| | 语言 | TypeScript | ^5.9.2 | | 运行时 | Node.js | [需人工补充: 最低 Node 版本] | | Web 框架 | Next.js (App Router) | ^15.5.6 | | UI | React | ^19.1.1 | | 样式 | Tailwind CSS | ^3.4.19 | | 组件 | shadcn/ui 风格 + Geist 字体 + @phosphor-icons/react ^2.1.10 | — | | 表单 | react-hook-form ^7.79.0 + zod ^3.25.76 | — | | API | Next.js Route Handlers | — | | ORM | Prisma | ^6.19.0 | | 数据库 | MySQL | 8.0(docker image `mysql:8.0`) | | 缓存/队列 | Redis 7-alpine + ioredis ^5.8.1 + BullMQ ^5.78.1 | 本机 WSL 实测 6.0.16 有警告 | | RPA | Playwright ^1.61.0;可选 playwright-extra + stealth | — | | 队列 UI | Bull Board (@bull-board/* ^8.0.0) + Hono ^4.12.25 | — | | 鉴权 | jose ^5.10.0 + bcryptjs ^3.0.2 | — | | 测试 | Vitest ^3.2.4 + @playwright/test ^1.61.0 | — | | 构建/脚本 | tsx ^4.20.5 | — | | 部署 | Docker Compose(mysql/redis/next-app/rpa-worker) | — | **目录结构语义化说明** | 路径 | 职责 | 数据流向 | |------|------|----------| | `app/` | Next.js 页面与 API Routes(薄路由层) | 宿主/Admin HTTP → `modules/*` | | `app/api/quotes/` | 询价创建与轮询 | → `modules/quote/orchestrator` → BullMQ | | `app/api/addresses/mothership-candidates/` | 地址候选、preheat、confirm | → BullMQ `address-candidates` job | | `components/` | 宿主内嵌查价 UI、管理端组件 | hooks → API | | `modules/quote/` | 询价编排、校验、幂等、fallback | L1→L2→RPA→L3 状态机 | | `modules/pricing/` | 加价引擎(仅 raw_freight,0~30%) | 读 MarkupConfig → 写 QuoteRecord | | `modules/cache/` | Redis L1/L2/L3、限流、熔断 | 读写 Redis | | `workers/rpa/` | BullMQ Consumer + Playwright 填表/抓价 | 消费 queue → 写 MySQL/Redis | | `workers/rpa/quote-capture/` | Network-First 报价捕获(axel/quote 契约解析) | submit 后监听 network → normalize | | `workers/rpa/address-adapter/` | 地址选择抽象(real/mock) | DOM + axel search/place API | | `workers/rpa/kernel/` | RPA 步骤编排(address/cargo/quote steps) | 内部状态机 | | `lib/` | prisma/redis/response/rpa env 等基础设施 | 被 app/modules/workers 引用 | | `prisma/` | Schema、migrations、seed | MySQL 持久化 | | `scripts/` | probe、prove、smoke、录证、准确度测试 | CLI 验收/排障 | | `cursor_project_rules/` | AI 编码规则 + implementation-plan 逐步日志 | 必读 | | `.rpa/` | storageState、session、chain-proof 产物 | 勿提交敏感态(gitignore) | | `.dev/logs/` | worker/next 运行日志、失败截图 | 排障 | **端到端数据流** ``` 宿主 EmbeddedQuoteWidget → POST /api/addresses/mothership-candidates(BullMQ RPA 枚举) → 用户确认弹窗 → POST /api/quotes(QuoteOrchestrator) → L1/L2 未命中 → BullMQ quote-rpa job → workers/rpa:openQuotePage → fillAddress → fillCargo → submit → capture axel/quote → 写 L2/L3 + quote_record → GET /api/quotes/{id} 轮询返回 ``` --- ### 2. 当前开发状态快照 (Current State Snapshot) **已完成模块**(单测/集成/E2E 在 Mock 或隔离路径下通过) | 模块 | 验收依据 | |------|----------| | Phase 0~4 底座 | M1:POST /quotes、L2 缓存、幂等、校验 400 | | 鉴权 RBAC + Service Token | middleware + 单测 | | 三级缓存 + 限流 + 熔断 | redis-cache / rate-limiter 单测 | | 加价引擎 | engine.test.ts TC-103 | | 询价编排 + fallback + 超时 sweeper | orchestrator / timeout-sweeper 单测 | | BullMQ 队列 + Worker 管理 API | rpa status/pause | | 宿主内嵌查价 UI + 地址确认弹窗 | embed-demo + E2E | | 管理端(预警/RPA/队列/指标) | admin 路由 | | CI lint + test:unit + test:integration + build | `.github/workflows/ci.yml` | | RPA v1.2 骨架 | QuotePageAdapter、selector env、Network-First capture、contract guard | | 阶段 0 录证 | `.rpa/phase0/quote-contract-summary.json`:headed 人工可拿到 axel/quote | | 货物填表(mock 路径) | fillCargoInDrawer + cargo-reset 单测 | **进行中任务** | 任务 | 进度 | 说明 | |------|------|------| | **M2-v1.2** 真实 RPA probe 3/3 | **待跑** | `npm run probe:rpa:3x` | | **人工 docx 系统验收** | **Done** | `npm run probe:human-doc-system` 10/10(Direct 优先 → Widget fallback) | | task-133~135 CI probe 门禁 | 待执行 | TC-601 | **最近 3 次关键变更** | 次序 | 变更 | 原因 | 影响模块 | |------|------|------|----------| | 1 | **Step 11.75~11.78** Direct 优先报价 + preknown synthetic 地址 | 禁止 Widget 默认路径;修复 Bridge 误点 | `quote-strategy.ts`、`mothership-address-adapter.ts` | | 2 | **probe:human-doc-system** | 系统 API 全链路验收(非 RPA 直调) | `scripts/probe-human-doc-system.ts` | | 3 | 代码库清理 | 删除过时修复计划、debug 脚本、废弃 re-export | `docs/`、`scripts/`、`workers/rpa/` | --- ### 3. 问题与阻塞项登记 (Issues & Blockers Registry) > **2026-06-26 状态**:人工 docx 10 组系统验收 `probe:human-doc-system` **10/10 PASS**(`.rpa/human-doc-system-probe/system-e2583d51.json`)。BLK-001~003 在 preknown + synthetic 路径已缓解;回归以 `npm run probe:human-doc-system` 为准。 | 问题ID | 状态 | 问题描述 | 复现路径/报错信息 | 已尝试的解决方案(含失败原因) | 下一步建议方案 | 相关文件路径 | |--------|------|----------|-------------------|------------------------------|----------------|--------------| | **BLK-001** | 已缓解 | **P0** 地址 DOM 点选不触发 `GET place/{placeId}`,commit 失败 | 历史:embed-demo → worker `no_place_request` | description-click / keyboard / pac-dispatch 等;**11.77** preknown 跳过 legacy 点选、走 synthetic | 新地址回归 `probe:human-doc-system`;headed 录证见 `docs/真人录证-commit时刻.md` | `mothership-address-adapter.ts`、`address-commit.ts` | | **BLK-002** | 已缓解 | **P0** 填 delivery 后 pickup 丢失(widget 互斥) | 历史:dual-address `pickupLostAfterDelivery=true` | 交替 refill(11.48);preknown synthetic(11.77) | 同上 | `address-side.ts`、`address-commit.ts` | | **BLK-003** | 已缓解 | submit 后无 `axel/quote`(contract=0) | 历史:commit 失败导致无 quote | 根因是地址未 commit;Direct-first + Widget fallback(11.75) | 监控 worker 日志 `axel/quote`;勿在 commit 通前改 contract 层 | `quote-capture/pipeline.ts`、`quote-strategy.ts` | | **BLK-004** | 已关闭 | mock/fixture 假通过误导验收 | `prove:rpa-chain` PASS 但价非 MotherShip | mock-chain-fixture **已删除(11.45)** | 验收 `RPA_ADDRESS_MODE=real` + 无 fixture | `.rpa/chain-proof-72b4e980.json`(历史样例) | | **BLK-005** | 已更新 | direct POST axel/quote 价与 UI 不一致 | 历史:direct 价偏低 | **11.75** Direct 优先、失败再 Widget fallback | 勿单独启用 Widget 并行/预热 | `quote-strategy.ts`、`axel-quote-direct.ts` | | **BLK-006** | 开放 | L2 缓存假阳性 | `创建即 done + source_type=cache` | 探针用 `uniqueWeight` 防命中空 cache | 验收前换 cargo 或 `FLUSHDB` | `modules/cache/redis-cache.ts` | | **BLK-007** | 开放 | 端到端超时 420s | commit 失败时前端超时 | 提 timeout 不能解决 commit;commit 通后约 2–3 分钟/单 | commit 稳定后评估 browser warm/session | `lib/constants/quote.ts` | | **BLK-008** | 开放 | WSL Redis/MySQL 间歇断连 | worker `ECONNREFUSED 6379/3307` | keepalive + orphan 回收 | 长跑前 `npm run dev:infra:start` | `lib/redis.ts`、`queue-recovery.ts` | | **BLK-009** | 开放 | 候选 API 60~180s 慢 | POST `/api/addresses/mothership-candidates` | 枚举优化、Worker 队列 | 候选慢可接受;`pickBestMothershipCandidate` 降权 Bridge | `address-candidates.ts`、`pick-mothership-candidate.ts` | | **BLK-010** | 开放 | storageState 货物残留 | 摘要 2296Lbs vs 期望 500Lbs | freshCargoContext + cargo-reset | real 路径验证 `assertWidgetCargoSummary` | `cargo-reset.ts`、`session-manager.ts` | | **BLK-011** | 冻结 | 架构过度重构拖慢排障 | kernel/驻留页多轮 | 11.30 放弃驻留页默认路径 | **冻结架构**;仅改 adapter + commit | `workers/rpa/kernel/` | **根因链(历史 → 当前)** ``` [历史] placeApiHitCount=0 → commit FAILED → 无 axel/quote → 420s 超时 [当前] preknown synthetic + Direct-first → probe:human-doc-system 10/10(约 2.4min 全量) ``` --- ### 4. 架构决策记录 (ADR) **ADR-1:Network-First 报价捕获(禁止 DOM 刮价)** - **决策**:报价仅来自 `services.mothership.com/axel/quote` 网络响应,经 `quote-contract-guard` + `payload-decode` 解析;禁止 DOM price extraction。 - **原因**:MotherShip SPA 档位与 DOM 不同步;阶段 0 HAR 已验证 API 契约稳定。 - **权衡**:放弃 DOM fallback;submit 后必须触发真实 quote 请求,**强依赖地址/货物 commit 成功**。 **ADR-2:BullMQ 统一 RPA Worker(候选 + 询价同进程)** - **决策**:地址候选与询价均入 BullMQ,同一 `workers/rpa/index.ts` 消费;`withRpaSessionLock` 串行。 - **原因**:spawn 子进程与 worker 双浏览器争用 `.rpa/mothership-storage.json`,行为不一致。 - **权衡**:单 worker 吞吐低;Worker 不可用时才 spawn 回退(stall 60s)。 **ADR-3:MotherShip 地址须用户确认 + place API commit** - **决策**:RPA 枚举候选 → 前端弹窗确认 → 询价时按 `display_label` 点选;commit 以 `GET place/{placeId}` 网络证据为准。 - **原因**:PRD GD 要求点选;仅 DOM 填值不满足 MotherShip widget 状态机。 - **权衡**:端到端 180~360s;**当前 place commit 未打通即全链路失败**。 **ADR-4:Next.js Route Handlers 薄层 + modules 业务编排** - **决策**:API Route 只鉴权/校验/调 orchestrator;禁止 Route 内写复杂业务。 - **原因**:可测试性、分层清晰、Worker 与 API 共享 modules。 - **权衡**:无 Server Actions;宿主必须 HTTP 集成。 **ADR-5:放弃驻留页默认路径(`RPA_PARKED_SESSION=false`)** - **决策**:候选完成后不默认 park page;询价 job 独立开页 + storageState;browser warm 保留。 - **原因**:驻留页需 worker+confirm+同进程,验证周期 >5min 且 fetch failed 频发;网络等待占 80%+。 - **权衡**:候选→询价无法零重开;性能优化有限。 --- ### 5. 新 AI 接手指令 (Onboarding Instructions) **启动/运行命令** ```bash cp .env.example .env # 首次 npm run dev:infra:setup # WSL MySQL+Redis(Windows 推荐) npm run dev:start # infra + migrate + seed + next + worker + scheduler # 分项 npm run dev # Next.js :3000 npm run worker:rpa # RPA Worker(另开终端) npm run worker:scheduler # 超时清扫 # 验证 npm run smoke:connect # MySQL/Redis npm run verify:rpa-stack # 四步全栈验收 npm run prove:rpa-chain # 端到端 → .rpa/chain-proof-*.json RPA_HEADLESS=false npm run probe:rpa # headed RPA 调试 # 测试 npm run lint npm run test:unit npm run test:integration npm run test:e2e npm run go-no-go -- --mock # 停止 npm run dev:stop ``` **环境变量依赖**(名称为准,勿写值) | 变量 | 用途 | |------|------| | `DATABASE_URL` | MySQL 连接(本地通常端口 3307) | | `REDIS_URL` | Redis 连接 | | `JWT_SECRET` | 管理端 JWT | | `HOST_SERVICE_TOKENS` | 宿主 Service Token → customer_id 映射 | | `MOTHERSHIP_QUOTE_URLS` | RPA 入口 URL 列表(必填,禁止 dashboard) | | `RPA_SELECTOR_*` | 8 项 Playwright selector(PICKUP/DELIVERY/WIDGET/STREET/SUGGESTION/CARGO/SUBMIT 等) | | `RPA_STORAGE_STATE_PATH` | Playwright storageState 路径 | | `RPA_MOCK_MODE` | `false`=真实 RPA;`true` 仅 CI/单测 | | `RPA_ADDRESS_MODE` | `real` / `mock`(mock 不能用于验收) | | `RPA_HEADLESS` / `RPA_HEADED` | 有头调试 | | `RPA_BROWSER_LOCALE` | 推荐 `en-US` | | `RPA_BROWSER_CHANNEL` | 推荐 `chrome` | | `RPA_USE_STEALTH` | 默认 `false`(曾破坏 widget) | | `RPA_PARKED_SESSION` | 默认 `false` | | `RPA_QUEUE_ENABLED` | Worker 是否消费队列 | | `RPA_WORKER_ID` | Worker 标识(空则自动生成) | | `MOTHERSHIP_QUOTE_API_URL_PATTERNS` | axel/quote telemetry 白名单 | | `MOTHERSHIP_EMAIL` / `MOTHERSHIP_PASSWORD` | 仅 login 页时需要 | | `DEV_INFRA_MODE` | `native` / `docker` / `auto` | | `MYSQL_HOST_PORT` / `MYSQL_ROOT_PASSWORD` | Docker/WSL MySQL | **首要任务优先级** 1. **P0** 回归门禁:`npm run probe:human-doc-system` 10/10(改地址/RPA 后必跑;worker 需 `dev:stop` + `dev:start` 重载) 2. **P0** 实网验收:`npm run prove:rpa-chain` 在 `RPA_ADDRESS_MODE=real` 下 PASS(非 cache、非 fixture) 3. **P1** embed-demo 标准样例(LA 90001 → Dallas 75201,2 托 500lb)返回 `source_type=rpa` 且价与 MotherShip 页面一致 4. **P2** M2 门禁:`npm run probe:rpa:3x` 连续 exit 0 5. **禁止**:在 commit 未通前改 timeout/stealth/contract/kernel/驻留页;禁止 Widget 并行/预热/默认路径 **项目特殊约定** | 类别 | 约定 | |------|------| | 编码前必读 | `cursor_project_rules/global-rules.mdc`、`implementation-plan.mdc`、`domain-rules.mdc` | | 分层 | `app/api` 薄路由 → `modules` 业务 → `workers/rpa` 长任务 | | 命名 | DB snake_case;TS camelCase/PascalCase;env UPPER_SNAKE | | 金额 | DECIMAL(12,2);`ROUND_HALF_UP`;禁止 float 运算 | | 界面文案 | **全部中文**(MotherShip 抓取字段除外) | | 红线 | 一次询价=1 次 RPA;cache key 不含 service_level;禁止 dashboard URL;禁止 RPA mock 上线 | | Git | 禁止 main 直推;不提交 `.env`/`.rpa/` 敏感态 | | 完成步骤 | 在 `implementation-plan.mdc` 写 `Done` + 两行执行记录 | | 禁止库/模式 | 禁止 DOM 刮价;禁止 mock-chain-fixture;禁止 default dashboard URL;禁止空 catch | | Windows 开发 | 优先 `npm run dev:start`(PowerShell);WSL native infra 优于 Docker Desktop | **关键产物路径** | 路径 | 内容 | |------|------| | `.dev/logs/worker-rpa.log` | Worker 实网日志 | | `.dev/logs/rpa-failure-*.png` | RPA 失败截图 | | `.rpa/chain-proof-*.json` | prove 链路报告 | | `.rpa/dual-address-compare-*.json` | 双地址 network 对比 | | `.rpa/phase0/quote-contract-summary.json` | axel/quote API 契约 | --- ## 项目信息 ### 做什么 通过 **Playwright RPA** 抓取 Mothership 公开报价页,一次询价返回 **4 档**报价(standard/guaranteed × lowest/fastest),经加价引擎输出给**宿主系统**内嵌查价组件。中台自身仅提供 **admin 管理端**(预警、RPA 状态、队列监控)。 ### 不做什么(MVP 边界) - 不使用 Mothership 官方 API - 无独立客户/运营登录、无运营加价 UI - 无 CC 对接、多币种、下单、对账、Priority1 ### 技术栈 | 层 | 选型 | |----|------| | Web | Next.js 15(App Router)+ React 19 + Tailwind + shadcn/ui | | API | Next.js Route Handlers | | 数据 | MySQL 8 + Prisma | | 缓存/队列 | Redis 7 + BullMQ | | RPA | Playwright(可选 Patchright) | | 测试 | Vitest + Playwright E2E | | 部署 | Docker Compose | ### 核心文档(按阅读顺序) | 文档 | 用途 | |------|------| | [查价系统-PRD.md](./查价系统-PRD.md) v0.6 | 业务规则、GD 决策、验收标准(**最高业务权威**) | | [查价系统-技术设计.md](./查价系统-技术设计.md) v1.2 | 架构、目录、API、RPA 规格(**实现权威**) | | [查价系统-工程任务拆解-v1.2.md](./查价系统-工程任务拆解-v1.2.md) | 原子任务与开发顺序 | | [查价系统-UI设计.md](./查价系统-UI设计.md) | 页面、交互、状态机 | | [参考-Mothership-RPA-开源借鉴与解阻塞.md](./参考-Mothership-RPA-开源借鉴与解阻塞.md) | RPA 排障参考 | | [AI 项目交接文档](./查价项目.md#ai-项目交接文档) | 完整 handoff:身份/状态/阻塞/ADR/接手指令 | **切换 AI 必读** | | `cursor_project_rules/` | AI/人工编码规则与实施日志 | implementation-plan 逐步记录 | ### 目录结构(关键路径) ``` chajia/ ├── app/ # Next.js 页面 + API Routes ├── components/ # 前端组件(含宿主内嵌查价) ├── modules/ # 业务逻辑(quote/cache/pricing/alert) ├── workers/rpa/ # BullMQ Consumer + Playwright ├── lib/ # prisma/redis/response/rpa ├── prisma/ # schema + migrations + seed ├── scripts/ # probe、smoke、go-no-go、录制 ├── __tests__/ # unit / integration / e2e ├── docs/ # 产品与工程文档 └── cursor_project_rules/ # 项目规则 + implementation-plan ``` ### 角色与鉴权 | 角色 | 方式 | 能力 | |------|------|------| | 宿主系统 | `Authorization: Bearer ` | 代 `customer_id` 询价、历史、加价配置 | | 管理员 | JWT + RBAC | 预警、RPA 运维、`/admin/*` | 联调样例: - 宿主 token:见 `.env` 的 `HOST_SERVICE_TOKENS`(如 `demo-host-token` → `CUST_001`) - 管理端:`admin_demo` / `Demo@123`(仅开发环境) ### 联调标准样例 | 字段 | 值 | |------|-----| | 路线 | LA(90001) → Dallas(75201) | | 单托重量 | 500 lb | | 单托尺寸 | 48×40×48 in | | 托盘数 | 2 | | 货物类型 | `general_freight` | --- ## 常用命令 ### 环境准备 ```bash # 复制环境变量(勿提交 .env) cp .env.example .env # 一键启动(推荐,含 infra + app + worker) npm run dev:start # 仅 Docker infra(MySQL + Redis) docker compose up -d mysql redis # WSL/native infra 引导(Windows 开发机) npm run dev:infra:setup ``` ### 数据库 ```bash npm run db:validate # Prisma schema 校验 npm run db:migrate # 本地迁移(开发) npm run db:deploy # 生产迁移 npm run db:seed # 种子数据 ``` ### 开发运行 ```bash npm run dev # Next.js :3000 npm run worker:rpa # RPA Worker(另开终端) npm run worker:scheduler # 超时清扫 Worker npm run dev:stop # 停止本地进程 npm run dev:clean # 清理临时产物 ``` ### 测试与门禁 ```bash npm run lint # TypeScript 类型检查 npm run test:unit # 单元测试 npm run test:integration # 集成测试 npm run test:e2e # Playwright E2E npm run test # 全量 Vitest npm run smoke:connect # MySQL/Redis 连通 npm run smoke:api # API 冒烟 npm run smoke:rpa # 真实 RPA 冒烟(需 env 配齐) # RPA 探针(Go/No-Go 门禁:连续 3 次 exit 0) npm run probe:rpa npm run probe:rpa:3x npm run probe:human-doc-system # 人工 docx 10 组,系统 API 全链路 npm run probe:random-address-quote npm run go-no-go # 上线前指标检查 ``` ### RPA 录制与排障 ```bash # P0 真人完整链路录证(HAR+录像+Trace+Console+Network,定位 place commit 瞬间) npm run record:human-commit-baseline # 详见 docs/真人录证-commit时刻.md # headed 录制 Mothership 报价流程 → 更新 MOTHERSHIP_QUOTE_URLS / RPA_SELECTOR_* npm run record:mothership # P0 真人 vs RPA 网络差分 $env:RPA_HEADLESS="false" npm run compare:human-rpa-network ``` ### Docker 全栈 ```bash docker compose up -d --build docker compose exec next-app npx prisma migrate deploy docker compose exec next-app npx prisma db seed ``` ### 访问地址 | 入口 | URL | |------|-----| | 宿主内嵌演示 | http://localhost:3000/embed-demo | | 管理端预警 | http://localhost:3000/admin/alerts | | 管理端 RPA | http://localhost:3000/admin/rpa | | 队列监控 | http://localhost:3000/admin/queues | --- ## 编码规范 ### 分层(禁止跨层) ``` app/api/* → 薄路由,只调 modules modules/* → 业务编排(orchestrator、pricing、cache) workers/rpa/* → 长任务 RPA,不经 HTTP 直调 DB 以外层 lib/* → 基础设施封装 components/* → UI,经 hooks/api 调后端 ``` - API Route **不得**写复杂业务逻辑 - RPA Worker **不得**绕过 `QuoteProvider` 接口直接返回假数据(CI 除外 `RPA_MOCK_MODE=true`) ### 命名 | 范围 | 规范 | 示例 | |------|------|------| | DB 字段 | snake_case | `pallet_count`, `raw_freight` | | TS 变量/函数 | camelCase | `cargoHash`, `getQuote` | | TS 类型/组件 | PascalCase | `QuoteRecord`, `QuoteForm` | | 环境变量 | UPPER_SNAKE | `MOTHERSHIP_QUOTE_URLS` | | API 路径 | kebab/REST | `/api/quotes/{quote_id}` | ### 金额与精度 - 存储与计算:`DECIMAL(12,2)` - 舍入:`ROUND_HALF_UP`,保留 2 位 - **禁止**用 `float` / `number` 做金额运算 ### API 响应 统一包络: ```json { "code": 0, "message": "ok", "data": { } } ``` 错误使用标准 `error_code`(见 PRD §5.6 / `domain-rules.mdc` §10)。 ### 异步与错误 - 所有 `async` 必须 `catch` 并 `log`(禁止空 catch) - 用户可见操作必须有 loading + 防重复提交 + 成功/失败反馈 - 外部调用(RPA/Redis/MySQL)须处理超时与失败分类 ### 前端 - **界面文案一律中文**(除 Mothership 原文抓取字段) - 查价表单须支持地址联想点选、`pallet_count`、kg/cm 展示与 lb/in 换算 - 轮询:`GET /quotes/{id}`,间隔 2s,最长 30s ### Git - 禁止在 `main` 直接开发;使用 feature 分支 - Commit:`feat|fix|docs|refactor(module): 描述`(可附 PRD/任务编号) - 不提交 `.env`、`.rpa/` 敏感状态(除示例模板) ### 变更流程 1. 先查 PRD / 技术设计 / `implementation-plan.mdc` 是否已有规则 2. 数据结构变更 → Prisma migration → 再改代码 3. 改逻辑 → 补/跑对应测试 4. 完成一步 → 在 `implementation-plan.mdc` 标记 `Done` + 两行执行记录 --- ## 红线(绝对不能违反) ### 业务红线 | # | 规则 | 违反后果 | |---|------|----------| | R1 | **一次询价 = 1 次 RPA**,单次必须返回 **4 档** | 成本翻倍、验收失败 | | R2 | `service_level` / `rate_option` **不得进入** cache key | 缓存串档、价格错误 | | R3 | 地址必须联想**点选**,须 `place_id` + `selected_from_suggestions=true` | Mothership 拒单或 RPA 失败 | | R4 | 使用 `pallet_count`(托盘数),**禁止**件数/箱数字段 | 与 Mothership 口径不一致 | | R5 | 加价仅对 `raw_freight`,上限 30%;未配置客户 0% | 财务口径错误 | | R6 | L3 stale **仅** RPA 失败降级只读,正常路径不得读 L3 | 展示过期价 | | R7 | `MOTHERSHIP_QUOTE_URLS` 必填且 headed 录制验证;**禁止**默认 `dashboard.mothership.com` 或 `/quote` | RPA 100% 失败 | | R8 | login 页且无凭据 → `STRUCT_CHANGE`,**禁止**误报 `SESSION_EXPIRED` | 排障误导、错误重试 | | R9 | RPA selector **禁止**硬编码上线;必须 `RPA_SELECTOR_*` env 或 `lib/rpa/selector-specs.ts` 统一管理 | 页面改版即挂 | | R10 | `RPA_MOCK_MODE=false` 为验收/生产默认;Mock 仅 CI/单测 | 假数据上线 | ### 工程红线 | # | 规则 | |---|------| | E1 | PRD 与代码冲突时 **以 PRD 为准**;实现细节以技术设计为准 | | E2 | 禁止擅自新增/修改 API 字段、错误码、表字段(须先改 PRD + migration) | | E3 | 禁止空 catch、静默失败、吞异常 | | E4 | 禁止占位 UI / 假按钮 / 「敬请期待」入口 | | E5 | 禁止物理删除业务数据;使用逻辑删除 `is_deleted` | | E6 | 禁止跳过 migration 直接改库;禁止代码引用未迁移字段 | | E7 | 禁止在 main 分支直接提交;禁止 `git push -f` 到 main | | E8 | 禁止提交密钥(`.env`、storageState、真实 token) | | E9 | 禁止未授权升级依赖或引入未评审大包 | | E10 | 多租户:`customer_id` 必须校验归属,禁止越权查历史/报价 | ### 安全红线 | # | 规则 | |---|------| | S1 | 查价 API 必须校验宿主 Service Token | | S2 | 管理端 API 必须 JWT + RBAC | | S3 | 高危操作须审计日志(`AuditLog`) | | S4 | 限流:单客户 60 次/分钟(见 PRD) | --- ## 技术方案 ### 架构总览 ``` 宿主系统(内嵌查价 UI) │ Bearer Token + customer_id ▼ Next.js API(QuoteOrchestrator) ├─ L1 idem:{request_id} ──命中──► 直接返回 ├─ L2 quote:{cargo_hash} ──命中──► 加价后返回 └─ 未命中 ──► BullMQ(quote-rpa) ──► RPA Worker │ │ │ ├─ QuotePageAdapter(多 URL 探测) │ ├─ session-manager(storageState) │ └─ mothership.ts(填表+四档抓取) ▼ MySQL quote_record / Redis L2/L3 ``` ### 缓存策略(GD-2) | 层 | Key | TTL | 用途 | |----|-----|-----|------| | L1 | `idem:{request_id}` | 24h | 幂等完整响应 | | L2 | `quote:{cargo_hash}` | 3min | 四档原始价(未加价) | | L3 | `stale:{cargo_hash}` | 30min | RPA 失败降级 | 读取顺序:**L1 → L2 → RPA →(失败)L3**。 ### 询价状态机 ``` processing → done (cache|rpa|stale) | failed (QUOTE_UNAVAILABLE|QUOTE_TIMEOUT) done --3min--> expired ``` ### RPA 方案(v1.2) 1. `QuotePageAdapter` 按 `MOTHERSHIP_QUOTE_URLS` 顺序探测可用入口 2. `session-manager` 加载/写回 `RPA_STORAGE_STATE_PATH` 3. 地址:键入 → 等待联想 → **click** 选项 → fill-verify 4. 单次提交 → 结果页切换 standard/guaranteed × lowest/fastest → 抓 4 档 5. 失败分类:入口错误 / 地址未锁定 / 验证码 / 结构变更 / 数据无效 上线门禁:`probe-rpa` **连续 3 次 exit 0**(TC-601 + §12.7)。 ### 加价公式 ``` markup_amount = ROUND_HALF_UP(raw_freight × markup_percent / 100, 2) final_total = ROUND_HALF_UP(raw_total + markup_amount, 2) ``` ### 关键 API | 方法 | 路径 | 调用方 | |------|------|--------| | POST | `/api/quotes` | 宿主 | | GET | `/api/quotes/{quote_id}` | 宿主(轮询) | | GET | `/api/quotes/history` | 宿主 | | PUT | `/api/markup-configs/{customer_id}` | 宿主 | | GET | `/api/alerts` | 管理员 | ### 环境变量(RPA 最小集) | 变量 | 必填 | 说明 | |------|------|------| | `MOTHERSHIP_QUOTE_URLS` | 是 | 逗号分隔匿名报价 URL | | `RPA_SELECTOR_*` | 是 | 录制产出 selector | | `RPA_STORAGE_STATE_PATH` | 否 | 默认 `.rpa/mothership-storage.json` | | `RPA_MOCK_MODE` | 否 | 默认 `false` | | `RPA_HEADLESS` | 否 | 调试设 `false` | | `DATABASE_URL` / `REDIS_URL` | 是 | 基础设施 | 完整列表见 `.env.example` 与技术设计 §6.2。 --- ## 新人上手路径 1. **先读本文「AI 项目交接文档」** → PRD §1–3 → 技术设计 §1、§7 2. `cp .env.example .env`,配 `DATABASE_URL` / `REDIS_URL` / `HOST_SERVICE_TOKENS` 3. `npm run dev:start` 或 `docker compose up -d` + `npm run dev` 4. 打开 http://localhost:3000/embed-demo 走通查价 5. RPA 开发:先 `npm run record:mothership`,再 `npm run probe:rpa` 6. 接任务前查 [工程任务拆解 v1.2](./查价系统-工程任务拆解-v1.2.md) 当前 Phase --- ## 排障速查 | 现象 | 优先检查 | |------|----------| | **embed 询价超时/failed** | **先看「AI 项目交接文档」§3 BLK-001~003**;worker 日志 `no_place_request` | | 创建即 done + cache | L2 假阳性;换 cargo 或清 Redis `quote:*` | | place commit 失败 | headed 对比 network:`GET place/{id}` 是否触发 | | RPA 进登录页 | `MOTHERSHIP_QUOTE_URLS` 是否 dashboard;storageState 是否失效 | | preCheck 失败 | `RPA_SELECTOR_*` 是否过期;headed 重录 | | 地址填不进去 | 是否缺少 `place_id`;联想是否 click 而非 Enter | | 只返回 2 档 | 违反 GD-1,检查四档抓取逻辑 | | 429 | 限流;检查 `customer_id` 请求频率 | | Worker 不消费 | Redis 连通、`RPA_QUEUE_ENABLED`、BullMQ 队列 | 失败截图目录:`.dev/logs/`(如 `rpa-failure-*.png`)。 --- ## 文档维护 - 业务变更 → 先改 PRD → 同步技术设计 → 更新本文「项目信息/红线/技术方案」摘要 - 命令变更 → 以 `package.json` `scripts` 为准,同步本文「常用命令」 - 规则变更 → 同步 `cursor_project_rules/domain-rules.mdc`