22 KiB
22 KiB
AI 数字销售助手 — AI 开发者快速指南
本文档面向 AI 编程助手,帮助你在最短时间内理解项目全貌并继续开发。
项目目标(务必理解)
构建一个能完全模仿真人销售在微信上与客户对话的 AI 系统。
核心差异:
- 数字销售分身:上传销售真实聊天记录,AI 提取其个人风格(方言/句式/语气/性格),构建"数字分身"
- 四维画像驱动:基于 销售风格 / 项目资料 / 聊天技巧 / 客户画像 综合理解场景后生成回复
- 自主运营:自动回复(按客户等级分策略)、主动触达(生日/活动)、画像自迭代、等级动态评估
- 最终愿景:销售只处理 AI 搞不定的关键对话,日常维护/跟进/促单全部由 AI 代劳
你写的每一行代码都应服务于:让 AI 更像那个销售、更懂那个客户、更专业地推进销售进程。
技术栈
Go 1.26 + Fiber v2 + GORM(MySQL) + MongoDB + Redis + Wire DI + 原生前端(零构建)
调用链:
HTTP → Fiber中间件(JWT) → Router → Service → Biz → Impl/Mongo → LLM
↓
registerCommon 统一包装 {code:0, message, data}
目录结构(★ 标注当前活跃开发区域)
ai_scheduler/
├── cmd/server/
│ ├── main.go # 启动入口
│ ├── wire.go # Wire DI 声明(//go:build wireinject)
│ └── wire_gen.go # Wire 自动生成(禁止手动编辑)
├── config/config.yaml # MySQL/Mongo/Redis/JWT/微信协议地址
├── internal/
│ ├── biz/ # ★ 业务逻辑层
│ │ ├── advice_wx.go # 微信回调处理 + 消息流水 + 会话聚合 + ★ 托管自动注册
│ │ ├── advice_wx_send.go # 微信消息发送(含模拟真人打字延迟)
│ │ ├── advice_strategy.go # 智能策略(四级回复/主动对话)
│ │ ├── advice_client.go # 客户管理 + 等级评估
│ │ ├── advice_customer_new.go # ★ 微信好友 CRUD + 差集同步(ai_advice_customer)
│ │ ├── advice_label.go # ★ 标签同步(与 DB 比对增删改)
│ │ ├── advice_chat.go # AI 对话(LLM 调用 + ChatWithSession 复用优化)
│ │ ├── advice_evaluate.go # 客户等级评估
│ │ ├── advice_proactive.go # 主动触达
│ │ ├── advice_iterate.go # 画像自迭代
│ │ ├── handle/、llm_service/ # ⚠️ 历史死代码,忽略
│ │ └── provider_set.go
│ ├── services/advice/ # ★ HTTP Handler 层
│ │ ├── wxhook.go # 回调 + 会话 Handler
│ │ ├── wx_proxy.go # 微信 API 代理(分发到 wxDispatchMap)
│ │ ├── wx_ws_hub.go # WebSocket Hub(实时推送)
│ │ ├── customer_new.go # ★ 好友 CRUD Handler(Diff 同时返回标签)
│ │ ├── label.go # ★ 标签 Handler(List/Sync)
│ │ └── provider_set.go
│ ├── server/
│ │ ├── router/router.go # 路由总装 + registerCommon 中间件
│ │ ├── router/advicer.go # 全部业务路由(~80 个接口)
│ │ └── http.go # NewHTTPServer 参数列表需同步
│ ├── entitys/advicer_data.go # ★ 核心 DTO(最频繁修改)
│ ├── data/
│ │ ├── model/ # MySQL GORM 模型(*.gen.go 禁止手动编辑)
│ │ │ ├── ai_advice_customer.gen.go # ★ 微信好友表
│ │ │ └── ai_advice_label.gen.go # ★ 标签表
│ │ ├── impl/ # MySQL 数据访问(继承 DataTempBase[T])
│ │ │ ├── advice_customer_impl.go
│ │ │ └── advice_label_impl.go
│ │ ├── mongo_model/ # MongoDB 集合模型
│ │ └── constants/ # Prompt 模板、模型映射
│ ├── middleware/ # JWT 认证
│ ├── jobs/advice_task.go # 定时任务(cron)
│ └── pkg/wx/ # 微信协议封装(100+ API 端点)
├── web/ # ★ 前端(零构建,静态托管)
│ ├── project.html # ★ 版本号管理(修改 JS 后必须更新 ?v=N)
│ └── assets/
│ ├── css/theme.css # 深色科技风设计系统
│ └── js/
│ ├── core.js # 核心库(Core.api / Core.wxApi / Toast / Modal)
│ ├── wx.js # ★ 微信工作台核心(state/通讯录/标签/WS/好友同步)
│ ├── wx_login.js # ★ 登录模块(二维码/在线检测/登出)
│ └── wx_chat.js # ★ 聊天模块(会话/消息/联系人详情气泡)
└── sql/ # 数据库 DDL
├── 04_ai_advice_customer.sql
└── 05_ai_advice_label.sql
数据模型
MySQL 核心表
| 表名 | 说明 | 关键字段 |
|---|---|---|
ai_advice_customer |
★ 微信好友(self_wxid + user_name 唯一键) |
label_list, session_id(当前活跃会话) |
ai_advice_label |
★ 微信标签(self_wxid + label_id 唯一键) |
label_id, label_name |
ai_advice_advicer |
销售基本信息 | wx_device_id(即 appId) |
ai_advice_project |
项目信息 | wx_token |
ai_advice_admin |
管理员账号 | |
ai_advice_session |
AI 对话会话 | advicer_id(托管场景动态取最新版本) |
MongoDB 核心集合
| 集合名 | 说明 | 关键字段 |
|---|---|---|
advicer_wx_msg |
★ 微信消息流水 | appId, selfWxid, wxid, direction, msgType, content, read |
advicer_version |
销售画像 | dialectFeatures, sentencePatterns, toneTags, personalityTags |
advicer_client |
客户画像 | clientLevel, personalInfo, purchasePurpose, coreDemands |
advicer_project |
项目资料维度(结构化) | regionValue, competitionComparison, coreSellingPoints, supportingFacilities, developerBacking |
advicer_project_data |
★ 项目资料(扁平栏目) | projectId + 动态栏目(key=栏目名, value=内容),与 advicer_project 是两个独立的集合 |
advicer_talk_skill |
聊天技巧维度 | needsMining, painPointResponse, closingTechniques, communicationRhythm, valueBuilding |
advicer_activity |
产品活动 | name, content, startAt, endAt |
命名差异
- MySQL:蛇形(
user_name) - MongoDB:驼峰 bson(
userName) - 前端 JS:驼峰(
userName) - API 响应:snake_case(
user_name),前端手动映射
前端架构
核心机制
- 零构建:
<script>标签按序引入,无打包工具 - IIFE 模式:每个 JS 文件
(function(global){...})(window)包裹 - 版本号:
<script src="xxx.js?v=42">— 修改 JS/CSS 后必须更新project.html中的版本号 - 全局命名空间:
Core(核心库)、WxN(微信模块共享)、Wx(挂载点)
微信工作台模块(WxN 命名空间)
| 文件 | 职责 |
|---|---|
wx.js |
state 管理 / 生命周期 / 通讯录 / 标签 / WebSocket / 好友差集同步 |
wx_login.js |
二维码登录 / 在线检测 / 登出 |
wx_chat.js |
会话列表 / 消息渲染 / 发送 / 联系人详情悬浮气泡 |
架构要点:
wx.js创建WxN命名空间,通过Object.defineProperty暴露stategetter- 子模块通过
WxN.state访问最新状态(不在闭包中缓存 state 引用) - 子模块通过
WxN.xxx = fn导出函数 - 加载顺序:
wx.js→wx_login.js→wx_chat.js
Core.js 速查
Core.api(path, params) // 管理后台 API(POST JSON,JWT,自动解包 data)
// 完整路径:/api/v1/admin/advice/admin/{path}
Core.wxApi(path, params) // 微信代理 API(自动注入 projectId)
// 完整路径:/api/v1/admin/advice/admin/wx/{path}
Core.toast(msg, type) // type: "ok"|"err"|"warn"|"info"
Core.modal({title, body}) // 模态框
Core.confirm(msg) // 确认对话框,返回 Promise<boolean>
Core.esc(str) // HTML 转义
关键流程
初始化流程
页面加载 → mount() → freshState()
→ 解析 URL hash (#wx-advicer=advicerId)
→ fetchDeviceAndCheck(advicerId)
→ Core.api("advicer/info", {advicerId}) 获取 wx_device_id
→ state.app = { appId: wx_device_id } ← 始终从 API 获取,不缓存
→ checkOnline()
→ 在线 → checkOnlineLoop()(内含 connectWs())
loadProfile().then(loadConversations()) ← 需要 wxid
loadContacts() → syncCustomerDiff() ← diff 同时返回标签
loadLabels() ← 与微信同步标签到 DB
→ 离线 → getQrCode() 显示二维码
好友 + 标签同步流程
loadContacts() → syncCustomerDiff()
→ POST advicer/customer/diff { selfWxid, userNames: friends }
→ 后端返回 { list: [已有好友], diff: [缺失userName], labels: [标签列表] }
→ 用 list 填充 state.brief(联系人简要)
→ 用 labels 填充 state.labels + state.labelMap
→ diff 不为空时:分批 getBriefInfo → batch_add 入库
loadLabels()(并行)
→ Core.wxApi("label/list") 获取微信标签
→ POST advicer/label/sync 同步到 DB(比对增删改)
→ loadLabelsFromDb() 重新加载
消息回调流程
上游协议服务 → POST /api/v1/advicer/wx/callback
→ advice_wx.go:解析 AddMsg → 过滤 gh_ 公众号/msgType=other
→ 去重(AppId + NewMsgId)→ 入库 MongoDB advicer_wx_msg
→ WebSocket 广播 new_message(按 appId 匹配推送)
→ 触发智能策略(客户文本消息异步 AI 决策)
→ 触发托管自动注册(文本私聊消息异步检查)
托管自动注册流程
消息回调 → 文本私聊 → goroutine tryHostingAutoRegis
→ (1) 查 advicer(FindByWxDeviceId)→ hosting_enabled=1?
→ (2) 查 projectInfo → 取项目级 wxToken
→ (3) 若 peerWxid == "filehelper" 且 advicer.reply_filehelper=1 → 跳过标签检查,直接进入回复流程
否则调微信 contacts/getDetailInfo 取好友 labelList("1,2,3" 形式)
labelList 为空 → 直接 return,不做回复
→ (4) 取 ai_advice_label 中该销售 hosting=1 的标签 ID 列表
→ (5) 判断好友 labelList 与托管标签是否有交集,无交集 → return
→ (6) 查 ai_advice_customer 取 session_id
→ (7) 无会话 → autoRegisSession 全量加载数据并注册:
- 加载版本数据(advicer_version)
- 加载销售技巧(advicer_talk_skill,有 HostingSkillId 按 ID 取,无则按 projectId 取最新)
- 加载客户画像(advicer_client,按 projectId+advicerId+wxid 查)
- 加载项目资料(advicer_project 结构化 + advicer_project_data 扁平栏目)
- 构建 ChatData → Regis 存入 Redis(key=chatdata:{sessionId})
→ (8) hostingChatAndSend:
- 从 Redis 取 ChatData → buildChatPromptResponse 全量注入提示词
- Chat 生成 AI 回复 → 拆段 → 模拟打字延迟 → 分段发送
关键设计:
- 标签判断改用微信实时返回的 labelList,不再依赖 customer 表缓存的标签
- projectInfo 提前到步骤 2 查询(后续 getDetailInfo 和 Chat 都需要 wxToken)
- 客户昵称优先使用微信备注名(Remark),回退到 NickName,最后才用 customer 表中的昵称
- customer 表仅用于存取 session_id,不再承担标签判断职责
- 文件传输助手(filehelper)特殊路径:销售开启
reply_filehelper后,跳过标签检查,直接作为客户进行 AI 回复
ChatData 与全量提示词注入
ChatData 结构(entitys/advicer.go):
type ChatData struct {
ClientInfo *AdvicerClientMongoEntity // 客户画像(advicer_client)
TalkSkill *AdvicerTalkSkillMongoEntity // 销售技巧(advicer_talk_skill)
ProjectInfo *AdvicerProjectMongoEntity // 项目资料-结构化(advicer_project)
ProjectData map[string]interface{} // 项目资料-扁平栏目(advicer_project_data)
AdvicerInfo *AiAdviceAdvicerEntity // 销售信息(MySQL)
AdvicerVersion map[string]interface{} // 销售人设维度数据(advicer_version)
RuleDimension string // 项目风控红线
}
提示词构建流程(buildChatPromptResponse):
System Prompt = BasePrompt + Mission + RuleDimension + BasePrompt2
+ [项目信息] (advicer_project JSON)
+ [项目资料] (advicer_project_data JSON)
+ [销售信息] (advicer MySQL JSON)
+ [销售人设风格] (advicer_version JSON)
+ [客户信息] (advicer_client JSON)
+ [销售技巧] (advicer_talk_skill JSON)
+ [输出格式] (永远在最后)
---
System Prompt = 聊天记录(客户/我 对话格式)
---
User Message = 当前消息
数据流:
- Regis 时:
autoRegisSession全量加载所有数据 → 构建 ChatData →json.Marshal存入 Redis(key=chatdata:{sessionId},TTL 1h) - Chat 时:从 Redis 取 ChatData →
compactJSON(递归去空值省 token)→ 注入 system prompt compactJSON:序列化前递归去除空字符串、null、空数组、空对象,减少无效 token
注意:advicer_project 和 advicer_project_data 是两个独立的 MongoDB 集合,前者存结构化维度(区域价值/竞品对比等),后者存扁平栏目数据(key=栏目名)。两者都需要加载。
模拟真人发送算法:
- AI 回复拆分为多条短消息(每条 ≤80 字,按换行/句末标点拆分)
- 初始延迟 3~8 秒(模拟阅读 + 思考)
- 每条消息间延迟 = 思考时间(2~5s) + 打字时间(每字 300~500ms)
- 提示词约束:每条消息 10~30 字,用
\n分隔
WebSocket 实时通信
前端 connectWs() → /api/v1/advicer/ws?token=JWT
→ onopen 发送 {"action":"subscribe","appId": state.app.appId}
→ 后端 Broadcast(appId, event, data) → 匹配 client.appId → 推送
→ 前端 onmessage → handleWsEvent()
→ "new_message" → WxN.handleNewMessage(data)
→ "online_status" → WxN.handleOnlineStatus(data)
当前开发状态(截至 2026-09-27)
已完成
- 微信工作台:登录(二维码/在线检测)、通讯录、会话、聊天、AI 建议
- 好友差集同步:
ai_advice_customer表 +customer/diff+batch_add/batch_update - 标签同步:
ai_advice_label表 +label/sync+label/list - 标签在 diff 接口一并返回(进入工作台时一次请求拿到好友 + 标签)
- 标签 tab:展开/折叠、好友计数、loading 状态
- 好友/会话列表展示联系人标签小标签
- 联系人详情悬浮气泡(hover popup,含视口边界检测)
- WebSocket 实时推送新消息 + 在线状态
- 去掉 localStorage 缓存(brief/labels 全部内存 state)
- 托管标签自动会话注册:检查标签交集 → 自动注册 AI 会话 → 回写 session_id
- 托管自动回复:注册会话 → Chat → 拆段 → 模拟真人打字延迟 → 分段发送
- 项目级 wx_token 支持:SendTextWithToken 方法,托管场景使用项目级 token
- 查询优化:消除重复查询(projectInfo/advicer/session),新注册路径从 ~16 次降至 ~10 次 DB
- 文件传输助手自动回复:销售级开关
reply_filehelper,开启后跳过标签检查直接 AI 回复 - filehelper 普通客户化改造:走标准 customer 流程复用 session_id
- ChatData 全量数据注入:Regis 时加载全部数据(项目/销售/客户/技巧/人设),Chat 时从 Redis 取出全量注入提示词
- 提示词 token 优化:
compactJSON递归去空值,减少无效 token
待开发(下一步)
1. 聊天算法深度优化
目标:进一步提升 AI 回复的真实感,让对话更自然、更像真人销售。
优化方向:
- 回复节奏优化:根据消息内容复杂度动态调整延迟(简单问候快一些,复杂问题慢一些)
- 上下文感知回复:结合客户画像、历史对话、当前时间段等因素调整回复风格
- 多轮对话连贯性:确保多轮对话中话题过渡自然,不重复、不突兀
- 情绪感知:识别客户情绪(犹豫/急迫/不满),动态调整回复语气和节奏
- 回复长度自适应:根据客户消息长度调整回复长度(客户发短句,AI 也回短句)
- 提示词迭代:根据实际对话效果持续优化 BasePrompt,减少 AI 味、增强人味
涉及文件:
internal/data/constants/prompt.go(提示词模板)internal/biz/advice_wx.go(splitReplyToChunks / hostingChatAndSend)internal/biz/advice_wx_send.go(typingDelay 算法)internal/biz/advice_chat.go(Chat / ChatWithSession)internal/biz/advice_strategy.go(智能策略回复)
2. 标签标记为客户标签 → 客户管理联动
目标:给标签打上"客户标记",被标记标签下的好友自动识别为客户,在客户管理模块体现,并在销售托管(自动回复)开启时仅对这些客户生效。
设计思路:
ai_advice_label表新增is_customer_tag TINYINT(1) DEFAULT 0字段(1=客户标签)- 新增 API:
advicer/label/mark_customer{ selfWxid, labelId, isCustomerTag: bool } - 前端标签 tab:每个标签右侧增加"客户标签"开关/标记按钮
- 客户管理模块(
advice_client.go/client.go):- 查询客户时,筛选
ai_advice_customer.label_list包含已标记标签 ID 的记录 - 客户列表展示来源标签名称
- 查询客户时,筛选
- 智能策略(
advice_strategy.go):- 自动回复/主动触达判断时,仅对"客户标签下的好友"生效(非客户标签的好友不触发 AI 自动回复)
- 读取
ai_advice_label中is_customer_tag=1的标签 ID 集合,与消息发送者的label_list取交集
涉及文件:
- 后端:
ai_advice_label.gen.go(加字段)、advice_label_impl.go(加 MarkCustomer 方法)、advice_label.go(加 MarkCustomer biz)、label.go(加 handler)、advicer_data.go(加 DTO)、advice_strategy.go(过滤逻辑) - 前端:
wx.js(标签 tab 渲染标记按钮)、theme.css(标记样式) - SQL:
06_ai_advice_label_customer_tag.sql(ALTER TABLE 或迁移脚本)
3. 好友/会话列表 → 给好友打标签
目标:在好友列表和会话列表中,可以对好友(非 chatroom)添加到指定标签,类似微信原生标签管理。
设计思路:
- 利用微信协议 API
contacts/modifyFriendLabel(或类似端点)修改好友的标签 - 前端:联系人详情悬浮气泡(popup)中增加"管理标签"入口,点击弹出标签选择弹窗
- 展示当前好友已有的标签(从
state.brief[wxid].labelList解析) - 展示所有可用标签(
state.labels) - 支持勾选/取消,确认后调用微信 API 修改,再更新 DB(
ai_advice_customer.label_list)
- 展示当前好友已有的标签(从
- 修改后需同步更新
state.brief[wxid].labelList并重新渲染
涉及文件:
- 后端:可能需要新增微信代理 API(
pkg/wx/qs_*.go+api.go+wx_proxy.gowxDispatchMap) - 前端:
wx_chat.js(popup 中加"管理标签"按钮 + 标签选择弹窗)、wx.js(标签更新逻辑) - DB:更新
ai_advice_customer.label_list字段
Wire 依赖注入须知
新增模块后必须:
- 各层
provider_set.go添加 Provider http.go的NewHTTPServer参数列表添加 Servicerouter.go的SetupRoutes参数列表添加 Servicerouter/advicer.go的AdvicerRouterRegist参数列表添加 Service- 执行
cd cmd/server && wire重新生成wire_gen.go go build ./cmd/server编译验证
避坑清单
*.gen.go和wire_gen.go禁止手动编辑- Mongo 全量更新是
$set语义 — 必须"先读后合并再写",否则清空未提交字段 - 微信代理响应必须设
skip_response_wrap— 否则被registerCommon二次包装成{data:{code,data}} - 前端修改必须更新版本号 —
project.html中?v=N,不更新则浏览器用旧缓存 state.app.appId不缓存 — 每次 mount 从advicer/infoAPI 实时获取checkOnlineLoop()已内含connectWs()— 不要额外调用,否则连接级联断开loadProfile()返回 Promise — 依赖state.app.wxid的操作必须在其.then()中执行- 子模块通过
WxN.stategetter 访问状态 — 不要在闭包中缓存 state 引用 builder.Cond查询用指针 — DataTemp 方法接受*builder.Cond指针dataTemp.ReqPageBo分页字段是Limit— 不是PageSize- 新增 Service 后同步三个文件 —
http.go、router.go、advicer.go函数签名都要加参数 - 会话/消息查询用
selfWxid筛选 — 不用appId(appId 会变) - WebSocket 订阅 appId 必须与广播 appId 一致 — 前端用
wx_device_id,后端用回调的Appid字段 - 历史遗留代码(
internal/biz/handle、llm_service)是死代码,可忽略 advicer_project和advicer_project_data是两个独立的 MongoDB 集合 — 前者存结构化维度(RegionValue/CompetitionComparison 等),后者存扁平栏目数据(key=栏目名, value=内容)。加载项目数据时两者都要查- HostingSkillId 为空时取最新 — 用
VersionList(projectId)取第一条,不是跳过 - PreviousResponseID 续写不继承 system prompt — Chat 时必须从 Redis 取 ChatData 重新注入全部数据,不能依赖 context cache 续写
- 修改 JS/CSS 后必须更新版本号 — 否则浏览器用旧缓存,看起来像没改
编译与部署
# 编译(仅验证业务代码)
go build ./internal/entitys/ ./internal/services/advice/ ./cmd/server/
# 本地运行
go run cmd/server/main.go
# Wire 重新生成
cd cmd/server && wire
# Docker 部署
docker-compose up -d
最后更新:2026-09-27