5926c73ff1
- 数据库: PostgreSQL schema + 3名销售/21客户/341消息/6成交 - 采集代理: mock_sync.py 模拟微信聊天同步 - AI分析: analyze.py 规则模式 + 千问LLM模式 - 后端: FastAPI 11个API接口 - 前端: 仪表盘/销售列表/客户列表/成交记录/交流分析/录入成交 - 交流分析: 全部客户对话概览 + LLM标准范式对话生成 - 部署: systemd + nginx, 已部署至 sale.all8ai.top
17 KiB
17 KiB
微信营销管理系统 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
-- 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 场景模板结构
每个场景模板定义一段完整的客户对话:
{
"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 规则引擎 — 客户识别
输入:联系人 + 该联系人的全部消息
规则:
- 消息数 ≤ 5 →
is_customer = false - 关键词匹配(产品咨询类):
- 症状词:湿疹、过敏、鼻炎、腹泻、便秘、免疫力、体质、红疹
- 产品词:益生菌、菌株、进口、配方、成分、丹麦、鼠李糖、无敏
- 价格词:价格、多少钱、费用、报价、优惠、套餐、盒、周期
- 成交词:买、下单、付款、发货、试试、来几盒、定了
- 命中 ≥ 2 个关键词 →
is_customer = true
- 行业推断:toC 场景统一标记为"宝妈"
- 意向等级:
- high:提到成交词 + 价格词 ≥ 1
- medium:提到症状词 + 产品词,有价格讨论
- low:只有一般咨询,无价格讨论
- none:无业务关键词
4.3 规则引擎 — 沟通摘要
输入:客户会话的全部消息
规则:
- 阶段判断:
- 成交:包含"定了""下单""付款""发货""来几盒""试试""买"
- 报价:包含"多少钱""价格""套餐""298""798""1499"
- 异议处理:包含"贵""考虑""商量""副作用""安全""效果""对比""合生元"
- 需求发现:包含"湿疹""过敏""鼻炎""症状""多大""几个月"
- 建立联系:消息少于 5 条且为初次沟通
- 异议提取:匹配"贵""太贵""考虑""商量""老公""副作用""安全吗""有没有效""没用过""合生元""对比"
- 摘要生成:按时间顺序提取关键消息,拼接为摘要文本
- 下一步建议:根据阶段和最后一条消息内容推断
4.4 LLM 模式(可选)
当 --mode llm 时,调用本地 Ollama:
- 模型:qwen2.5(或本机已有的模型)
- 输入:最近 50 条消息
- 输出:JSON 结构化结果
- 超时:30 秒/请求
- 失败回退到规则模式
五、Web 后端设计
5.1 FastAPI 路由
# 仪表盘
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/ 目录:
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 交互流程
- 打开页面 → 加载仪表盘
- 点击"客户列表" → 加载客户列表
- 点击客户 → 切换到客户详情 → 加载基本信息 + 聊天记录 + 成交
- 点击"成交录入" → 表单 → 提交 → 刷新列表
- 点击"销售列表" → 加载销售统计
七、一键启动脚本设计
7.1 run_demo.sh 流程
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 流程
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/ |
完全独立,互不影响。