From 08eb5f458763ec813b74a492d4ecdb77231d2002 Mon Sep 17 00:00:00 2001 From: bgy Date: Thu, 7 May 2026 17:27:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=8D=E5=8A=A1=E5=92=8C?= =?UTF-8?q?=E9=98=9F=E5=88=97=E7=9B=91=E6=8E=A7=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 147 +++++++---------- .../scheduletask/ScheduleTaskApplication.java | 2 + .../scheduletask/mapper/AlertRecordMapper.java | 20 +++ .../scheduletask/mapper/HeartBeatMapper.java | 18 +++ .../scheduletask/mapper/MqMonitorMapper.java | 15 ++ .../com/topsail/scheduletask/pojo/AlertRecord.java | 18 +++ .../com/topsail/scheduletask/pojo/MqMonitor.java | 17 ++ .../scheduletask/pojo/ServiceHeartbeat.java | 15 ++ .../scheduletask/service/MqMonitorService.java | 176 +++++++++++++++++++++ .../topsail/scheduletask/task/HeartBeatTask.java | 38 +++++ .../scheduletask/task/MqAccumulationMonitor.java | 28 ++++ .../scheduletask/task/ServiceDownMonitor.java | 81 ++++++++++ .../scheduletask/util/ApplicationContextUtil.java | 35 ++++ .../topsail/scheduletask/util/DingDingUtil.java | 92 +++++++++++ src/main/resources/application.properties | 9 +- .../scheduletask/mapper/AlertRecordMapper.xml | 30 ++++ .../scheduletask/mapper/HeartBeatMapper.xml | 27 ++++ .../scheduletask/mapper/MqMonitorMapper.xml | 21 +++ src/main/resources/schema.sql | 39 +++++ 19 files changed, 736 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/topsail/scheduletask/mapper/AlertRecordMapper.java create mode 100644 src/main/java/com/topsail/scheduletask/mapper/HeartBeatMapper.java create mode 100644 src/main/java/com/topsail/scheduletask/mapper/MqMonitorMapper.java create mode 100644 src/main/java/com/topsail/scheduletask/pojo/AlertRecord.java create mode 100644 src/main/java/com/topsail/scheduletask/pojo/MqMonitor.java create mode 100644 src/main/java/com/topsail/scheduletask/pojo/ServiceHeartbeat.java create mode 100644 src/main/java/com/topsail/scheduletask/service/MqMonitorService.java create mode 100644 src/main/java/com/topsail/scheduletask/task/HeartBeatTask.java create mode 100644 src/main/java/com/topsail/scheduletask/task/MqAccumulationMonitor.java create mode 100644 src/main/java/com/topsail/scheduletask/task/ServiceDownMonitor.java create mode 100644 src/main/java/com/topsail/scheduletask/util/ApplicationContextUtil.java create mode 100644 src/main/java/com/topsail/scheduletask/util/DingDingUtil.java create mode 100644 src/main/resources/com/topsail/scheduletask/mapper/AlertRecordMapper.xml create mode 100644 src/main/resources/com/topsail/scheduletask/mapper/HeartBeatMapper.xml create mode 100644 src/main/resources/com/topsail/scheduletask/mapper/MqMonitorMapper.xml create mode 100644 src/main/resources/schema.sql 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