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

}

测试

相关推荐
摇滚侠3 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯5 小时前
Spring Boot测试框架详解
java·spring boot·后端
你的人类朋友6 小时前
什么是断言?
前端·后端·安全
程序员小凯7 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
i学长的猫7 小时前
Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
后端·ruby on rails·ruby
用户21411832636028 小时前
别再为 Claude 付费!Codex + 免费模型 + cc-switch,多场景 AI 编程全搞定
后端
茯苓gao8 小时前
Django网站开发记录(一)配置Mniconda,Python虚拟环境,配置Django
后端·python·django
Cherry Zack8 小时前
Django视图进阶:快捷函数、装饰器与请求响应
后端·python·django
程序媛徐师姐8 小时前
Java基于SpringBoot的茶叶商城系统,附源码+文档说明
java·spring boot·java springboot·茶叶商城系统·java茶叶商城系统·茶叶·java茶叶商城
爱读源码的大都督9 小时前
为什么有了HTTP,还需要gPRC?
java·后端·架构