使用 Spring Boot 实现钉钉消息发送消息

钉钉官方文档(Webhook同步数据)

在这篇博客中,我们将详细介绍如何使用 Spring Boot 集成钉钉机器人,构建一个发送钉钉消息的服务,并通过 OkHttp 实现 HTTP 请求,同时使用 Hutool 提供便捷的 POST 请求工具。


功能概述

本服务的主要功能是通过钉钉机器人接口发送 Markdown 格式的消息。

  • 自动化生成签名(sign)以保障接口安全。
  • 支持 @ 特定用户或全体成员。
  • 使用 OkHttpClient 发送 POST 请求。
  • 支持以 JSON 格式组织消息内容。

实现步骤

1. 新建群聊(添加机器人)

设置关键词keyword,加签获取secret

完成后拿到Webhook

2. 配置依赖

在项目的 pom.xml 文件中引入必要的依赖:

xml 复制代码
<dependencies>
  <!-- Spring Boot Starter -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>

  <!-- Hutool 工具类 -->
  <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.10</version>
  </dependency>

  <!-- OkHttp -->
  <dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
  </dependency>

  <!-- FastJSON 2 -->
  <dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.36</version>
  </dependency>
</dependencies>

3. 配置钉钉机器人参数

钉钉机器人支持text、link、markdown、actionCard、feedCard 这几种消息类型,本文使用的markdown, 如果需要使用其他类型可以查看官方文档钉钉官方文档(Webhook同步数据)

参数 参数类型 是否必填 说明
msgtype String 消息类型,此时固定为:markdown。
title String 首屏会话透出的展示内容。
text String markdown格式的消息。
atMobiles Array 在content里添加被@人的手机号。 提示:只有在群内的成员才可被@,非群内成员手机号会被脱敏
atUserIds Array 在content里添加被@人的用户userid。
isAtAll Boolean 是否@所有人。

application.yml 文件中配置钉钉机器人的参数:

yaml 复制代码
ding:
  webhook: "https://oapi.dingtalk.com/robot/send?access_token=add7e41373b48a88ae9be86a2065e525xxxxxxxxx"
  keyword: "收到客户提单"
  secret: "xxxxxxxxxxx9fe24ff0d72ccxxxxxxx"
  white-ip: []
  at-all: true
  at-user-ids: []
  at-mobiles: []

4. 配置markdown消息

java 复制代码
import cn.hutool.core.util.ArrayUtil;
import lombok.*;
import javax.validation.constraints.NotBlank;
import java.util.Arrays;

@Data
public class MarkDownMessage {

    private static String keyword = "[收到客户提单]";

    /**
     * 消息类型,固定为markdown
     */
    @Setter(AccessLevel.NONE)
    private final String msgtype = "markdown";

    private MarkDown markdown = new MarkDown();

    private AT at = new AT();

    /**
     * 消息内容
     */
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public class MarkDown {
        /**
         * 首屏会话透出的展示内容
         */
        @NotBlank
        @Setter
        private String title = "客户信息";
        /**
         * markdown格式的消息
         * note: 如果钉钉配置中使用了keyword,那么消息体中必须包含keyword;
         * 如果使用了@功能,也要包含@人的手机号,格式为@150xxxxxxxx
         */
        @NotBlank
        private String text;

        public void setText(String text) {
            StringBuilder sb = new StringBuilder();
            if (ArrayUtil.isNotEmpty(at.getAtMobiles())) {
                Arrays.stream(at.getAtMobiles()).forEach(sb::append);
            }
            sb.append("\n")
                    .append(keyword)
                    .append("\n")
                    .append(text);
            this.text = sb.toString();
        }

        public void setText(String serverName, String env, String url, String msg, String level,
                            String scene) {
            StringBuilder sb = new StringBuilder();
            if (ArrayUtil.isNotEmpty(at.getAtMobiles())) {
                Arrays.stream(at.getAtMobiles()).forEach(mobile -> {
                    sb.append("@")
                            .append(mobile);
                });
            }
            sb.append("\n")
                    .append(keyword)
                    .append("\n")
                    .append("客户信息:" + msg);

            this.text = sb.toString();
        }
    }

    /**
     * 使用@消息配置
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class AT {
        /**
         * 被@人的手机号
         * note: 在text内容里要有@人的手机号
         */
        private String[] atMobiles;
        /**
         * 被@人的用户userid
         */
        private String[] atUserIds;
        /**
         * 是否@所有人
         */
        private boolean isAtAll;
    }
}

5. 创建服务实现类

服务类 SendDingServiceImpl 核心逻辑如下:

实现核心功能

java 复制代码
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.entity.sms.MessageReq;
import com.ruoyi.common.core.domain.model.msg.MarkDownMessage;
import com.ruoyi.common.core.domain.model.resp.DingRep;
import com.ruoyi.system.service.SendDingMsgService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URLEncoder;

@Slf4j
@Service
public class SendDingServiceImpl implements SendDingMsgService {

    @Value("${ding.webhook}")
    private String webhook;

    @Value("${ding.at-all}")
    private boolean isAll;

    @Value("${ding.at-user-ids}")
    private String[] ids;

    @Value("${ding.at-mobiles}")
    private String[] mobiles;

    @Value("${ding.secret}")
    private String secret;

    private final OkHttpClient okHttpClient;

    public SendDingServiceImpl() {
        okHttpClient = new OkHttpClient();
    }


    @Override
    public void send(MessageReq req) {

        String type = "application/json; charset=utf-8";

        MarkDownMessage markDownMsg = new MarkDownMessage();
        MarkDownMessage.AT at = markDownMsg.getAt();
        at.setAtAll(isAll);
        at.setAtMobiles(mobiles);
        at.setAtUserIds(ids);

        MarkDownMessage.MarkDown markdown = markDownMsg.getMarkdown();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("客户姓名", req.getName());
        jsonObject.put("联系方式", req.getPhone());
        jsonObject.put("公司名称", req.getCorporate());
        jsonObject.put("工作邮箱", req.getMailbox());
        jsonObject.put("需求信息", req.getDemand());
        markdown.setText("", "", "", jsonObject.toJSONString(), "", "");

        try {
            RequestBody body = RequestBody.create(MediaType.parse(type), JSONObject.toJSONString(markDownMsg));
            Request.Builder builder = new Request.Builder().url(webhook + "&timestamp=" + System.currentTimeMillis() + "&sign=" + getSign(secret));
            builder.addHeader("Content-Type", type).post(body);
            Request request = builder.build();
            Response response = okHttpClient.newCall(request).execute();
            String rep = response.body().string();
            DingRep dingRep = JSONObject.parseObject(rep, DingRep.class);
            if (dingRep.getErrcode() == 0 || dingRep.getErrmsg().equals("ok")) {
                log.info("钉钉发送消息成功,发送内容:{}", markDownMsg.toString());
            } else {
                log.warn("钉钉发送消息失败,发送内容:{}, 失败内容:{}", markDownMsg.toString(), dingRep.toString());
            }
        } catch (IOException e) {
            log.error("钉钉发送消息失败,失败内容:{}", e.getMessage());
        }
    }

    public String getSign(String secret) {

        String sign = null;
        try {

            Long timestamp = System.currentTimeMillis();
            String stringToSign = timestamp + "\n" + secret;
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
            byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
            sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");

        } catch (Exception e) {

            log.error("获取签名失败,失败内容:{}", e.getMessage());
        }

        return sign;
    }
}

6. MessageReq

java 复制代码
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MessageReq implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("用户姓名")
    private String name;

    @ApiModelProperty("联系方式")
    private String phone;

    @ApiModelProperty("公司名称")
    private String corporate;

    @ApiModelProperty("工作邮箱")
    private String mailbox;

    @ApiModelProperty("需求")
    private String demand;

}

测试

相关推荐
我本是机械人7 分钟前
MVCC实现原理及其作用
java·数据结构·数据库·后端·mysql·算法
路在脚下@8 分钟前
SpringMVC的消息转换器
java·spring boot
武昌库里写JAVA19 分钟前
Redis奇幻之旅(四)4. Redis Cluster
java·开发语言·spring boot·学习·课程设计
Pandaconda22 分钟前
【Golang 面试题】每日 3 题(二十二)
开发语言·笔记·后端·面试·golang·go·channel
m0_7482571842 分钟前
【Spring】Spring实现加法计算器和用户登录
java·后端·spring
ss2731 小时前
基于SpringBoot实现的保障性住房管理系统
java·spring boot·后端
ccmjga1 小时前
升级 Spring Boot 3 配置讲解 — JDK 23 会给 SpringBoot 带来什么特性?
java·spring boot·后端·spring·gradle·spring security
编程|诗人1 小时前
Ruby语言的数据库编程
开发语言·后端·golang
SyntaxSage1 小时前
Ruby语言的学习路线
开发语言·后端·golang
云端 架构师1 小时前
Ruby语言的并发编程
开发语言·后端·golang