Spring Boot Admin 使用钉钉机器人通知服务的上下线以及健康检查

2023-02-24 10:05:44

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&timestamp=%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、注意:关键词需要包含在发送给钉钉的消息内容中
注意关键词

SpringBootAdmin官方文档

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、自定义的方式可以自定义消息内容,消息内容更丰富,而配置方式的消息内容会受到配置文件的约束。

  • 作者:世界与我
  • 原文链接:https://blog.csdn.net/qq_30505421/article/details/126406126
    更新时间:2023-02-24 10:05:44