通知神器 — Java调用钉钉群自定义机器人

钉钉群机器人概述:https://developers.dingtalk.com/document/app/overview-of-group-robots
自定义机器人接入:https://developers.dingtalk.com/document/app/custom-robot-access
开发企业内部机器人:https://developers.dingtalk.com/document/app/develop-enterprise-internal-robots
服务端SDK下载:https://developers.dingtalk.com/document/resourcedownload/download-server-sdk
服务端API调试工具:https://developers.dingtalk.com/document/resourcedownload/api-explorer

获取自定义机器人webhook

PC端入口

打开钉钉,点击头像,选择机器人管理

在机器人管理页面选择自定义机器人,输入机器人名字并选择要发送消息的群,同时可以为机器人设置机器人头像

移动端入口

在指定钉钉群(或者随便拉两个人建个群,然后把别人T出去)的群设置里选择 群智能助手 -> 添加机器人 -> 自定义(通过Webhook接入自定义服务)

然后,添加机器人,设置名字,添加成功后设置webhook(非常重要)

完成必要的安全设置

目前有3种安全设置方式,请根据需要选择一种:

(1)自定义关键词

自定义关键词:最多可以设置10个关键词,消息中至少包含其中1个关键词才可以发送成功

例如添加了一个自定义关键词:监控报警,则这个机器人所发送的消息,必须包含监控报警这个词,才能发送成功

(2)加签

timestamp+"\n"+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)

然后把timestamp和第一步得到的签名值拼接到URL中:

1
https://oapi.dingtalk.com/robot/send?access_token=XXXXXX&timestamp=XXX&sign=XXX
  • timestamp 当前时间戳,单位是毫秒,与请求调用时间误差不能超过1小时
  • secret 密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串

附:签名计算示例代码(Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 = "this is 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);
}
}

(3)IP地址(段)

设定后,只有来自IP地址范围内的请求才会被正常处理。支持两种设置方式:IP地址和IP地址段,暂不支持IPv6地址白名单

获取Webhook地址

完成安全设置后,复制出机器人的Webhook地址,可用于向这个群发送消息,格式如下:

1
https://oapi.dingtalk.com/robot/send?access_token=XXXXXX

请保管好此Webhook 地址,不要公布在外部网站上,泄露后有安全风险

使用自定义机器人

获取到Webhook地址后,用户可以向这个地址发起HTTP POST 请求,即可实现给该钉钉群发送消息。

注意

  • 发起POST请求时,必须将字符集编码设置成UTF-8
  • 每个机器人每分钟最多发送20条。消息发送太频繁会严重影响群成员的使用体验,大量发消息的场景 (譬如系统监控报警) 可以将这些信息进行整合,通过markdown消息以摘要的形式发送到群里

当前自定义机器人支持文本 (text)、链接 (link)、markdown(markdown)、ActionCard、FeedCard消息类型,请根据自己的使用场景选择合适的消息类型,达到最好的展示样式。详情参考:消息类型与数据格式

自定义机器人发送消息时,可以通过手机号码指定被@人列表。在被@人列表里面的人员收到该消息时,会有@消息提醒。免打扰会话仍然通知提醒,首屏出现有人@你

1
2
3
curl 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text", "text": {"content": "hello world"}, "at": {"atMobiles": ["166********"], "isAtAll": true}}'

测试自定义机器人

SDK请求示例(Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?access_token=566cc69da782ec******");
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype("text");
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent("测试文本消息");
request.setText(text);
OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
at.setAtMobiles(Arrays.asList("132xxxxxxxx"));
// isAtAll类型如果不为Boolean,请升级至最新SDK
at.setIsAtAll(true);
request.setAt(at);

request.setMsgtype("link");
OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
link.setMessageUrl("https://www.dingtalk.com/");
link.setPicUrl("");
link.setTitle("时代的火车向前开");
link.setText("这个即将发布的新版本,创始人xx称它为红树林。而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是红树林");
request.setLink(link);

request.setMsgtype("markdown");
OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
markdown.setTitle("杭州天气");
markdown.setText("#### 杭州天气 @156xxxx8827\n" +
"> 9度,西北风1级,空气良89,相对温度73%\n\n" +
"> ![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)\n" +
"> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n");
request.setMarkdown(markdown);
OapiRobotSendResponse response = client.execute(request);

PHP程序测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php  
function request_by_curl($remote_server, $post_string) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $remote_server);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_HTTPHEADER, array ('Content-Type: application/json;charset=utf-8'));
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 线下环境不用开启curl证书验证, 未调通情况可尝试添加该代码
// curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
// curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}

$webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxxxxx";
$message="我就是我, 是不一样的烟火";
$data = array ('msgtype' => 'text','text' => array ('content' => $message));
$data_string = json_encode($data);

$result = request_by_curl($webhook, $data_string);
echo $result;
?>

消息类型及数据格式

  • text类型
1
2
3
4
5
6
7
8
9
10
11
12
{
"msgtype": "text",
"text": {
"content": "我就是我, @150XXXXXXXX 是不一样的烟火"
},
"at": {
"atMobiles": [
"150XXXXXXXX"
],
"isAtAll": false
}
}
  • link类型
1
2
3
4
5
6
7
8
9
{
"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"
}
}
  • markdown类型
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"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
}
}
  • 整体跳转ActionCard类型
1
2
3
4
5
6
7
8
9
10
11
12
{
"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"
}
  • 独立跳转ActionCard类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"msgtype": "actionCard",
"actionCard": {
"title": "我 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
"text": "![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png) \n\n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
"hideAvatar": "0",
"btnOrientation": "0",
"btns": [
{
"title": "内容不错",
"actionURL": "https://www.dingtalk.com/"
},
{
"title": "不感兴趣",
"actionURL": "https://www.dingtalk.com/"
}
]
}
}
  • FeedCard类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"msgtype": "feedCard",
"feedCard": {
"links": [
{
"title": "时代的火车向前开1",
"messageURL": "https://www.dingtalk.com/",
"picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
},
{
"title": "时代的火车向前开2",
"messageURL": "https://www.dingtalk.com/",
"picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
}
]
}
}

常见问题

当出现以下错误时,表示消息校验未通过,请查看机器人的安全设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 消息内容中不包含任何关键词
{
"errcode":310000,
"errmsg":"keywords not in content"
}

// timestamp 无效
{
"errcode":310000,
"errmsg":"invalid timestamp"
}

// 签名不匹配
{
"errcode":310000,
"errmsg":"sign not match"
}

// IP地址不在白名单
{
"errcode":310000,
"errmsg":"ip X.X.X.X not in whitelist"
}

使用sdk调用自定义机器人

DefaultDingTalkClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) throws ApiException {
final String serverUrl =
"https://oapi.dingtalk.com/robot/send?access_token=*******************************************************";

DingTalkClient client = new DefaultDingTalkClient(serverUrl);
OapiRobotSendRequest request = new OapiRobotSendRequest();

At at = new At();
at.setIsAtAll("true");
request.setAt(at);

// 若上一步isAtAll没有设置true,则根据此处设置的手机号来@指定人
List<String> mobiles = new ArrayList<>();
mobiles.add("166********");
at.setAtMobiles(mobiles);

// 以下是设置各种消息格式的方法
sentText(request);
// sendLink(request);
// sendMarkdown(request);
// sendActionCard(request);
// sendFeedCard(request);

OapiRobotSendResponse response = client.execute(request);
System.out.println(response.getErrmsg());
}

text文本消息

1
2
3
4
5
6
7
public static void sentText(OapiRobotSendRequest request) {
Text text = new Text();
text.setContent("sdk demo");

request.setMsgtype("text");
request.setText(text);
}

link链接消息

1
2
3
4
5
6
7
8
9
10
public static void sendLink(OapiRobotSendRequest request) {
Link link = new Link();
link.setTitle("好消息!好消息!");
link.setText("本群与百度成功达成合作关系,今后大家有什么不懂的可以直接百度搜索,不用再群里提问浪费时间啦!");
link.setMessageUrl("https://www.baidu.com");
link.setPicUrl("http://www.baidu.com/img/bd_logo1.png");

request.setMsgtype("link");
request.setLink(link);
}

markdown消息

1
2
3
4
5
6
7
8
9
10
11
public static void sendMarkdown(OapiRobotSendRequest request) {
Markdown markdown = new Markdown();
markdown.setTitle("好消息!好消息!");
markdown.setText(
"#### 杭州天气 @156xxxx8827\n> 9度,西北风1级,空气良89,相对温度73%\n\n"
+ "> ![screenshot](https://img2018.cnblogs.com/blog/1775867/201908/1775867-20190823214506246-1471056011.png)\n"
+ "> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n");

request.setMsgtype("markdown");
request.setMarkdown(markdown);
}

ActionCard消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void sendActionCard(OapiRobotSendRequest request) {
Actioncard actioncard = new Actioncard();
actioncard.setTitle("乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身");
actioncard.setText(
"![screenshot](@lADOpwk3K80C0M0FoA) \n"
+ " ### 乔布斯 20 年前想打造的苹果咖啡厅 Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划");
actioncard.setHideAvatar("0");
actioncard.setBtnOrientation("1");
// actioncard.setSingleTitle("阅读全文");
// actioncard.setSingleURL("https://www.baidu.com/");
List<Btns> btns = new ArrayList<>();
Btns btn0 = new Btns();
btn0.setTitle("内容不错");
btn0.setActionURL("https://www.qq.com/");
btns.add(btn0);

Btns btn1 = new Btns();
btn1.setTitle("不感兴趣");
btn1.setActionURL("https://www.baidu.com/");
btns.add(btn1);

actioncard.setBtns(btns);
request.setMsgtype("actionCard");
request.setActionCard(actioncard);
}

FeedCard消息

类似公众号头条、次条消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void sendFeedCard(OapiRobotSendRequest request) {
Feedcard feedcard = new Feedcard();
List<Links> linksList = new ArrayList<>();

Links links0 = new Links();
links0.setTitle("时代的火车向前开1");
links0.setMessageURL(
"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");
links0.setPicURL("https://www.dingtalk.com/");
linksList.add(links0);

Links links1 = new Links();
links1.setTitle("时代的火车向前开2");
links1.setMessageURL(
"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");
links1.setPicURL("https://www.dingtalk.com/");
linksList.add(links1);

feedcard.setLinks(linksList);

request.setMsgtype("feedCard");
request.setFeedCard(feedcard);
}

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2021 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :