企业微信 AI 智能客服实战:Spring Boot+Vue 接入豆包、扣子、DeepSeek
企业微信AI智能客服别让人通宵盯着回:Spring Boot + Vue 把豆包、DeepSeek、扣子接到消息回调里
摘要:私域客服的痛点不是「没有大模型」,而是客户随时来、标准化问题占人工、人一接手机器人必须闭嘴。本文用 Spring Boot + Vue 把豆包、DeepSeek、扣子接到企业微信消息回调里:回调进线程池、厂家可插拔、口令/关键词/AI/自动拉人四段互斥、暂停键放 Redis。全文含真实菜单名、源码和现网边界,可直接照着落地。
目录
- 1. 产品里三个入口,别搞混
- 2. 技术架构:回调进线程池,厂家可插拔
- 3. 一条客户消息,服务端怎么走
- 4. 智能体怎么存、怎么路由
- 5. 人机协同:暂停键在 Redis,不在会话表
- 6. 现网边界(写进文章比吹得满更有转化)
- 7. 落地清单(给要对着做的同学)
- 8. 总结
技术栈说明
本教程全文使用的底层API调用地址:https://wechatapi.apifox.cn/
代码调用示例参考官网:https://www.jikehudong.com/
开发语言:c + java
开发框架:Spring Boot + Vue
私域客服的真实痛点不是「没有大模型」,而是这三件事叠在一起:
- 客户随时会来:晚上 9 点问价格、周末问怎么进群,人不可能 24 小时盯着手机。
- 标准化问题不该占人工:价格、地址、活动口令,本来就该规则先挡一层。
- 人一旦接手,机器人必须闭嘴:客服已经在工作台打字了,AI 再回一句,客户会觉得你们在演戏。
很多团队的做法是:ChatGPT 开个网页,复制粘贴到企微。这解决不了多账号、会话暂停、下班停机、口令入群抢消息这些问题。
我们在 极客聚合(企业微信聚合平台) 里把这条链路做成了现网功能:智能体在后台配一次,账号勾一下「AI 接管」,客户发来的文本走回调,服务端异步调厂家,再用 SendTextMsg 打回手机。下面全部是真实菜单名和源码,没有 GPT / 通义,厂家只有 豆包、DeepSeek、扣子。
1. 产品里三个入口,别搞混
AI 客服不是「一个大开关」,而是三层配置:
| 入口 | 菜单路径 | 干什么 |
|---|---|---|
| 智能体目录 | AI客服设置 | 配厂家、Key、模型、人设,测连通 |
| 账号绑定 | 企微账号管理 → 账号设置 → AI 智能回复 | 这个号开不开 AI、绑哪个智能体、好友还是群 |
| 会话接管 | 客服工作台 | 标签「AI 接管中 / AI 已暂停」,按钮「暂停 AI / 恢复 AI」 |
页面上的引导文案写得很直白:先在本页配智能体,再到账号设置里绑定。

点「新建智能体」只有三家,没有「随便填一个 OpenAI 兼容地址」的万能入口。豆包走火山方舟 ep- 接入点,DeepSeek 走 deepseek-chat,扣子走 PAT + Bot ID。

智能体配好后,去 企微账号管理 → 账号设置,先点账号卡片:

进账号后能看到四张能力卡。注意 AI 那张的产品定义——关键词未命中时才由智能体接管,不是大模型包打天下:

点进「AI 智能回复」,开关文案也把人机边界写死了:
AI 接管本账号:关键词 / 口令未命中的文本消息交给智能体回复;工作台人工发送后该会话会暂停 AI。

工作台是人工最后一公里。账号绑定 AI 且选中会话后,标题会出现「AI 接管中」,输入栏左侧有「暂停 AI」。人工点发送,前端会把该会话标成已暂停,后端落库时再写 Redis pause key,两边一起保证机器人不再抢话。
2. 技术架构:回调进线程池,厂家可插拔
整体不是「前端调大模型再发给客户」。客户消息先到企微网关,网关把回调打到我们的 Spring Boot,落 Redis / MySQL 之后再异步走自动化。前端只负责配智能体、绑账号、暂停会话。

用 mermaid 把同一张图画成可维护版本:
关键设计就四条:
- 回调不能阻塞。
scheduleAfterIncomingMessage丢进独立线程池,避免关键词 / AI / 拉群把第三方回调拖超时。 - 规则互斥,不是并行喷。 同一条文本:口令入群命中 → 不再走关键词;关键词命中 → 不再走 AI;AI 回成功 → 不再自动拉人。
- 厂家可插拔。
AiAgentRouter按provider找AiAgentClient:doubao/deepseek共用 OpenAI 兼容chat/completions,coze走/v3/chat非流式 + 轮询。 - 人机状态在 Redis。 pause 无 TTL(人停了就一直停,直到点「恢复 AI」);同会话 2 秒间隔;
server_id去重 2 天。
3. 一条客户消息,服务端怎么走
3.1 回调落库后立刻调度
wxworkMessageRedisService.saveMessage(msg);
wxworkRobotAutomationExecutor.scheduleAfterIncomingMessage(msg);
自动化入口先过滤:无 uuid、本端发出(isSent=1)、非文本、不在工作时间、没有 automation_json,直接 return。工作时间按 Asia/Shanghai 判断,避免 JVM 时区是 UTC 时白天被当成下班。
3.2 四段互斥链
boolean cmdHit = tryCommandJoin(root.getJSONObject("command"), msg, content.trim(), sender, root);
boolean kwHit = false;
if (!cmdHit) {
kwHit = tryKeywordReply(root.getJSONObject("keyword"), msg, content, sender);
}
boolean aiHit = false;
if (!cmdHit && !kwHit) {
aiHit = tryAiReply(root.getJSONObject("ai"), msg, content, sender);
}
if (!cmdHit && !kwHit && !aiHit) {
tryAutoPull(root.getJSONObject("autopull"), msg, sender);
}
产品语义对应到代码就是:
- 客户发「进群暗号」→ 调
InvitationToRoomLink,这条消息被消费掉。 - 客户发「价格」→ 关键词规则回固定文本,不浪费 Token。
- 上面都没打中 → AI 用最近 20 条文本当上下文。
- AI 也没回(没开、暂停、范围不符)→ 才考虑「私聊首次消息自动拉群」。
欢迎语是另一套 Handler,不走这条工作时间时钟。别把「加好友 3 秒欢迎语」和大模型混成一个开关。
3.3 tryAiReply 真正会跳过的情况
if (ai == null || !isJsonTruthy(ai, "enabled")) return false; // 账号没开 AI
if (ai.getLong("agentId") == null) return false; // 没绑智能体
if (!aiScopeMatched(ai, isRoomMsg)) return false; // 好友/群聊范围
if (aiSessionService.isPaused(uuid, sendUserid, isRoomMsg)) return false;
if (aiSessionService.alreadyReplied(uuid, msg.getServerId())) return false;
if (!aiSessionService.tryAcquireInterval(uuid, sendUserid, isRoomMsg, 2)) return false;
通过之后:拉最近 20 条文本 → 转成 user/assistant → AiAgentRouter.chat → 成功则 SendTextMsg,并把发出去的文本再写入消息 Redis,工作台才能回显。
AI 只回文本。图片、文件、语音不会丢给大模型,回调里非 msgtype 0/2 在自动化入口就被跳过。
4. 智能体怎么存、怎么路由
表很薄,凭证放 config_json,页面上的 API Key / 模型 / Bot ID 都是展开字段:
CREATE TABLE IF NOT EXISTS sys_ai_agent (
agent_id BIGINT NOT NULL AUTO_INCREMENT,
agent_name VARCHAR(64) NOT NULL,
provider VARCHAR(32) NOT NULL COMMENT 'doubao/deepseek/coze',
enabled CHAR(1) NOT NULL DEFAULT '1',
config_json MEDIUMTEXT NULL,
last_error VARCHAR(500) DEFAULT NULL,
PRIMARY KEY (agent_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI智能体目录';
账号侧绑定不另建表,塞在该企微号的 automation_json:
{
"keyword": { "enabled": true, "scope": ["customer"], "rules": [] },
"command": { "enabled": false, "rules": [] },
"autopull": { "enabled": false, "mode": "all", "delaySec": 0 },
"ai": {
"enabled": true,
"agentId": "3",
"scope": ["customer"]
}
}
路由只有十几行,加第四家厂家就是再实现一个 AiAgentClient:
@Component
public class AiAgentRouter {
@Autowired
private List<AiAgentClient> clients;
public AiChatResult chat(SysAiAgent agent, List<AiChatMessage> history,
String userText, String conversationId) throws Exception {
for (AiAgentClient c : clients) {
if (c != null && c.supports(agent.getProvider())) {
return c.chat(agent, history, userText, conversationId);
}
}
throw new Exception("不支持的厂家:" + agent.getProvider());
}
}
豆包 / DeepSeek 组 messages 时会带上系统提示词(客服人设)和历史,默认 max_tokens=800、超时 60s。扣子不把本地历史全塞进去,而是把 conversation_id 存在 Redis,30 天,让 Bot 自己续上下文。
页面上「测试连接」打的是同一条路由,固定发一句「你好」,成功会清 last_error,失败写回卡片上的红色错误,运营不用翻日志也能看出 Key 填错了还是接入点 ID 填成了模型名。
5. 人机协同:暂停键在 Redis,不在会话表
private static final String PAUSE = "wxwork:ai:pause:";
private static final String INTERVAL = "wxwork:ai:interval:";
private static final String DEDUP = "wxwork:ai:dedup:";
private static final String COZE = "wxwork:ai:coze:conv:";
// pause:wxwork:ai:pause:{uuid}:{peerId}_{isRoom} 无 TTL
// interval:setIfAbsent 2 秒
// dedup:wxwork:ai:dedup:{uuid}:{serverId} TTL 2 天
三条接口给工作台:
| 接口 | 作用 |
|---|---|
GET /system/aiAgent/sessionStatus | 标题展示「AI 接管中」还是「AI 已暂停」 |
POST /system/aiAgent/sessionPause | 点「暂停 AI」 |
POST /system/aiAgent/sessionResume | 点「恢复 AI」 |
更关键的是:客服在工作台点发送,保存消息时会无条件 pauseSession。客服不用记得去点暂停,只要自己回了,这个会话的 AI 就停。
工作时间是整条自动化的总闸,不只关 AI。没配规则 = 全天有效;配了 09:00–18:00 且启用,晚上关键词、口令、AI、自动拉人全部失效。欢迎语仍然独立。

6. 现网边界
能做的:
- 多账号共用智能体目录,每个号自己决定开不开、绑哪一个。
- 好友 / 群聊范围可多选;默认只好友。
- 口令必须私聊且完全一致;关键词默认「包含」,可改完全匹配。
- 人工发送自动暂停;可手动恢复。
- 厂家调用失败写
last_error,卡片上直接红字。
明确没做、避免售前被问穿:
- 不回图片 / 语音 / 文件,只回文本。
- 没有 GPT、通义、本地 Ollama。要接第四家,得加 Client,不是改个 Base URL。
- 没有按标签分流智能体。一个号同一时间只绑一个
agentId。 - 没有知识库检索(RAG)。人设靠系统提示词,扣子侧知识库在扣子控制台配,不在我们后台。
- 自动拉人当前是「全部客户首次私聊」,不是按标签拉群。
- 暂停 key 无 TTL:客服忘了点「恢复 AI」,这个会话会一直停,这是有意的,防止误唤醒。
7. 落地清单
- 建
sys_ai_agent,菜单放 AI客服设置(/aiCustomer)。 - 账号
automation_json增加ai.enabled / ai.agentId / ai.scope。 - 回调里
saveMessage之后scheduleAfterIncomingMessage,线程池不要用ForkJoinPool.commonPool()。 - Redis 准备 pause / interval / dedup;人工
saveMessage时pauseSession。 - 工作时间规则按上海时区;欢迎语不要挂在同一把锁上。
- 工作台读
automation.ai决定要不要渲染「AI 接管中」和「暂停 AI」。
8. 总结
大模型本身越来越便宜,难的是把它嵌进企微私域的运营节奏里:规则先挡、AI 兜底、人一出手就让位、下班就全体静音。极客聚合这条链路已经按这个节奏在跑。
如果你也在管一批企微号,被「晚上没人回」「客服和机器人抢话」「口令进群和大模型互相打架」折磨过,欢迎在评论区留言场景(几号、要不要群聊 AI、用豆包还是扣子)。需要看演示或交流私域自动化的,直接私信,我们按现网功能给你对一下是否匹配,不会拿还没做的能力画饼。
系列相关:多账号扫码托管、工作台 WebSocket 未读、工作时间总闸,可以和本文对照着看——AI 客服只是自动化链上的第三段,前面还有口令和关键词,后面还有自动拉人。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)