钉钉机器人 AI 问答系统技术实现
1、引言
在自动化运维、远程调度与智能任务执行场景中,单纯的系统消息推送已无法满足灵活的人机交互需求,依托钉钉机器人实现外部指令下发、系统实时监听、任务自动执行的双向交互模式,是轻量化远程管控系统的有效方案。机器人实时监听钉钉群内用户发送的自定义指令,通过解析、校验指令内容,自动触发后端对应的业务任务,完成程序启停、数据处理、脚本执行、运维操作等自定义工作,并可将任务执行结果实时回显至钉钉群,真正实现通过钉钉远程指挥、控制系统自动化干活的核心能力,适配轻量化远程运维、业务手动触发、即时任务调度等实际业务场景。
本文基于 Spring AI + Ollama + 钉钉机器人 构建了一个智能问答系统。用户在钉钉群中 @机器人发送指令,程序通过 Stream 模式实时监听机器人消息,将用户问题转发给 Ollama 部署的大模型(qwen3.5:4b)进行处理,最后将 AI 回复结果返回给钉钉用户。
2、流程架构
用户 @机器人 发送消息
│
▼
┌──────────────────────────────────────────────┐
│ 钉钉开放平台 (Stream 长连接) │
└──────────────────────────────────────────────┘
│ 回调通知
▼
┌──────────────────────────────────────────────┐
│ DingTalkRobotServiceImpl │
│ │
│ ┌────────────┐ ┌──────────────────────┐ │
│ │ 消息监听 │──▶│ handleMessage() │ │
│ │ (Stream) │ │ 提取消息文本 │ │
│ └────────────┘ └─────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ ChatClient │ │
│ │ (Spring AI) │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Ollama │ │
│ │ qwen3.5:4b │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ reply() │ │
│ │ BotReplier │ │
│ └────────┬─────────┘ │
│ │ │
└────────────────────────────┼─────────────────┘
│
▼
钉钉用户收到 AI 回复
3、代码
3.1 消息监听与启动 (Stream 模式)
DingTalkRobotServiceImpl 采用钉钉开放平台提供的 Stream 模式(长连接回调)来实时接收机器人消息,不需要暴露 HTTP 端点。应用启动时自动初始化 Stream 客户端:
@PostConstruct
public void start() {
if (!properties.isEnabled()) {
log.info("钉钉机器人 Stream 监听已禁用 (dingtalk.robot.enabled=false)");
return;
}
streamThread = new Thread(this::doStartStreamClient, "dingtalk-robot-stream");
// 设置为守护线程,避免应用退出时 Stream 客户端未正常关闭
streamThread.setDaemon(true);
streamThread.start();
log.info("钉钉机器人 Stream 客户端已启动, topic={}", properties.getTopic());
}
关键设计点:
- @PostConstruct:应用启动后自动执行,无需手动触发。
- 守护线程:Stream 客户端的
start()方法是阻塞的,必须放在独立线程中运行;设为守护线程能确保 JVM 退出时自动终止。 - 可开关控制:通过
enabled配置项控制是否启用监听,方便开发调试时关闭。
3.2 Stream 客户端构建与回调注册
private void doStartStreamClient() {
try {
streamClient = OpenDingTalkStreamClientBuilder
.custom()
.credential(new AuthClientCredential(
properties.getClientId(), properties.getClientSecret()))
.registerCallbackListener(properties.getTopic(),
(ChatbotMessage message) -> {
// 1. 处理消息,交由 AI 大模型生成回复
String result = handleMessage(message);
// 2. 通过 sessionWebhook 回复用户
reply(message, result);
return new JSONObject();
})
.build();
// start() 为阻塞方法,需在独立线程中执行
streamClient.start();
} catch (Exception e) {
log.error("钉钉机器人 Stream 客户端异常", e);
}
}
核心机制:
- 通过
OpenDingTalkStreamClientBuilder构建客户端,使用clientId+clientSecret进行身份认证。 registerCallbackListener("/v1.0/im/bot/messages/get", callback)注册消息回调,每当用户 @机器人发送消息时,钉钉平台就会推送ChatbotMessage到回调函数。- 回调函数中依次执行「AI 处理」和「回复用户」两个步骤。
3.2.1 AI 大模型调用
private String handleMessage(ChatbotMessage message) {
// 获取消息内容
String prompt = message.getText().getContent();
log.info("收到消息, {}", prompt);
// 同步调用 Ollama,获取完整返回文本
return chatClient.prompt()
.user(prompt)
.call()
.content();
}
处理流程:
- 从
ChatbotMessage中提取用户发送的文本内容。 - 通过 Spring AI 的
ChatClient将用户消息作为 prompt 发送给 Ollama 模型。 - 使用同步调用
.call()获取模型的完整回复文本。
ChatClient 的配置如下:
@Bean
@Primary
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem("必须中文回复。")
.build();
}
设置了默认系统提示词为"必须中文回复",确保 AI 始终用中文回答。
3.2.2 消息回复
private void reply(ChatbotMessage message, String result) {
try {
// 使用 sessionWebhook 回复消息
String sessionWebhook = message.getSessionWebhook();
if (StrUtil.isBlank(sessionWebhook)) {
return;
}
// 通过 BotReplier 回复
BotReplier replier = new BotReplier(sessionWebhook);
replier.replyText("处理结果:" + result);
log.info("已通过 sessionWebhook 回复消息");
} catch (Exception e) {
log.error("reply error", e);
}
}
回复机制:
sessionWebhook是钉钉在每次消息回调中提供的临时回话地址,无需额外鉴权即可直接回复。BotReplier封装了回复逻辑,调用replyText()即可将 AI 结果发送回对话。
3.3 主动发送消息
除了被动回复,系统还支持主动向用户发送消息:
public String sendMessage(List<String> userIds, String content, String msgKey) {
String accessToken = getAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set(DingTalkConstant.ACCESS_TOKEN_HEADER, accessToken);
JSONObject msgParam = new JSONObject();
msgParam.put("content", content);
JSONObject requestBody = new JSONObject();
requestBody.put("robotCode", properties.getRobotCode());
requestBody.put("userIds", userIds);
requestBody.put("msgKey", msgKey);
requestBody.put("msgParam", msgParam.toJSONString());
HttpEntity<String> entity = new HttpEntity<>(requestBody.toJSONString(), headers);
ResponseEntity<String> response = restTemplate.exchange(
DingTalkConstant.SEND_MESSAGE_URL, HttpMethod.POST, entity, String.class);
return response.getBody();
}
主动发送需要先通过 getAccessToken() 获取钉钉 Access Token,再调用钉钉 OpenAPI 的 batchSend 接口。
/**
* 获取钉钉 Access Token
*
* @return accessToken
*/
@Override
public String getAccessToken() {
String url = String.format(DingTalkConstant.GET_TOKEN_URL, properties.getClientId(), properties.getClientSecret());
ResponseEntity<JSONObject> response = restTemplate.exchange(
url, HttpMethod.GET, null, JSONObject.class);
JSONObject body = response.getBody();
if (body != null && body.getInteger("errcode") == 0) {
String token = body.getString("access_token");
log.debug("获取 accessToken 成功");
return token;
}
throw new RuntimeException("获取 accessToken 失败: " + body);
}
3.4 优雅关闭
@PreDestroy
public void stop() {
if (streamClient != null) {
try {
streamClient.stop();
log.info("钉钉机器人 Stream 客户端已关闭");
} catch (Exception e) {
log.error("关闭 Stream 客户端失败", e);
}
}
if (streamThread != null && streamThread.isAlive()) {
streamThread.interrupt();
}
}
应用关闭时通过 @PreDestroy 自动停止 Stream 客户端并中断监听线程,确保资源正确释放。
4、完整调用链路
1. 启动阶段
@PostConstruct → start() → 创建守护线程 → doStartStreamClient()
→ OpenDingTalkStreamClientBuilder 构建客户端 → 注册回调监听 → streamClient.start()
2. 消息处理阶段(用户 @机器人 发送消息)
钉钉平台推送 ChatbotMessage → 回调函数触发
→ handleMessage(message)
→ 提取 message.getText().getContent()
→ chatClient.prompt().user(prompt).call().content()
→ Ollama 推理 → 返回结果
→ reply(message, result)
→ 获取 sessionWebhook
→ BotReplier.replyText("处理结果:" + result)
→ 用户收到回复
3. 主动推送阶段(业务触发)
sendMessage(userIds, content)
→ getAccessToken() 调用钉钉 /gettoken 接口
→ POST /v1.0/robot/oToMessages/batchSend
→ 用户收到消息
4. 关闭阶段
@PreDestroy → stop()
→ streamClient.stop()
→ streamThread.interrupt()
5、关键设计优势
- 零 HTTP 端点:Stream 模式不需要暴露公网端口,降低了网络配置复杂度和安全风险。
- 本地 AI 推理:Ollama 部署在本地服务器,数据不出网,保障信息安全。
- 守护线程设计:Stream 阻塞线程不影响 Spring Boot 主线程,应用可同时处理其他业务(如定时任务、HTTP 接口)。
- 开关控制:
enabled配置项支持灵活启停,开发时关闭监听不影响 AI 功能的单独调试。 - 优雅启停:
@PostConstruct+@PreDestroy实现全自动生命周期管理,无需人工干预。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)