钉钉官方文档(Webhook同步数据)
在这篇博客中,我们将详细介绍如何使用 Spring Boot 集成钉钉机器人,构建一个发送钉钉消息的服务,并通过 OkHttp 实现 HTTP 请求,同时使用 Hutool 提供便捷的 POST 请求工具。
功能概述
本服务的主要功能是通过钉钉机器人接口发送 Markdown 格式的消息。
- 自动化生成签名(
sign
)以保障接口安全。 - 支持 @ 特定用户或全体成员。
- 使用
OkHttpClient
发送 POST 请求。 - 支持以 JSON 格式组织消息内容。
实现步骤
1. 新建群聊(添加机器人)
设置关键词keyword,加签获取secret
完成后拿到Webhook
2. 配置依赖
在项目的 pom.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
文件中配置钉钉机器人的参数:
ding:webhook: "https://oapi.dingtalk.com/robot/send?access_token=add7e41373b48a88ae9be86a2065e525xxxxxxxxx"keyword: "收到客户提单"secret: "xxxxxxxxxxx9fe24ff0d72ccxxxxxxx"white-ip: []at-all: trueat-user-ids: []at-mobiles: []
4. 配置markdown消息
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@AllArgsConstructorpublic class MarkDown {/*** 首屏会话透出的展示内容*/@NotBlank@Setterprivate String title = "客户信息";/*** markdown格式的消息* note: 如果钉钉配置中使用了keyword,那么消息体中必须包含keyword;* 如果使用了@功能,也要包含@人的手机号,格式为@150xxxxxxxx*/@NotBlankprivate 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@AllArgsConstructorpublic class AT {/*** 被@人的手机号* note: 在text内容里要有@人的手机号*/private String[] atMobiles;/*** 被@人的用户userid*/private String[] atUserIds;/*** 是否@所有人*/private boolean isAtAll;}
}
5. 创建服务实现类
服务类 SendDingServiceImpl
核心逻辑如下:
实现核心功能
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();}@Overridepublic 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 + "×tamp=" + 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
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;}