基于scala向钉钉群组发送消息实践

一个祥和的月黑风高夜,系统在有条不紊运行着. lol s9赛季群里有一句没一句的聊着,但钉钉声此起彼伏,各种系统信息接踵而至,大家把注意力从视频直播,转向钉钉消息.有针对性的各自分析问题,很快钉钉声再次停歇,大家欢庆lpl胜利,嗨皮继续.
--- 钉钉使用实录

钉钉有了阿里加成,成为最近几年最热门即时通信和办公软件.大有微信主导日常,钉钉支持工作的趋势.在众多的功能中,支持向群组发送消息给运维提供了方便, 引言就是运维的一个例子.

本文实现基于scala向钉钉群组发送消息功能.

内容导航
  • 实现原理 - 介绍实现的原理

  • 开发优缺点对比

  • 代码外部依赖

  • 功能介绍 - 分别支持功能,实现方式

  • 代码

实现原理

钉钉聊天群内支持的群机器人, 类似QQ 群机器人, 可以发天气, 讲笑话那样; 钉钉群机器人支持自定义机器人, 允许开发者管理机器人做预警消息通知.本文是基于该功能实现向群组发送消息功能的.

咱们是使用自定义机器人来完成消息发送.钉钉给我们开了一扇窗,我们要好好使用.

钉钉群机器人官方自我介绍,详见传送门机器人自我介绍, 也可以使用链接地址: https://ding-doc.dingtalk.com/doc#/serverapi3/iydd5h

在赘述下,钉钉支持的消息样式:

  • text类型

  • link类型

  • markdown类型

  • 整体跳转ActionCard类型

  • 独立跳转ActionCard类型

  • FeedCard类型

从简约到典雅格式,反正总有一款格式适合你.我们实现的基本的文本格式.

开发方式
  • SDK jar的方式开发
    官方提供Jar包,包含需要的API,可以直接使用.

    • 优点

    它是官方提供封装依赖包,提供完整的功能支持和依赖包含.只需要调用api接口完成.https://ding-doc.dingtalk.com/doc#/serverapi3/iydd5h测试机器人章节还提供javaPHP的样例.

    • 缺点

    jar包并不是官方提供的标准api,发生变化如何识别,是个问题.另外,如何同步更新依赖也是个问题.

  • 通过Http的方式开发

    • 优点

      不需要依赖官方依赖包,满足官方消息格式就能完成.

    • 缺点

      重复造轮子.嘎嘎,用等哥的话说,其实我不怕.

接下来,我们展示代码实现逻辑.

代码依赖

代码开发依赖库如下所示,版本使用根据自己需要选择.

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>${configuration.version}</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>

</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore.version}</version>
</dependency>

功能介绍
  • 支持代理服务器发送消息

    应用系统部署环境如果是需要公司网关,消息直接发送就会失败.代理设置是解决的思路.我们支持设置代理的功能,用户根据需要设置代理服务器.实现代码如下:

    def setProxy(proxy: String, port: Int, username: String, psswd: String): (RequestConfig, BasicCredentialsProvider) = {
    import org.apache.http.auth.{AuthScope, UsernamePasswordCredentials}
    val host = new HttpHost(proxy, port)
    val provider = new BasicCredentialsProvider()
    provider.setCredentials(new AuthScope(host), new UsernamePasswordCredentials(username, psswd))

    (RequestConfig.custom.setProxy(host).build, provider)
    }

    proxy, port, username, psswd代理设置需要参数,返回类型是RequestConfig, BasicCredentialsProvider,在后面调用是需要设置到httpClient和HttpPost中,后文会说明.

  • 支持文本格式发送

    钉钉支持固定几类消息发送,错误的格式导致响应失败.正确的消息样式如下:

    { "msg":{ "msgtype":"text", "text":{ "content":"test #keyword#" } } }

    代码实现如下:

    public class TextMessage {
    private String text;
    private TextMessage() {};
    public TextMessage(String text) {
    this.text = text;
    }

    public String toJsonString() {
    Map<String, Object> items = new HashMap<String, Object>();
    items.put("msgtype", "text");

    Map<String, String> textContent = new HashMap<String, String>();
    byte[] byt = StringUtils.getBytesUtf8(text);
    String txt = StringUtils.newStringUtf8(byt);
    textContent.put("content", txt);
    items.put("text", textContent);

    return JSON.toJSONString(items);
    }
    }

  • 支持安全方式:群组关键词

    为了安全,钉钉支持多种消息校验机制,除了access token之外,用户可选择关键词模式,消息加密模式,和过滤ip地址方式.
    这种方式在消息体内包含群组机器指定的关键字,群组可以正常接受消息.
    我们选择关键词模式.

  • 支持Http请求发送

    http请求包括构建HttpPost和send2个接口.如下所示:

    • 构建HttpPost

      def constructHttpPost(msg: String): HttpPost = {
      val post = new HttpPost(webHook + accessToken)
      val entity = new StringEntity(msg, StandardCharsets.UTF_8)
      post.setEntity(entity)
      post.addHeader("Content-Type", "application/json; charset=utf-8")

      post
      }

    • 消息发送send 接口

      val post = constructHttpPost(msg)
      val client: CloseableHttpClient = if (isProxyEnable) {
      //根据是否使用代理来决定http post参数设置
      val (config, provider) = setProxy(proxy, port, username, password)
      post.setConfig(config)
      HttpClients.custom().setDefaultCredentialsProvider(provider).build()
      } else {
      HttpClients.custom().build()
      }

      val rsp = client.execute(post) //执行send操作

  • 完整实现代码:

    //webHook,accessToken组合起来构成发送的https链接,在设置群组机器人时,记得保存哦.
    //群组机器设置方式: https://ding-doc.dingtalk.com/doc#/serverapi3/iydd5h
    //根据自己的需要传递过来
    class Robot(webHook: String, accessToken: String) {
    lazy val jsonObj = new JSONObject()

    //校验代理是否被激活
    def isProxyEnable: Boolean = proxy.nonEmpty && port != 0

    def setProxy(proxy: String, port: Int, username: String, psswd: String): (RequestConfig, BasicCredentialsProvider) = {
    import org.apache.http.auth.{AuthScope, UsernamePasswordCredentials}
    val host = new HttpHost(proxy, port)
    val provider = new BasicCredentialsProvider()
    provider.setCredentials(new AuthScope(host), new UsernamePasswordCredentials(username, psswd))

    (RequestConfig.custom.setProxy(host).build, provider)
    }

    def constructHttpPost(msg: String): HttpPost = {
    val post = new HttpPost(webHook + accessToken)
    val entity = new StringEntity(msg, StandardCharsets.UTF_8)
    post.setEntity(entity)
    post.addHeader("Content-Type", "application/json; charset=utf-8")

    post
    }

    def sendMsg(msg: String): DingTalkResponse = {
    val res = DingTalkResponse(HttpStatus.SC_NOT_FOUND)
    val post = constructHttpPost(msg)
    val client: CloseableHttpClient = if (isProxyEnable) {
    val (config, provider) = setProxy(proxy, port, username, password)
    post.setConfig(config)
    HttpClients.custom().setDefaultCredentialsProvider(provider).build()
    } else {
    HttpClients.custom().build()
    }

    try {
    val rsp = client.execute(post) //消息发送
    try {//资源释放处理
    if (rsp.getStatusLine().getStatusCode == HttpStatus.SC_OK) {
    val result: String = EntityUtils.toString(rsp.getEntity)
    val toJson = jsonObj.getJSONObject(result)
    println(result)
    val errCode = toJson.getInteger("errcode")
    val msg = toJson.getString("errmsg")
    res.copy(status = HttpStatus.SC_OK, errorCode = errCode, errMsg = msg)
    }
    }
    catch {
    case io: IOException => println(s"http response IOException {io}") case cpe: ClientProtocolException => println(s"http ClientProtocolException{cpe}")
    case ex: Exception => throw ex
    } finally if (rsp != null) rsp.close()
    } catch {
    case ex: Exception => println(s"http response exception ${ex}")
    } finally {
    if (client != null) client.close()
    }

    res
    }
    }
    //结果响应解析类
    case class DingTalkResponse(status: Int, errorCode: Int = 0, errMsg: String = "")
    把依赖库,和上面代码组合就可以直接运行了哦

后记

本文代码实现文本消息发送,如果看官姥爷有需要,其它方式代码也会贴出来.
希望你能有收获,高兴的话上个赞撒.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容