Files
wxsales/demo/1-design.md
T
freedakgmail 5926c73ff1 微信营销管理系统 - 益童宝销售管理平台
- 数据库: PostgreSQL schema + 3名销售/21客户/341消息/6成交
- 采集代理: mock_sync.py 模拟微信聊天同步
- AI分析: analyze.py 规则模式 + 千问LLM模式
- 后端: FastAPI 11个API接口
- 前端: 仪表盘/销售列表/客户列表/成交记录/交流分析/录入成交
- 交流分析: 全部客户对话概览 + LLM标准范式对话生成
- 部署: systemd + nginx, 已部署至 sale.all8ai.top
2026-07-14 22:31:29 +08:00

477 lines
17 KiB
Markdown
Raw 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.
# 微信营销管理系统 MVP 技术设计
> 文档日期:2026-07-14
> 版本:v0.1
> 依赖:0-req.md
---
## 一、系统架构
### 1.1 部署拓扑
单机部署,所有组件运行在本机 macOS 上。
```
┌──────────────────────────────────────────────────────────┐
│ 本机 macOS │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ mock_sync │ │ mock_sync │ │ mock_sync │ │
│ │ 销售A (id=1) │ │ 销售B (id=2) │ │ 销售C (id=3) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ PostgreSQL 17 (端口 5434) │ │
│ │ 数据库: wxchat_sales │ │
│ │ socket: demo/db/socket │ │
│ └──────────────────┬───────────────────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌─────────┐ ┌────────────┐ │
│ │ analyze.py│ │ server │ │ index.html │ │
│ │ AI分析 │ │ FastAPI │ │ 前端页面 │ │
│ └───────────┘ └────┬────┘ └────────────┘ │
│ │ │
│ ▼ │
│ http://127.0.0.1:8770 │
└──────────────────────────────────────────────────────────┘
```
### 1.2 端口分配
| 服务 | 端口 | 说明 |
|---|---|---|
| PostgreSQL | 5434 | 避开现有 5433 |
| FastAPI + 前端 | 8770 | 避开现有 8765 |
### 1.3 目录结构
```
wxsales/demo/
├── 0-req.md # 需求规格
├── 1-design.md # 本文档
├── 2-impl.md # 实施计划
├── 3-data-spec.md # 模拟数据规格
├── README.md # 项目说明
├── requirements.txt # Python 依赖
├── run_demo.sh # 一键启动
├── stop_demo.sh # 一键停止
├── db/
│ ├── schema.sql # 建表 SQL
│ ├── init_db.sh # 数据库初始化脚本
│ ├── socket/ # Unix socket 目录
│ └── data/ # PostgreSQL 数据目录(自动生成)
├── agent/
│ └── mock_sync.py # 模拟采集代理
├── ai/
│ └── analyze.py # AI 分析服务(规则引擎 + 可选 LLM)
└── web/
├── server.py # FastAPI 后端
└── static/
└── index.html # 单页前端
```
---
## 二、数据库设计
### 2.1 ER 关系
```
salesperson 1───* contact 1───* conversation 1───* message
│ │
│ 1───* customer *───1
│ │
│ 1───* deal *───1
```
### 2.2 完整建表 SQL
```sql
-- salesperson
CREATE TABLE salesperson (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
team TEXT,
wx_account TEXT NOT NULL,
device_id TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
-- contact
CREATE TABLE contact (
id SERIAL PRIMARY KEY,
salesperson_id INT NOT NULL REFERENCES salesperson(id),
wx_username TEXT NOT NULL,
nickname TEXT,
remark TEXT,
display_name TEXT NOT NULL,
is_group BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(salesperson_id, wx_username)
);
-- conversation
CREATE TABLE conversation (
id SERIAL PRIMARY KEY,
salesperson_id INT NOT NULL REFERENCES salesperson(id),
contact_id INT REFERENCES contact(id),
wx_identifier TEXT NOT NULL,
conv_type TEXT NOT NULL DEFAULT 'single',
last_synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(salesperson_id, wx_identifier)
);
-- message
CREATE TABLE message (
id BIGSERIAL PRIMARY KEY,
salesperson_id INT NOT NULL REFERENCES salesperson(id),
conversation_id INT NOT NULL REFERENCES conversation(id),
sender_wx_username TEXT NOT NULL,
sender_display_name TEXT,
message_type TEXT NOT NULL,
raw_content TEXT,
normalized_content TEXT,
created_at TIMESTAMPTZ NOT NULL,
source_shard TEXT NOT NULL,
source_table TEXT NOT NULL,
source_local_id BIGINT NOT NULL,
UNIQUE(source_shard, source_table, source_local_id)
);
-- customer
CREATE TABLE customer (
id SERIAL PRIMARY KEY,
salesperson_id INT NOT NULL REFERENCES salesperson(id),
contact_id INT NOT NULL REFERENCES contact(id),
customer_name TEXT,
industry TEXT,
intent_level TEXT,
key_needs TEXT[],
reason TEXT,
summary TEXT,
stage TEXT,
objections TEXT[],
next_action TEXT,
last_analysis TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(salesperson_id, contact_id)
);
-- deal
CREATE TABLE deal (
id SERIAL PRIMARY KEY,
salesperson_id INT NOT NULL REFERENCES salesperson(id),
customer_id INT REFERENCES customer(id),
contact_id INT REFERENCES contact(id),
product_name TEXT NOT NULL,
amount NUMERIC(12,2) NOT NULL,
deal_date DATE NOT NULL,
status TEXT NOT NULL DEFAULT 'closed',
notes TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 索引
CREATE INDEX idx_msg_conv_time ON message(conversation_id, created_at);
CREATE INDEX idx_msg_salesperson ON message(salesperson_id);
CREATE INDEX idx_customer_salesperson ON customer(salesperson_id);
CREATE INDEX idx_deal_salesperson ON deal(salesperson_id);
CREATE INDEX idx_deal_customer ON deal(customer_id);
```
### 2.3 与 MVP.md 的差异
- `conversation` 表增加 `wx_identifier` 字段作为唯一约束的一部分,因为同一销售可能有多个会话指向同一联系人
- `customer` 表增加了 `summary``stage``objections``next_action` 字段,用于存储沟通摘要分析结果(FR-4)
- `message` 表的 `source_shard` 在模拟环境中用 `mock_<salesperson_id>` 格式
---
## 三、模拟采集代理设计
### 3.1 模块结构
```
mock_sync.py
├── main() # 入口,解析参数
├── generate_contacts() # 生成模拟联系人
├── generate_conversations() # 生成模拟会话
├── generate_messages() # 生成模拟消息(基于场景模板)
├── sync_to_central() # 写入中央 PostgreSQL
└── SCENARIOS # 预设销售场景模板
```
### 3.2 场景模板结构
每个场景模板定义一段完整的客户对话:
```python
{
"name": "湿疹宝宝咨询后成交",
"stage": "成交",
"contact": {
"nickname": "辰辰妈妈",
"remark": "辰辰妈-湿疹-2岁",
"industry": "宝妈",
"intent_level": "high",
},
"messages": [
{"sender": "customer", "type": "text", "content": "你好,我家宝宝2岁,湿疹反复好几个月了,朋友推荐你这边益生菌"},
{"sender": "sales", "type": "text", "content": "辰辰妈您好!宝宝湿疹确实让人心疼,请问现在湿疹主要在哪些部位?有用过什么药吗?"},
{"sender": "customer", "type": "text", "content": "脸上和手臂都有,医生开了激素药膏,但停了就复发"},
{"sender": "sales", "type": "text", "content": "理解,激素药膏只能暂时压制。益生菌是从肠道调节免疫,从根本上降低过敏反应。我们用的是丹麦进口的鼠李糖乳杆菌,有专门针对儿童湿疹的临床验证"},
{"sender": "sales", "type": "image", "content": "[图片:临床验证报告截图]"},
{"sender": "customer", "type": "text", "content": "这个是进口的?安全吗?2岁能吃吗?"},
{"sender": "sales", "type": "text", "content": "是的,丹麦进口菌株,0岁以上就能用,无敏配方,不含牛奶蛋白和麸质。很多宝妈反馈坚持吃2-3个月湿疹明显好转"},
{"sender": "customer", "type": "text", "content": "多少钱?怎么卖的?"},
{"sender": "sales", "type": "text", "content": "单盒298元30袋,建议先吃3盒一个周期,3盒套餐798元算下来每天不到9块钱"},
{"sender": "sales", "type": "image", "content": "[图片:产品包装图]"},
{"sender": "customer", "type": "text", "content": "3盒798是吧,效果不好怎么办?"},
{"sender": "sales", "type": "text", "content": "我们有售后指导,期间有任何问题随时找我。另外我发您几个同情况宝妈的反馈看看"},
{"sender": "sales", "type": "link", "content": "[链接:宝妈真实反馈合集]"},
{"sender": "customer", "type": "text", "content": "好的,那先来3盒试试"},
{"sender": "sales", "type": "text", "content": "好的辰辰妈!3盒套餐798元,您方便现在付款吗?我这边给您安排发货"},
]
}
```
### 3.3 增量同步机制
- 每次运行使用递增的 `source_local_id`
- `UNIQUE(source_shard, source_table, source_local_id)` 保证不重复
- `source_shard` = `mock_{salesperson_id}`
- `source_table` = `Msg_{conversation_wx_identifier_hash}`
- 消息时间从基准时间开始递增,每次运行追加新消息
### 3.4 非客户联系人模板
生成广告、社交等非客户联系人,消息内容为:
- 广告推送:"【XX商城】年中大促..."
- 社交寒暄:"在吗?""最近怎么样"
- 群聊通知:系统消息、转发内容
---
## 四、AI 分析服务设计
### 4.1 双模式架构
```
analyze.py
├── analyze_customers() # 客户识别
│ ├── rule_mode() # 规则引擎(默认)
│ └── llm_mode() # LLM 模式(可选)
├── analyze_summaries() # 沟通摘要
│ ├── rule_mode() # 规则引擎(默认)
│ └── llm_mode() # LLM 模式(可选)
└── main() # 入口
```
### 4.2 规则引擎 — 客户识别
**输入**:联系人 + 该联系人的全部消息
**规则**
1. 消息数 ≤ 5 → `is_customer = false`
2. 关键词匹配(产品咨询类):
- 症状词:湿疹、过敏、鼻炎、腹泻、便秘、免疫力、体质、红疹
- 产品词:益生菌、菌株、进口、配方、成分、丹麦、鼠李糖、无敏
- 价格词:价格、多少钱、费用、报价、优惠、套餐、盒、周期
- 成交词:买、下单、付款、发货、试试、来几盒、定了
- 命中 ≥ 2 个关键词 → `is_customer = true`
3. 行业推断:toC 场景统一标记为"宝妈"
4. 意向等级:
- high:提到成交词 + 价格词 ≥ 1
- medium:提到症状词 + 产品词,有价格讨论
- low:只有一般咨询,无价格讨论
- none:无业务关键词
### 4.3 规则引擎 — 沟通摘要
**输入**:客户会话的全部消息
**规则**
1. 阶段判断:
- 成交:包含"定了""下单""付款""发货""来几盒""试试""买"
- 报价:包含"多少钱""价格""套餐""298""798""1499"
- 异议处理:包含"贵""考虑""商量""副作用""安全""效果""对比""合生元"
- 需求发现:包含"湿疹""过敏""鼻炎""症状""多大""几个月"
- 建立联系:消息少于 5 条且为初次沟通
2. 异议提取:匹配"贵""太贵""考虑""商量""老公""副作用""安全吗""有没有效""没用过""合生元""对比"
3. 摘要生成:按时间顺序提取关键消息,拼接为摘要文本
4. 下一步建议:根据阶段和最后一条消息内容推断
### 4.4 LLM 模式(可选)
`--mode llm` 时,调用本地 Ollama
- 模型:qwen2.5(或本机已有的模型)
- 输入:最近 50 条消息
- 输出:JSON 结构化结果
- 超时:30 秒/请求
- 失败回退到规则模式
---
## 五、Web 后端设计
### 5.1 FastAPI 路由
```python
# 仪表盘
GET /api/dashboard
{total_messages, active_customers, monthly_deal_amount, salespersons: [...]}
# 销售列表
GET /api/salespersons
[{id, name, team, wx_account, message_count, customer_count, deal_amount, last_synced_at}]
# 客户列表
GET /api/customers?salesperson_id=&intent_level=
[{id, customer_name, industry, intent_level, last_analysis, contact_display_name}]
# 客户详情
GET /api/customers/{id}
{id, customer_name, industry, intent_level, key_needs, summary, stage, objections, next_action, ...}
# 客户聊天记录
GET /api/customers/{id}/messages?page=1&page_size=50
[{id, sender_display_name, message_type, normalized_content, created_at}]
# 客户成交
GET /api/customers/{id}/deals
[{id, product_name, amount, deal_date, status}]
# 成交录入
POST /api/deals
body: {salesperson_id, customer_id, product_name, amount, deal_date, notes?}
# 成交列表
GET /api/deals?salesperson_id=&start_date=&end_date=
[{id, salesperson_name, customer_name, product_name, amount, deal_date, status}]
# 触发分析
POST /api/analyze
body: {mode: "rule"|"llm"}
{customers_analyzed, summaries_generated}
# 同步状态
GET /api/sync/status
[{salesperson_id, name, last_synced_at, message_count}]
```
### 5.2 静态文件托管
FastAPI 直接托管 `web/static/` 目录:
```python
app.mount("/", StaticFiles(directory="web/static", html=True))
```
API 路由挂在 `/api/*` 前缀下,前端页面通过 fetch 调用。
---
## 六、Web 前端设计
### 6.1 技术选型
- 单页 HTML + TailwindCSS CDN
- 原生 JavaScript,无框架
- Chart.js CDN(仪表盘图表)
### 6.2 页面结构
```
index.html
├── <nav> 顶部导航
│ ├── 仪表盘
│ ├── 销售列表
│ ├── 客户列表
│ └── 成交记录
├── <main> 内容区(SPA 切换)
│ ├── #dashboard-panel
│ ├── #salespersons-panel
│ ├── #customers-panel
│ ├── #customer-detail-panel(含聊天记录 + 成交)
│ ├── #deals-form-panel
│ └── #deals-list-panel
└── <script> 路由 + 数据加载
```
### 6.3 交互流程
1. 打开页面 → 加载仪表盘
2. 点击"客户列表" → 加载客户列表
3. 点击客户 → 切换到客户详情 → 加载基本信息 + 聊天记录 + 成交
4. 点击"成交录入" → 表单 → 提交 → 刷新列表
5. 点击"销售列表" → 加载销售统计
---
## 七、一键启动脚本设计
### 7.1 run_demo.sh 流程
```bash
1. 检查 PostgreSQL 17 是否安装
2. 启动 PostgreSQL(端口 5434,数据目录 demo/db/data
3. 等待数据库就绪
4. 执行 schema.sql 建表
5. 插入 3 名预置销售
6. 运行 mock_sync.py --salesperson-id 1
7. 运行 mock_sync.py --salesperson-id 2
8. 运行 mock_sync.py --salesperson-id 3
9. 运行 analyze.py --mode rule
10. 启动 FastAPI (uvicorn) 后台运行
11. 输出 http://127.0.0.1:8770
```
### 7.2 stop_demo.sh 流程
```bash
1. 停止 uvicorn 进程
2. 停止 PostgreSQL
3. 清理 PID 文件
```
### 7.3 幂等保证
- 数据库已存在则跳过 initdb
- 表已存在则跳过建表(`CREATE TABLE IF NOT EXISTS`
- 销售记录用 `INSERT ... ON CONFLICT DO NOTHING`
- mock_sync 天然增量(UNIQUE 约束)
- analyze 用 `last_analysis IS NULL` 过滤
---
## 八、错误处理
| 场景 | 处理方式 |
|---|---|
| PostgreSQL 未安装 | 脚本报错并提示安装命令 |
| 端口被占用 | 脚本检测并提示 |
| mock_sync 连接失败 | 打印错误并退出,不影响其他销售 |
| analyze 无客户可分析 | 打印提示,正常退出 |
| FastAPI 启动失败 | 打印端口冲突提示 |
| 前端 API 调用失败 | 显示错误提示,不白屏 |
---
## 九、与现有系统的隔离
| 维度 | 现有系统 | 本演示系统 |
|---|---|---|
| 数据目录 | `ni/work/postgres/data` | `demo/db/data` |
| 数据库名 | `wechat_knowledge` | `wxchat_sales` |
| 端口 | 5433 | 5434 |
| Socket | `ni/work/postgres/socket` | `demo/db/socket` |
| Web 端口 | 8765 | 8770 |
| 代码目录 | `ni/work/` | `demo/` |
完全独立,互不影响。