- HANDOVER.md: 完整交接文档(架构/数据模型/API/业务流/已知坑/工作区状态/待办) - PRD-智能比价助手.md: 比价功能产品需求文档(供外部平台参考) - 数据维护脚本: 清理AI垃圾品类SQL、修复损坏标题SQL、线索重分类/重抓脚本
449 lines
25 KiB
Markdown
449 lines
25 KiB
Markdown
# 平台助手 — 项目交接文档
|
||
|
||
**交接日期:** 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 发布**,接手时建议先跑通这条链路的本地/生产实测,再动其他模块。祝顺利!
|