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": "提示:动态线程池配置变更实时通知(无限制)"
+ }
+ ]
+ }
+ ]
+ }
+}