# 微信营销管理系统 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_` 格式 --- ## 三、模拟采集代理设计 ### 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 ├──