Files
2026-08-27 16:12:37 +08:00

176 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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-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 接受,重推主场景是新增消息)。