转载

Java(SpringBoot)实现钉钉机器人消息推送

零、前言

上一次做消息推送,是 微信公众号 的定时消息通知。

由于自己当时的水平不够,加上企鹅家的开发文档普遍不太友好,导致根本看不懂文档在写什么,不得不去看第三方博客来学习公众号的开发。

这次就不一样了,昨天刚看了一下,阿里的开发文档比鹅厂要清晰的多,而且在同一功能上,使用了 多种语言 作为示例代码,可以说很友好了。可能这就是阿里和鹅厂的区别吧...辣鸡文档和好文档的区别...

本着“授之以渔”的态度,写了这篇文章,作为 官方文档 的补充。

Java(SpringBoot)实现钉钉机器人消息推送

一、在群里添加机器人

群设置智能群助手 中添加 自定义机器人 ,它长这个样子:

Java(SpringBoot)实现钉钉机器人消息推送

比较关键的一步,是进行 安全设置

加密方式一共有三种,既可以选择一种也可以使用多种方式组合:

  • 自定义关键词
  • 加签
  • IP地址

各种加密方式的介绍,详见官网:

https://ding-doc.dingtalk.com...

为了让博客起到效果,我们选择相对安全、也比较难的 加签 方式。

选择 加签 之后,把密钥复制出来,然后就可以点确定了。

二、构建请求地址和内容

先看看官方文档怎么描述 加签 的:

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

第二步,把  timestamp 和第一步得到的 签名值 拼接到URL中。

官方的解释很高大上,其实原理很简单,就是把 机器人密钥 加密后,放在 URL的参数 中,所以我们需要分别获取 时间戳密钥 ,组合一下,加密一下,再拼接一下就好了,如图:

Java(SpringBoot)实现钉钉机器人消息推送

I have a Pen, I have an Apple, Oh~ Applepen~

官方给出了这样的示例代码:

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);
    }
}

然而,org.apache.commons.codec.binary.Base64不是Java的内置类,也就是说,示例代码 并不能直接拿过来用

Java(SpringBoot)实现钉钉机器人消息推送

查了一下,发现Java8中内置的java.util已经包含了Base64,因此用它替换掉原来的codec,无需再引入第三方包:

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

public class ding {
    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 = Mac.getInstance("HmacSHA256");
        //初始化Mac对象,设置Mac对象操作的字符串是UTF-8类型,加密方式是SHA256
        mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
        //把字符串转化成字节形式
        byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
        //新建一个Base64编码对象
        Base64.Encoder encoder = Base64.getEncoder();
        //把上面的字符串进行Base64加密后再进行URL编码
        String sign = URLEncoder.encode(new String(encoder.encodeToString(signData)),"UTF-8");
        //分别输出时间戳和加密信息
        System.out.println(timestamp);
        System.out.println(sign);
    }
}

用最笨的方法,在终端执行一下看看:

Java(SpringBoot)实现钉钉机器人消息推送

成功输出了 时间戳验证信息

我们测试上述代码的时候,可以 手动拼接 URL,直接发起请求:

(URL一共有三个参数: access_token、timestamp、sign ,需要换成自己的,也就是上面 终端输出的结果

//替换参数后,在终端执行
curl 'https://oapi.dingtalk.com/robot/send?access_token=70c168d03e73728ef36abea63c3c10048cbd054913cfeb&timestamp=1584607421017&sign=gJ3l4mhnlMuHxK1qFUx1kKUSdjuCNntsdG%2Bv%2BTCrLQM%3D' /
   -H 'Content-Type: application/json' /
   -d '{"msgtype": "text", 
        "text": {
             "content": "我就是我, 是不一样的烟火"
        },
        "sign": "gJ3l4mhnlMuHxK1qFUx1kKUSdjuCNntsdG%2Bv%2BTCrLQM%3D"
      }'

然后就出现了:

Java(SpringBoot)实现钉钉机器人消息推送

经过测试,代码正常运行,接下来就是部署到生产环境了。

三、部署代码

我们需要先找一下Spring如何发起HTTP请求。

以前,笔者只用过前台的HttpClient,对于后台的HTTP工具并不了解。

一开始尝试用Spring内置的RestTemplate,去网上查了它的用法,写了一堆代码,但怎么也不成功。由于从来没用过RestTemplate,也没耐心去看它的源码,于是放弃。

后来,只能老老实实的用apache的httpClient,查了一下用法,虽然有点麻烦,很多操作没法自动完成,但还算通俗易懂,而且它的包托管在Maven上,导入很方便。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.9</version>
</dependency>

httpClient的使用很灵活,这里使用的是 POST 方式,有 一个参数 ,发起POST请求时,必须将字符集编码设置成 UTF-8

粗略步骤如图:

Java(SpringBoot)实现钉钉机器人消息推送

直接来一段稍微改一下就能用的代码:

public class DingService {
    //请求地址以及access_token
    String Webhook = "https://oapi.dingtalk.com/robot/send?access_token=YOUR TOKEN";
    //密钥
    String secret = "YOUR SECRET";

    /*
    ** 生成时间戳和验证信息
    */
    
    public String encode() throws Exception {
        //获取时间戳
        Long timestamp = System.currentTimeMillis();
        //把时间戳和密钥拼接成字符串,中间加入一个换行符
        String stringToSign = timestamp + "/n" + this.secret;
        //声明一个Mac对象,用来操作字符串
        Mac mac = Mac.getInstance("HmacSHA256");
        //初始化,设置Mac对象操作的字符串是UTF-8类型,加密方式是SHA256
        mac.init(new SecretKeySpec(this.secret.getBytes("UTF-8"), "HmacSHA256"));
        //把字符串转化成字节形式
        byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
        //新建一个Base64编码对象
        Base64.Encoder encoder = Base64.getEncoder();
        //把上面的字符串进行Base64加密后再进行URL编码
        String sign = URLEncoder.encode(new String(encoder.encodeToString(signData)),"UTF-8");
        System.out.println(timestamp);
        System.out.println(sign);
        String result = "&timestamp=" + timestamp + "&sign=" + sign;
        return result;
    };

    /* param: message 要发送的信息
    ** return: void 无返回值
    ** 作用:把传入的message发送给钉钉机器人*/

    public void dingRequest(String message){
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        String url = null;
        try {
            url = this.Webhook + this.encode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        HttpPost httpPost = new HttpPost(url);
        //设置http的请求头,发送json字符串,编码UTF-8
        httpPost.setHeader("Content-Type", "application/json;charset=utf8");
        //生成json对象传入字符
        JSONObject result = new JSONObject();
        JSONObject text = new JSONObject();
        text.put("content", message);
        result.put("text", text);
        result.put("msgtype", "text");
        String jsonString = JSON.toJSONString(result);
        StringEntity entity = new StringEntity(jsonString, "UTF-8");
        //设置http请求的内容
        httpPost.setEntity(entity);
        // 响应模型
        CloseableHttpResponse response = null;
        try {
            // 由客户端执行(发送)Post请求
            response = httpClient.execute(httpPost);
            // 从响应模型中获取响应实体
            HttpEntity responseEntity = response.getEntity();
            System.out.println("响应状态为:" + response.getStatusLine());
            if (responseEntity != null) {
                System.out.println("响应内容长度为:" + responseEntity.getContentLength());
                System.out.println("响应内容为:" + EntityUtils.toString(responseEntity));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 释放资源
                if (httpClient != null) {
                    httpClient.close();
                }
                if (response != null) {
                    response.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

总结

消息推送别烦恼,这个功能非常好。

只要原理好好搞,融会贯通没烦恼。

写完代码心情好,庆祝一下少不了。

出门上街到处跑,去吃秘制小汉堡。

其实消息推送的功能并不难,只是由于初次接触,需要查很多的文档,在这个过程中,锻炼了文本阅读能力和独立解决问题的能力。

参考资料

Java如何进行Base64的编码(Encode)与解码(Decode)

Spring RestTemplate介绍

Spring--Http请求--HttpClient

原文  https://segmentfault.com/a/1190000022077236
正文到此结束
Loading...