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());
}

关键设计点:

  1. @PostConstruct:应用启动后自动执行,无需手动触发。
  2. 守护线程:Stream 客户端的 start() 方法是阻塞的,必须放在独立线程中运行;设为守护线程能确保 JVM 退出时自动终止。
  3. 可开关控制:通过 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();
}

处理流程:

  1. 从 ChatbotMessage 中提取用户发送的文本内容。
  2. 通过 Spring AI 的 ChatClient 将用户消息作为 prompt 发送给 Ollama 模型。
  3. 使用同步调用 .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、关键设计优势

  1. 零 HTTP 端点:Stream 模式不需要暴露公网端口,降低了网络配置复杂度和安全风险。
  2. 本地 AI 推理:Ollama 部署在本地服务器,数据不出网,保障信息安全。
  3. 守护线程设计:Stream 阻塞线程不影响 Spring Boot 主线程,应用可同时处理其他业务(如定时任务、HTTP 接口)。
  4. 开关控制enabled 配置项支持灵活启停,开发时关闭监听不影响 AI 功能的单独调试。
  5. 优雅启停@PostConstruct + @PreDestroy 实现全自动生命周期管理,无需人工干预。
Logo

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

更多推荐