一个祥和的月黑风高夜,系统在有条不紊运行着. 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 在测试机器人章节还提供java和PHP的样例.
- 缺点
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 {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 = "")
把依赖库,和上面代码组合就可以直接运行了哦
后记
本文代码实现文本消息发送,如果看官姥爷有需要,其它方式代码也会贴出来.
希望你能有收获,高兴的话上个赞撒.
网友评论