1、使用自定义的AbstractStatusChangeNotifier
以下代码是参考:
SpringBoot2整合SpringBootAdmin监控管理服务上下线(跟我学SpringCloud系列)
Spring Cloud Admin健康检查 邮件、钉钉群通知
在任一配置类中注入
@Bean
public DingDingNotifier dingDingNotifier(InstanceRepository repository) {
return new DingDingNotifier(repository);
}
写一个自定义类,继承AbstractStatusChangeNotifier 重写 shouldNotify() 和 doNotify()
/**
* 正常情况下我们一般只会重写 doNotify(InstanceEvent event, Instance instance)这个方法,
* 但实际上还要重写shouldNotify(InstanceEvent event, Instance instance)方法
* 父类中不会对上线的状态进行推送
*/
@Slf4j
public class DingDingNotifier extends AbstractStatusChangeNotifier {
public DingDingNotifier(InstanceRepository repository) {
super(repository);
}
//默认情况下监控不到上线的状态
//因为父类 AbstractStatusChangeNotifier 的属性 ignoreChanges 忽略了上线状态,不进行通知
//故 重写 ignoreChanges 属性 和shouldNotify()方法(其实只修改了ignoreChanges 属性)
//将 UNKNOWN:UP 改为 *:* 可以监控所有的状态变化
private final String[] ignoreChanges = {"*:*"};
/**
* 消息模板
*/
private static final String template = "<<<%s>>> \n 【服务名】: %s(%s) \n 【状态】: %s(%s) \n 【服务ip】: %s \n 【详情】: %s";
private String titleAlarm = "服务警告";//这里必须要和配置的钉钉机器人中的关键字保持一致
@Override
protected boolean shouldNotify(InstanceEvent event, Instance instance) {
if (event instanceof InstanceStatusChangedEvent) {
InstanceStatusChangedEvent statusChange = (InstanceStatusChangedEvent) event;
String from = this.getLastStatus(event.getInstance());
String to = statusChange.getStatusInfo().getStatus();
return Arrays.binarySearch(ignoreChanges, from + ":" + to) < 0
&& Arrays.binarySearch(ignoreChanges, "*:" + to) < 0
&& Arrays.binarySearch(ignoreChanges, from + ":*") < 0;
}
return false;
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
String serverName = instance.getRegistration().getName();
InstanceId instanceId = event.getInstance();
String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
String serviceUrl = instance.getRegistration().getServiceUrl();
Map<String, Object> details = instance.getStatusInfo().getDetails();
log.info("Instance {} ({}) is {}", serverName, instanceId, status);
String serverStatus = null;
switch (status) {
case "DOWN": // 监控的健康信息中只要有一个down,都会提示服务down了
serverStatus = "健康检查没通过";
break;
case "OFFLINE":
serverStatus = "服务离线";
break;
case "UP":
serverStatus = "服务上线";
break;
case "UNKNOWN":
serverStatus = "服务未知异常";
break;
default:
break;
}
String messageText = String.format(template, titleAlarm, serverName, instanceId, status, serverStatus, serviceUrl, JSONObject.toJSONString(details));
return Mono.fromRunnable(() -> DingDingMessageUtil.sendTextMessage(messageText));
}
}
向钉钉群发送消息工具类
@Slf4j
public class DingDingMessageUtil {
private static String webhookUrl = "xxxxxx";
private static String secret = "xxxxxxx";
public static void sendTextMessage(String msg) {
try {
Message message = new Message();
message.setMsgtype("text");
message.setText(new MessageInfo(msg));
URL url = new URL(buildUrl());
// 建立 http 连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Type", "application/Json; charset=UTF-8");
conn.connect();
OutputStream out = conn.getOutputStream();
String textMessage = JSONObject.toJSONString(message);
byte[] data = textMessage.getBytes();
out.write(data);
out.flush();
out.close();
InputStream in = conn.getInputStream();
byte[] data1 = new byte[in.available()];
in.read(data1);
log.info("{}", new String(data1));
} catch (Exception e) {
e.printStackTrace();
}
}
private static String buildUrl() {
Long timestamp = System.currentTimeMillis();
return String.format("%s×tamp=%s&sign=%s", webhookUrl, timestamp, getSign(timestamp));
}
private static String getSign(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"));
return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
} catch (Exception ex) {
ex.printStackTrace();
}
return "";
}
}
消息内容类
public class Message {
private String msgtype;
private MessageInfo text;
public String getMsgtype() {
return msgtype;
}
public void setMsgtype(String msgtype) {
this.msgtype = msgtype;
}
public MessageInfo getText() {
return text;
}
public void setText(MessageInfo text) {
this.text = text;
}
}
public class MessageInfo {
private String content;
public MessageInfo(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
遇到的问题:
1、服务上线没有提醒
参考的代码这样写,虽然解决了上线问题,但是还是有问题的。
private String[] ignoreChanges = new String[]{"UNKNOWN:UP","DOWN:UP","OFFLINE:UP"};
因为调用shouldNotify#Arrays.binarySearch(array, key)
的方法,需要传入的array
是有序的;若把"UNKNOWN:UP","DOWN:UP","OFFLINE:UP"
用在下面的第二种方式,则会发现服务上线还是没有提醒
默认情况下上线状态的通知被忽略了
因为父类 AbstractStatusChangeNotifier 的属性 ignoreChanges 忽略了上线状态,不进行通知
故 重写 ignoreChanges 属性 和shouldNotify()方法(其实只修改了ignoreChanges 属性,同时重写shouldNotify())
效果图:
2、注意:关键词需要包含在发送给钉钉的消息内容中
2、高版本的springbootadmin可以直接在yml配置文件进行配置
试过2.3.0版本的没有封装钉钉通知
我使用的依赖版本:
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.6.2</version>
</dependency>
配置文件,只需关注boot.admin.notify后的配置就行
spring:
application:
name: sba-monitor
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
passowrd: nacos
group: xxxxx
#修改admin健康监控的访问路径
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
# 使用的邮箱服务 qq 163等
mail:
host: smtp.qq.com
username: xxxxxx@qq.com # 发送者
password: xxxxxxxxx # 授权码
boot:
admin:
notify:
mail:
to: xxxxxx@qq.com #收件人
from: xxxxxx@qq.com #发件人
enabled: false # 不启用
dingtalk:
enabled: true # 启用钉钉通知
webhookUrl: https://oapi.dingtalk.com/robot/send?access_token=xxxxxxx # 向钉钉群发送消息的地址
secret: xxxxxxx # 秘钥
message: '服务警告: #{instance.registration.name} #{instance.id} is #{event.statusInfo.status}' # 消息内容
ignoreChanges: ["*:*"] # 忽略从old到new的状态 这里是不忽略的意思
我们自定义的 ignoreChanges
属性会在 AbstractStatusChangeNotifier
调用时对ignoreChanges
排序,所以图方便直接["*:*"]
AbstractStatusChangeNotifier
源码
public abstract class AbstractStatusChangeNotifier extends AbstractEventNotifier {
......
/**
* List of changes to ignore. Must be in Format OLD:NEW, for any status use * as
* wildcard, e.g. *:UP or OFFLINE:*
*/
private String[] ignoreChanges = { "UNKNOWN:UP" };
/**
*...... 省略代码
*/
public void setIgnoreChanges(String[] ignoreChanges) {
String[] copy = Arrays.copyOf(ignoreChanges, ignoreChanges.length);
Arrays.sort(copy);//对 ignoreChanges 进行排序
this.ignoreChanges = copy;
}
public String[] getIgnoreChanges() {
return ignoreChanges;
}
}
效果图:
总结:
1、第一种自定义的方式,服务上线的提醒很快,第二种配置的方式比较慢,原因未知;
2、自定义的方式可以自定义消息内容,消息内容更丰富,而配置方式的消息内容会受到配置文件的约束。