From 7915df10a49c78adb31191241dd7732c26837ac9 Mon Sep 17 00:00:00 2001 From: imyzt Date: Tue, 23 Nov 2021 19:42:41 +0800 Subject: [PATCH] feat:add lark send message handler --- .../starter/alarm/DingSendMessageHandler.java | 8 +- .../starter/alarm/NotifyPlatformEnum.java | 10 +- .../alarm/lark/LarkAlarmConstants.java | 30 +++ .../alarm/lark/LarkSendMessageHandler.java | 187 ++++++++++++++++++ .../starter/config/MessageAlarmConfig.java | 7 + .../main/resources/properties/lark/alarm.json | 185 +++++++++++++++++ .../resources/properties/lark/notice.json | 143 ++++++++++++++ 7 files changed, 565 insertions(+), 5 deletions(-) create mode 100644 hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkAlarmConstants.java create mode 100644 hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkSendMessageHandler.java create mode 100644 hippo4j-spring-boot-starter/src/main/resources/properties/lark/alarm.json create mode 100644 hippo4j-spring-boot-starter/src/main/resources/properties/lark/notice.json diff --git a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/DingSendMessageHandler.java b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/DingSendMessageHandler.java index 2c481db2..542d7401 100644 --- a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/DingSendMessageHandler.java +++ b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/DingSendMessageHandler.java @@ -32,9 +32,9 @@ import java.util.concurrent.TimeUnit; @AllArgsConstructor public class DingSendMessageHandler implements SendMessageHandler { - private String active; + private final String active; - private InstanceInfo instanceInfo; + private final InstanceInfo instanceInfo; @Override public String getType() { @@ -68,7 +68,7 @@ public class DingSendMessageHandler implements SendMessageHandler { "最大线程数:%d \n\n " + "当前线程数:%d \n\n " + "活跃线程数:%d \n\n " + - "最大线程数:%d \n\n " + + "最大任务数:%d \n\n " + "线程池任务总量:%d \n\n " + " --- \n\n " + "队列类型:%s \n\n " + @@ -214,7 +214,7 @@ public class DingSendMessageHandler implements SendMessageHandler { try { dingTalkClient.execute(request); } catch (ApiException ex) { - log.error("Ding failed to send message", ex.getMessage()); + log.error("Ding failed to send message", ex); } } diff --git a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/NotifyPlatformEnum.java b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/NotifyPlatformEnum.java index bedff82f..65e2d4c4 100644 --- a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/NotifyPlatformEnum.java +++ b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/NotifyPlatformEnum.java @@ -8,6 +8,14 @@ package cn.hippo4j.starter.alarm; */ public enum NotifyPlatformEnum { - DING + /** + * 钉钉 + */ + DING, + /** + * 飞书 + */ + LARK, + ; } diff --git a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkAlarmConstants.java b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkAlarmConstants.java new file mode 100644 index 00000000..9c770dd5 --- /dev/null +++ b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkAlarmConstants.java @@ -0,0 +1,30 @@ +package cn.hippo4j.starter.alarm.lark; + +/** + * lark alarm constants. + * + * @author imyzt + * @date 2021-11-23 19:29 + */ +public class LarkAlarmConstants { + + /** + * lark bot url + */ + public static final String LARK_BOT_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/"; + + /** + * lark 报警 json文件路径 + */ + public static final String ALARM_JSON_PATH = "classpath:properties/lark/alarm.json"; + + /** + * lark 配置变更通知 json文件路径 + */ + public static final String NOTICE_JSON_PATH = "classpath:properties/lark/notice.json"; + + /** + * lark at format + */ + public static final String LARK_AT_FORMAT = "%s"; +} diff --git a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkSendMessageHandler.java b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkSendMessageHandler.java new file mode 100644 index 00000000..e00c09bf --- /dev/null +++ b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/alarm/lark/LarkSendMessageHandler.java @@ -0,0 +1,187 @@ +package cn.hippo4j.starter.alarm.lark; + +import cn.hippo4j.common.model.InstanceInfo; +import cn.hippo4j.common.model.PoolParameterInfo; +import cn.hippo4j.starter.alarm.NotifyDTO; +import cn.hippo4j.starter.alarm.NotifyPlatformEnum; +import cn.hippo4j.starter.alarm.SendMessageHandler; +import cn.hippo4j.starter.core.DynamicThreadPoolExecutor; +import cn.hippo4j.starter.core.GlobalThreadPoolManage; +import cn.hippo4j.starter.toolkit.thread.QueueTypeEnum; +import cn.hippo4j.starter.toolkit.thread.RejectedTypeEnum; +import cn.hippo4j.starter.wrapper.DynamicThreadPoolWrapper; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.ResourceUtils; + +import java.io.FileNotFoundException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static cn.hippo4j.starter.alarm.lark.LarkAlarmConstants.*; + +/** + * Send lark notification message. + * + * @author imyzt + * @date 2021/11/22 21:12 + */ +@Slf4j +@AllArgsConstructor +public class LarkSendMessageHandler implements SendMessageHandler { + + private final String active; + + private final InstanceInfo instanceInfo; + + @Override + public String getType() { + return NotifyPlatformEnum.LARK.name(); + } + + @Override + public void sendAlarmMessage(NotifyDTO notifyConfig, DynamicThreadPoolExecutor pool) { + larkAlarmSendMessage(notifyConfig, pool); + } + + @Override + public void sendChangeMessage(NotifyDTO notifyConfig, PoolParameterInfo parameter) { + larkChangeSendMessage(notifyConfig, parameter); + } + + @SneakyThrows + private void larkAlarmSendMessage(NotifyDTO notifyConfig, DynamicThreadPoolExecutor pool) { + String afterReceives = getReceives(notifyConfig); + + BlockingQueue queue = pool.getQueue(); + + String larkAlarmJson = getLarkJson(ALARM_JSON_PATH); + + String text = String.format(larkAlarmJson, + // 环境 + active.toUpperCase(), + // 线程池ID + pool.getThreadPoolId(), + // 应用名称 + instanceInfo.getAppName(), + // 实例信息 + instanceInfo.getIdentify(), + // 报警类型 + notifyConfig.getTypeEnum(), + // 核心线程数 + pool.getCorePoolSize(), + // 最大线程数 + pool.getMaximumPoolSize(), + // 当前线程数 + pool.getPoolSize(), + // 活跃线程数 + pool.getActiveCount(), + // 最大任务数 + pool.getLargestPoolSize(), + // 线程池任务总量 + pool.getCompletedTaskCount(), + // 队列类型名称 + queue.getClass().getSimpleName(), + // 队列容量 + queue.size() + queue.remainingCapacity(), + // 队列元素个数 + queue.size(), + // 队列剩余个数 + queue.remainingCapacity(), + // 拒绝策略名称 + pool.getRejectedExecutionHandler().getClass().getSimpleName(), + // 拒绝策略次数 + pool.getRejectCount(), + // 告警姓名 + afterReceives, + // 当前时间 + DateUtil.now(), + // 报警频率 + notifyConfig.getInterval() + ); + + execute(notifyConfig, text); + } + + @SneakyThrows + private void larkChangeSendMessage(NotifyDTO notifyConfig, PoolParameterInfo parameter) { + String threadPoolId = parameter.getTpId(); + DynamicThreadPoolWrapper poolWrap = GlobalThreadPoolManage.getExecutorService(threadPoolId); + if (poolWrap == null) { + log.warn("Thread pool is empty when sending change notification, threadPoolId :: {}", threadPoolId); + return; + } + + String afterReceives = getReceives(notifyConfig); + + ThreadPoolExecutor customPool = poolWrap.getExecutor(); + + String larkNoticeJson = getLarkJson(NOTICE_JSON_PATH); + + /** + * hesitant e.g. ➲ ➜ ⇨ ➪ + */ + String text = String.format(larkNoticeJson, + // 环境 + active.toUpperCase(), + // 线程池名称 + threadPoolId, + // 应用名称 + instanceInfo.getAppName(), + // 实例信息 + instanceInfo.getIdentify(), + // 核心线程数 + customPool.getCorePoolSize() + " ➲ " + parameter.getCoreSize(), + // 最大线程数 + customPool.getMaximumPoolSize() + " ➲ " + parameter.getMaxSize(), + // 线程存活时间 + customPool.getKeepAliveTime(TimeUnit.SECONDS) + " ➲ " + parameter.getKeepAliveTime(), + // 阻塞队列 + QueueTypeEnum.getBlockingQueueNameByType(parameter.getQueueType()), + // 阻塞队列容量 + (customPool.getQueue().size() + customPool.getQueue().remainingCapacity()) + " ➲ " + parameter.getCapacity(), + // 拒绝策略 + customPool.getRejectedExecutionHandler().getClass().getSimpleName(), + RejectedTypeEnum.getRejectedNameByType(parameter.getRejectedType()), + // 告警姓名 + afterReceives, + // 当前时间 + DateUtil.now() + ); + + execute(notifyConfig, text); + } + + private String getLarkJson(String filePath) throws FileNotFoundException { + return FileUtil.readString(ResourceUtils.getFile(filePath), StandardCharsets.UTF_8); + } + + private String getReceives(NotifyDTO notifyConfig) { + if (StrUtil.isBlank(notifyConfig.getReceives())) { + return ""; + } + return Arrays.stream(notifyConfig.getReceives().split(",")) + .map(receive -> String.format(LARK_AT_FORMAT, receive)) + .collect(Collectors.joining(" ")); + } + + private void execute(NotifyDTO notifyConfig, String text) { + String serverUrl = LARK_BOT_URL + notifyConfig.getSecretKey(); + + try { + HttpRequest.post(serverUrl).body(text).execute(); + } catch (Exception ex) { + log.error("Lark failed to send message", ex); + } + } + +} diff --git a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/config/MessageAlarmConfig.java b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/config/MessageAlarmConfig.java index d32709e0..87ae2fd4 100644 --- a/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/config/MessageAlarmConfig.java +++ b/hippo4j-spring-boot-starter/src/main/java/cn/hippo4j/starter/config/MessageAlarmConfig.java @@ -2,6 +2,7 @@ package cn.hippo4j.starter.config; import cn.hippo4j.common.model.InstanceInfo; import cn.hippo4j.starter.alarm.*; +import cn.hippo4j.starter.alarm.lark.LarkSendMessageHandler; import cn.hippo4j.starter.remote.HttpAgent; import lombok.AllArgsConstructor; import org.apache.logging.log4j.util.Strings; @@ -38,6 +39,12 @@ public class MessageAlarmConfig { return new DingSendMessageHandler(active, instanceInfo); } + @Bean + public SendMessageHandler larkSendMessageHandler() { + String active = environment.getProperty("spring.profiles.active", Strings.EMPTY); + return new LarkSendMessageHandler(active, instanceInfo); + } + @Bean public AlarmControlHandler alarmControlHandler() { return new AlarmControlHandler(); diff --git a/hippo4j-spring-boot-starter/src/main/resources/properties/lark/alarm.json b/hippo4j-spring-boot-starter/src/main/resources/properties/lark/alarm.json new file mode 100644 index 00000000..b0e21a57 --- /dev/null +++ b/hippo4j-spring-boot-starter/src/main/resources/properties/lark/alarm.json @@ -0,0 +1,185 @@ +{ + "msg_type": "interactive", + "card": { + "config": { + "wide_screen_mode": true + }, + "header": { + "template": "red", + "title": { + "content": "[🔥警报] %s 动态线程池运行告警", + "tag": "plain_text" + } + }, + "elements": [ + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** 线程池ID:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 应用名称:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 应用实例:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 报警类型:** %s", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** 核心线程数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 最大线程数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 当前线程数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 活跃线程数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 最大任务数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 线程池任务总量:** %s", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** 队列类型:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 队列容量:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 队列元素个数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 队列剩余个数:** %s", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** 拒绝策略:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 拒绝策略执行次数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "user_id": "** OWNER:** %s", + "tag": "at" + } + }, + { + "is_short": true, + "text": { + "content": "** 播报时间: ** %s", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "tag": "note", + "elements": [ + { + "tag": "plain_text", + "content": "提示: %s 分钟内此线程池不会重复告警(可配置)" + } + ] + } + ] + } +} + diff --git a/hippo4j-spring-boot-starter/src/main/resources/properties/lark/notice.json b/hippo4j-spring-boot-starter/src/main/resources/properties/lark/notice.json new file mode 100644 index 00000000..0d4bd4e2 --- /dev/null +++ b/hippo4j-spring-boot-starter/src/main/resources/properties/lark/notice.json @@ -0,0 +1,143 @@ +{ + "msg_type": "interactive", + "card": { + + "config": { + "wide_screen_mode": true + }, + "header": { + "template": "greed", + "title": { + "content": "[通知] %s 动态线程池参数变更", + "tag": "plain_text" + } + }, + "elements": [ + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** 线程池ID:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 应用名称:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 应用实例:** %s", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** 核心线程数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 最大线程数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 线程存活时间:** %s / SECONDS", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** 队列类型:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 队列容量:** %s", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "fields": [ + { + "is_short": true, + "text": { + "content": "** AGO 拒绝策略:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** NOW 拒绝策略执行次数:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** OWNER:** %s", + "tag": "lark_md" + } + }, + { + "is_short": true, + "text": { + "content": "** 播报时间: ** %s", + "tag": "lark_md" + } + } + ], + "tag": "div" + }, + { + "tag": "hr" + }, + { + "tag": "note", + "elements": [ + { + "tag": "plain_text", + "content": "提示:动态线程池配置变更实时通知(无限制)" + } + ] + } + ] + } +}