diff --git a/pom.xml b/pom.xml
index 73731ef..f7cb5b3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,97 +1,91 @@
-
+
4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.1.8.RELEASE
-
-
+
com.topsail
scheduletask
0.0.1-SNAPSHOT
+ jar
+
scheduletask
- scheduletask
+ 定时任务调度服务
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.18
+
+
1.8
+ 2.1.4
+ 5.8.10
org.springframework.boot
- spring-boot-starter-amqp
-
-
- org.mybatis.spring.boot
- mybatis-spring-boot-starter
- 2.1.0
+ spring-boot-starter
- mysql
- mysql-connector-java
- 8.0.13
+ org.springframework.boot
+ spring-boot-starter-web
+
org.springframework.boot
- spring-boot-starter-test
+ spring-boot-starter-amqp
+
- org.springframework.amqp
- spring-rabbit-test
- test
+ org.springframework.boot
+ spring-boot-starter-mail
+
- com.alibaba
- fastjson
- 1.2.15
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ ${mybatis.version}
+
- com.google.code.gson
- gson
+ mysql
+ mysql-connector-java
+ 8.0.33
+ runtime
+
- com.influxdb
- influxdb-client-java
- 1.8.0
+ org.springframework.boot
+ spring-boot-starter-quartz
-
-
-
-
-
-
-
-
- org.springframework
- spring-test
- 4.3.6.RELEASE
+ cn.hutool
+ hutool-all
+ ${hutool.version}
- junit
- junit
- 4.12
+ com.alibaba
+ fastjson
+ 1.2.83
+
org.projectlombok
lombok
- 1.18.6
-
-
-
- com.github.pagehelper
- pagehelper-spring-boot-starter
- 1.2.10
+ true
-
+
org.springframework.boot
- spring-boot-starter-mail
+ spring-boot-starter-test
+ test
@@ -100,45 +94,16 @@
org.springframework.boot
spring-boot-maven-plugin
- 2.1.8.RELEASE
-
-
- org.mybatis.generator
- mybatis-generator-maven-plugin
- 1.3.5
-
-
-
- mysql
- mysql-connector-java
- 5.1.40
-
-
- com.itfsw
- mybatis-generator-plugin
- 1.2.20
-
-
+
+
+
+ org.projectlombok
+ lombok
+
+
+
-
-
- src/main/java
-
- **/*.properties
- **/*.xml
-
- false
-
-
- src/main/resources
-
- **/*.properties
- **/*.xml
-
- false
-
-
-
+
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/ScheduleTaskApplication.java b/src/main/java/com/topsail/scheduletask/ScheduleTaskApplication.java
index a07890b..3bd9ae4 100644
--- a/src/main/java/com/topsail/scheduletask/ScheduleTaskApplication.java
+++ b/src/main/java/com/topsail/scheduletask/ScheduleTaskApplication.java
@@ -3,11 +3,13 @@ package com.topsail.scheduletask;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.TimeZone;
@SpringBootApplication
@MapperScan("com.topsail.scheduletask.mapper")
+@EnableScheduling
public class ScheduleTaskApplication {
public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
diff --git a/src/main/java/com/topsail/scheduletask/mapper/AlertRecordMapper.java b/src/main/java/com/topsail/scheduletask/mapper/AlertRecordMapper.java
new file mode 100644
index 0000000..29afe55
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/mapper/AlertRecordMapper.java
@@ -0,0 +1,20 @@
+package com.topsail.scheduletask.mapper;
+
+import com.topsail.scheduletask.pojo.AlertRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface AlertRecordMapper {
+ void insert(AlertRecord alertRecord);
+
+ List selectByStatus(String status);
+
+ List selectByType(String alertType);
+
+ int updateStatus(@Param("id") Long id, @Param("status") String status, @Param("handleTime") java.util.Date handleTime);
+
+ List selectRecentAlerts(@Param("minutes") int minutes);
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/mapper/HeartBeatMapper.java b/src/main/java/com/topsail/scheduletask/mapper/HeartBeatMapper.java
new file mode 100644
index 0000000..7b64d73
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/mapper/HeartBeatMapper.java
@@ -0,0 +1,18 @@
+package com.topsail.scheduletask.mapper;
+
+import com.topsail.scheduletask.pojo.ServiceHeartbeat;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface HeartBeatMapper {
+ void upsertHeartbeat(ServiceHeartbeat heartbeat);
+
+ List selectTimeoutService(@Param("timeout") int timeout);
+
+ List selectAll();
+
+ int deleteByServiceAndIp(@Param("serviceName") String serviceName, @Param("instanceIp") String instanceIp);
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/mapper/MqMonitorMapper.java b/src/main/java/com/topsail/scheduletask/mapper/MqMonitorMapper.java
new file mode 100644
index 0000000..b4f59d8
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/mapper/MqMonitorMapper.java
@@ -0,0 +1,15 @@
+package com.topsail.scheduletask.mapper;
+
+import com.topsail.scheduletask.pojo.MqMonitor;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MqMonitorMapper {
+ void insert(MqMonitor mqMonitor);
+
+ List selectByTopic(String topic);
+
+ List selectRecentByTopic(String topic);
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/pojo/AlertRecord.java b/src/main/java/com/topsail/scheduletask/pojo/AlertRecord.java
new file mode 100644
index 0000000..d63dc4a
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/pojo/AlertRecord.java
@@ -0,0 +1,18 @@
+package com.topsail.scheduletask.pojo;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class AlertRecord {
+ private Long id;
+ private String alertType;
+ private String alertLevel;
+ private String alertMessage;
+ private String serviceName;
+ private String topic;
+ private String status;
+ private Date createTime;
+ private Date handleTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/pojo/MqMonitor.java b/src/main/java/com/topsail/scheduletask/pojo/MqMonitor.java
new file mode 100644
index 0000000..180169e
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/pojo/MqMonitor.java
@@ -0,0 +1,17 @@
+package com.topsail.scheduletask.pojo;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class MqMonitor {
+ private Long id;
+ private String mqType;
+ private String topic;
+ private String groupName;
+ private Long accumulationCount;
+ private Long delayTime;
+ private Date monitorTime;
+ private Date createTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/pojo/ServiceHeartbeat.java b/src/main/java/com/topsail/scheduletask/pojo/ServiceHeartbeat.java
new file mode 100644
index 0000000..f84759e
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/pojo/ServiceHeartbeat.java
@@ -0,0 +1,15 @@
+package com.topsail.scheduletask.pojo;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class ServiceHeartbeat {
+ private Long id;
+ private String serviceName;
+ private String instanceIp;
+ private Date lastHeartbeat;
+ private Date createTime;
+ private Date updateTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/service/MqMonitorService.java b/src/main/java/com/topsail/scheduletask/service/MqMonitorService.java
new file mode 100644
index 0000000..bb9b02c
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/service/MqMonitorService.java
@@ -0,0 +1,176 @@
+package com.topsail.scheduletask.service;
+
+import com.topsail.scheduletask.pojo.AlertRecord;
+import com.topsail.scheduletask.pojo.MqMonitor;
+import com.topsail.scheduletask.mapper.AlertRecordMapper;
+import com.topsail.scheduletask.mapper.MqMonitorMapper;
+import com.topsail.scheduletask.util.DingDingUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class MqMonitorService {
+ public static final Logger LOG = LoggerFactory.getLogger(MqMonitorService.class);
+
+ private final MqMonitorMapper mqMonitorMapper;
+ private final AlertRecordMapper alertRecordMapper;
+ private final DingDingUtil dingDingUtil;
+ private final AmqpService amqpService;
+
+ @Value("${monitor.mq.max-accumulation:1000}")
+ private long maxAccumulation;
+
+ @Value("${monitor.mq.max-delay:30000}")
+ private long maxDelay;
+
+ private static final Map MONITOR_QUEUES = new HashMap<>();
+ static {
+ MONITOR_QUEUES.put("order_queue", "order");
+ MONITOR_QUEUES.put("pay_queue", "pay");
+ }
+
+ private Map lastAlertTime = new ConcurrentHashMap<>();
+ private static final long ALERT_INTERVAL = 60000;
+
+ @Autowired
+ public MqMonitorService(MqMonitorMapper mqMonitorMapper, AlertRecordMapper alertRecordMapper,
+ DingDingUtil dingDingUtil, AmqpService amqpService) {
+ this.mqMonitorMapper = mqMonitorMapper;
+ this.alertRecordMapper = alertRecordMapper;
+ this.dingDingUtil = dingDingUtil;
+ this.amqpService = amqpService;
+ }
+
+ public void monitorAllQueues() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ for (Map.Entry entry : MONITOR_QUEUES.entrySet()) {
+ String queueName = entry.getKey();
+ String queueType = entry.getValue();
+
+ try {
+ monitorQueue(queueName, queueType, sdf);
+ } catch (Exception e) {
+ LOG.error("监控队列失败 - Queue: {}, Type: {}", queueName, queueType, e);
+ }
+ }
+ }
+
+ private void monitorQueue(String queueName, String queueType, SimpleDateFormat sdf) {
+ long accumulation = amqpService.getQueueMessageCount(queueName);
+
+ if (accumulation < 0) {
+ LOG.debug("队列 [{}] 不存在或获取消息数量失败", queueName);
+ return;
+ }
+
+ long delay = estimateDelay(queueName, accumulation);
+
+ saveMonitorRecord(queueName, queueType, accumulation, delay);
+
+ LOG.debug("RabbitMQ监控 - Queue: {}, Type: {}, 堆积: {}, 延迟估计: {}ms",
+ queueName, queueType, accumulation, delay);
+
+ String key = queueName;
+ Long lastTime = lastAlertTime.get(key);
+
+ if (accumulation > maxAccumulation) {
+ handleAccumulationAlert(key, lastTime, queueName, queueType, accumulation, sdf);
+ } else if (delay > maxDelay) {
+ handleDelayAlert(key, lastTime, queueName, queueType, delay, sdf);
+ } else {
+ lastAlertTime.remove(key);
+ }
+ }
+
+ private long estimateDelay(String queueName, long accumulation) {
+ if (accumulation == 0) {
+ return 0;
+ }
+
+ long avgConsumeTimePerMsg = 100;
+ return accumulation * avgConsumeTimePerMsg;
+ }
+
+ private void saveMonitorRecord(String queueName, String queueType, long accumulation, long delay) {
+ MqMonitor mqMonitor = new MqMonitor();
+ mqMonitor.setMqType("RabbitMQ");
+ mqMonitor.setTopic(queueName);
+ mqMonitor.setGroupName(queueType);
+ mqMonitor.setAccumulationCount(accumulation);
+ mqMonitor.setDelayTime(delay);
+ mqMonitor.setMonitorTime(new Date());
+ mqMonitorMapper.insert(mqMonitor);
+ }
+
+ private void handleAccumulationAlert(String key, Long lastTime, String queueName, String queueType,
+ long accumulation, SimpleDateFormat sdf) {
+ if (lastTime == null || System.currentTimeMillis() - lastTime > ALERT_INTERVAL) {
+ String msg = String.format(
+ "【RabbitMQ堆积告警】\n队列:%s\n类型:%s\n堆积数量:%d\n阈值:%d\n监控时间:%s",
+ queueName, queueType, accumulation, maxAccumulation, sdf.format(new Date())
+ );
+ LOG.error(msg);
+ dingDingUtil.sendMarkdownAlert("RabbitMQ堆积告警", msg);
+
+ saveAlertRecord("MQ_ACCUMULATION", "WARN", msg, queueName, null);
+ lastAlertTime.put(key, System.currentTimeMillis());
+ }
+ }
+
+ private void handleDelayAlert(String key, Long lastTime, String queueName, String queueType,
+ long delay, SimpleDateFormat sdf) {
+ if (lastTime == null || System.currentTimeMillis() - lastTime > ALERT_INTERVAL) {
+ String msg = String.format(
+ "【RabbitMQ延迟告警】\n队列:%s\n类型:%s\n延迟时间:%dms\n阈值:%dms\n监控时间:%s",
+ queueName, queueType, delay, maxDelay, sdf.format(new Date())
+ );
+ LOG.error(msg);
+ dingDingUtil.sendMarkdownAlert("RabbitMQ延迟告警", msg);
+
+ saveAlertRecord("MQ_ACCUMULATION", "WARN", msg, queueName, null);
+ lastAlertTime.put(key, System.currentTimeMillis());
+ }
+ }
+
+ private void saveAlertRecord(String alertType, String alertLevel, String alertMessage,
+ String topic, String serviceName) {
+ AlertRecord alert = new AlertRecord();
+ alert.setAlertType(alertType);
+ alert.setAlertLevel(alertLevel);
+ alert.setAlertMessage(alertMessage);
+ alert.setTopic(topic);
+ alert.setServiceName(serviceName);
+ alert.setStatus("UNHANDLED");
+ alertRecordMapper.insert(alert);
+ }
+
+ public Map getMonitorQueues() {
+ return MONITOR_QUEUES;
+ }
+
+ public long getMaxAccumulation() {
+ return maxAccumulation;
+ }
+
+ public long getMaxDelay() {
+ return maxDelay;
+ }
+
+ public void addMonitorQueue(String queueName, String queueType) {
+ MONITOR_QUEUES.put(queueName, queueType);
+ }
+
+ public void removeMonitorQueue(String queueName) {
+ MONITOR_QUEUES.remove(queueName);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/task/HeartBeatTask.java b/src/main/java/com/topsail/scheduletask/task/HeartBeatTask.java
new file mode 100644
index 0000000..312c505
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/task/HeartBeatTask.java
@@ -0,0 +1,38 @@
+package com.topsail.scheduletask.task;
+
+import com.topsail.scheduletask.pojo.ServiceHeartbeat;
+import com.topsail.scheduletask.mapper.HeartBeatMapper;
+import com.topsail.scheduletask.util.ApplicationContextUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.net.InetAddress;
+import java.util.Date;
+
+@Slf4j
+@Component
+public class HeartBeatTask {
+
+ @Autowired
+ private HeartBeatMapper heartBeatMapper;
+
+ @Scheduled(fixedRate = 10000)
+ public void heartbeat() {
+ try {
+ String serviceName = ApplicationContextUtil.getApplicationName();
+ String ip = InetAddress.getLocalHost().getHostAddress();
+
+ ServiceHeartbeat heartbeat = new ServiceHeartbeat();
+ heartbeat.setServiceName(serviceName);
+ heartbeat.setInstanceIp(ip);
+ heartbeat.setLastHeartbeat(new Date());
+
+ heartBeatMapper.upsertHeartbeat(heartbeat);
+ log.debug("心跳上报成功 - 服务: {}, IP: {}", serviceName, ip);
+ } catch (Exception e) {
+ log.error("心跳上报失败", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/task/MqAccumulationMonitor.java b/src/main/java/com/topsail/scheduletask/task/MqAccumulationMonitor.java
new file mode 100644
index 0000000..6328206
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/task/MqAccumulationMonitor.java
@@ -0,0 +1,28 @@
+package com.topsail.scheduletask.task;
+
+import com.topsail.scheduletask.service.MqMonitorService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class MqAccumulationMonitor {
+
+ private final MqMonitorService mqMonitorService;
+
+ @Autowired
+ public MqAccumulationMonitor(MqMonitorService mqMonitorService) {
+ this.mqMonitorService = mqMonitorService;
+ }
+
+ @Scheduled(fixedRate = 30000)
+ public void monitorMq() {
+ try {
+ mqMonitorService.monitorAllQueues();
+ } catch (Exception e) {
+ log.error("RabbitMQ监控任务执行失败", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/task/ServiceDownMonitor.java b/src/main/java/com/topsail/scheduletask/task/ServiceDownMonitor.java
new file mode 100644
index 0000000..7ef8b82
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/task/ServiceDownMonitor.java
@@ -0,0 +1,81 @@
+package com.topsail.scheduletask.task;
+
+import com.topsail.scheduletask.pojo.AlertRecord;
+import com.topsail.scheduletask.pojo.ServiceHeartbeat;
+import com.topsail.scheduletask.mapper.AlertRecordMapper;
+import com.topsail.scheduletask.mapper.HeartBeatMapper;
+import com.topsail.scheduletask.util.DingDingUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+public class ServiceDownMonitor {
+
+ @Autowired
+ private HeartBeatMapper heartBeatMapper;
+
+ @Autowired
+ private AlertRecordMapper alertRecordMapper;
+
+ @Autowired
+ private DingDingUtil dingDingUtil;
+
+ @Value("${monitor.service.timeout:30}")
+ private int timeout;
+
+ private Map lastAlertTime = new ConcurrentHashMap<>();
+ private static final long ALERT_INTERVAL = 60000;
+
+ @Scheduled(fixedRate = 60000)
+ public void checkServiceDown() {
+ try {
+ List deadList = heartBeatMapper.selectTimeoutService(timeout);
+
+ if (deadList.isEmpty()) {
+ log.debug("所有服务心跳正常");
+ return;
+ }
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ for (ServiceHeartbeat dead : deadList) {
+ String key = dead.getServiceName() + "_" + dead.getInstanceIp();
+ Long lastTime = lastAlertTime.get(key);
+
+ if (lastTime == null || System.currentTimeMillis() - lastTime > ALERT_INTERVAL) {
+ String msg = String.format(
+ "【服务宕机告警】\n服务名:%s\n实例IP:%s\n最后心跳:%s\n超时时间:%d秒",
+ dead.getServiceName(),
+ dead.getInstanceIp(),
+ sdf.format(dead.getLastHeartbeat()),
+ timeout
+ );
+ log.error(msg);
+ dingDingUtil.sendMarkdownAlert("服务宕机告警", msg);
+
+ AlertRecord alert = new AlertRecord();
+ alert.setAlertType("SERVICE_DOWN");
+ alert.setAlertLevel("ERROR");
+ alert.setAlertMessage(msg);
+ alert.setServiceName(dead.getServiceName());
+ alert.setStatus("UNHANDLED");
+ alertRecordMapper.insert(alert);
+
+ lastAlertTime.put(key, System.currentTimeMillis());
+ }
+ }
+ } catch (Exception e) {
+ log.error("服务宕机监控异常", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/util/ApplicationContextUtil.java b/src/main/java/com/topsail/scheduletask/util/ApplicationContextUtil.java
new file mode 100644
index 0000000..c84c6cd
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/util/ApplicationContextUtil.java
@@ -0,0 +1,35 @@
+package com.topsail.scheduletask.util;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ApplicationContextUtil implements ApplicationContextAware {
+ private static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext ctx) throws BeansException {
+ applicationContext = ctx;
+ }
+
+ public static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+ public static String getApplicationName() {
+ if (applicationContext != null) {
+ return applicationContext.getEnvironment().getProperty("spring.application.name", "unknown-service");
+ }
+ return "unknown-service";
+ }
+
+ public static T getBean(Class clazz) {
+ return applicationContext.getBean(clazz);
+ }
+
+ public static Object getBean(String name) {
+ return applicationContext.getBean(name);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/topsail/scheduletask/util/DingDingUtil.java b/src/main/java/com/topsail/scheduletask/util/DingDingUtil.java
new file mode 100644
index 0000000..a8ef6cc
--- /dev/null
+++ b/src/main/java/com/topsail/scheduletask/util/DingDingUtil.java
@@ -0,0 +1,92 @@
+package com.topsail.scheduletask.util;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class DingDingUtil {
+
+ @Value("${monitor.dingding.webhook:}")
+ private String webhook;
+
+ @Value("${monitor.dingding.enabled:true}")
+ private boolean enabled;
+
+ public void sendAlert(String message) {
+ if (!enabled || webhook == null || webhook.isEmpty()) {
+ log.info("钉钉告警已禁用或未配置,消息: {}", message);
+ return;
+ }
+
+ try {
+ Map body = new HashMap<>();
+ Map text = new HashMap<>();
+ text.put("content", message);
+ body.put("msgtype", "text");
+ body.put("text", text);
+
+ HttpResponse response = HttpRequest.post(webhook)
+ .header("Content-Type", "application/json;charset=utf-8")
+ .body(JSON.toJSONString(body))
+ .execute();
+
+ if (response.isOk()) {
+ String result = response.body();
+ JSONObject jsonResult = JSON.parseObject(result);
+ if ("0".equals(jsonResult.getString("errcode"))) {
+ log.info("钉钉告警发送成功");
+ } else {
+ log.error("钉钉告警发送失败: {}", result);
+ }
+ } else {
+ log.error("钉钉告警请求失败,状态码: {}", response.getStatus());
+ }
+ } catch (Exception e) {
+ log.error("钉钉告警发送异常", e);
+ }
+ }
+
+ public void sendMarkdownAlert(String title, String text) {
+ if (!enabled || webhook == null || webhook.isEmpty()) {
+ log.info("钉钉告警已禁用或未配置,标题: {}, 内容: {}", title, text);
+ return;
+ }
+
+ try {
+ Map body = new HashMap<>();
+ Map markdown = new HashMap<>();
+ markdown.put("title", title);
+ markdown.put("text", text);
+ body.put("msgtype", "markdown");
+ body.put("markdown", markdown);
+
+ HttpResponse response = HttpRequest.post(webhook)
+ .header("Content-Type", "application/json;charset=utf-8")
+ .body(JSON.toJSONString(body))
+ .execute();
+
+ if (response.isOk()) {
+ String result = response.body();
+ JSONObject jsonResult = JSON.parseObject(result);
+ if ("0".equals(jsonResult.getString("errcode"))) {
+ log.info("钉钉Markdown告警发送成功");
+ } else {
+ log.error("钉钉Markdown告警发送失败: {}", result);
+ }
+ } else {
+ log.error("钉钉Markdown告警请求失败,状态码: {}", response.getStatus());
+ }
+ } catch (Exception e) {
+ log.error("钉钉Markdown告警发送异常", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index cf86bb6..1f9c6db 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -49,4 +49,11 @@ spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.socketFactory.port=994
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
-mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
\ No newline at end of file
+mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
+
+### 监控配置 ###
+monitor.service.timeout=30
+monitor.mq.max-accumulation=1000
+monitor.mq.max-delay=30000
+monitor.dingding.webhook=
+monitor.dingding.enabled=false
\ No newline at end of file
diff --git a/src/main/resources/com/topsail/scheduletask/mapper/AlertRecordMapper.xml b/src/main/resources/com/topsail/scheduletask/mapper/AlertRecordMapper.xml
new file mode 100644
index 0000000..48c70fb
--- /dev/null
+++ b/src/main/resources/com/topsail/scheduletask/mapper/AlertRecordMapper.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+ INSERT INTO alert_record (alert_type, alert_level, alert_message, service_name, topic, status)
+ VALUES (#{alertType}, #{alertLevel}, #{alertMessage}, #{serviceName}, #{topic}, #{status})
+
+
+
+
+
+
+
+ UPDATE alert_record
+ SET status = #{status}, handle_time = #{handleTime}
+ WHERE id = #{id}
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/com/topsail/scheduletask/mapper/HeartBeatMapper.xml b/src/main/resources/com/topsail/scheduletask/mapper/HeartBeatMapper.xml
new file mode 100644
index 0000000..d7d55e7
--- /dev/null
+++ b/src/main/resources/com/topsail/scheduletask/mapper/HeartBeatMapper.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ INSERT INTO service_heartbeat (service_name, instance_ip, last_heartbeat)
+ VALUES (#{serviceName}, #{instanceIp}, #{lastHeartbeat})
+ ON DUPLICATE KEY UPDATE
+ last_heartbeat = VALUES(last_heartbeat),
+ update_time = CURRENT_TIMESTAMP
+
+
+
+
+
+
+
+ DELETE FROM service_heartbeat
+ WHERE service_name = #{serviceName} AND instance_ip = #{instanceIp}
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/com/topsail/scheduletask/mapper/MqMonitorMapper.xml b/src/main/resources/com/topsail/scheduletask/mapper/MqMonitorMapper.xml
new file mode 100644
index 0000000..9f2b18b
--- /dev/null
+++ b/src/main/resources/com/topsail/scheduletask/mapper/MqMonitorMapper.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ INSERT INTO mq_monitor (mq_type, topic, group_name, accumulation_count, delay_time, monitor_time)
+ VALUES (#{mqType}, #{topic}, #{groupName}, #{accumulationCount}, #{delayTime}, #{monitorTime})
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
new file mode 100644
index 0000000..32b58a7
--- /dev/null
+++ b/src/main/resources/schema.sql
@@ -0,0 +1,39 @@
+CREATE TABLE IF NOT EXISTS `service_heartbeat` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `service_name` varchar(64) NOT NULL COMMENT '服务名',
+ `instance_ip` varchar(32) NOT NULL COMMENT '实例IP',
+ `last_heartbeat` datetime NOT NULL COMMENT '最后心跳时间',
+ `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+ `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_service_instance` (`service_name`,`instance_ip`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务心跳表';
+
+CREATE TABLE IF NOT EXISTS `mq_monitor` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `mq_type` varchar(32) NOT NULL DEFAULT 'RocketMQ' COMMENT 'MQ类型',
+ `topic` varchar(128) NOT NULL COMMENT '队列主题',
+ `group_name` varchar(128) DEFAULT NULL COMMENT '消费组',
+ `accumulation_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '堆积数量',
+ `delay_time` bigint(20) DEFAULT 0 COMMENT '延迟时间(ms)',
+ `monitor_time` datetime NOT NULL COMMENT '监控时间',
+ `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_topic` (`topic`),
+ KEY `idx_monitor_time` (`monitor_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MQ堆积监控表';
+
+CREATE TABLE IF NOT EXISTS `alert_record` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `alert_type` varchar(32) NOT NULL COMMENT '告警类型(SERVICE_DOWN/MQ_ACCUMULATION)',
+ `alert_level` varchar(16) NOT NULL DEFAULT 'WARN' COMMENT '告警级别',
+ `alert_message` text COMMENT '告警消息',
+ `service_name` varchar(64) DEFAULT NULL COMMENT '服务名',
+ `topic` varchar(128) DEFAULT NULL COMMENT 'MQ主题',
+ `status` varchar(16) NOT NULL DEFAULT 'UNHANDLED' COMMENT '处理状态',
+ `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+ `handle_time` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_alert_type` (`alert_type`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='告警记录表';
\ No newline at end of file