platform-assistant/HANDOVER.md
Sisyphus 5a49a37235 docs: 项目交接文档 + 比价功能PRD + 数据维护脚本
- HANDOVER.md: 完整交接文档(架构/数据模型/API/业务流/已知坑/工作区状态/待办)
- PRD-智能比价助手.md: 比价功能产品需求文档(供外部平台参考)
- 数据维护脚本: 清理AI垃圾品类SQL、修复损坏标题SQL、线索重分类/重抓脚本
2026-09-30 09:35:05 +08:00

449 lines
25 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 平台助手 — 项目交接文档
**交接日期:** 2026-09-30
**交接对象:** 接手继续开发的智能体 / 开发者
**文档目标:** 让接手方无需历史对话即可完整了解项目现状、架构、业务逻辑、已知问题与待办事项,可直接开始开发。
---
## 1. 项目是什么
**平台助手(内部代号 pingtai)** 是面向 B2B 采购场景的智能询比价工作台:
- **采集**:自动抓取 3 家央企招标公告(中电建 / 新矿集团 / 中核集团),关键词+AI 双通道分类入库
- **线索管理**:线索列表 / 详情 / 星级评分 / 状态流转(待复核 → 询价中 → 协作中)/ 跟进日志
- **询价发布**:勾选线索一键发布到外部 B2B 产业平台(通过 MCP 通道调 `publish_rfq`),含发布前确认弹窗(补联系人/电话/地址)、物料清单 AI 提取、超长标题 AI 压缩
- **比价**:供应商报价 Excel/CSV 上传 → 自动解析 → 商品对齐去重 → 三种采购策略对比(集中/拆分/限 K 家)→ 采购建议报告 + CSV 导出
- **生图**:商品图片三阶段生成(图库 → Bing 搜索 → MiniMax AI 生成)
- **品类库**:关键词优先 + AI 反哺的品类字典(ProductCategory)
- **管理后台**:租户/用户/系统配置/计划/MCP 对接配置
**在线地址(生产):** https://direv.cloud(Nginx → Next.js 3001 / NestJS 4000)
**演示账号:** `admin` / `admin123`(username + password,**不是 email**)
---
## 2. 技术栈与架构
| 层 | 技术 | 说明 |
|----|------|------|
| 前端 | Next.js 14 App Router + React 18 + Tailwind CSS 3 | **SPA 无路由**,useState 切换 14 个页面组件 |
| 后端 | NestJS 10 + TypeScript | 13 个子模块,端口 4000 |
| 数据库 | SQLite + Prisma ORM | `apps/api/prisma/dev.db`,15 个模型 |
| 采集脚本 | Node.js / Python | `scripts/tender-fetcher/`,CrawlerService 用 `execSync` 调度 |
| AI | MiniMax M2.7 (推理模型) | 品类分类 / 物料提取 / 标题压缩,endpoint `https://api.minimaxi.com/v1/chat/completions` |
| 外部对接 | MCP (Streamable HTTP, POST) | 集成 Integration 表 → product_network B2B 平台 |
| 部署 | 腾讯云 172.81.241.109 + PM2 + Nginx + certbot | `scripts/deploy.sh` 一键部署 |
### 架构图
```
┌─────────────────────────────┐
浏览器(3001) ←│ Next.js 14 (pingtai-web) │
└─────────────────────────────┘
│ /api/*
┌────────▼─────────────────────┐
│ NestJS 10 (pingtai-api:4000) │
│ ├─ crawler: execSync 调脚本 │
│ ├─ lead: 评分/状态/发布 │
│ ├─ integration: MCP client │
│ └─ comparison/image/... │
└────────┬─────────┬───────────┘
│ │
SQLite(dev.db) MiniMax API
│
┌────────▼─────────────────────┐
│ MCP server (devbox:3002/8095)│
│ → product_network B2B 平台 │
└──────────────────────────────┘
```
> ⚠️ MCP server 端口 **3002(Node)/8095(Java)** 都在 devbox 开发机上,不是本地端口。平台助手本地 web 是 **3001**、API 是 **4000**。两套 MCP server 后端数据库不同,appId/appSecret 互不通用。
---
## 3. 目录结构
```
平台助手/
├── apps/
│ ├── web/ # Next.js 14 SPA
│ │ └── src/
│ │ ├── app/page.tsx # SPA 根,useState<Page> 切换页面
│ │ ├── components/ # 14 个页面 + AuthProvider/Sidebar/Providers/ErrorBoundary
│ │ └── lib/apiBase.ts# API_BASE (NEXT_PUBLIC_API_URL,本地回退 localhost:4000)
│ └── api/ # NestJS 10
│ ├── prisma/
│ │ ├── schema.prisma # 15 个模型
│ │ └── dev.db # SQLite
│ └── src/
│ ├── admin/ audit-log/ auth/ common/ comparison/ crawler/
│ ├── image/ inquiry/ integration/ lead/ product/ product-category/
│ ├── reports/ rules/ users/ prisma/
│ └── app.module.ts / main.ts
├── packages/shared/ # 共享 TS 类型(编译型,无运行时依赖)
└── scripts/
├── tender-fetcher/ # fetch-v1.2.js(中电建) fetch-xkgroup.py(新矿) fetch-cnncecp.py(中核)
├── direv-publish/ # direv 平台发布脚本(10 个 .py,历史保留)
├── parse_products.py # Excel/CSV → JSON(比价用)
├── search_image.py / process_image.py # 生图辅助
├── backfill_category.js / clean_category.js / reclassify_leads.js 等 # 数据维护
└── deploy.sh # 一键部署
```
---
## 4. 开发环境搭建
```bash
npm run setup # npm install + db:init(prisma generate → migrate → seed)
npm run dev # 同时启动前端(3001)和后端(4000)
npm run dev:api # 仅后端(nest start --watch,用 dist/ 编译产物)
npm run dev:web # 仅前端
npm run db:init # 改 schema 后必跑
npm run db:reset # 删 db 重来
npm run build # nest build + next build
npm run typecheck # tsc --noEmit 所有包(无 root tsconfig,各包独立)
npm run lint
npm run dev:daemon # nohup 后台启动(PID 写入 .dev-api.pid / .dev-web.pid)
npm run dev:stop # kill PID 文件记录的进程
```
**端口速查(本地):** web=3001, api=4000。⚠️ 本地 `next dev` 偶发 fallback 到 3000(3001 被 `admin_new` Vite 项目占用时),以终端实际打印为准。
### 后端陷阱
- `nest start --watch` 用 `dist/` 编译产物:Prisma schema 变更后必须 `npm run build -w apps/api` 重建 dist 再重启,单纯 `prisma generate` 不够
- 端口 4000 残留进程:`dev:stop` 只杀 PID 文件进程,多余进程 `lsof -ti:4000 | xargs kill -9`
- Prisma 变更流程:改 schema → `npm run db:init`
- 后端无测试框架
### 前端陷阱
- SPA 无路由:`page.tsx` 用 `useState<Page>` 切换,导航走 `onNavigate` prop,参数走 props
- 路径别名 `@/` → `src/`
- Prettier: noSemi, singleQuote, trailingComma: all, printWidth: 100
- Tailwind Token:`secondary(#0051d5)`, `border-subtle(#E2E8F0)`, `surface-container` 系
---
## 5. 数据库模型(15 个)
| 模型 | 用途 | 关键字段 |
|------|------|----------|
| **Lead** | 线索核心表 | title/noticeId/detailUrl/level(C-B-A)/status/star/amount/category/organization/region/regionDetail/source/contact/phone/deadline/quantity/unit/notes/aiScore/scoringDetails/publish/publishTime/inquiryNo/direvUrl/assignedTo/followLogs;`@@unique([source, noticeId])` 去重键 |
| **Rule** | 规则中心 | name/type/config(JSON)/enabled |
| **RuleVersion** | 规则版本 | ruleId/version/config/userId |
| **Report** | 报表 | title/type/status/config/channels/fileUrl |
| **AuditLog** | 审计日志 | action/detail/operator/ip |
| **CrawlerConfig** | 采集器配置 | name/targetUrl/status/type(bid_xxx)/sourceName/scriptPath/schedule(cron)/keywords/categories(JSON品类名数组)/lastRunAt/totalRuns/successRuns/failedRuns |
| **Inquiry** | 询价(历史表) | 类似 Lead,冗余保留 |
| **Product** | 供应商报价条目(比价) | name/spec/unit/price/quantity/category/brand/supplier/minOrder/imageUrl |
| **Tenant** | 租户 | companyName/plan/status/validUntil/platformUrl/apiKey |
| **User** | 用户 | email/username/password/role/tenantId |
| **GeneratedImage** | 生成图片 | productName/url/status/style/sourceHash |
| **ImageLibrary** | 图库 | productName/keyword/filePath/source(baidu)/style/ratio |
| **SystemConfig** | 系统配置 | key(unique)/value/group |
| **Integration** | MCP 对接配置 | name/endpoint/appId/apiKey(AES-256-GCM 加密)/customerCode/status/tools/companyUserId |
| **ProductCategory** | 品类字典 | name(unique)/keywords(JSON数组)/isAIGenerated |
### 种子数据(seed.ts)
- 15 电缆型号 × 5 供应商 = 75 Product + 管理用户(admin/admin123)
- crawlerConfig 3 条:中电建 + 新矿集团 + 中核集团(中核默认 paused)
---
## 6. API 端点总览
### 认证 `/api/auth`
| POST /api/auth/login | 登录(username+password,JWT 存 localStorage `auth`) |
| GET /api/auth/profile | 当前用户 |
### 线索 `/api/leads`(核心)
| GET /api/leads | 列表(query: status/level/search/star/source/category/assignedTo/timeRange + 分页) |
| GET /api/leads/stats | 统计(OverviewPage 用) |
| GET/PUT/DELETE /api/leads/:id | 详情/更新/删除 |
| POST /api/leads/:id/to-rfq, /to-collab | 状态流转:转询价 / 转协作 |
| POST /api/leads/:id/publish | **发布到 B2B 平台**,可选 body `{contactPhone?, contactName?, deliveryAddress?}` |
| POST /api/leads/batch-publish | body `{ids: string[]}`,循环调 publish |
| POST /api/leads/:id/follow-up (+ /:index/delete) | 跟进日志 |
| POST /api/leads/:id/level | 改优先级 |
| POST /api/leads/calculate-score | 重算 AI 评分 |
### 采集器 `/api/crawlers`
GET/POST/PUT /api/crawlers(:id) | CRUD
POST /api/crawlers/:id/run, /pause, /resume | 运行/暂停/恢复(联动 cron 注册)
### 比价 `/api/comparison`
GET /api/comparison | 商品分组比价数据
POST /api/comparison/upload | multipart file+supplier 上传报价
POST /api/comparison/suggestion | 生成采购建议报告
### 品类 `/api/categories`
GET/POST/PUT/DELETE | ProductCategory CRUD(InquiryPage 筛选项动态加载)
### 其他
- `/api/products` + upload-image(商品管理)
- `/api/images` generate/parse/approve/regenerate(生图)
- `/api/integrations` + :id/connect/disconnect/call-tool + test(MCP 配置)
- `/api/admin` tenants/users/plan-config/configs/stats(管理后台)
- `/api/rules` + versions/rollback
- `/api/reports` + :id/send
- `/api/inquiries` / `/api/users` / `/api/audit-logs` / `/api/health`
- `/api/lead-notes/extract/:leadId`(notes 提取)
---
## 7. 前端页面清单(14 个)
| 页面组件 | 功能 | 数据对接状态 |
|----------|------|---------------|
| LoginPage | 登录 | ✅ POST /api/auth/login |
| OverviewPage | 概览(统计卡片+线索列表) | ✅ Promise.all([leads/stats, leads]) |
| InquiryPage | 询价列表 + 筛选/搜索/星级/批量发布 + **发布前确认弹窗** | ✅ leads + publish(含弹窗传 overrides) |
| LeadDetailPage | 线索详情 + 状态流转 + 跟进日志 | ✅ leads/:id + 各流转端点 |
| ComparisonPage | 比价三步向导(上传→对比→建议) | ✅ comparison 三端点 |
| ProductPage | 商品管理 + 生图 | ✅ products + upload-image |
| ImagePage | 图片库/生成 | ✅ images |
| RuleCenterPage | 规则中心 + **采集器配置 Tab**(品类多选框) | ✅ crawlers + categories |
| ReportCenterPage | 报表 | ⚠️ 纯 mock |
| SystemMonitorPage | 系统监控 | ⚠️ 纯 mock |
| AdminPage | 管理后台(5 子区块:租户/用户/统计/configs/计划,configs 含 MCP 对接配置) | ✅ 已实现 |
| Sidebar / Providers / AuthProvider / ErrorBoundary | 框架 | — |
---
## 8. 核心业务流程
### 8.1 采集入库(crawler.service.ts)
```
CrawlerService.run(config)
├─ type=bid_powerchina → runPowerChina(): execSync('node fetch-v1.2.js --keyword "X"')
├─ type=bid_xkgroup → runGenericScript(): execSync('python3 fetch-xkgroup.py')
└─ 其他 bid_xxx → runGenericScript()(脚本名从 type 推断)
↓
results → categoryInference.batchInfer() # 品类推断(关键词匹配 → 未命中走 MiniMax)
↓
upsert 到 Lead(去重键 @@unique([source, noticeId]))
```
**关键词解析(resolveKeywords,最近重构):**
1. 优先 `config.categories`(品类名数组)→ 查 ProductCategory 表把每个品类的 keywords 展开合并
2. 兜底 `config.keywords`(旧字段,直接解析)
3. 都没有 → `['']`(抓全部)
**中电建特殊处理:** 每个关键词有独立状态文件 `downloaded_<keyword>.json`(不是只有一个 downloaded.json);结果从 `~/Desktop/招标公告/<date>/downloaded_<kw>.json` 读取。`~/desktop` 是 symlink → `/data/pingtai`(生产持久化)。
**公告 ID 提取坑:** `substr(summary, instr(summary, '公告ID:') + 6, 32)`(`+6` 而非 `+8`,字节/字符偏移曾出 bug)。
### 8.2 品类分类(品类方案 C:关键词优先 + AI 反哺)
```
采集入库/手动回填时对线索标题:
1. 关键词匹配 ProductCategory.keywords contains 标题 → 命中直接写
2. 未命中 → MiniMax API 分类 → 写品类 + 反写关键词到 ProductCategory
3. 同类标题下次直接走关键词,不再调 AI
```
- 关键文件:`product-category/category-inference.service.ts`(AI 推断+反写)、`crawler.service.ts batchInfer()`、`notes-extractor.service.ts`(PDF/HTML 提取要点)
- **MiniMax 配置**:endpoint `https://api.minimaxi.com/v1/chat/completions`,model `MiniMax-M2.7`(env `MINIMAX_MODEL` 可覆盖),key 在 `apps/api/.env` 的 `MINIMAX_API_KEY`
- **⚠️ M2.7 是推理模型**:max_tokens 要够(物料提取正文 2000、标题提取 800),否则输出全被 `<think>` 吃掉;输出常混 `<think>`、`让我分析`、`**加粗**` 等解释文字,需要清洗
- 历史经验:AI 不可全信,2026-07-01 曾批量回填 507 条,结果 AI 新建 ~25 个细分类需人工 review。决策:新数据走关键词,老数据人工 review
### 8.3 发布询价到 B2B 平台(lead.service.ts publishToDirev)
```
POST /api/leads/:id/publish { contactPhone?, contactName?, deliveryAddress? }
→ publishToDirev(id, overrides)
1. 组装 publishArgs(title/description/qty/notes/attachments/deadline/deliveryDate/contact...)
2. 企业级硬编码字段:
inquiryParty='郑州巨变网络科技有限公司', freightFlag=1(含运费), procurementType=1(公开采购),
invoiceType=2(增值税专票), quotationDemand=1(需要报价), validDays=7
⚠️ paymentMethod 不传(传了报"企业字典找不到")
⚠️ 上述 flag 必须 int,传字符串会报错(已修复过的坑)
3. 超长标题(>50字)→ TitleCompressorService.compress() AI 压缩 → 失败硬截断兜底
4. MaterialExtractorService.extractItems() 实时提取物料清单 items
5. integrationService.executeTool('publish_rfq', publishArgs)(retries:2)
6. 成功 → 更新 lead 状态(published/inquiryNo/direvUrl);失败 → ServiceUnavailableException
```
**publishArgs 字段映射细节:**
| 字段 | 来源 | 备注 |
|------|------|------|
| title | lead.title(>50 字先 AI 压缩) | 平台 varchar(50) 限制 |
| description | lead.notes → summary → title | W31 P1 |
| qty | lead.quantity \|\| 1 | |
| notes | lead.notes | 商家报价时看到 |
| attachments | lead.attachments(JSON) \|\| [detailUrl] | |
| deadline | lead.deadline 前 10 位 | YYYY-MM-DD |
| deliveryDate | deadline + 30 天 | |
| contactName | overrides.contactName → lead.contact → '待补充' | 弹窗可编辑 |
| contactPhone | overrides.contactPhone → lead.phone → '待补充' | 必填,弹窗补全 |
| deliveryAddress | overrides.deliveryAddress → regionDetail → region | 弹窗可编辑 |
| userId/enterpriseId | Integration.companyUserId | MCP connect 时获取 |
### 8.4 MCP 集成(integration.service.ts)
- **协议**:Streamable HTTP(`StreamableHTTPClientTransport`),POST 方法
- **认证头**:`x-app-id` + `x-app-secret`(多租户 APIKey,替代旧 `x-api-key`),AES-256-GCM 加密存储(`ENCRYPT_KEY` in .env,32 bytes hex)
- **Session 管理(已实现)**:`buildSessionAwareFetch(integrationId)` 自定义 fetch wrapper——从 initialize 响应头提取 `mcp-session-id` 缓存 → 后续请求自动注入 → disconnect/重连清理
- **charset 修复(已实现)**:wrapper 检测 content-type 缺 charset 时补 `application/json; charset=utf-8`(否则 Java 解析器中文 Content-Length 不匹配报 JSON 错误)
- 17 个 MCP tool(询价5/商品4/比价4/推理4),发布调 `publish_rfq`
### 8.5 标题压缩(TitleCompressorService,新增)
- 位置:`apps/api/src/lead/title-compressor.service.ts`(已注册到 LeadModule)
- 逻辑:`compress(title, maxLength)`:
1. `title.length <= maxLength` 直接返回(**≤50 字不调 AI**)
2. 未配 `MINIMAX_API_KEY` → 硬截断 `title.slice(0, maxLength)`
3. 调 MiniMax,**要求 JSON 结构化输出** `{"title":"..."}`(对抗 M2.7 多变思考噪音,行级正则过滤不可靠)
4. `extractJsonTitle()` 正则提取 title 字段;解析不到 → 硬截断
5. 结果仍超长 → slice 截断兜底
- max_tokens 800(400 会被明文思考耗尽导致答案截断)
- 生产验证:6 个真实标题全部压缩达标(62字→25字等)
### 8.6 比价(comparison.service.ts)
```
上传报价 (POST upload: file+supplier)
→ parse_products.py 解析 Excel/CSV → Product 入库
→ GET /api/comparison:
按 name.trim().toLowerCase() 分组 → 组内按 supplier 去重留最低价
→ 每组算 bestPrice/bestSupplier/savings/priceSpread
→ POST /api/comparison/suggestion:
三种策略对比:
A. 无限制拆分(每 SKU 选最低总价供应商)
B. 单一供应商(Σ 全 SKU 总价排名,取第 1)
C. 限制 K 家(C(n,K) 组合枚举 + 每 SKU 取组合内最低价,K=2/3/4)
→ recommendations 自然语言建议 + CSV 导出
```
### 8.7 生图(image.service.ts)
三阶段:图库(ImageLibrary) → Bing 搜索(search_image.py) → MiniMax AI 生成;`process_image.py` 去水印+裁剪+白底合成。
---
## 9. 已知问题 / 坑(接手必读)
### 9.1 生产部署相关(⚠️ 最近出现)
1. **页面打开报"服务器内部错误" / `Failed to find Server Action`**(web 日志常见):
- 根因:**本地 build 的 `.next/` 直接 rsync 到生产**,生产 `node_modules` 版本与本地不一致 → Server Action ID 对不上 → 页面交互报错
- **解决办法(AGENTS.md 明确记录)**:在生产服务器上执行 `cd /opt/pingtai/apps/web && npx next build` 重建 `.next`,再 `pm2 restart pingtai-web`
- 若浏览器缓存旧 HTML 导致,提示用户强刷(Cmd+Shift+R)
2. **deploy.sh 不含 DB 迁移**:Prisma 变更部署前,需在生产 `apps/api/` 下手动 `npx prisma migrate deploy && npx prisma generate`,否则 schema 不同步
3. `pm2 restart` 默认不更新环境变量,需 `--update-env`(deploy.sh 已带)
### 9.2 数据质量(采集器只抓列表页)
| 字段 | 填写率(2026-07-16 生产统计 815 条) | 说明 |
|------|------|------|
| contact | 0% | 采集器未访问详情页 |
| phone | 0% | 同上 |
| quantity | 0% | 同上 |
| unit | 0% | 同上 |
| notes | 0% | 同上 |
当前应对:发布前弹窗手动补全。自动化提取(类似 notes-extractor 的详情页 AI 提取)**待实现**。
### 9.3 MiniMax / AI
- M2.7 输出噪音(`<think>`、`让我分析`、`**加粗**`)——所有 AI 调用都要清洗
- 生产 `.env` 已配 `MINIMAX_API_KEY` + `MINIMAX_MODEL`;本地 `.env` 需自行配置,否则走兜底
- 若 `MAX_COMPRESS_LEN` 等常量调整,注意 PROMPT 里 `'45'` 是字符串 replace,保持一致
### 9.4 采集器
- 中电建 state 文件 key:`systemId_招采公告`,**不要手动删除**
- 新矿集团脚本偶尔 `ReadTimeout`(日志见 Python traceback),不影响页面,重跑即可
- 新增采集器通用流程:放脚本到 `tender-fetcher/`,输出 `~/Desktop/招标公告/<date>/xxx_downloaded.json`,前端新建 CrawlerConfig(type=`bid_xxx`、sourceName、scriptPath),无需改代码
- cron 用 6 字段格式(兼容 cron@4.4.0)
### 9.5 MCP / B2B 发布
- `code:10021 "未获取到用户信息"`:产业平台修复中(创建 APIKey 已关联 companyUserId,后端直接使用)
- `paymentMethod` 传 "23" 报"企业字典找不到" → **不传**,后端用默认
- 字段类型必须是 int(freightFlag/procurementType/invoiceType),字符串会报错
- title 超 50 字已由 TitleCompressorService 前置处理
---
## 10. 当前工作区状态(未提交改动,2026-09-30)
> ⚠️ **接手第一件事:决定这些改动是否保留/提交/继续开发。**
### 已改文件(未 commit)
| 文件 | 改动内容 | 状态 |
|------|----------|------|
| `apps/api/src/lead/title-compressor.service.ts` | **新文件**:标题 AI 压缩服务(JSON 结构化输出方案) | ✅ 已实现+生产实测通过 |
| `apps/api/src/lead/lead.module.ts` | 注册 TitleCompressorService | ✅ |
| `apps/api/src/lead/lead.service.ts` | publishToDirev 集成标题压缩(>50 字先压缩再校验) | ✅ |
| `apps/api/src/crawler/crawler.service.ts` | resolveKeywords(品类→关键词展开)+ 每关键词独立状态文件 + 日志增强 | ✅ |
| `apps/web/src/components/InquiryPage.tsx` | 新增搜索框 fSearch → GET /api/leads?search= | ✅ |
| `apps/web/src/components/RuleCenterPage.tsx` | 采集器配置从 keywords 改为 **品类多选框**(categories JSON 数组) | ✅ |
| `scripts/tender-fetcher/fetch-v1.2.js` | 中电建脚本增强(过滤/回溯) | ✅ |
### 未跟踪文件
- `scripts/cleanup_garbage_categories.sql` — 清理 AI 垃圾品类
- `scripts/fix_corrupted_titles.sql` — 修复损坏标题
- `scripts/reclassify_leads.js` / `scripts/refetch_corrupted.js` — 历史数据维护
- `PRD-智能比价助手.md` — 比价功能 PRD(供外部平台参考)
### 工作区 vs 生产差异
- **本地有大量未提交改动,生产未部署**(生产 `.next/` 构建于 2026-09-03)
- 若需上线这些改动:`npm run typecheck` → `bash scripts/deploy.sh` → **生产重建 web `.next`**(见 9.1)
---
## 11. 生产部署与运维
- **服务器**:172.81.241.109(`SSH_HOST` 在 deploy.sh,如 IP 变更改这里)
- **SSH**:`sshpass -p 'Jubianshuzi@2026' ssh ubuntu@172.81.241.109`
- **一键部署**:`bash scripts/deploy.sh`(build + rsync dist/.next/源码 + pm2 restart --update-env)
- **路径**:代码 `/opt/pingtai/`,数据 `/data/pingtai/`(dev.db / uploads / library / 招标公告)
- **进程**:pm2 托管 `pingtai-api`(4000) + `pingtai-web`(3001),systemd 自启
- **反代**:Nginx 80/443 → 3001;`/api/*` → 4000;`/uploads/*` → `/data/pingtai/uploads/`
- **HTTPS**:Let's Encrypt,certbot 自动续期 timer 已开
- **常用操作**:
```bash
pm2 list / pm2 logs pingtai-api / pm2 restart pingtai-api --update-env
tail -f /var/log/pingtai/api.out-1.log
sqlite3 /data/pingtai/dev.db # 直接查 DB
tar czf pingtai-backup-$(date +%Y%m%d).tgz /data/pingtai/
```
- **历史包袱**:服务器上旧 Postgres/Redis 部署备份在 `/home/ubuntu/platform-assistant.bak-*`,旧 nginx conf 在 `/etc/nginx/conf.d/platform.conf.bak-*`,可回退
- **采集目录 symlink**:`~/desktop -> /data/pingtai`(必须指向 `/data/pingtai`,不是 `/data/pingtai/招标公告`,否则双层嵌套)
---
## 12. 待办事项 / 建议的下一步
1. **决策未提交改动**:title-compressor + crawler 品类化 + InquiryPage 搜索 + RuleCenterPage 品类多选框,是否继续/提交/部署
2. **修复生产页面 500**:若线上仍有 Server Action 报错,生产 `npx next build` 重建(见 9.1)
3. **详情页内容提取**(P1 数据质量):采集器入库后加类似 notes-extractor 的 AI 提取步骤,补 contact/phone/quantity/unit(当前全缺)
4. **空品类历史线索 review**:AdminPage 品类管理人工 review AI 新建的细分类(合并/删除)
5. **ReportCenterPage / SystemMonitorPage**:仍是 mock,可接真实数据
6. **比价模块增强**(参考 PRD-智能比价助手.md):报价有效期、供应商画像权重、多租户
7. **MCP 发布卡点跟进**:`code:10021` 等产业平台修复状态
---
## 13. 协作约定(对后续智能体)
- 语言:中文(对话/注释/文档/commit)
- **改动前必须对齐**:涉及具体修改按 AGENTS.md 协作流程(7 个技术维度:场景/触发/重要程度/数据/边界/样式/部署),不擅自开干
- 例外:拼写错误、单文件 typo、用户明确"按你判断做"、紧急 bug
- 改完必须端到端验证,不只"代码看起来对"
- 涉及数据库结构、生产配置、依赖升级要明确告知用户
- 提交信息用中文
---
**结束语:** 本项目最核心的链路是 **采集 → 分类 → 询价 → MCP 发布**,接手时建议先跑通这条链路的本地/生产实测,再动其他模块。祝顺利!