企业微信AI智能客服别让人通宵盯着回:Spring Boot + Vue 把豆包、DeepSeek、扣子接到消息回调里

摘要:私域客服的痛点不是「没有大模型」,而是客户随时来、标准化问题占人工、人一接手机器人必须闭嘴。本文用 Spring Boot + Vue 把豆包、DeepSeek、扣子接到企业微信消息回调里:回调进线程池、厂家可插拔、口令/关键词/AI/自动拉人四段互斥、暂停键放 Redis。全文含真实菜单名、源码和现网边界,可直接照着落地。

目录

技术栈说明
本教程全文使用的底层API调用地址:https://wechatapi.apifox.cn/
代码调用示例参考官网:https://www.jikehudong.com/
开发语言:c + java
开发框架:Spring Boot + Vue

私域客服的真实痛点不是「没有大模型」,而是这三件事叠在一起:

  1. 客户随时会来:晚上 9 点问价格、周末问怎么进群,人不可能 24 小时盯着手机。
  2. 标准化问题不该占人工:价格、地址、活动口令,本来就该规则先挡一层。
  3. 人一旦接手,机器人必须闭嘴:客服已经在工作台打字了,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 把同一张图画成可维护版本:

存储

Spring Boot

Vue 管理端

企微网关

企微客户端

私聊客户

群聊

消息回调

SendTextMsg

AI客服设置

账号设置 AI智能回复

工作台 暂停/恢复AI

WxworkCallbackController

WxworkRobotAutomationExecutor
异步线程池 4~16

口令入群 → 关键词 → AI → 自动拉人

AiAgentRouter

豆包 Ark

DeepSeek

扣子 Coze

MySQL
sys_ai_agent
automation_json

Redis
pause / interval / dedup

消息 Redis

关键设计就四条:

  1. 回调不能阻塞scheduleAfterIncomingMessage 丢进独立线程池,避免关键词 / AI / 拉群把第三方回调拖超时。
  2. 规则互斥,不是并行喷。 同一条文本:口令入群命中 → 不再走关键词;关键词命中 → 不再走 AI;AI 回成功 → 不再自动拉人。
  3. 厂家可插拔。 AiAgentRouterproviderAiAgentClientdoubao / deepseek 共用 OpenAI 兼容 chat/completionscoze/v3/chat 非流式 + 轮询。
  4. 人机状态在 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/assistantAiAgentRouter.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. 落地清单

  1. sys_ai_agent,菜单放 AI客服设置/aiCustomer)。
  2. 账号 automation_json 增加 ai.enabled / ai.agentId / ai.scope
  3. 回调里 saveMessage 之后 scheduleAfterIncomingMessage,线程池不要用 ForkJoinPool.commonPool()
  4. Redis 准备 pause / interval / dedup;人工 saveMessagepauseSession
  5. 工作时间规则按上海时区;欢迎语不要挂在同一把锁上。
  6. 工作台读 automation.ai 决定要不要渲染「AI 接管中」和「暂停 AI」。

8. 总结

大模型本身越来越便宜,难的是把它嵌进企微私域的运营节奏里:规则先挡、AI 兜底、人一出手就让位、下班就全体静音。极客聚合这条链路已经按这个节奏在跑。

如果你也在管一批企微号,被「晚上没人回」「客服和机器人抢话」「口令进群和大模型互相打架」折磨过,欢迎在评论区留言场景(几号、要不要群聊 AI、用豆包还是扣子)。需要看演示或交流私域自动化的,直接私信,我们按现网功能给你对一下是否匹配,不会拿还没做的能力画饼。

系列相关:多账号扫码托管、工作台 WebSocket 未读、工作时间总闸,可以和本文对照着看——AI 客服只是自动化链上的第三段,前面还有口令和关键词,后面还有自动拉人。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐