自定义钉钉机器人实现预警推送

注:本文代码适用于Java环境。可直接使用。

钉钉最近升级,发送群消息新增了安全设置,20191031已更新到最新版

背景

在实际的开发过程当中,有时候在一些特殊的地方,比如请求第三方接口,连接第三方中间件等过程中,总是会碰到一些莫名其妙的报错问题,要想让程序报错时及时通知到开发人员,最简单的实现方式之一,则可采用阿里的钉钉群进行自定义机器人进行通知。

成果展示

可以通过钉钉群自定义的群机器人,艾特相关的技术人员关注某些特定的业务执行过程或者及时处理某些异常报错接口。
在这里插入图片描述

接入过程

创建自定义机器人

将需要接收信息的人员拉入一个钉钉群,自己取一个高大上的群名哈,然后点击群机器人 -> 添加机器人 -> 选择自定义机器人 -> 根据提示完成机器人创建。如果需要的话,可以为机器人设置一个头像。点击“完成添加”,完成后会生成webhook地址,如下图:
在这里插入图片描述
点击“复制”按钮,即可获得这个机器人对应的Webhook地址,其格式如下:

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

使用自定义机器人

自定义机器人发送消息时,可以通过手机号码指定“被@人列表”。在“被@人列表”里面的人员收到该消息时,会有@消息提醒(免打扰会话仍然通知提醒,首屏出现“有人@你”)。
消息发送频率限制:
每个机器人每分钟最多发送20条。消息发送太频繁会严重影响群成员的使用体验,大量发消息的场景 (譬如系统监控报警) 可以将这些信息进行整合,通过markdown消息以摘要的形式发送到群里。当然,也可以针对不同的业务创建多个不同的机器人。

以下是Java程序中发送自定义钉钉机器人的工具类(text类型&markdown类型):
效果:
在这里插入图片描述
在这里插入图片描述
发送消息(各种类型均可)工具类:(可直接使用)

package com.github.collection.common.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.collection.common.constant.enums.DingMsgPhoneEnum;
import com.github.collection.common.constant.enums.DingTokenEnum;
import com.xiaoleilu.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;


/**
 * 钉钉消息发送工具类
 *
 * 机器人发送消息频率限制:每个机器人每分钟最多发送20条。如果超过20条,会限流10分钟。
 */
@Slf4j
public class DingDingMsgSendUtils {

    /**
     * 调用钉钉官方接口发送钉钉消息(旧版本,不需要配置安全设置)
     * @param accessToken
     * @param textMsg
     */
    private static void dealDingDingMsgSend(String accessToken, String textMsg) {
        HttpClient httpclient = HttpClients.createDefault();
        String WEBHOOK_TOKEN = "https://oapi.dingtalk.com/robot/send?access_token=" + accessToken;
        HttpPost httppost = new HttpPost(WEBHOOK_TOKEN);
        httppost.addHeader("Content-Type", "application/json; charset=utf-8");
        StringEntity se = new StringEntity(textMsg, "utf-8");
        httppost.setEntity(se);

        try {
            HttpResponse response = httpclient.execute(httppost);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                String result= EntityUtils.toString(response.getEntity(), "utf-8");
                log.info("【发送钉钉群消息】消息响应结果:" + JSON.toJSONString(result));
            }
        } catch (Exception e) {
            log.error("【发送钉钉群消息】error:" + e.getMessage(), e);
        }
    }

    /**
     * 调用钉钉官方接口发送钉钉消息(新版本,需要配置安全设置)
     * @param accessToken
     * @param secret
     * @param textMsg
     */
    private static void dealDingDingMsgSendNew(String accessToken, String secret, String textMsg) {
        Long timestamp = System.currentTimeMillis();
        String sign = getSign(secret, timestamp);
        String url = "https://oapi.dingtalk.com/robot/send?access_token=" + accessToken + "&timestamp=" + timestamp + "&sign=" + sign;
        try {
            log.info("【发送钉钉群消息】请求参数:url = {}, textMsg = {}", url, textMsg);
            String res = HttpUtil.post(url, textMsg);
            log.info("【发送钉钉群消息】消息响应结果:" + res);
        } catch (Exception e) {
            log.warn("【发送钉钉群消息】请求钉钉接口异常,errMsg = {}", e);
        }
    }

    /**
     * 计算签名
     * @param secret 密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符
     * @param timestamp
     * @return
     */
    private static String getSign(String secret, Long timestamp){
        try {
            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");
            log.info("【发送钉钉群消息】获取到签名sign = {}", sign);
            return sign;
        } catch (Exception e) {
            log.error("【发送钉钉群消息】计算签名异常,errMsg = {}", e);
            return null;
        }
    }

    /**
     * 发送钉钉群消息
     * @param accessToken
     * @param content
     */
    public static void sendDingDingGroupMsg(String accessToken, String content) {
        String textMsg = "{ \"msgtype\": \"text\", \"text\": {\"content\": \"" + content + "\"}}";
        dealDingDingMsgSend(accessToken, textMsg);
    }

    /**
     * 发送钉钉群消息(可以艾特人)
     * @param accessToken 群机器人accessToken
     * @param content 发送内容
     * @param atPhone 艾特人电话,如:13867400741,15608457257,15072328011
     */
    public static void sendDingDingGroupMsg(String accessToken, String content, String atPhone) {
        String textMsg = "{\n" +
                "    \"msgtype\": \"text\", \n" +
                "    \"text\": {\n" +
                "        \"content\": \""+ content +"\"\n" +
                "    }, \n" +
                "    \"at\": {\n" +
                "        \"atMobiles\": [\n" +
                "            " + atPhone +
                "        ], \n" +
                "        \"isAtAll\": false\n" +
                "    }\n" +
                "}";
        dealDingDingMsgSend(accessToken, textMsg);
    }

    /**
     * 发送钉钉群消息(link类型)
     * @param accessToken
     * @param title 消息标题
     * @param text 消息内容。如果太长只会部分展示
     * @param picUrl 图片URL
     * @param messageUrl 点击消息跳转的URL
     */
    public static void sendDingDingLinkGroupMsg(String accessToken, String title, String text, String picUrl, String messageUrl) {
        String textMsg = "{\n" +
                "    \"msgtype\": \"link\", \n" +
                "    \"link\": {\n" +
                "        \"text\": \""+text+"\", \n" +
                "        \"title\": \""+title+"\", \n" +
                "        \"picUrl\": \""+picUrl+"\", \n" +
                "        \"messageUrl\": \""+messageUrl+"\"\n" +
                "    }\n" +
                "}";
        dealDingDingMsgSend(accessToken, textMsg);
    }

    /**
     * markdown类型
     * @param accessToken
     * @param title
     * @param text
     * @param atMobiles
     */
    public static void sendDingDingMarkdownGroupMsg(String accessToken, String secret, String title, String text, String atMobiles) {
        log.info("【发送钉钉群消息】正在发送markdown类型的钉钉消息...");
        JSONObject markdown = new JSONObject();
        markdown.put("title", title);
        markdown.put("text", text);

        JSONObject at = new JSONObject();
        at.put("atMobiles", atMobiles);
        at.put("isAtAll", false);

        JSONObject textMsg = new JSONObject();
        textMsg.put("msgtype", "markdown");
        textMsg.put("markdown", markdown);
        textMsg.put("at", at);

        dealDingDingMsgSendNew(accessToken, secret, textMsg.toJSONString());
    }

    /**
     * 整体跳转ActionCard类型
     * @param accessToken
     * @param title
     * @param text
     * @param singleTitle
     * @param singleURL
     */
    public static void sendDingDingActionCardGroupMsg(String accessToken, String title, String text, String singleTitle, String singleURL) {
        String textMsg = "{\n" +
                "    \"actionCard\": {\n" +
                "        \"title\": \""+title+"\", \n" +
                "        \"text\": \"![screenshot](https://mmbiz.qpic.cn/mmbiz_jpg/jyPD8edcUjEwQ1Tdotpq94VE4G1wIMAxQyI2Oe7RaDRT0iaBRD2KdOL0iaL56jBWQX5Fzq3S7R66pyuEIZW83Ulw/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)"+text+"\", \n" +
                "        \"hideAvatar\": \"0\", \n" +
                "        \"btnOrientation\": \"0\", \n" +
                "        \"singleTitle\" : \""+singleTitle+"\",\n" +
                "        \"singleURL\" : \""+singleURL+"\"\n" +
                "    }, \n" +
                "    \"msgtype\": \"actionCard\"\n" +
                "}";
        dealDingDingMsgSend(accessToken, textMsg);
    }

    /**
     * 独立跳转ActionCard类型
     * @param accessToken
     * @param title
     * @param text
     * @param singleTitle
     * @param singleURL
     */
    public static void sendDingDingActionCardGroupMsg2(String accessToken, String title, String text, String singleTitle, String singleURL) {
        String textMsg = "{\n" +
                "    \"actionCard\": {\n" +
                "        \"title\": \"乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身\", \n" +
                "        \"text\": \"![screenshot](@lADOpwk3K80C0M0FoA) \n" +
                " ### 乔布斯 20 年前想打造的苹果咖啡厅 \n" +
                " Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划\", \n" +
                "        \"hideAvatar\": \"0\", \n" +
                "        \"btnOrientation\": \"0\", \n" +
                "        \"btns\": [\n" +
                "            {\n" +
                "                \"title\": \"内容不错\", \n" +
                "                \"actionURL\": \"https://www.dingtalk.com/\"\n" +
                "            }, \n" +
                "            {\n" +
                "                \"title\": \"不感兴趣\", \n" +
                "                \"actionURL\": \"https://www.dingtalk.com/\"\n" +
                "            }\n" +
                "        ]\n" +
                "    }, \n" +
                "    \"msgtype\": \"actionCard\"\n" +
                "}\n";
        dealDingDingMsgSend(accessToken, textMsg);
    }

    /**
     * FeedCard类型
     * @param accessToken
     * @param title
     * @param text
     * @param singleTitle
     * @param singleURL
     */
    public static void sendDingDingFeedCardGroupMsg(String accessToken, String title, String text, String singleTitle, String singleURL) {
        String textMsg = "{\n" +
                "    \"feedCard\": {\n" +
                "        \"links\": [\n" +
                "            {\n" +
                "                \"title\": \"时代的火车向前开\", \n" +
                "                \"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\", \n" +
                "                \"picURL\": \"https://www.dingtalk.com/\"\n" +
                "            },\n" +
                "            {\n" +
                "                \"title\": \"时代的火车向前开2\", \n" +
                "                \"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\", \n" +
                "                \"picURL\": \"https://www.dingtalk.com/\"\n" +
                "            }\n" +
                "        ]\n" +
                "    }, \n" +
                "    \"msgtype\": \"feedCard\"\n" +
                "}";
        dealDingDingMsgSend(accessToken, textMsg);
    }

    /**
     * 发送钉钉群消息(可以艾特人)- 技术专用
     * @param content 发送内容
     */
    public static void sendDingDingGroupMsg(String content){
        sendDingDingGroupMsg(DingTokenEnum.SEND_SMS_BY_DEVELOPER_TOKEN.getToken(), content, DingMsgPhoneEnum.DEVELOPER_PHONE.getPhone());
    }


    public static void main(String[] args) {
//        sendDingDingGroupMsg(DingTokenEnum.SEND_SMS_BY_DEVELOPER_TOKEN.getToken(), "【系统消息】钉钉消息推送测试,by:songfayuan...", DingMsgPhoneEnum.DEVELOPER_PHONE.getPhone());
//        sendDingDingLinkGroupMsg(DingTokenEnum.SEND_SMS_BY_DEVELOPER_TOKEN.getToken(), "【系统消息】", "我喜欢,驾驭着代码在风驰电掣中创造完美!我喜欢,操纵着代码在随心所欲中体验生活!我喜欢,书写着代码在时代浪潮中完成经典!每一段新的代码在我手中诞生对我来说就象观看刹那花开的感动!", "https://blog.csdn.net/u011019141/static/img/homepage_icon_welcome.b870f20.jpg", "https://blog.csdn.net/u011019141");

        String title = "杭州天气";
        String text = "#### 杭州天气 @138*****41\n" +
                "> 9度,西北风1级,空气良89,相对温度73%\n\n" +
                "> ![screenshot](http://i.weather.com.cn/images/cn/news/2019/10/23/1571815463327062988.jpg)\n"  +
                "> ###### 10点20分发布 [天气](http://www.weather.com.cn/weather/101210101.shtml) \n";
        String atMobiles = "[138*****41,156*****57,150*****011]";
        sendDingDingMarkdownGroupMsg(DingTokenEnum.SEND_SMS_BY_MARKDOWN_TOKEN.getToken(), DingTokenEnum.SEND_SMS_BY_MARKDOWN_TOKEN.getSecret(),title, text, atMobiles);

//        sendDingDingActionCardGroupMsg(DingTokenEnum.SEND_SMS_BY_DEVELOPER_TOKEN.getToken(), "【系统消息】", "2323", "阅读全文", "https://blog.csdn.net/u011019141");
//        sendDingDingActionCardGroupMsg2(DingTokenEnum.SEND_SMS_BY_DEVELOPER_TOKEN.getToken(), "【系统消息】", "2323", "阅读全文", "https://blog.csdn.net/u011019141");
//        sendDingDingFeedCardGroupMsg(DingTokenEnum.SEND_SMS_BY_DEVELOPER_TOKEN.getToken(), "【系统消息】", "2323", "阅读全文", "https://blog.csdn.net/u011019141");
    }

}

自定义群机器人webhook地址中access_token值的枚举类:

package com.github.collection.common.constant.enums;

import lombok.Getter;

/**
 * 钉钉消息群机器人access_token、secret
 */
@Getter
public enum DingTokenEnum {

    SEND_SMS_GROUP("替换成你自己机器人的access_token", "替换成你自己机器人的secret", "短信发送异常通知"),
    SEND_SMS_BY_MARKET_IMPORT_TOKEN("替换成你自己机器人的access_token", "替换成你自己机器人的secret", "导入通知消息"),
    SEND_SMS_BY_PRE_CASE_WARN_TOKEN("替换成你自己机器人的access_token","替换成你自己机器人的secret",  "预警消息通知"),
    SEND_SMS_BY_MARKDOWN_TOKEN("替换成你自己机器人的access_token", "替换成你自己机器人的secret", "系统消息通知,markdown类型"),
    SEND_SMS_BY_DEVELOPER_TOKEN("替换成你自己机器人的access_token", "替换成你自己机器人的secret", "系统消息通知,技术专用");


    private String token;  //机器人 Webhook 地址中的 access_token

    private String secret;  //密钥,机器人安全设置页面,加签一栏下面显示的SEC开头的字符串

    private String name;

    DingTokenEnum(String token, String secret, String name) {
        this.token = token;
        this.secret = secret;
        this.name = name;
    }

}

需要艾特人的手机号枚举类
注:手机号需是钉钉注册手机号

package com.github.****.common.constant.enums;

import lombok.Getter;

/**
 * 钉钉消息接收用户,配置钉钉绑定的电话即可
 */
@Getter
public enum DingMsgPhoneEnum {
    GENERAL_PURPOSE("138****0741,156****7257,150****8011,188****535", "通用(包含技术,产品,领导等)"),
    DEVELOPER_PHONE("138****0741,156****7257,150****8011,188****3535", "技术人员"),
    PRODUCT_PERSONNEL_PHONE("", "产品人员"),
    DATA_ANALYST_PHONE("", "数据分析人员");

    private String phone;

    private String name;

    DingMsgPhoneEnum(String phone, String name) {
        this.phone = phone;
        this.name = name;
    }

}

最后,在你需要发送消息的地方,直接通过调用DingDingMsgSendUtils工具即可发送钉钉消息。

DingDingMsgSendUtils.sendDingDingGroupMsg(DingTokenEnum.SEND_SMS_BY_DEVELOPER_TOKEN.getToken(), "【系统消息】"+profile+"环境,XXX任务开始执行...", DingMsgPhoneEnum.DEVELOPER_PHONE.getPhone());

上面事例中的profile为环境参数,在Springboot和SpringCloud项目中,可以直接通过如下代码进行获取:

@Value("${spring.profiles.active}")
private String profile;

文中使用到的HttpUtil工具需引入资源:

<dependency>
          <groupId>com.xiaoleilu</groupId>
           <artifactId>hutool-all</artifactId>
           <version>3.3.2</version>
       </dependency>

延伸

以上采用的是***text类型***进行消息发送,想支持更多的发送格式,请移步到钉钉开发文档:https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq

### 将 Spark 集成到钉钉 API 实现预警功能 #### 介绍 为了实现在数据处理过程中遇到异常情况时能够及时通知相关人员,可以通过集成 Apache Spark 和钉钉机器人API来实现这一目标。当Spark作业检测到特定条件满足时(比如错误率超过阈值、性能指标下降等),它会触发向指定群组发送消息的通知机制。 #### 准备工作 - **获取DingTalk Webhook URL**: 登录企业内部使用的钉钉后台管理系统,创建自定义机器人并启用安全设置中的关键字验证或签名认证等方式确保安全性;记录下生成的WebHook地址用于后续开发。 - **安装必要的库文件** 对于Python版本的PySpark项目来说,除了常规的数据分析包外还需要引入`requests`模块以便于发起HTTP请求给第三方平台: ```bash pip install requests ``` #### 编写代码逻辑 下面是一个简单的例子展示如何利用 PySpark 进行数据分析的同时调用 DingTalk 的 webhook 接口来进行报警: ```python import json from pyspark.sql import SparkSession import requests def send_dingtalk_message(webhook_url, content): headers = {'Content-Type': 'application/json'} data = { "msgtype": "text", "text": {"content": f"{content}"} } response = requests.post(url=webhook_url, headers=headers, data=json.dumps(data)) if response.status_code != 200: raise Exception(f"Failed to post message {response.text}") if __name__ == "__main__": spark = SparkSession.builder.appName("Alert System").getOrCreate() # 假设我们有一个DataFrame df 来存储日志信息或者其他监控数据 df = ... error_rate_threshold = 0.1 total_count = df.count() failed_tasks = df.filter(df['status'] == 'failed').count() current_error_rate = float(failed_tasks)/total_count dingtalk_webhook = "<your-dingtalk-webhook-url>" alert_content = "" if current_error_rate >= error_rate_threshold: alert_content += f"[ALERT] Error rate ({current_error_rate:.2%}) exceeds threshold({error_rate_threshold}).\n" if alert_content: try: send_dingtalk_message(dingtalk_webhook, alert_content) except Exception as e: print(e) spark.stop() ``` 此脚本首先初始化了一个 Spark 应用程序实例,并加载了一些模拟的日志数据作为 DataFrame 对象 `df`. 然后计算失败任务的比例并与预设阈值比较。如果超过了设定的标准,则构建一条警告信息并通过之前准备好的函数将其推送钉钉聊天室中去[^1].
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宋发元

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值