为实现更好的沟通,现需要实现以自定义形式向钉钉群聊中发送文本内容及图片消息的功能。
(一)明确需求。即向钉钉群聊内发送自定义文本或图片消息
(二)阅读钉钉开发手册。钉钉具有一套面向开发者的开发手册,其上描述了钉钉对外提供的API接口,我们可以通过调用接口实现相应功能。
(三)了解接口要求。在我们调用接口时,需要向其中传递符合规范的参数,并以规定的方式调用,避免出错。若返回的消息出错,阅读错误码文档,对症下药进行修改。
(四)搭建开发环境编写代码。项目基于Springboot开发,Springboot确实能为开发省下了不少麻烦事,也不必再花费大量时间在配置上。
(五)测试
首先我们要明确这是一个后端项目,并且需要对群聊进行开发,因此对应钉钉开发文档的服务端API中的群聊机器人
钉钉群机器人开发文档链接
如群机器人开发文档所示,首先我们得创建机器人并设置安全模式,安全模式分为三种,分别是关键字,加签(加密),IP地址限制
我们必须选取其中的任意一种或多种。
对各项安全模式进行讲解:
(1)方式一,自定义关键词
最多可以设置10个关键词,消息中至少包含其中1个关键词才可以发送成功。
例如:添加了一个自定义关键词:监控报警
则这个机器人所发送的消息,必须包含 监控报警 这个词,才能发送成功。
关键字模式调用的接口URL模板为
https://oapi.dingtalk.com/robot/send?access_token=XXXXXX
(2)方式二,加签
要求如下
第一步,把timestamp+"\n"+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)。
其中需要两个参数,分别为timestamp与secret
参数 | 说明 |
---|---|
timestamp | 当前时间戳,单位是毫秒,与请求调用时间误差不能超过1小时 |
secret | 密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串 |
Java代码
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import java.net.URLEncoder;
public class Test {
public static void main(String[] args) throws Exception {
Long timestamp = System.currentTimeMillis();
String secret = "secret";//此处填写安全设置页面下面的密钥字符串
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"));
String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)),"UTF-8");
System.out.println(sign);
}
}
此处获得的sign需要在调用接口时传入,否则调用失败
加签模式调用的接口URL模板为
https://oapi.dingtalk.com/robot/send?access_token=XXXXXX×tamp=XXX&sign=XXX
(3)方式三,IP地址(段)
设定后,只有来自IP地址范围内的请求才会被正常处理。支持两种设置方式:IP、IP段,暂不支持IPv6地址白名单,格式如下:
IP模式调用的接口URL模板为
https://oapi.dingtalk.com/robot/send?access_token=XXXXXX
群聊机器人创建好后,需要确定传输消息的格式
钉钉群聊机器人允许发送五种不同类型的数据格式,分别是Text,Link,MarkDown,ActionCard与FeedCard
1、Text
消息格式为:
{
"msgtype": "text",
"text": {
"content": "我就是我, 是不一样的烟火@156xxxx8827"
},
"at": {
"atMobiles": [
"156xxxx8827",
"189xxxx8325"
],
"isAtAll": false
}
}
参数 | 参数类型 | 必须 | 说明 |
---|---|---|---|
msgtype | String | 是 | 消息类型,此时固定为:text |
content | String | 是 | 消息内容 |
atMobiles | Array | 否 | 被@人的手机号(在content里添加@人的手机号) |
isAtAll | Boolean | 否 | 是否@所有人 |
2、Link
消息格式为:
{
"msgtype": "link",
"link": {
"text": "这个即将发布的新版本,创始人xx称它为红树林。而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是红树林",
"title": "时代的火车向前开",
"picUrl": "",
"messageUrl": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI"
}
}
参数 | 参数类型 | 必须 | 说明 |
---|---|---|---|
msgtype | String | 是 | 消息类型,此时固定为:link |
title | String | 是 | 消息标题 |
text | String | 是 | 消息内容。如果太长只会部分展示 |
messageUrl | String | 是 | 点击消息跳转的URL |
picUrl | String | 否 | 图片URL |
3、MarkDown
消息格式为:
{
"msgtype": "markdown",
"markdown": {
"title":"杭州天气",
"text": "#### 杭州天气 @150XXXXXXXX \n> 9度,西北风1级,空气良89,相对温度73%\n> ![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png)\n> ###### 10点20分发布 [天气](https://www.dingtalk.com) \n"
},
"at": {
"atMobiles": [
"150XXXXXXXX"
],
"isAtAll": false
}
}
参数 | 参数类型 | 必须 | 说明 |
---|---|---|---|
msgtype | String | 是 | 此消息类型为固定markdown |
title | String | 是 | 首屏会话透出的展示内容 |
text | String | 是 | markdown格式的消息 |
atMobiles | Array | 否 | 被@人的手机号(在text内容里需要有@手机号) |
isAtAll | Boolean | 否 | 是否@所有人 |
ps:在MarkDown消息类型中,text部分接受MarkDown的语法规则
4、ActionCard
ActionCard类型可分为两类,一类是整体跳转的ActionCard类型,一类是独立跳转的ActionCard类型。
(1)整体跳转的ActionCard类型
消息格式为:
{
"actionCard": {
"title": "乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
"text": "![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)
### 乔布斯 20 年前想打造的苹果咖啡厅
Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
"btnOrientation": "0",
"singleTitle" : "阅读全文",
"singleURL" : "https://www.dingtalk.com/"
},
"msgtype": "actionCard"
}
参数 | 参数类型 | 必须 | 说明 |
---|---|---|---|
msgtype | String | 是 | 此消息类型为固定actionCard |
title | String | 是 | 首屏会话透出的展示内容 |
text | String | 是 | markdown格式的消息 |
singleTitle | String | 是 | 单个按钮的标题。(设置此项和singleURL后btns无效) |
singleURL | String | 是 | 点击singleTitle按钮触发的URL |
btnOrientation | String | 否 | 0-按钮竖直排列,1-按钮横向排列 |
(2)独立跳转的ActionCard类型
消息格式为:
{
"actionCard": {
"title": "乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
"text": "![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)
### 乔布斯 20 年前想打造的苹果咖啡厅
Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
"btnOrientation": "0",
"btns": [
{
"title": "内容不错",
"actionURL": "https://www.dingtalk.com/"
},
{
"title": "不感兴趣",
"actionURL": "https://www.dingtalk.com/"
}
]
},
"msgtype": "actionCard"
}
参数 | 参数类型 | 必须 | 说明 |
---|---|---|---|
msgtype | String | 是 | 此消息类型为固定actionCard |
title | String | 是 | 首屏会话透出的展示内容 |
text | String | 是 | markdown格式的消息 |
btns | Array | 是 | 按钮 |
└title | String | 是 | 按钮标题 |
└actionURL | String | 是 | 点击按钮触发的URL |
btnOrientation | String | 否 | 0-按钮竖直排列,1-按钮横向排列 |
5、FeedCard
消息格式为:
{
"feedCard": {
"links": [
{
"title": "时代的火车向前开",
"messageURL": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
"picURL": "https://www.dingtalk.com/"
},
{
"title": "时代的火车向前开2",
"messageURL": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
"picURL": "https://www.dingtalk.com/"
}
]
},
"msgtype": "feedCard"
}
参数 | 参数类型 | 必须 | 说明 |
---|---|---|---|
msgtype | String | 是 | 此消息类型为固定feedCard |
title | String | 是 | 单条信息文本 |
messageURL | String | 是 | 点击单条信息到跳转链接 |
picURL | String | 是 | 单条信息后面图片的URL |
明确消息的格式后,我们得构造消息,并调用相应接口
其中以文本类型为例,在前端页面上使用Ajax技术构造规范的消息格式如下
<script type="text/javascript">
$(function () {
var obj = JSON.stringify({
"msgtype": "text",
"text": {
"content": "我就是我, 是不一样的烟火@156xxxx8827"
},
"at": {
"atMobiles": [
"156xxxx8827",
"189xxxx8325"
],
"isAtAll": false
}
});
console.log(obj);
$.ajax({
url:'url',
type:'POST',
dataType: 'JSON',
contentType:'application/json;charset=UTF-8',
data: obj,
success:function(data){
console.log(data);
}
})
})
</script>
在后端代码中接收前端传来的JSON字符串,并却对进行解析,分析其消息内容,根据消息类型交调用相应的接口,即可实现群聊机器人发送自定义消息。
@RequestMapping("/url")
@ResponseBody
public static String keyword(@RequestBody String msg){
JSONObject jsonObject = JSON.parseObject(msg);
String msgtype = jsonObject.getString("msgtype");
switch (msgtype){
case "text":
TextMessage textMessage = JSON.toJavaObject(jsonObject, TextMessage.class);
return new Connection().keyword(Util.javaBeanToJson(textMessage));
case "link":
LinkMessage linkMessage = JSON.toJavaObject(jsonObject, LinkMessage.class);
return new Connection().keyword(Util.javaBeanToJson(linkMessage));
case "markdown":
MarkdownMessage markdownMessage = JSON.toJavaObject(jsonObject, MarkdownMessage.class);
return new Connection().keyword(Util.javaBeanToJson(markdownMessage));
case "actionCard":
ActionCardMessage actionCardMessage = JSON.toJavaObject(jsonObject, ActionCardMessage.class);
return new Connection().keyword(Util.javaBeanToJson(actionCardMessage));
case "feedCard":
FeedCardMessage feedCardMessage = JSON.toJavaObject(jsonObject, FeedCardMessage.class);
return new Connection().keyword(Util.javaBeanToJson(feedCardMessage));
}
return null;
}
1、获取到Webhook地址后,用户可以向这个地址发起HTTP POST 请求,即可实现给该钉钉群发送消息。注意,发起POST请求时,必须将字符集编码设置成UTF-8。
2、当前机器人尚不支持应答机制。
(一)开发文档实为重要!其中规范了接口的调用方式与规范。
(二)消息类型过多的时候,建议将消息进行抽象,提取其公共部分,并加以补充。
(三)根据Message的Tpye进行分类处理,而不使用重载的方式处理。
(四)对于JSON类型的参数,建议开发时以AJAX方式传参,利于构造与调试。
项目地址:https://github.com/Nannan78/dingdingRobot
---再小的帆,也能远航!