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

17 KiB
Raw Blame History

微信营销管理系统 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 表增加了 summarystageobjectionsnext_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 规则引擎 — 客户识别

输入:联系人 + 该联系人的全部消息

规则

  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 路由

# 仪表盘
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 交互流程

  1. 打开页面 → 加载仪表盘
  2. 点击"客户列表" → 加载客户列表
  3. 点击客户 → 切换到客户详情 → 加载基本信息 + 聊天记录 + 成交
  4. 点击"成交录入" → 表单 → 提交 → 刷新列表
  5. 点击"销售列表" → 加载销售统计

七、一键启动脚本设计

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/

完全独立,互不影响。