使用 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;

}

测试

相关推荐
uzong38 分钟前
后端系统设计文档模板
后端
幽络源小助理42 分钟前
SpringBoot+Vue车票管理系统源码下载 – 幽络源免费项目实战代码
vue.js·spring boot·后端
猫吻鱼44 分钟前
【系列文章合集】【全部系列文章合集】
spring boot·dubbo·netty·langchain4j
uzong1 小时前
软件架构指南 Software Architecture Guide
后端
又是忙碌的一天1 小时前
SpringBoot 创建及登录、拦截器
java·spring boot·后端
勇哥java实战分享2 小时前
短信平台 Pro 版本 ,比开源版本更强大
后端
学历真的很重要2 小时前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
计算机毕设VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
上进小菜猪3 小时前
基于 YOLOv8 的智能杂草检测识别实战 [目标检测完整源码]
后端
韩师傅3 小时前
前端开发消亡史:AI也无法掩盖没有设计创造力的真相
前端·人工智能·后端