176 lines
12 KiB
Markdown
176 lines
12 KiB
Markdown
# 阿里国际站询盘数据采集与管理系统 · 项目介绍
|
||
|
||
> 基于 2026-08-27 代码现状整理(扩展 `d:\alibb` + 服务端 `d:\alibb-server`)。
|
||
> 部署细节见 [DEPLOY-ALIBB.md](DEPLOY-ALIBB.md),后端设计见 [superpowers/specs/2026-07-13-alibaba-inquiry-backend-design.md](superpowers/specs/2026-07-13-alibaba-inquiry-backend-design.md)。
|
||
|
||
## 1. 项目概述
|
||
|
||
本项目是一套**阿里国际站(alibaba.com)询盘数据的采集与管理系统**,由三个部分组成:
|
||
|
||
| 组成部分 | 位置 | 技术 | 角色 |
|
||
|---|---|---|---|
|
||
| **采集端** Chrome 扩展 | `d:\alibb`(本仓库) | Chrome MV3、原生 JS | 抓取询盘列表、客户资料、行为数据、聊天记录 |
|
||
| **服务端** 后端 API | `d:\alibb-server\backend` | Node.js + Express + TypeScript + Prisma + MySQL | 接收推送、幂等入库、提供查询/统计/导出 API |
|
||
| **服务端** 管理后台 | `d:\alibb-server\frontend` | Vue3 + Element Plus + Pinia + ECharts | 询盘列表/详情/聊天查看/统计报表 |
|
||
|
||
核心链路:
|
||
|
||
```
|
||
浏览器(登录阿里主账号)
|
||
Chrome 扩展 content.js
|
||
├─ 列表抓取(DOM 翻页)→ 详情抓取(DOM)→ 聊天抓取(阿里内部 API 直拉)
|
||
├─ 双写 A(本地):chrome.storage 汇聚 + customerId 去重 → CSV 导出
|
||
└─ 双写 B(推送):IndexedDB 队列 → alarm 每分钟消费 → POST /api/ingest(Bearer Token)
|
||
↓ 失败指数退避(≤8 次),成功出队
|
||
服务器(阿里云 ECS,1Panel + OpenResty + MySQL 8.4,:23010)
|
||
后端 zod 校验 → 幂等入库(同客户合并 / 消息级去重 / 早返回)
|
||
↓
|
||
管理员浏览器 → Vue3 后台(JWT 登录):列表筛选 / 聊天详情 / 统计 / CSV 导出
|
||
```
|
||
|
||
使用模式:主管理员账号一人操作扩展手动触发抓取(非实时同步),后台数据 = 最近一次抓取的快照;询盘记录的 `manager` 字段天然区分业务员,无需多人登录。
|
||
|
||
---
|
||
|
||
## 2. 采集端:Chrome MV3 扩展(本仓库)
|
||
|
||
### 2.1 文件结构
|
||
|
||
| 文件 | 行数/大小 | 职责 |
|
||
|---|---|---|
|
||
| [manifest.json](../manifest.json) | — | MV3 清单:权限 `storage/activeTab/alarms/unlimitedStorage/scripting`;host 覆盖 `message/alicrm/onetalk.alibaba.com`;`optional_host_permissions` 运行时授权任意后端地址 |
|
||
| [content.js](../content.js) | ~1300 行 | 核心:注入 `message.alibaba.com` 的抓取状态机 |
|
||
| [background.js](../background.js) | 145 行 | Service Worker:推送队列调度 + 本地数据汇聚(双写) |
|
||
| [push/queue.js](../push/queue.js) | — | IndexedDB 持久化队列(库 `inquiry_push`) |
|
||
| [push/worker.js](../push/worker.js) | — | 队列消费:POST `/api/ingest`,指数退避,8 次后标 failed |
|
||
| [push/payload.js](../push/payload.js) | — | 页面数据 → 后端 zod schema 的字段适配层 |
|
||
| [push/config.js](../push/config.js) | — | 推送配置(`chrome.storage.local`) |
|
||
| [popup.html](../popup.html) / [popup.js](../popup.js) | — | 操作界面:抓取控制 + 后端推送配置 |
|
||
| [tests/](../tests/) | 5 个文件 | vitest 单测(config/queue/payload/sync/worker,fake-indexeddb) |
|
||
| [scripts/](../scripts/) | — | `e2e-push.mjs`(推送端到端)、`verify-chat-range.mjs`、`verify-risk-detect.mjs` |
|
||
| [使用手册.html](../使用手册.html) | — | 面向操作者的图文五步指南 |
|
||
|
||
### 2.2 抓取状态机与断点续抓
|
||
|
||
[content.js](../content.js) 的核心是**跨页面持久化的状态机**:`state.phase ∈ idle | list | detail | paused | done`,存储于 `chrome.storage.local`(键 `alibaba_scraper_state`)。每跳转一个详情页 content script 就被销毁重建,靠恢复 `queueIndex` 继续队列 —— 整轮抓取可跨导航、可暂停恢复、可扩展重载续跑。
|
||
|
||
抓取编排由 `processCurrentDetailPage()` 完成:等待渲染 → 聊天抓取(API 优先)→ 详情提取 → **双写**(本地 `storeDetail` + 推送 `enqueue`)→ 跳下一条。
|
||
|
||
### 2.3 数据抓取能力
|
||
|
||
- **列表页**:DOM 解析 `a.inquiry-item`(询价单号/时间/买家/国家/消息预览/负责人/状态等 13 字段),100 条/页,自动翻页;
|
||
- **详情页**:客户资料(邮箱/电话/公司/注册时间等)、买家标签(8 词表)、买家特征(12 词表)、近 90 天行为数据(产品浏览/有效询盘/RFQ/登录天数/垃圾询盘/黑名单数)、店内行为与最近询盘产品;
|
||
- **聊天记录**(最复杂模块):
|
||
- **API 直拉主路径**:`querySummary.htm` 拿会话加密参数 → `getOpMessages.htm` 游标循环翻页(复用页面 cookie 凭据),单询盘实测 8000+ 条;实测全量 4576 询盘 + 68058 条消息零风控;
|
||
- **DOM 滚动回退**:API 失败时模拟 wheel 事件滚动 `.common-load-more` 懒加载;
|
||
- **同客户去重**:同买家多询盘共享同一会话,只抓首个(`seenChatCustomer`);
|
||
- **消息归一化**(`apiMsgToChat`):方向(incoming/outgoing/system)、富媒体(`img:`/`file:`/`video:` 前缀 + clouddisk 直链构造)、翻译(嵌套 JSON 双重 parse)。
|
||
|
||
### 2.4 反风控设计(贯穿全文件)
|
||
|
||
| 机制 | 说明 |
|
||
|---|---|
|
||
| 随机时延 | 翻页 1.2–3s、聊天每轮 2–5s、每 15 轮插入 30–90s"模拟走神" |
|
||
| 作息时段 | 默认 8–12 / 14–18 点工作,会话级 ±30 分钟抖动,非工作时段自动挂起 |
|
||
| 风控检测 | `RISK_KEYWORDS`(验证码/captcha/blocked 等)命中 → 暂停 + popup 红色横幅报警;只浅扫 API 响应顶层字段,不扫消息正文防误判 |
|
||
| 跳过策略 | 注销/管控账号(`SKIP_KEYWORDS`)直接跳过不暂停 |
|
||
| 增量水位 | 按 inquiryId 记录已抓最新消息(`lastTopMsg_*`),重抓只拉增量,只前进不倒退 |
|
||
| 时间范围 | 近 1/2/3 月或自定义起止;`until` 跳游标直接从截止时间往回翻,大幅减少请求量 —— 同时是风控减压手段 |
|
||
|
||
### 2.5 推送链路(可靠性)
|
||
|
||
```
|
||
content.js 抓完一条 → sendMessage 'enqueue' → SW 写 IndexedDB(不丢)
|
||
→ 立即 + alarm 'push-tick'(每 1 分钟)触发 processQueue()
|
||
→ 每批 3 条,POST {serverUrl}/api/ingest(Bearer Token)
|
||
→ 成功标 sent;失败指数退避(2^n 秒,封顶 1h),8 次后标 failed(popup 红字提示)
|
||
```
|
||
|
||
关键设计:worker **每次自己 `loadConfig()`**(无状态),因为 MV3 SW 休眠重启会清空模块级变量 —— 缓存配置会导致永不推送。
|
||
|
||
### 2.6 popup 界面
|
||
|
||
抓取模式(仅列表/列表+详情)、条数(全部/前10/前50)、customerId 去重开关、聊天上限、**聊天时间范围**(全部/近1-3月/自定义日期)、后端推送设置(服务器 URL + Token + 授权域名 + 启用开关,默认指向生产 `http://219.157.255.213:23010`)、实时推送状态条(已推/待推/失败,2s 刷新)、开始/暂停/恢复/导出 CSV/清空数据。
|
||
|
||
---
|
||
|
||
## 3. 服务端后端(d:\alibb-server\backend)
|
||
|
||
### 3.1 技术栈与结构
|
||
|
||
Express 4 + TypeScript + Prisma 5 + MySQL 8,zod 校验,Docker 两阶段构建(node:20-slim),容器启动自动 `prisma migrate deploy`。目录:`src/{routes,services,middleware,utils,config,lib}` + `prisma/` + `tests/`(vitest + testcontainers 真库集成测试)。
|
||
|
||
### 3.2 API 清单
|
||
|
||
| 方法 + 路径 | 鉴权 | 功能 |
|
||
|---|---|---|
|
||
| `GET /api/health` | 无 | 探活(DB 连通性) |
|
||
| `POST /api/ingest/` | Ingest Token + 60次/min 限流 | 扩展推送入口,幂等入库 |
|
||
| `POST /api/auth/login` / `refresh` | 无 / JWT | 管理员登录(bcrypt)、续签 |
|
||
| `GET /api/inquiries` | JWT | 列表:分页 + 国家/业务员/状态/时间筛选 + 关键词搜索 + 排序 |
|
||
| `GET /api/inquiries/:id` | JWT | 详情 + 客户 + 行为 + 消息数 |
|
||
| `GET /api/inquiries/:id/messages` | JWT | 聊天游标分页(复合键 `(sendTime, messageId)`,微信式向上翻页) |
|
||
| `GET /api/inquiries/:id/messages/export` | JWT | 单询盘聊天 CSV 流式导出(预留) |
|
||
| `GET /api/stats/overview` | JWT | 总数 / 国家 Top10 / 业务员分布 / 近 12 月趋势 |
|
||
| `GET /api/export` | JWT | 询盘列表 CSV 导出(21 列中文表头,与列表筛选一致) |
|
||
|
||
### 3.3 ingest 幂等核心(亮点)
|
||
|
||
1. **同客户合并**:同一 customerId 只保留一条"代表询盘"(最早创建),后续询盘追加进 `allInquiries` JSON 数组,消息统一归入代表 —— 不产生冗余行;
|
||
2. **消息级幂等**:按 `message_id` 唯一约束 `createMany skipDuplicates`,500 条/批,单批失败只计数不回滚(`has_failed_batches` 标记,下次推送强制重插);
|
||
3. **早返回优化**:"消息数 + 最大 sendTime + 无失败批次" 三项指纹未变 → 跳过批量插入直接返回,避免重复重写几千条消息;
|
||
4. customer upsert 用 `skipNulls`,本次没抓到的字段不覆盖已落库值。
|
||
|
||
### 3.4 数据模型(4 张表)
|
||
|
||
- **customers**:`customer_id` 唯一;资料 + 行为七字段(可空,区分"无数据"与"为 0")+ 店内行为 JSON 列;
|
||
- **inquiries**:`inquiry_id` 唯一;`all_inquiries`(同客户合并)、`messages_count`/`last_send_time`(幂等指纹)、`has_failed_batches`;
|
||
- **chat_messages**:`message_id` 唯一(幂等键);`content MEDIUMTEXT`;复合索引 `(inquiry_id, send_time, message_id)` 恰好覆盖游标分页;
|
||
- **users**:管理员(bcrypt,空表时按环境变量播种)。
|
||
|
||
### 3.5 鉴权
|
||
|
||
两套并行:扩展 → 静态 `INGEST_TOKEN`(`timingSafeEqual` 常量时间比较防时序攻击);后台 → JWT(HS256,7 天,登录签发)。
|
||
|
||
---
|
||
|
||
## 4. 管理后台前端(d:\alibb-server\frontend)
|
||
|
||
### 4.1 页面
|
||
|
||
| 路由 | 页面 | 功能要点 |
|
||
|---|---|---|
|
||
| `/login` | 登录 | JWT + open-redirect 防护 |
|
||
| `/inquiries` | 询盘列表 | 筛选栏(国家/业务员/状态/时间范围/搜索/排序)、表格分页、**CSV 导出复用当前筛选**;列表状态存 sessionStorage,进详情返回后恢复 |
|
||
| `/inquiries/:id` | 询盘详情 | 客户信息卡 + 行为数据卡(复刻阿里详情页行为区)+ **同会话多询盘列表** + 完整聊天 |
|
||
| `/stats` | 统计 | 数字卡(总数/本月新增)+ ECharts:国家 Top10 环形图、业务员柱状图、12 月趋势折线 |
|
||
|
||
### 4.2 聊天组件(核心体验)
|
||
|
||
- **虚拟滚动**(vue-virtual-scroller):几千条消息流畅渲染,可变高度气泡自动测量;
|
||
- **游标分页**:首屏拉最新一页自动滚到底,顶部按钮向上加载历史,prepend 后保持视觉位置不跳动;
|
||
- **微信式气泡**:incoming 左 / outgoing 右 / system 居中;消息类型协议(`img:`/`file:`/`video:`/纯链接/纯文本);
|
||
- **XSS 防护**:文本一律插值不用 v-html;富媒体 URL 走 alibaba 域名白名单,纯 URL 只许 http/https。
|
||
|
||
### 4.3 工程细节
|
||
|
||
axios 封装(401 全局跳登录、错误脱敏不含 token)、Pinia auth store(JWT 存 localStorage,MVP 可接受)、路由守卫、全套 vitest 单测、双 lint(oxlint + eslint)。
|
||
|
||
---
|
||
|
||
## 5. 部署与运维
|
||
|
||
生产环境(详见 [DEPLOY-ALIBB.md](DEPLOY-ALIBB.md)):
|
||
|
||
- 阿里云 ECS `219.157.255.213`(Ubuntu 26.04),对外端口 **23010**;
|
||
- 1Panel 面板管理:OpenResty(host 网络,站点 `alibaba`)→ SPA 静态 + `/api/*` 反代 → `alibb-backend` 容器(127.0.0.1:8090)→ 1Panel MySQL 8.4(库 `alibaba`,utf8mb4,仅本机可达);
|
||
- `client_max_body_size 50m` 配合大聊天 payload;备份:mysqldump → 本机 `D:\alibb-server\backups\`(权威)+ 服务器副本;
|
||
- 三份代码位置:扩展 `d:\alibb`(master)、后端 `d:\alibb-server\backend`(master)、前端 `d:\alibb-server\frontend`(main);服务器重装恢复 runbook 见 [superpowers/plans/](superpowers/plans/)。
|
||
|
||
## 6. 已知限制
|
||
|
||
- 客户资料**折叠面板字段抓不到**(阿里校验 `event.isTrusted` 只认真实鼠标点击),好在多数买家本就是 "-";
|
||
- 公网明文 HTTP(23010),内网/测试可接受,长期公网建议套域名 + HTTPS;
|
||
- 数据时效 = 上次手动抓取快照,需浏览器保持阿里页打开;
|
||
- 同 messageId 内容被上游修订时早返回会跳过更新(MVP 接受,重推主场景是新增消息)。
|