platform-assistant/AGENTS.md
Sisyphus afac18e843 feat(F002): W26 + W29.x + W30 + W31 平台助手全部改动首次 commit
- W26: NestJS API + Next.js Web + Prisma + SQLite scaffold
- W26: Lead 模型 + Crawler + 询价发布助手 (线索池 + 一键发布)
- W29.x: MCP 集成 (Integration 模型 + connectMcp + publish_rfq 5 工具端到端跑通)
- W30: 3 字段补充 (notes / quotationType / quotationDemand)
- W31 P1: 自动填 enterpriseId / userId (Prisma companyUserId 字段 + get_app_identity tool + lead.service.ts publishArgs)
- W31: detail 详情页 + admin UI 集成管理 (OpenAppListView + AuditLogListView)

关联: ~/Documents/02-平台开发/产品开发/F002-W31-交接文档.md
GIT-AUDIT-REPORT-2026-06-24.md
2026-06-24 11:07:11 +08:00

13 KiB
Raw Blame History

AGENTS.md

语言:中文。所有对话、注释、文档、提交信息均使用中文。

项目结构

/
├── apps/
│   ├── web/          # Next.js 14 App Router + Tailwind CSS 3
│   └── api/          # NestJS 10 + Prisma ORM + SQLite
├── packages/
│   └── shared/       # 共享 TS 类型(编译型,无运行时依赖)
└── scripts/
    ├── parse_products.py     # 解析 Excel/CSV → JSON
    ├── tender-fetcher/       # 招标公告抓取脚本
    │   ├── fetch-v1.2.js          # 中电建
    │   ├── fetch-xkgroup.py       # 新矿集团
    │   ├── fetch-cnncecp.py       # 中核集团
    │   └── lead_evaluation_report.py
    └── direv-publish/        # direv 平台自动发布脚本(10 个 .py)

开发命令

npm run setup        # 首次:npm install + db:init
npm run dev          # 同时启动前端(3001)和后端(4000)
npm run dev:api      # 仅后端(nest start --watch,用 dist/ 编译产物)
npm run dev:web      # 仅前端
npm run db:init      # prisma generate → migrate → seed
npm run db:reset     # 删 db 文件后 db:init
npm run build        # nest build + next build
npm run lint         # eslint + next lint
npm run typecheck    # tsc --noEmit 所有包(无 root tsconfig,各包独立)
npm run dev:daemon   # nohup 后台启动(PID 写入 .dev-api.pid / .dev-web.pid)
npm run dev:stop     # kill PID 文件中记录的进程

端口速查 (本地开发)

端口 服务 启动方式
3001 平台助手 web (Next.js) npm run dev:web 或 npm run dev
4000 平台助手 API (NestJS) npm run dev:api 或 npm run dev
3002 devbox MCP server (Node.js) ssh devbox + PM2, 不在 Mac 本地

注意:3002 是 devbox 上的 MCP server 端口, 不是平台助手 web 端口。 打开浏览器访问平台助手用 http://localhost:3001/ (不是 3002)。

AdminPage.tsx / schema.prisma 里出现的 100.111.84.16:3002/mcp 是 MCP server 地址 (填到 ConfigTab 的"endpoint" 字段), 不是 web 入口。

后端陷阱

  • 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(含 generate + migrate + seed)
  • SQLite:apps/api/prisma/dev.db;种子数据(seed.ts):15 电缆型号 × 5 供应商 = 75 Product + 管理用户;crawlerConfig 保留 3 条(中电建 + 新矿集团 + 中核集团,中核默认 paused)
  • CrawlerService(新增):bid_powerchina 类型 → execSync('node fetch-v1.2.js');bid_xkgroup 类型 → execSync('python3 fetch-xkgroup.py')。脚本路径硬编码为 scripts/tender-fetcher/,结果从 ~/Desktop/招标公告/<date>/downloaded.json 读取,upsert 到 Lead 表
  • Lead 表 noticeId 从 summary 中提取时曾因 substr 字符/字节偏移混淆导致截断错误(+8 应为 +6)。提取公告 ID 统一用 substr(summary, instr(summary, '公告ID:') + 6, 32)
  • Lead 表 @@unique([source, noticeId]):去重键,CrawlerService.run() 用 upsert 写入,重复则更新 summary/detailUrl
  • Lead 表 detailUrl:独立字段,前端显示为可点击链接。脚本保存 id 后重构 URL(中电建用 r.id 而非 systemId;新矿需补 &userId=guest&accountId=2066845847806639104 认证参数)
  • 后端无测试框架

前端陷阱

  • SPA 无路由:page.tsx 用 useState<Page> 切换 14 个组件(含 LoginPage + Sidebar + Providers)。导航通过 onNavigate prop 传递 page 值,参数通过 props
  • API base:src/lib/apiBase.ts 暴露 API_BASE,构建期由 NEXT_PUBLIC_API_URL 注入。本地开发不设环境变量时回退到 http://localhost:4000,生产构建为 /api(Nginx 反代到 4000)
  • JWT 存 localStorage key auth,AuthProvider 启动时验证
  • 3000 端口被占用,next dev 自动 fallback 到 3001
  • 路径别名 @/ → src/
  • Prettier: noSemi, singleQuote, trailingComma: all, printWidth: 100
  • Tailwind 设计 Token:secondary(#0051d5), border-subtle(#E2E8F0), surface-container 系

前端数据对接状态

页面 状态
ComparisonPage ✅ GET api/comparison,POST upload/suggestion
ImagePage ✅ GET api/images,POST generate/parse(MiniMax 生图)
ProductPage ✅ GET/POST/PATCH/DELETE api/products + POST upload-image
OverviewPage ✅ Promise.all([GET api/leads/stats, GET api/leads])
InquiryPage ✅ GET api/leads,POST api/leads/{id}/publish
LeadDetailPage ✅ GET api/leads/{id},POST to-rfq/to-collab/publish/follow-up
RuleCenterPage 含"采集器配置"Tab:GET/POST/PUT api/crawlers,POST run/pause/resume
ReportCenterPage 纯 mock
SystemMonitorPage 纯 mock
AdminPage 已实现,5 个子区块(tenants/users/stats/configs/plans),configs 含 MCP 对接配置
LoginPage POST api/auth/login

演示账号

admin / admin123(username + password,不是 email;AGENTS.md 旧版写的 admin@platform.com 不对,后端 LoginDto 用 username 字段)

数据库模型

User, Lead(含 AI 评分/状态流转/跟进日志), Rule/RuleVersion, Report, AuditLog, CrawlerConfig(含 keywords/categories + cron schedule), Inquiry, Product(含 supplier/imageUrl), GeneratedImage

采集器关键事实

  • 调度基于 @nestjs/schedule + SchedulerRegistry,onModuleInit 时从 DB 加载 active 配置注册 cron
  • pause/resume/update 联动调度器的注册与注销
  • CrawlerConfig 的 schedule 字段存储 cron 表达式,前端可选:每4小时、每6小时、每天早8点、每天晚7点、手动
  • 运行采集器有即时反馈 Modal(请求完自动更新状态,也可"后台运行"关闭弹窗)
  • 脚本路径相对于运行时工作目录,实际为 scripts/tender-fetcher/
  • 通用化新增采集器:PowerChina 保持特殊处理(JSON 格式不同)。其他网站只需:
    1. 在 scripts/tender-fetcher/ 放脚本(如 fetch-xxx.py),输出 ~/desktop/招标公告/<date>/xxx_downloaded.json
    2. 前端"采集器配置"新建记录,type 填 bid_xxx,sourceName 填来源显示名,scriptPath 填脚本文件名
    3. 无需改代码,runGenericScript() 自动推断脚本和 JSON 文件路径
  • CrawlerConfig 新增 sourceName(线索来源显示名)和 scriptPath(脚本文件名,可选,默认从 type 推断)

生产部署(腾讯云 124.222.182.242 / direv.cloud)

  • 路径:/opt/pingtai/(代码) + /data/pingtai/(持久化数据,dev.db / uploads / library / 招标公告)
  • 进程:pm2 托管 pingtai-api(NestJS 4000)+ pingtai-web(Next.js 3001),systemd 自启(/etc/systemd/system/pm2-ubuntu.service)
  • 反代:Nginx 80/443 → 3001;/api/* → 4000;/uploads/* → /data/pingtai/uploads/ 静态直出
  • HTTPS:Let's Encrypt(certbot),自动续期 timer 已开
  • 本地重部署流程:
    1. npm run build 重新编译 api/web
    2. rsync 同步 dist/.next + 源码到 /opt/pingtai/
    3. pm2 restart pingtai-api pingtai-web(注意 pm2 restart 默认不更新环境变量,要 --update-env)
    4. Prisma 变更后必须先 npx prisma migrate deploy && npx prisma generate(在 apps/api/ 下)
  • 常见操作:
    • pm2 list / pm2 logs / pm2 restart pingtai-api
    • 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/
  • 历史包袱:服务器上旧的 5月22日 Postgres/Redis 部署已备份到 /home/ubuntu/platform-assistant.bak-*,旧 nginx conf 在 /etc/nginx/conf.d/platform.conf.bak-*,如需恢复可回退
  • 采集器脚本:scripts/tender-fetcher/ 写出的 ~/desktop/招标公告/ 通过 symlink ~/desktop -> /data/pingtai 落到持久化卷;symlink 必须指向 /data/pingtai(不是 /data/pingtai/招标公告),否则会双层嵌套

CODE MAP

Symbol Type Location Role
AppModule module apps/api/src/app.module.ts 根模块,导入 13 个子模块
bootstrap() function apps/api/src/main.ts NestJS 应用启动入口
page.tsx page apps/web/src/app/page.tsx SPA 根,useState 切换 14 页面
AuthProvider context apps/web/src/components/AuthProvider.tsx JWT 认证上下文
CrawlerService.run() method apps/api/src/crawler/crawler.service.ts 采集器入口,execSync 调度脚本
ImageService class apps/api/src/image/image.service.ts 三阶段生图(图库→Bing→AI)
runGenericScript() method apps/api/src/crawler/crawler.service.ts 通用化采集器调度
search_image.py script scripts/search_image.py Bing 搜索 top N 候选
process_image.py script scripts/process_image.py 去水印+裁剪+白底合成

product_network B2B 平台对接(2026-06-16 新增,MCP 主通道)

对接架构

平台助手 NestJS (4000)
    ↓ IntegrationService.callTool()
    ↓ MCP 协议 (Streamable HTTP)
    ↓ x-app-id + x-app-secret (多租户 APIKey, 替代旧的 x-api-key)
renren-mcp-server (Node.js, devbox:3002)
    ↓ 直调 Java 业务方法
    ↓
product_network 7 个 Java 服务

注: MCP server 端口 3002 是 devbox 开发机 上的端口, 不是平台助手本地端口 (3001/4000)。

设计原则

  • MCP 主通道:17 个 tool(询价 5 + 商品 4 + 比价 4 + 推理 4)
  • REST 兼容层:5 个端点(健康/兑换/订阅/订单/撤销)
  • APIKey 认证:Header x-api-key,AES-256-GCM 加密存储(密钥 ENCRYPT_KEY in .env,32 bytes hex)
  • 公司账号代理:每客户 1 个 sys_user (user_type=99)

关键代码路径

文件 说明
apps/api/prisma/schema.prisma Integration 表(MCP 连接配置)
apps/api/src/integration/integration.service.ts MCP client 管理(connect/listTools/callTool)
apps/api/src/integration/integration.controller.ts REST API(CRUD + test + connect)
apps/api/src/integration/integration.module.ts NestJS 模块
apps/api/src/lead/lead.service.ts publishToDirev() 调 MCP publish_rfq tool
apps/web/src/components/AdminPage.tsx → ConfigTab MCP 对接配置(嵌入系统配置页面)

使用流程

  1. 前端「运营管理 → 系统配置」页面顶部"MCP 对接配置"区域
  2. 点击「新增对接」,填入:名称、MCP Server 地址、APIKey、客户编码
  3. 点击「连接」验证 MCP server 可达并获取工具列表
  4. 发布询价时,自动通过 MCP 调用 publish_rfq tool

MCP Client 实现

// integration.service.ts 核心逻辑
const client = new Client({ name: 'platform-assistant', version: '1.0.0' })
const transport = new StreamableHTTPClientTransport(
  new URL(endpoint),
  { requestInit: { headers: { 'x-api-key': apiKey } } }
)
await client.connect(transport)
const tools = await client.listTools()
await client.callTool({ name: 'publish_rfq', arguments: { title, ... } })

API 端点

方法 路径 说明
GET /api/integrations 列表
POST /api/integrations 创建
PUT /api/integrations/:id 更新
DELETE /api/integrations/:id 删除
POST /api/integrations/:id/connect 连接 MCP
POST /api/integrations/:id/disconnect 断开
POST /api/integrations/test 测试连接
POST /api/integrations/:id/call-tool 调用工具

注意事项

  • MCP server 地址格式:http://host:port/mcp(Streamable HTTP transport)
  • APIKey 通过 Header x-api-key 传递
  • 客户编码通过 Header x-customer-code 传递(可选)
  • APIKey 加密:AES-256-GCM,存 DB 为 base64 密文(iv12+tag16+ciphertext),读 API 时自动解密。旧明文数据读取时 fallback 不加密(兼容)
  • 连接状态存储在数据库,服务重启后需重新连接
  • 发布失败降级为本地模式(生成假 inquiryNo)

ANTI-PATTERNS

  • 前端不加路由库,保持 SPA useState 模式
  • 不提交 secrets 到 git(JWT_SECRET 已在 .env.production,但 .gitignore 未排除)
  • 不删除旧采集器 state 文件(中电建用 systemId_招采公告 作 key)
  • 不依赖 Postgres/Redis(docker-compose.yml 是历史遗留,实际只用 SQLite)
  • 不在后端代码中硬编码脚本路径以外的文件路径