看了一下钉钉的文档,可发现通过调用Webhook地址可将告警消息发送到群聊里来实现消息通知的功能
获取调用地址
- 先创建一个钉钉群,并创建自定义机器人
选择其中一项安全设置,可加强安全性,防止Webhook地址泄密被乱发消息
- 创建成功,复制Webhook地址,等下需要用到
创建SpringBoot应用
添加yml配置
spring:
boot:
admin:
notify:
dingtalk:
enabled: true
webhook-token: https://oapi.dingtalk.com/robot/send?access_token=ef4c4ff2c97e3b8d316c1d1d4febfc80a84814bf0bd9bba00283da24bbd0a26d
获取签名信息等(测试用)
因为我选的是加签的安全认证 所以测试时需要带上sign和时间戳信息
public static void main(String[] args) throws Exception {
//获取时间戳
Long timestamp = System.currentTimeMillis();
//定义密钥
String secret = "SEC466154ca1061db3b135e4d76e989f3acc1091a8917ddecbb053524964f5beb80";
//把时间戳和密钥拼接成字符串,中间加入一个换行符
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);
}
测试
测试命令: (这里需要将access_token换成自己的)
curl 'https://oapi.dingtalk.com/robot/send?access_token=ef4c4ff2c97e3b8d316c1d1d4febfc80a84814bf0bd9bba00283da24bbd0a26d' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text","text": {"content":"我就是我, 是不一样的烟火"}}'
也可以使用postman等其他工具进行测试:
应用代码
这里其实可以使用SpringBoot Admin来搭配钉钉做应用监控的 我这里只是写的一个简单demo
引入httpclient
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version>
</dependency>
<!--钉钉官方依赖-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>1.0.1</version>
</dependency>
package com.eg.dingbot.robot;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.util.Base64;
/**
* @author Eg
* @date 2022-03-14
*/
public class NotifieBot {
//请求地址以及access_token
String Webhook = "https://oapi.dingtalk.com/robot/send?access_token=ef4c4ff2c97e3b8d316c1d1d4febfc80a84814bf0bd9bba00283da24bbd0a26d";
//密钥
String secret = "SEC466154ca1061db3b135e4d76e989f3acc1091a8917ddecbb053524964f5beb80";
/**
* @return 生成时间戳和加密结果
* @throws Exception
*/
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 = "×tamp=" + timestamp + "&sign=" + sign;
return result;
};
/**
* 把传入的message发送给钉钉机器人
* @param message 要发送的信息
* @throws JSONException
*/
public void dingRequest(String message) throws JSONException {
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();
}
}
}
@Test
public void test() throws Exception {
NotifieBot notifieBot = new NotifieBot();
notifieBot.dingRequest("test");
}
}
经过测试 机器人已经可以正常收到消息了
方法抽取
发送消息
/**
* 发送消息
* @param messageText
*/
private void sendTextMessage(String messageText){
DingTalkClient client = new DefaultDingTalkClient(beforyMonitorProperties.getDingtalkNotify().getWebhookToken());
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype(beforyMonitorProperties.getDingtalkNotify().getMsgType());
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(messageText);
request.setText(text);
try {
client.execute(request);
} catch (ApiException e) {
log.info("[ERROR] sendMessage", e);
}
}
配置类
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration.CompositeNotifierConfiguration;
import de.codecentric.boot.admin.server.config.AdminServerNotifierAutoConfiguration.NotifierTriggerConfiguration;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
@Configuration
@ConditionalOnProperty(
prefix = "spring.boot.admin.notify.dingtalk",
name = {"webhook-token"}
)
@AutoConfigureBefore({NotifierTriggerConfiguration.class, CompositeNotifierConfiguration.class})
public class DingTalkNotifierConfiguration {
@Bean
@ConditionalOnMissingBean
@ConfigurationProperties(prefix = "spring.boot.admin.notify.dingtalk")
public DingTalkNotifier dingTalkNotifier(InstanceRepository repository) {
return new DingTalkNotifier(repository);
}
}
实现服务通知
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
import reactor.core.publisher.Mono;
@Data
@AllArgsConstructor
public class DingTalkNotifier extends AbstractStatusChangeNotifier {
private static final String DEFAULT_MESSAGE = "*#{instance.registration.name}* (#{instance.id}) is *#{event.statusInfo.status}**";
private final SpelExpressionParser parser = new SpelExpressionParser();
private RestTemplate restTemplate = new RestTemplate();
private String webhookToken;
private String atMobiles;
private String msgtype = "markdown";
private String title = "服务告警";
private Expression message;
public DingTalkNotifier(InstanceRepository repository) {
super(repository);
this.message = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
return Mono.fromRunnable(() -> restTemplate.postForEntity(webhookToken, createMessage(event, instance),Void.class));
}
private HttpEntity<Map<String, Object>> createMessage(InstanceEvent event,Instance instance) {
Map<String, Object> messageJson = new HashMap<>();
HashMap<String, String> params = new HashMap<>();
params.put("text", this.getMessage(event, instance));
params.put("title", this.title);
messageJson.put("atMobiles", this.atMobiles);
messageJson.put("msgtype", this.msgtype);
messageJson.put(this.msgtype, params);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return new HttpEntity<>(messageJson, headers);
}
private String getAtMobilesString(String s) {
StringBuilder atMobiles = new StringBuilder();
String[] mobiles = s.split(",");
for (String mobile : mobiles) {
atMobiles.append("@").append(mobile);
}
return atMobiles.toString();
}
private String getMessage(InstanceEvent event,Instance instance) {
Map<String, Object> root = new HashMap<>();
root.put("event", event);
root.put("instance", instance);
root.put("lastStatus", getLastStatus(event.getInstance()));
StandardEvaluationContext context = new StandardEvaluationContext(root);
context.addPropertyAccessor(new MapAccessor());
return message.getValue(context, String.class);
}
}