开发监控看板组管理CRUD

设计告警阈值配置功能
This commit is contained in:
gaoyutao
2026-01-23 18:39:20 +08:00
parent 493ecba0ba
commit 5564b03ed2
58 changed files with 1233 additions and 1986 deletions
-1
View File
@@ -14,7 +14,6 @@
<module>tongran-job</module>
<module>tongran-file</module>
<module>tongran-mtragent</module>
<module>tongran-frp</module>
</modules>
<artifactId>tongran-modules</artifactId>
-116
View File
@@ -1,116 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.tongran</groupId>
<artifactId>tongran-modules</artifactId>
<version>3.6.6</version>
</parent>
<artifactId>tongran-modules-frp</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--rocketmq消息队列-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version> 4.9.0</version>
</dependency>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- tongran Common Log -->
<dependency>
<groupId>com.tongran</groupId>
<artifactId>tongran-common-log</artifactId>
</dependency>
<!-- tongran Common Swagger-->
<dependency>
<groupId>com.tongran</groupId>
<artifactId>tongran-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>com.tongran</groupId>
<artifactId>tongran-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- yml调用pom变量 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.28</version>
</dependency>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,25 +0,0 @@
package com.tongran.frp;
import com.tongran.common.security.annotation.EnableCustomConfig;
import com.tongran.common.security.annotation.EnableRyFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* 平台管理模块
*
* @author tongran
*/
@EnableCustomConfig
@EnableRyFeignClients
@SpringBootApplication
@EnableAsync
public class TongRanFrpApplication
{
public static void main(String[] args)
{
SpringApplication.run(com.tongran.frp.TongRanFrpApplication.class, args);
System.out.println("TongRanFrp模块启动成功");
}
}
@@ -1,77 +0,0 @@
package com.tongran.frp.config;
import com.tongran.frp.consumer.RocketMsgListener;
import com.tongran.frp.enums.MessageTopic;
import com.tongran.frp.model.ConsumerMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 消费者配置
*/
@RefreshScope
@Configuration
@Slf4j
public class ConsumerConfig {
@Autowired
private ConsumerMode consumerMode;
@Autowired
private RocketMsgListener rocketMsgListener;
@Bean
public DefaultMQPushConsumer getRocketMQConsumer() {
//构建客户端连接
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerMode.getAgentGroup());
//
consumer.setNamesrvAddr(consumerMode.getNamesrvAddr());
consumer.setConsumeThreadMin(consumerMode.getConsumeThreadMin());
consumer.setConsumeThreadMax(consumerMode.getConsumeThreadMax());
consumer.registerMessageListener(rocketMsgListener);
/**
* 1. CONSUME_FROM_LAST_OFFSET:第一次启动从队列最后位置消费,后续再启动接着上次消费的进度开始消费
* 2. CONSUME_FROM_FIRST_OFFSET:第一次启动从队列初始位置消费,后续再启动接着上次消费的进度开始消费
* 3. CONSUME_FROM_TIMESTAMP:第一次启动从指定时间点位置消费,后续再启动接着上次消费的进度开始消费
* 以上所说的第一次启动是指从来没有消费过的消费者,如果该消费者消费过,那么会在broker端记录该消费者的消费位置,如果该消费者挂了再启动,那么自动从上次消费的进度开始
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
/**
* CLUSTERING (集群模式) :默认模式,同一个ConsumerGroup(groupName相同)每个consumer只消费所订阅消息的一部分内容,同一个ConsumerGroup里所有的Consumer消息加起来才是所
* 订阅topic整体,从而达到负载均衡的目的
* BROADCASTING (广播模式) :同一个ConsumerGroup每个consumer都消费到所订阅topic所有消息,也就是一个消费会被多次分发,被多个consumer消费。
* 需要注意的是,在广播模式下,每个Consumer都会独立地处理相同的消息副本。这可能会导致一些潜在的问题,例如消息重复处理或者资源浪费。因此,在使用广播模式时,请确保消息的处理逻辑是幂等的,并仔细考虑系统资源的消耗。
*/
// consumer.setMessageModel(MessageModel.BROADCASTING);
consumer.setVipChannelEnabled(false);
consumer.setConsumeMessageBatchMaxSize(consumerMode.getConsumeMessageBatchMaxSize());
try {
/**
* 订阅topic,可以对指定消息进行过滤,例如:"TopicTest","tagl||tag2||tag3",*或null表示topic所有消息
* 由于官方并没有给直接订阅全部消息示例 所以使用list列表循环订阅所有topic
*/
// 获取所有topic列表
MessageTopic messageTopic = new MessageTopic();
List<String> allTopics = messageTopic.RocketMQTopicList();
//订阅所有topic
for (String topic : allTopics) {
consumer.subscribe(topic,"*");
}
consumer.start();
log.info("消费者初始化成功:{}", consumer);
} catch (MQClientException e) {
e.printStackTrace();
log.error("消费者初始化失败:{}",e.getMessage());
}
return consumer;
}
}
@@ -1,56 +0,0 @@
package com.tongran.frp.config;
import com.tongran.frp.model.ProducerMode;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* mq搭建地址连接
* 生产者初者连接信息 具体看nacos配置
*/
@Configuration
@Slf4j
public class ProducerConfig {
/**
* 远程调用连接信息
*/
public static DefaultMQProducer producer;
/**
* 连接客户端信息配置 具体看nacos配置
*/
@Autowired
private ProducerMode producerMode;
@Bean
public DefaultMQProducer getRocketMQProducer() {
producer = new DefaultMQProducer(producerMode.getAgentGroup());
producer.setNamesrvAddr(producerMode.getNamesrvAddr());
//如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
if(producerMode.getMaxMessageSize()!=null){
producer.setMaxMessageSize(producerMode.getMaxMessageSize());
}
if(producerMode.getSendMsgTimeout()!=null){
producer.setSendMsgTimeout(producerMode.getSendMsgTimeout());
}
//如果发送消息失败,设置重试次数,默认为2次
if(producerMode.getRetryTimesWhenSendFailed()!=null){
producer.setRetryTimesWhenSendFailed(producerMode.getRetryTimesWhenSendFailed());
}
producer.setVipChannelEnabled(false);
try {
producer.start();
log.info("生产者初始化成功:{}",producer.toString());
} catch (MQClientException e) {
log.error("生产者初始化失败:{}",e.getMessage());
}
return producer;
}
}
@@ -1,94 +0,0 @@
package com.tongran.frp.consumer;
import com.alibaba.fastjson.JSON;
import com.tongran.frp.domain.DeviceMessage;
import com.tongran.frp.enums.MessageCodeEnum;
import com.tongran.frp.handler.MessageHandler;
import com.tongran.frp.producer.ConsumeException;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.UnsupportedEncodingException;
import java.util.List;
/**
* 消息监听
*/
@Slf4j
@Component
public class RocketMsgListener implements MessageListenerConcurrently {
@Autowired
private MessageHandler messageHandler;
/**
* 消费消息
* @param list msgs.size() >= 1
* DefaultMQPushConsumer.consumeMessageBatchMaxSize=1you can modify here
* 这里只设置为1,当设置为多个时,list中只要有一条消息消费失败,就会整体重试
* @param consumeConcurrentlyContext 上下文信息
* @return 消费状态 成功(CONSUME_SUCCESS)或者 重试 (RECONSUME_LATER)
*/
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
try{
//消息不等于空情况
if (!CollectionUtils.isEmpty(list)) {
//获取topic
for (MessageExt messageExt : list) {
// 解析消息内容
String body = new String(messageExt.getBody());
log.info("接受到的消息为:{}", body);
String tags = messageExt.getTags();
String topic = messageExt.getTopic();
String msgId = messageExt.getMsgId();
String keys = messageExt.getKeys();
int reConsume = messageExt.getReconsumeTimes();
// 消息已经重试了3次,如果不需要再次消费,则返回成功
if (reConsume == 3) {
// TODO 补偿信息
log.error("消息消费三次失败,消息内容:{}", body);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;//根据业务返回是否正常
}
if(MessageCodeEnum.TR_MTRAGENT_UP.getCode().equals(topic)){
// 拿到信息
DeviceMessage message = JSON.parseObject(body, DeviceMessage.class);
// 处理消息
messageHandler.handleMessage(message);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;//业务处理成功
}
// 根据不同的topic处理不同的业务 这里以订单消息为例子
}
}
// 消息消费失败
//broker会根据设置的messageDelayLevel发起重试,默认16次
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
} catch (Exception e) {
// 调用 handleException 方法处理异常并返回处理结果
return handleException(e);
}
}
/**
* 异常处理
*
* @param e 捕获的异常
* @return 消息消费结果
*/
private static ConsumeConcurrentlyStatus handleException(final Exception e) {
Class exceptionClass = e.getClass();
if (exceptionClass.equals(UnsupportedEncodingException.class)) {
log.error(e.getMessage());
} else if (exceptionClass.equals(ConsumeException.class)) {
log.error(e.getMessage());
} else{
log.error(e.getMessage());
}
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
@@ -1,30 +0,0 @@
package com.tongran.frp.consumer;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
/**
* 事物消息监听
*/
public class RocketMsgTransactionListenerImpl implements TransactionListener {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 在这里执行本地事务,比如数据库操作等
// 如果本地事务执行成功,返回 COMMIT_MESSAGE
// 如果本地事务执行失败,返回 ROLLBACK_MESSAGE
// 如果本地事务执行中,可以返回 UNKNOW,RocketMQ 将会检查事务状态,并根据状态处理消息
return LocalTransactionState.COMMIT_MESSAGE; // 根据实际情况返回对应的状态
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
// 检查本地事务状态,如果本地事务执行成功,返回 COMMIT_MESSAGE
// 如果本地事务执行失败,返回 ROLLBACK_MESSAGE
// 如果本地事务仍在执行中,返回 UNKNOW,RocketMQ 将会继续检查事务状态
return LocalTransactionState.COMMIT_MESSAGE; // 根据实际情况返回对应的状态
}
}
@@ -1,139 +0,0 @@
package com.tongran.frp.controller;
import com.tongran.common.security.annotation.InnerAuth;
import com.tongran.frp.producer.MessageProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 消息测试类Controller
*/
@RestController
@RequestMapping("/api/rocketMessage")
public class RocketMqController {
/**
* 发送同步消息
*/
@PostMapping("/sendSynchronizeMessage")
private Map sendSynchronizeMessage(){
MessageProducer messageProducer = new MessageProducer();
//调用MessageProducer配置好的消息方法
SendResult sendResult = messageProducer.sendSynchronizeMessage("order-message","order_message_tag","title","content");
Map<String,Object> result = new HashMap<>();
result.put("data",sendResult);
return result;
}
/**
* 发送单向消息
*/
@PostMapping("/sendOnewayMessage")
private Map sendOnewayMessage(@RequestParam("topic") String topic,@RequestParam("tag") String tag,@RequestParam("key") String key,@RequestParam("value") String value){
MessageProducer messageProducer = new MessageProducer();
//调用MessageProducer配置好的消息方法 topic需要你根据你们业务定制相应的
messageProducer.sendOnewayMessage("order-message","order_timeout_tag","title","content");
Map<String,Object> result = new HashMap<>();
result.put("msg","发送成功");
result.put("code",200);
return result;
}
/**
* 批量发送消息
*/
@PostMapping("/sendBatchMessage")
private Map sendBatchMessage(){
// 根据实际需求创建消息列表并返回
List<Message> messages = new ArrayList<>();
// 添加消息到列表
messages.add(new Message("order-message", "order_timeout_tag", "Message 1".getBytes()));
messages.add(new Message("order-message", "order_timeout_tag", "Message 2".getBytes()));
messages.add(new Message("order-message", "order_timeout_tag", "Message 3".getBytes()));
MessageProducer messageProducer = new MessageProducer();
//调用MessageProducer配置好的消息方法 topic需要你根据你们业务定制相应的
SendResult sendResult = messageProducer.sendBatchMessage(messages);
Map<String,Object> result = new HashMap<>();
result.put("data",sendResult);
return result;
}
/**
* 发送事物消息
*/
@PostMapping("/sendThingMessage")
private Map sendThingMessage(@RequestParam("topic") String topic,@RequestParam("tag") String tag,@RequestParam("key") String key,@RequestParam("value") String value){
MessageProducer messageProducer = new MessageProducer();
//调用MessageProducer配置好的消息方法 topic需要你根据你们业务定制相应的
SendResult sendResult = messageProducer.sendThingMessage("order-message","order_timeout_tag","title","content");
Map<String,Object> result = new HashMap<>();
result.put("data",sendResult);
return result;
}
/**
* 发送有序的消息
*/
@PostMapping("/sendOrderlyMessage")
private Map sendOrderlyMessage(){
// 根据实际需求创建消息列表并返回
List<Message> messages = new ArrayList<>();
// 添加消息到列表
messages.add(new Message("order-message", "order_timeout_tag", "Message 1".getBytes()));
messages.add(new Message("order-message", "order_timeout_tag", "Message 2".getBytes()));
messages.add(new Message("order-message", "order_timeout_tag", "Message 3".getBytes()));
Integer messageQueueNumber = 3;
MessageProducer messageProducer = new MessageProducer();
//调用MessageProducer配置好的消息方法 topic需要你根据你们业务定制相应的
SendResult sendResult = messageProducer.sendOrderlyMessage(messages,messageQueueNumber);
Map<String,Object> result = new HashMap<>();
result.put("data",sendResult);
return result;
}
/**
* 发送延迟消息
*/
@PostMapping("/sendDelayMessage")
private Map sendDelayMessage(@RequestParam("topic") String topic,@RequestParam("tag") String tag,@RequestParam("key") String key,@RequestParam("value") String value){
MessageProducer messageProducer = new MessageProducer();
//调用MessageProducer配置好的消息方法 topic需要你根据你们业务定制相应的
SendResult sendResult = messageProducer.sendDelayMessage("order-message","order_timeout_tag","title","content",4);
Map<String,Object> result = new HashMap<>();
result.put("data",sendResult);
return result;
}
/**
* 发送异步的消息
*/
@InnerAuth
@PostMapping("/sendAsyncProducerMessage")
private Map sendAsyncProducerMessage(@RequestParam("topic") String topic,@RequestParam("tag") String tag,@RequestParam("key") String key,@RequestParam("value") String value){
MessageProducer messageProducer = new MessageProducer();
//调用MessageProducer配置好的消息方法 topic需要你根据你们业务定制相应的
SendResult sendResult = messageProducer.sendAsyncProducerMessage(topic,tag,key,value);
Map<String,Object> result = new HashMap<>();
result.put("data",sendResult);
return result;
}
}
@@ -1,10 +0,0 @@
package com.tongran.frp.domain;
import lombok.Data;
@Data
public class DeviceMessage {
private String clientId;
private String dataType;
private String data;
}
@@ -1,15 +0,0 @@
package com.tongran.frp.domain.vo;
import lombok.Data;
@Data
public class AgentUpdateMsgVo {
/**文件地址,外网HTTP(S)地址 */
private String fileUrl;
/** 文件MD5 */
private String fileMd5;
/** 执行方式:0、立即执行;1、定时执行; */
private Integer method;
/** 定时时间,执行方式为1、定时执行时该字段必传 */
private long policyTime;
}
@@ -1,16 +0,0 @@
package com.tongran.frp.domain.vo;
import lombok.Data;
@Data
public class HopInfoVO {
private int hopNumber; // 跳数
private String ipAddress; // IP地址
private String hostname; // 主机名(如果有)
private double lossPercent; // 丢包率
private double avgLatency; // 平均延迟(ms)
private String country; // 国家
private String province; // 省份
private String city; // 城市
private String isp; // 运营商
}
@@ -1,13 +0,0 @@
package com.tongran.frp.domain.vo;
import lombok.Data;
@Data
public class MessageVo {
private String clientId;
private String dataType;
private String data;
}
@@ -1,19 +0,0 @@
package com.tongran.frp.domain.vo;
import lombok.Data;
import java.util.List;
@Data
public class MtrResultVo {
private String targetIp; // 目标IP
private String clientId; // 目标IP
private Long policyId; // 策略ID
private double firstLossPercent; // 第一次探测丢包率
private double finalLossPercent; // 最终丢包率
private long timestamp; // 探测时间戳
private boolean hasRetry; // 是否重试
private String errorMsg; // 错误信息
private List<HopInfoVO> hopInfos; // 每跳路由信息列表
}
@@ -1,22 +0,0 @@
package com.tongran.frp.domain.vo;
import lombok.Data;
import java.time.Instant;
@Data
public class PolicyTypeVo {
/** 服务器监控策略信息 */
private String monitors;
/** 服务器脚本策略信息 */
private String scripts;
/** agent更新信息 */
private String versions;
/** 路由信息 */
private String routes;
/** mtr策略 */
private String mtrPolicys;
/** 时间戳 */
private Long timestamp = Instant.now().getEpochSecond();
}
@@ -1,14 +0,0 @@
package com.tongran.frp.domain.vo;
import lombok.Data;
import java.time.Instant;
import java.util.List;
@Data
public class PolicyVo<T> {
/** 更新时间戳 */
private Long upTime = Instant.now().getEpochSecond();
/** 更新内容 */
private List<T> contents;
}
@@ -1,31 +0,0 @@
package com.tongran.frp.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* mtr探测策略配置对象 rm_mtr_policy_config
*
* @author tongran
* @date 2025-11-18
*/
@Data
public class RmMtrPolicyConfigVo
{
/** 开始时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/** 结束时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/** 探测频率(秒) */
private Long probeFrequency;
/** clientId和ip对应集合 */
private Map<String, List<String>> clientIdToIpsMap;
}
@@ -1,29 +0,0 @@
package com.tongran.frp.domain.vo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.time.Instant;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class RspVo {
/**
* 状态码,0、失败;1、成功
*/
private Integer resCode;
/**
* 描述
*/
private String resMag;
/**
* 描述
*/
private String resMsg;
private String result;
/**
* 时间戳
*/
private Long timestamp = Instant.now().getEpochSecond();
}
@@ -1,16 +0,0 @@
package com.tongran.frp.enums;
import lombok.Getter;
@Getter
public enum AlarmTypeEnum {
服务器下线("1", "服务器下线"),
交换机下线("2", "交换机下线"),
mtrAgent下线("3", "mtrAgent下线");
private final String code;
private final String msg;
AlarmTypeEnum(String code, String msg){
this.code = code;
this.msg = msg;
}
}
@@ -1,67 +0,0 @@
package com.tongran.frp.enums;
import lombok.Getter;
/**
* 用于传递topic和 tag
* 也用于接收消息后判断不同的消息处理不同的业务
*/
@Getter
public enum MessageCodeEnum {
/**
* agent数据采集的信息
*/
AGENT_MESSAGE_TOPIC("agent_up","agent数据采集的信息topic"),
TONGRAN_AGENT_UP("tongran_agent_up","agent数据采集的信息topic"),
TR_AGENT_UP("tr_agent_up","agent数据采集的信息topic v1.1"),
TR_MTRAGENT_UP("tr_mtragent_up","mtragent监测丢包率的信息topic"),
/**
* 系统消息
*/
NOTE_MESSAGE_TOPIC("system-message","系统消息服务模块topic名称"),
/**
* 用户消息
*/
USER_MESSAGE_TOPIC("user-message","用户消息服务模块topic名称"),
/**
* 订单消息
*/
ORDER_MESSAGE_TOPIC("order-message","订单消息服务模块topic名称"),
/**
* 用户消息tag
*/
USER_MESSAGE_TAG("user_message_tag","用户消息推送"),
/**
* 系统消息tag
*/
NOTE_MESSAGE_TAG("system_message_tag","系统消息推送"),
/**
* 订单消息
*/
ORDER_MESSAGE_TAG("order_message_tag","订单消息推送"),
/**
* 订单处理编号
*/
ORDER_TIMEOUT_TAG("order_timeout_tag","订单超时处理");
private final String code;
private final String msg;
MessageCodeEnum(String code, String msg){
this.code = code;
this.msg = msg;
}
}
@@ -1,21 +0,0 @@
package com.tongran.frp.enums;
import java.util.ArrayList;
import java.util.List;
/**
* 定义topic列表
*/
public class MessageTopic {
//在这里添加topic 用于批量订阅
public List<String> RocketMQTopicList(){
List<String> getTopicLists=new ArrayList<>();
// agent采集消息
// getTopicLists.add("agent_up");
getTopicLists.add("tr_frp_up");
return getTopicLists;
}
}
@@ -1,14 +0,0 @@
package com.tongran.frp.enums;
import lombok.Getter;
@Getter
public enum PushMethodEnum {
企业微信("1", "企业微信");
private final String code;
private final String msg;
PushMethodEnum(String code, String msg){
this.code = code;
this.msg = msg;
}
}
@@ -1,21 +0,0 @@
package com.tongran.frp.enums;
import lombok.Getter;
@Getter
public enum ServerLogoEnum {
交换卷文件的可用空间("systemSwapSizeFreeCollect", "交换卷/文件的可用空间(字节)"),
内存利用率("memoryUtilizationCollect", "内存利用率"),
可用交换空间百分比("systemSwapSizePercentCollect", "可用交换空间百分比"),
可用内存("memorySizeAvailableCollect", "可用内存"),
可用内存百分比("memorySizePercentCollect", "可用内存百分比"),
正在运行的进程数("procNumRunCollect", "正在运行的进程数"),
登录用户数("systemUsersNumCollect", "登录用户数"),
进程数("procNumCollect", "进程数");
private final String code;
private final String msg;
ServerLogoEnum(String code, String msg){
this.code = code;
this.msg = msg;
}
}
@@ -1,17 +0,0 @@
package com.tongran.frp.enums;
import lombok.Getter;
@Getter
public enum SwitchLogo {
设备CPU使用率("hwEntityCpuUsage", "设备CPU使用率"),
设备内存使用率("hwEntityMemUsage", "设备内存使用率(%)"),
系统平均功率("hwAveragePower", "系统平均功率(%)"),
系统实时功率("hwCurrentPower", "系统实时功率(%)");
private final String code;
private final String msg;
SwitchLogo(String code, String msg){
this.code = code;
this.msg = msg;
}
}
@@ -1,114 +0,0 @@
package com.tongran.frp.handler;
import com.tongran.common.core.enums.MsgEnum;
import com.tongran.frp.domain.DeviceMessage;
import com.tongran.frp.domain.vo.RspVo;
import com.tongran.frp.model.ProducerMode;
import com.tongran.frp.producer.MessageProducer;
import com.tongran.frp.utils.JsonDataParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* 设备消息处理器
*/
@Slf4j
@Component
@EnableScheduling
public class MessageHandler {
private final Map<String, Consumer<DeviceMessage>> messageHandlers = new HashMap<>();
// 心跳状态
private static final String HEARTBEAT_STATUS_PREFIX = "mtr:heartbeat:status:";
// 心跳时间
private static final String HEARTBEAT_TIME_PREFIX = "mtr:heartbeat:time:";
// 心跳告警
private static final String HEARTBEAT_ALERT_PREFIX = "mtr:heartbeat:alert:";
String HEARTBEAT_RECOVERY_COUNT_PREFIX = "mtr:heartbeat:recovery:count:";
String HEARTBEAT_COUNT_PREFIX = "mtr:heartbeat:count:";
private static final long HEARTBEAT_TIMEOUT = 30000; // 3分钟超时
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ProducerMode producerMode;
/**
* 初始化处理器映射
*/
@PostConstruct
public void init() {
registerHandler(MsgEnum.Agent版本更新应答.getValue(), this::handleAgentUpdateRspMessage);
// 其他类型消息可以单独注册处理器
registerHandler(MsgEnum.注册.getValue(), this::handleRegisterMessage);
registerHandler(MsgEnum.获取最新策略.getValue(), this::handleNewPolicyMessage);
}
private void handleRegisterMessage(DeviceMessage message) {
MessageProducer messageProducer = new MessageProducer();
}
/**
* agent更新响应
* @param message
*/
private void handleAgentUpdateRspMessage(DeviceMessage message) {
List<RspVo> rspVoList = JsonDataParser.parseJsonData(message.getData(), RspVo.class);
if (!rspVoList.isEmpty()) {
RspVo rsp = rspVoList.get(0);
if(rsp.getResCode() == 1){
}else{
}
}
}
/**
* 注册消息处理器
*/
private void registerHandler(String dataType, Consumer<DeviceMessage> handler) {
messageHandlers.put(dataType, handler);
}
/**
* 处理设备消息(对外暴露的主方法)
*/
public void handleMessage(DeviceMessage message) {
String dataType = message.getDataType();
Consumer<DeviceMessage> handler = messageHandlers.get(dataType);
if (handler != null) {
handler.accept(message);
} else {
log.warn("未知数据类型:{}", dataType);
}
}
// ========== 具体的消息处理方法 ==========
/**
* 获取最新策略
* @param deviceMessage
*/
private void handleNewPolicyMessage(DeviceMessage deviceMessage) {
MessageProducer messageProducer = new MessageProducer();
}
// 更新资源状态的公共方法
private void updateResourceStatus(String clientId, String status) {
log.info("开启更新资源状态========");
}
}
@@ -1,30 +0,0 @@
package com.tongran.frp.model;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* 消费者初始化
* 消费者连接信息 具体看nacos配置
*/
@Data
@Configuration
@Component
public class ConsumerMode {
@Value("${suning.rocketmq.namesrvAddr}")
private String namesrvAddr;
@Value("${suning.rocketmq.conumer.groupName}")
private String groupName ;
@Value("${suning.rocketmq.conumer.consumeThreadMin}")
private int consumeThreadMin;
@Value("${suning.rocketmq.conumer.consumeThreadMax}")
private int consumeThreadMax;
@Value("${suning.rocketmq.conumer.consumeMessageBatchMaxSize}")
private int consumeMessageBatchMaxSize;
@Value("${suning.rocketmq.conumer.agentTopic}")
private String agentTopic;
@Value("${suning.rocketmq.conumer.agentGroup}")
private String agentGroup;
}
@@ -1,29 +0,0 @@
package com.tongran.frp.model;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 生产者初始化
*/
@RefreshScope
@Data
@Configuration
public class ProducerMode {
@Value("${suning.rocketmq.producer.groupName}")
private String groupName;
@Value("${suning.rocketmq.producer.agentTopic}")
private String agentTopic;
@Value("${suning.rocketmq.producer.agentGroup}")
private String agentGroup;
@Value("${suning.rocketmq.namesrvAddr}")
private String namesrvAddr;
@Value("${suning.rocketmq.producer.maxMessageSize}")
private Integer maxMessageSize;
@Value("${suning.rocketmq.producer.sendMsgTimeout}")
private Integer sendMsgTimeout;
@Value("${suning.rocketmq.producer.retryTimesWhenSendFailed}")
private Integer retryTimesWhenSendFailed;
}
@@ -1,23 +0,0 @@
package com.tongran.frp.producer;
/**
* @author tongran
* 用于捕捉异常非受检异常(unchecked exception
* RuntimeException 和其子类的异常在编译时不需要进行强制性的异常处理,可以选择在运行时进行捕获和处理
* 可选择使用
*/
public class ConsumeException extends RuntimeException{
private static final long serialVersionUID = 4093867789628938836L;
public ConsumeException(String message) {
super(message);
}
public ConsumeException(Throwable cause) {
super(cause);
}
public ConsumeException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,225 +0,0 @@
package com.tongran.frp.producer;
import com.alibaba.fastjson.JSON;
import com.tongran.frp.consumer.RocketMsgTransactionListenerImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import static com.tongran.frp.config.ProducerConfig.producer;
/**
* 消息发送
*/
@Slf4j
public class MessageProducer {
/**
* 同步发送消息
* @param topic 主题
* @param tag 标签
* @param key 自定义的key,根据业务来定
* @param value 消息的内容
* 通过调用 send() 方法发送消息,阻塞等待服务器响应。
*/
public SendResult sendSynchronizeMessage(String topic, String tag, String key, String value){
String body = "topic:【"+topic+"】, tag:【"+tag+"】, key:【"+key+"】, value:【"+value+"";
try {
Message msg = new Message(topic,tag,key, value.getBytes(RemotingHelper.DEFAULT_CHARSET));
System.out.println("生产者发送消息:"+ JSON.toJSONString(value));
SendResult result = producer.send(msg);
return result;
} catch (UnsupportedEncodingException e) {
log.error("消息初始化失败!body{}",body);
} catch (MQClientException | InterruptedException | RemotingException | MQBrokerException e) {
log.error("消息发送失败! body{}",body);
}
return null;
}
/**
* 单向发送消息
* @param topic 主题
* @param tag 标签
* @param key 自定义的key,根据业务来定
* @param value 消息的内容
* 单向发送:通过调用 sendOneway() 方法发送消息,不关心发送结果,适用于对可靠性要求不高的场景。
*/
public void sendOnewayMessage(String topic, String tag, String key, String value){
String body = "topic:【"+topic+"】, tag:【"+tag+"】, key:【"+key+"】, value:【"+value+"";
try {
Message msg = new Message(topic,tag,key, value.getBytes(RemotingHelper.DEFAULT_CHARSET));
System.out.println("生产者发送消息:"+ JSON.toJSONString(value));
producer.sendOneway(msg);
} catch (UnsupportedEncodingException e) {
log.error("消息初始化失败!body{}",body);
} catch (MQClientException | InterruptedException | RemotingException e) {
log.error("消息发送失败! body{}",body);
}
}
/**
* 批量发送消息
* @param messages 消息列表
* 批量发送:通过调用 send() 方法并传入多条消息,实现批量发送消息。
*/
public SendResult sendBatchMessage(List<Message> messages){
String body = messages.toString();
try {
System.out.println("生产者发送消息:"+ messages);
// 发送批量消息
SendResult sendResult = producer.send(messages);
return sendResult;
} catch (MQClientException | InterruptedException | RemotingException e) {
log.error("消息发送失败! body{}",body);
} catch (MQBrokerException e) {
throw new RuntimeException(e);
}
return null;
}
/**
* 事务消息发送
* @param topic 主题
* @param tag 标签
* @param key 自定义的key,根据业务来定
* @param value 消息的内容
* 事务消息发送:通过使用事务监听器实现本地事务执行和消息发送的一致性。
*/
public SendResult sendThingMessage(String topic, String tag, String key, String value){
String body = "topic:【"+topic+"】, tag:【"+tag+"】, key:【"+key+"】, value:【"+value+"";
try {
// 实例化事务生产者
TransactionMQProducer transactionMQProducer = new TransactionMQProducer(producer.getProducerGroup());
transactionMQProducer.setNamesrvAddr(producer.getNamesrvAddr());
// 设置事务监听器
transactionMQProducer.setTransactionListener(new RocketMsgTransactionListenerImpl());
Message msg = new Message(topic,tag,key, value.getBytes(RemotingHelper.DEFAULT_CHARSET));
System.out.println("生产者发送消息:"+ JSON.toJSONString(value));
// 发送事务消息
TransactionSendResult sendResult = transactionMQProducer.sendMessageInTransaction(msg, null);
return sendResult;
} catch (UnsupportedEncodingException e) {
log.error("消息初始化失败!body{}",body);
} catch (MQClientException e) {
log.error("消息发送失败! body{}",body);
}
return null;
}
/**
* 发送有序的消息
* @param messagesList Message集合
* @param messageQueueNumber 消息队列数量,根据实际情况设定
* 顺序发送: messageQueueNumber 表示消息的业务标识,可以根据具体需求进行设置来保证消息按顺序发送。
*/
public SendResult sendOrderlyMessage(List<Message> messagesList, Integer messageQueueNumber) {
SendResult result = null;
for (Message message : messagesList) {
try {
result = producer.send(message, (list, msg, arg) -> {
Integer queueNumber = (Integer) arg;
//int queueIndex = queueNumber % list.size();
return list.get(queueNumber);
}, messageQueueNumber);//根据编号取模,选择消息队列
} catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
log.error("发送有序消息失败");
return result;
}
}
return result;
}
/**
* 发送延迟消息
* @param topic 主题
* @param tag 标签
* @param key 自定义的key,根据业务来定
* @param value 消息的内容
* 延迟发送:通过设置延迟级别来实现延迟发送消息。
*/
public SendResult sendDelayMessage(String topic, String tag, String key, String value, Integer level)
{
SendResult result = null;
try
{
Message msg = new Message(topic,tag,key, value.getBytes(RemotingHelper.DEFAULT_CHARSET));
log.info("生产者发送消息:"+ JSON.toJSONString(value));
//设置消息延迟级别,我这里设置5,对应就是延时一分钟
// "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"
msg.setDelayTimeLevel(level);
// 发送消息到一个Broker
result = producer.send(msg);
// 通过sendResult返回消息是否成功送达
log.info("发送延迟消息结果:======sendResult{}", result);
DateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info("发送时间:{}", format.format(new Date()));
return result;
}
catch (Exception e)
{
e.printStackTrace();
log.error("延迟消息队列推送消息异常:{},推送内容:{}", e.getMessage(), result);
}
return result;
}
/**
* 发送异步的消息
* @param topic 主题
* @param tag 标签
* @param key 自定义的key,根据业务来定
* @param value 消息的内容
* 通过调用 send() 方法,并传入一个 SendCallback 对象,在发送消息的同时可以继续处理其他逻辑,消息发送结果通过回调函数通知。
*/
public SendResult sendAsyncProducerMessage(String topic, String tag, String key, String value){
try {
//创建一个消息实例,指定主题、标签和消息体。
Message msg = new Message(topic,tag,key, value.getBytes(RemotingHelper.DEFAULT_CHARSET));
log.info("生产者发送消息:"+ JSON.toJSONString(value));
producer.send(msg,new SendCallback() {
// 异步回调的处理
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf("%-10d 异步发送消息成功 %s %n", msg, sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
System.out.printf("%-10d 异步发送消息失败 %s %n", msg, e);
e.printStackTrace();
}
});
} catch (MQClientException e) {
e.printStackTrace();
} catch (RemotingException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return null;
}
}
@@ -1,41 +0,0 @@
package com.tongran.frp.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class JsonDataParser {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 通用JSON解析方法(兼容对象和数组)
* @param jsonStr JSON字符串
* @param valueType 目标实体类类型
* @return 实体类List集合
*/
public static <T> List<T> parseJsonData(String jsonStr, Class<T> valueType) {
if (!StringUtils.hasText(jsonStr)) {
return new ArrayList<>();
}
try {
JsonNode rootNode = objectMapper.readTree(jsonStr);
if (rootNode.isArray()) {
// 处理数组格式JSON
return objectMapper.readValue(jsonStr,
objectMapper.getTypeFactory().constructCollectionType(List.class, valueType));
} else {
// 处理单个对象格式JSON
List<T> result = new ArrayList<>(1);
result.add(objectMapper.readValue(jsonStr, valueType));
return result;
}
} catch (Exception e) {
throw new RuntimeException("JSON解析失败: " + e.getMessage(), e);
}
}
}
@@ -1,149 +0,0 @@
package com.tongran.frp.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class SwitchJsonDataParser {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 通用JSON解析方法(兼容对象和数组)
* @param jsonStr JSON字符串
* @param valueType 目标实体类类型
* @return 实体类List集合
*/
public static <T> List<T> parseJsonData(String jsonStr, Class<T> valueType) {
if (!StringUtils.hasText(jsonStr)) {
return new ArrayList<>();
}
try {
JsonNode rootNode = objectMapper.readTree(jsonStr);
if (rootNode.isArray()) {
// 处理数组格式JSON
if (isStringArrayContainingJsonObjects((ArrayNode) rootNode)) {
// 处理包含JSON对象字符串的数组 - 转换为真正的对象数组
ArrayNode processedArray = processStringJsonArrayToObjectArray((ArrayNode) rootNode);
return convertJsonArrayToList(processedArray, valueType);
} else {
// 处理普通JSON数组
processJsonArray((ArrayNode) rootNode);
return convertJsonArrayToList((ArrayNode) rootNode, valueType);
}
} else {
// 处理单个对象格式JSON
if (rootNode.isObject()) {
processJsonObject((ObjectNode) rootNode);
}
List<T> result = new ArrayList<>(1);
result.add(objectMapper.treeToValue(rootNode, valueType));
return result;
}
} catch (Exception e) {
throw new RuntimeException("JSON解析失败: " + e.getMessage(), e);
}
}
/**
* 将JsonArray转换为List<T>
*/
private static <T> List<T> convertJsonArrayToList(ArrayNode arrayNode, Class<T> valueType) throws Exception {
List<T> result = new ArrayList<>();
for (int i = 0; i < arrayNode.size(); i++) {
JsonNode element = arrayNode.get(i);
result.add(objectMapper.treeToValue(element, valueType));
}
return result;
}
/**
* 判断是否是包含JSON对象字符串的字符串数组
*/
private static boolean isStringArrayContainingJsonObjects(ArrayNode arrayNode) {
if (arrayNode.size() == 0) return false;
JsonNode firstElement = arrayNode.get(0);
if (firstElement.isTextual()) {
try {
String strValue = firstElement.textValue();
// 检查是否是JSON对象格式的字符串
if (strValue.startsWith("{") && strValue.endsWith("}")) {
objectMapper.readTree(strValue);
return true;
}
} catch (Exception e) {
return false;
}
}
return false;
}
/**
* 处理包含JSON对象字符串的字符串数组,转换为真正的对象数组
*/
private static ArrayNode processStringJsonArrayToObjectArray(ArrayNode arrayNode) {
ArrayNode resultArray = objectMapper.createArrayNode();
for (int i = 0; i < arrayNode.size(); i++) {
JsonNode element = arrayNode.get(i);
if (element.isTextual()) {
try {
String jsonString = element.textValue();
JsonNode jsonNode = objectMapper.readTree(jsonString);
if (jsonNode.isObject()) {
// 处理JSON对象中的 noSuchInstance
processJsonObject((ObjectNode) jsonNode);
resultArray.add(jsonNode);
} else {
resultArray.add(element);
}
} catch (Exception e) {
// 如果解析失败,保持原样
resultArray.add(element);
}
} else {
resultArray.add(element);
}
}
return resultArray;
}
/**
* 处理JSON数组
*/
private static void processJsonArray(ArrayNode arrayNode) {
for (int i = 0; i < arrayNode.size(); i++) {
JsonNode element = arrayNode.get(i);
if (element.isObject()) {
processJsonObject((ObjectNode) element);
} else if (element.isArray()) {
processJsonArray((ArrayNode) element);
}
}
}
/**
* 处理JSON对象
*/
private static void processJsonObject(ObjectNode objectNode) {
objectNode.fields().forEachRemaining(entry -> {
JsonNode value = entry.getValue();
if (value.isTextual() && "noSuchInstance".equals(value.textValue())) {
objectNode.putNull(entry.getKey());
} else if (value.isObject()) {
processJsonObject((ObjectNode) value);
} else if (value.isArray()) {
processJsonArray((ArrayNode) value);
}
});
}
}
@@ -1,99 +0,0 @@
package com.tongran.frp.utils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashSet;
import java.util.Set;
public class TableRouterUtil {
// 日期格式
private static final DateTimeFormatter YEAR_MONTH_FORMAT =
DateTimeFormatter.ofPattern("yyyy_MM");
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 表名前缀
private static final String TABLE_PREFIX = "eps_initial_traffic";
// 表名前缀
private static final String TABLE_PREFIX_INITIAL = "initial_bandwidth_traffic";
/**
* 根据创建时间获取表名
* @param createTime 记录创建时间
* @return 对应的分表名称
* @throws IllegalArgumentException 如果createTime为null
*
* 示例:
* 2023-08-05 14:30:00 → eps_initial_traffic_2023_08_1_10
* 2023-08-15 09:15:00 → eps_initial_traffic_2023_08_11_20
* 2023-08-25 18:45:00 → eps_initial_traffic_2023_08_21_31
*/
public static String getTableName(LocalDateTime createTime) {
if (createTime == null) {
throw new IllegalArgumentException("创建时间不能为null");
}
String yearMonth = createTime.format(YEAR_MONTH_FORMAT);
int day = createTime.getDayOfMonth();
return String.format("%s_%s_%s",
TABLE_PREFIX_INITIAL,
yearMonth,
getDayRange(day));
}
/**
* 获取时间范围内涉及的所有表名
* @param startTime 开始时间 (格式: "yyyy-MM-dd HH:mm:ss")
* @param endTime 结束时间 (格式: "yyyy-MM-dd HH:mm:ss")
* @return 按时间顺序排列的表名集合
*/
public static Set<String> getTableNamesBetween(String startTime, String endTime) {
LocalDateTime start = parseDateTime(startTime);
LocalDateTime end = parseDateTime(endTime);
validateTimeRange(start, end);
Set<String> tableNames = new LinkedHashSet<>();
LocalDateTime current = start.withHour(0).withMinute(0).withSecond(0);
while (!current.isAfter(end)) {
tableNames.add(getTableName(current));
current = current.plusDays(1);
}
return tableNames;
}
// 解析字符串为LocalDateTime
private static LocalDateTime parseDateTime(String dateTimeStr) {
if (dateTimeStr == null || dateTimeStr.trim().isEmpty()) {
throw new IllegalArgumentException("时间字符串不能为空");
}
try {
return LocalDateTime.parse(dateTimeStr, DATE_TIME_FORMATTER);
} catch (Exception e) {
throw new IllegalArgumentException("时间格式必须为: yyyy-MM-dd HH:mm:ss", e);
}
}
// 获取日期区间
private static String getDayRange(int day) {
if (day < 1 || day > 31) {
throw new IllegalArgumentException("日期必须在1-31之间");
}
if (day <= 10) return "1_10";
if (day <= 20) return "11_20";
return "21_31";
}
// 验证时间范围
private static void validateTimeRange(LocalDateTime start, LocalDateTime end) {
if (start == null || end == null) {
throw new IllegalArgumentException("时间范围参数不能为null");
}
if (start.isAfter(end)) {
throw new IllegalArgumentException("开始时间不能晚于结束时间");
}
}
}
@@ -1,285 +0,0 @@
package com.tongran.frp.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WeChatWorkBot {
private static final ObjectMapper mapper = new ObjectMapper();
/**
* 发送基于模板的文本消息
* @param webhookUrl webhook地址
* @param template 消息模板,例如:"项目[项目名称]在[时间]发生[事件类型]"
* @param fieldValues 字段值的映射,key为中文字段名,value为实际值(支持String、Number、Boolean等)
* @return 是否发送成功
*/
public static boolean sendTemplateMessage(String webhookUrl, String template,
Map<String, Object> fieldValues) {
return sendTemplateMessage(webhookUrl, template, fieldValues, null, null, false);
}
/**
* 发送基于模板的文本消息(支持@功能)
* @param webhookUrl webhook地址
* @param template 消息模板
* @param fieldValues 字段值的映射
* @param mentionedMobiles 被@的用户列表(手机号)
* @param mentionedAll 是否@所有人
* @return 是否发送成功
*/
public static boolean sendTemplateMessage(String webhookUrl, String template,
Map<String, Object> fieldValues,
String[] mentionedMobiles, boolean mentionedAll) {
return sendTemplateMessage(webhookUrl, template, fieldValues, null, mentionedMobiles, mentionedAll);
}
/**
* 发送基于模板的文本消息(完整参数)
* @param webhookUrl webhook地址
* @param template 消息模板
* @param fieldValues 字段值的映射
* @param defaultValue 未找到字段时的默认值
* @param mentionedMobiles 被@的用户列表(手机号)
* @param mentionedAll 是否@所有人
* @return 是否发送成功
*/
public static boolean sendTemplateMessage(String webhookUrl, String template,
Map<String, Object> fieldValues, Object defaultValue,
String[] mentionedMobiles, boolean mentionedAll) {
try {
String actualContent = processTemplate(template, fieldValues, defaultValue);
return sendTextMessage(webhookUrl, actualContent, mentionedMobiles, mentionedAll);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 处理模板,替换字段占位符
* @param template 消息模板
* @param fieldValues 字段值映射
* @param defaultValue 默认值
* @return 处理后的消息内容
*/
public static String processTemplate(String template, Map<String, Object> fieldValues,
Object defaultValue) {
if (template == null) return "";
if (fieldValues == null || fieldValues.isEmpty()) {
return template;
}
Pattern pattern = Pattern.compile("\\[(.*?)\\]");
Matcher matcher = pattern.matcher(template);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String fieldName = matcher.group(1);
Object fieldValueObj = fieldValues.get(fieldName);
String fieldValue = convertToString(fieldValueObj);
// 如果字段值为空,使用默认值或保留原占位符
if (fieldValue == null || fieldValue.trim().isEmpty()) {
fieldValue = convertToString(defaultValue);
if (fieldValue == null) {
fieldValue = "[" + fieldName + "]";
}
}
// 对替换值进行转义,防止正则表达式特殊字符问题
matcher.appendReplacement(result, Matcher.quoteReplacement(fieldValue));
}
matcher.appendTail(result);
return result.toString();
}
/**
* 将对象转换为字符串
* @param obj 要转换的对象
* @return 字符串表示
*/
private static String convertToString(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof String) {
return (String) obj;
} else if (obj instanceof Number || obj instanceof Boolean) {
return String.valueOf(obj);
} else if (obj instanceof java.util.Date) {
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((java.util.Date) obj);
} else {
return obj.toString();
}
}
/**
* 验证模板中的字段是否都有对应的值
* @param template 消息模板
* @param fieldValues 字段值映射
* @return 是否所有字段都有值
*/
public static boolean validateTemplate(String template, Map<String, Object> fieldValues) {
if (template == null || fieldValues == null) {
return false;
}
Pattern pattern = Pattern.compile("\\[(.*?)\\]");
Matcher matcher = pattern.matcher(template);
while (matcher.find()) {
String fieldName = matcher.group(1);
Object fieldValue = fieldValues.get(fieldName);
String strValue = convertToString(fieldValue);
if (!fieldValues.containsKey(fieldName) ||
strValue == null ||
strValue.trim().isEmpty()) {
return false;
}
}
return true;
}
/**
* 获取模板中的所有字段名
* @param template 消息模板
* @return 字段名列表
*/
public static java.util.List<String> getTemplateFields(String template) {
java.util.List<String> fields = new java.util.ArrayList<>();
if (template == null) {
return fields;
}
Pattern pattern = Pattern.compile("\\[(.*?)\\]");
Matcher matcher = pattern.matcher(template);
while (matcher.find()) {
fields.add(matcher.group(1));
}
return fields;
}
/**
* 发送Markdown模板消息
* @param webhookUrl webhook地址
* @param template Markdown模板
* @param fieldValues 字段值映射
* @return 是否发送成功
*/
public static boolean sendMarkdownTemplateMessage(String webhookUrl, String template,
Map<String, Object> fieldValues) {
return sendMarkdownTemplateMessage(webhookUrl, template, fieldValues, null);
}
/**
* 发送Markdown模板消息
* @param webhookUrl webhook地址
* @param template Markdown模板
* @param fieldValues 字段值映射
* @param defaultValue 默认值
* @return 是否发送成功
*/
public static boolean sendMarkdownTemplateMessage(String webhookUrl, String template,
Map<String, Object> fieldValues,
Object defaultValue) {
try {
String actualContent = processTemplate(template, fieldValues, defaultValue);
return sendMarkdownMessage(webhookUrl, actualContent);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 发送文本消息
*/
public static boolean sendTextMessage(String webhookUrl, String content) {
return sendTextMessage(webhookUrl, content, null, false);
}
/**
* 发送文本消息(支持@功能)
*/
public static boolean sendTextMessage(String webhookUrl, String content,
String[] mentionedMobiles, boolean mentionedAll) {
try {
Map<String, Object> message = new HashMap<>();
message.put("msgtype", "text");
Map<String, Object> textContent = new HashMap<>();
textContent.put("content", content);
if (mentionedAll) {
textContent.put("mentioned_mobile_list", new String[]{"@all"});
} else if (mentionedMobiles != null && mentionedMobiles.length > 0) {
textContent.put("mentioned_mobile_list", mentionedMobiles);
}
message.put("text", textContent);
return sendMessage(webhookUrl, mapper.writeValueAsString(message));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 发送Markdown消息
*/
public static boolean sendMarkdownMessage(String webhookUrl, String content) {
try {
Map<String, Object> message = new HashMap<>();
message.put("msgtype", "markdown");
Map<String, Object> markdownContent = new HashMap<>();
markdownContent.put("content", content);
message.put("markdown", markdownContent);
return sendMessage(webhookUrl, mapper.writeValueAsString(message));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private static boolean sendMessage(String webhookUrl, String jsonBody) {
try {
URL url = new URL(webhookUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
connection.setConnectTimeout(5000);
connection.setReadTimeout(10000);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonBody.getBytes("UTF-8");
os.write(input, 0, input.length);
}
int responseCode = connection.getResponseCode();
return responseCode == 200;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
@@ -1,43 +0,0 @@
# Tomcat
server:
port: 9209
# Spring
spring:
application:
# 应用名称
name: tongran-frp
profiles:
# 环境配置
active: dev
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: ${spring.cloud.nacos.config.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
config:
# 配置中心地址
server-addr: 172.16.15.52:8848
# server-addr: 172.16.15.103:8848
# namespace: public
namespace: saas-prod
username: nacos
password: nacos
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
redisson:
singleServerConfig:
address: redis://localhost:6379
logging:
level:
com.tongran.app.mapper: DEBUG
@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="logs/tongran-frp" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 业务日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 错误日志输出 -->
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- FRP 模块日志级别控制 -->
<logger name="com.tongran.frp" level="info" additivity="false">
<appender-ref ref="console" /> <!-- 显式添加控制台 -->
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</logger>
<!-- 根日志配置 -->
<root level="info">
<appender-ref ref="console" />
</root>
</configuration>
@@ -0,0 +1,77 @@
package com.tongran.system.controller;
import com.tongran.common.core.web.controller.BaseController;
import com.tongran.common.core.web.domain.AjaxResult;
import com.tongran.common.log.annotation.Log;
import com.tongran.common.log.enums.BusinessType;
import com.tongran.common.security.annotation.RequiresPermissions;
import com.tongran.system.domain.RmMonitorGroup;
import com.tongran.system.service.IRmMonitorGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 监控看板分组Controller
*
* @author tongran
* @date 2026-01-23
*/
@RestController
@RequestMapping("/rmMonitorGroup")
@RequiresPermissions("system:monitorConfig")
public class RmMonitorGroupController extends BaseController
{
@Autowired
private IRmMonitorGroupService rmMonitorGroupService;
/**
* 查询监控看板分组列表
*/
@GetMapping("/list")
public AjaxResult list()
{
List<RmMonitorGroup> list = rmMonitorGroupService.selectRmMonitorGroupList(new RmMonitorGroup());
return success(list);
}
/**
* 新增监控看板分组
*/
@Log(title = "监控看板分组", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody RmMonitorGroup rmMonitorGroup)
{
return toAjax(rmMonitorGroupService.insertRmMonitorGroup(rmMonitorGroup));
}
/**
* 修改监控看板分组
*/
@Log(title = "监控看板分组", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody RmMonitorGroup rmMonitorGroup)
{
return toAjax(rmMonitorGroupService.updateRmMonitorGroup(rmMonitorGroup));
}
/**
* 删除监控看板分组
*/
@Log(title = "监控看板分组", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
int rows = rmMonitorGroupService.deleteRmMonitorGroupByIds(ids);
if(rows == -1){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.put(AjaxResult.CODE_TAG, 500);
ajaxResult.put(AjaxResult.MSG_TAG, "该分组内存在配置,不可删除");
return ajaxResult;
}
return toAjax(rows);
}
}
@@ -17,7 +17,8 @@ public class RmMonitorConfig extends BaseEntity
/** 主键ID */
private Long id;
/** 分组id */
private Long groupId;
/** 配置名称 */
@Excel(name = "配置名称")
private String configName;
@@ -49,5 +49,7 @@ public class RmMonitorConfigDetails extends BaseEntity
private String endTime;
/** 单位 */
private String unit;
/** 分组id */
private Long groupId;
}
@@ -0,0 +1,25 @@
package com.tongran.system.domain;
import com.tongran.common.core.annotation.Excel;
import com.tongran.common.core.web.domain.BaseEntity;
import lombok.Data;
/**
* 监控看板分组对象 rm_monitor_group
*
* @author tongran
* @date 2026-01-23
*/
@Data
public class RmMonitorGroup extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 分组名称 */
@Excel(name = "分组名称")
private String name;
}
@@ -0,0 +1,61 @@
package com.tongran.system.mapper;
import java.util.List;
import com.tongran.system.domain.RmMonitorGroup;
/**
* 监控看板分组Mapper接口
*
* @author tongran
* @date 2026-01-23
*/
public interface RmMonitorGroupMapper
{
/**
* 查询监控看板分组
*
* @param id 监控看板分组主键
* @return 监控看板分组
*/
public RmMonitorGroup selectRmMonitorGroupById(Long id);
/**
* 查询监控看板分组列表
*
* @param rmMonitorGroup 监控看板分组
* @return 监控看板分组集合
*/
public List<RmMonitorGroup> selectRmMonitorGroupList(RmMonitorGroup rmMonitorGroup);
/**
* 新增监控看板分组
*
* @param rmMonitorGroup 监控看板分组
* @return 结果
*/
public int insertRmMonitorGroup(RmMonitorGroup rmMonitorGroup);
/**
* 修改监控看板分组
*
* @param rmMonitorGroup 监控看板分组
* @return 结果
*/
public int updateRmMonitorGroup(RmMonitorGroup rmMonitorGroup);
/**
* 删除监控看板分组
*
* @param id 监控看板分组主键
* @return 结果
*/
public int deleteRmMonitorGroupById(Long id);
/**
* 批量删除监控看板分组
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteRmMonitorGroupByIds(Long[] ids);
}
@@ -0,0 +1,61 @@
package com.tongran.system.service;
import java.util.List;
import com.tongran.system.domain.RmMonitorGroup;
/**
* 监控看板分组Service接口
*
* @author tongran
* @date 2026-01-23
*/
public interface IRmMonitorGroupService
{
/**
* 查询监控看板分组
*
* @param id 监控看板分组主键
* @return 监控看板分组
*/
public RmMonitorGroup selectRmMonitorGroupById(Long id);
/**
* 查询监控看板分组列表
*
* @param rmMonitorGroup 监控看板分组
* @return 监控看板分组集合
*/
public List<RmMonitorGroup> selectRmMonitorGroupList(RmMonitorGroup rmMonitorGroup);
/**
* 新增监控看板分组
*
* @param rmMonitorGroup 监控看板分组
* @return 结果
*/
public int insertRmMonitorGroup(RmMonitorGroup rmMonitorGroup);
/**
* 修改监控看板分组
*
* @param rmMonitorGroup 监控看板分组
* @return 结果
*/
public int updateRmMonitorGroup(RmMonitorGroup rmMonitorGroup);
/**
* 批量删除监控看板分组
*
* @param ids 需要删除的监控看板分组主键集合
* @return 结果
*/
public int deleteRmMonitorGroupByIds(Long[] ids);
/**
* 删除监控看板分组信息
*
* @param id 监控看板分组主键
* @return 结果
*/
public int deleteRmMonitorGroupById(Long id);
}
@@ -120,8 +120,10 @@ public class RmMonitorConfigDetailsServiceImpl implements IRmMonitorConfigDetail
map.put("rmMonitorConfig", rmMonitorConfig);
resultMapList.add(map);
}else{
RmMonitorConfig configQuery = new RmMonitorConfig();
configQuery.setGroupId(queryParam.getGroupId());
// 查询所有配置
List<RmMonitorConfig> rmMonitorConfigs = rmMonitorConfigMapper.selectRmMonitorConfigList(new RmMonitorConfig());
List<RmMonitorConfig> rmMonitorConfigs = rmMonitorConfigMapper.selectRmMonitorConfigList(configQuery);
if (rmMonitorConfigs != null && !rmMonitorConfigs.isEmpty()) {
for (RmMonitorConfig rmMonitorConfig : rmMonitorConfigs) {
RmMonitorConfigDetails rmMonitorConfigDetails = new RmMonitorConfigDetails();
@@ -0,0 +1,111 @@
package com.tongran.system.service.impl;
import com.tongran.common.core.utils.DateUtils;
import com.tongran.system.domain.RmMonitorConfig;
import com.tongran.system.domain.RmMonitorGroup;
import com.tongran.system.mapper.RmMonitorConfigMapper;
import com.tongran.system.mapper.RmMonitorGroupMapper;
import com.tongran.system.service.IRmMonitorGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 监控看板分组Service业务层处理
*
* @author tongran
* @date 2026-01-23
*/
@Service
public class RmMonitorGroupServiceImpl implements IRmMonitorGroupService
{
@Autowired
private RmMonitorGroupMapper rmMonitorGroupMapper;
@Autowired
private RmMonitorConfigMapper rmMonitorConfigMapper;
/**
* 查询监控看板分组
*
* @param id 监控看板分组主键
* @return 监控看板分组
*/
@Override
public RmMonitorGroup selectRmMonitorGroupById(Long id)
{
return rmMonitorGroupMapper.selectRmMonitorGroupById(id);
}
/**
* 查询监控看板分组列表
*
* @param rmMonitorGroup 监控看板分组
* @return 监控看板分组
*/
@Override
public List<RmMonitorGroup> selectRmMonitorGroupList(RmMonitorGroup rmMonitorGroup)
{
return rmMonitorGroupMapper.selectRmMonitorGroupList(rmMonitorGroup);
}
/**
* 新增监控看板分组
*
* @param rmMonitorGroup 监控看板分组
* @return 结果
*/
@Override
public int insertRmMonitorGroup(RmMonitorGroup rmMonitorGroup)
{
rmMonitorGroup.setCreateTime(DateUtils.getNowDate());
return rmMonitorGroupMapper.insertRmMonitorGroup(rmMonitorGroup);
}
/**
* 修改监控看板分组
*
* @param rmMonitorGroup 监控看板分组
* @return 结果
*/
@Override
public int updateRmMonitorGroup(RmMonitorGroup rmMonitorGroup)
{
rmMonitorGroup.setUpdateTime(DateUtils.getNowDate());
return rmMonitorGroupMapper.updateRmMonitorGroup(rmMonitorGroup);
}
/**
* 批量删除监控看板分组
*
* @param ids 需要删除的监控看板分组主键
* @return 结果
*/
@Override
public int deleteRmMonitorGroupByIds(Long[] ids)
{
for (Long id : ids) {
RmMonitorConfig configQuery = new RmMonitorConfig();
configQuery.setGroupId(id);
List<RmMonitorConfig> rmMonitorConfigList = rmMonitorConfigMapper.selectRmMonitorConfigList(configQuery);
if(rmMonitorConfigList == null || rmMonitorConfigList.isEmpty()){
rmMonitorGroupMapper.deleteRmMonitorGroupById(id);
}else{
return -1;
}
}
return 1;
}
/**
* 删除监控看板分组信息
*
* @param id 监控看板分组主键
* @return 结果
*/
@Override
public int deleteRmMonitorGroupById(Long id)
{
return rmMonitorGroupMapper.deleteRmMonitorGroupById(id);
}
}
@@ -498,6 +498,16 @@ public class RmResourceRegistrationServiceImpl implements IRmResourceRegistratio
businessIpCount++;
}
if ("2".equals(network.getBindIp()) || "3".equals(network.getBindIp())) {
// ip提前
if(registration.getIp1PublicIp() == null){
registration.setIp1PublicIp(network.getPublicIp());
}
if(registration.getIp1Isp() == null){
registration.setIp1Isp(network.getIsp());
}
if(registration.getIp1Province() == null){
registration.setIp1Province(network.getProvince());
}
// 管理网IP处理
registration.setMgmtIsp(network.getIsp());
registration.setMgmtProvince(network.getProvince());
@@ -622,6 +632,16 @@ public class RmResourceRegistrationServiceImpl implements IRmResourceRegistratio
if(count > 0){
isVirth = true;
}
// ip提前
if(registration.getIp1PublicIp() == null){
registration.setIp1PublicIp(network.getPublicIp());
}
if(registration.getIp1Isp() == null){
registration.setIp1Isp(network.getIsp());
}
if(registration.getIp1Province() == null){
registration.setIp1Province(network.getProvince());
}
// 管理网IP处理
registration.setMgmtIsp(network.getIsp());
registration.setMgmtProvince(network.getProvince());
@@ -12,6 +12,7 @@
<result property="businessCode" column="business_code" />
<result property="businessName" column="business_name" />
<result property="deployDevice" column="deploy_device" />
<result property="groupId" column="group_id" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="createBy" column="create_by" />
@@ -19,7 +20,7 @@
</resultMap>
<sql id="selectRmMonitorConfigVo">
select id, config_name, monitor_start_time, resource_type, business_code, business_name, deploy_device, create_time, update_time, create_by, update_by from rm_monitor_config
select id, config_name, monitor_start_time, resource_type, business_code, business_name, deploy_device, group_id, create_time, update_time, create_by, update_by from rm_monitor_config
</sql>
<select id="selectRmMonitorConfigList" parameterType="RmMonitorConfig" resultMap="RmMonitorConfigResult">
@@ -31,6 +32,7 @@
<if test="businessCode != null and businessCode != ''"> and business_code = #{businessCode}</if>
<if test="businessName != null and businessName != ''"> and business_name like concat('%', #{businessName}, '%')</if>
<if test="deployDevice != null and deployDevice != ''"> and deploy_device = #{deployDevice}</if>
<if test="groupId != null"> and group_id = #{groupId}</if>
</where>
</select>
@@ -48,6 +50,7 @@
<if test="businessCode != null">business_code,</if>
<if test="businessName != null">business_name,</if>
<if test="deployDevice != null and deployDevice != ''">deploy_device,</if>
<if test="groupId != null">group_id,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="createBy != null">create_by,</if>
@@ -60,6 +63,7 @@
<if test="businessCode != null">#{businessCode},</if>
<if test="businessName != null">#{businessName},</if>
<if test="deployDevice != null and deployDevice != ''">#{deployDevice},</if>
<if test="groupId != null">#{groupId},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="createBy != null">#{createBy},</if>
@@ -76,6 +80,7 @@
<if test="businessCode != null">business_code = #{businessCode},</if>
<if test="businessName != null">business_name = #{businessName},</if>
<if test="deployDevice != null and deployDevice != ''">deploy_device = #{deployDevice},</if>
<if test="groupId != null">group_id = #{groupId},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="createBy != null">create_by = #{createBy},</if>
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tongran.system.mapper.RmMonitorGroupMapper">
<resultMap type="RmMonitorGroup" id="RmMonitorGroupResult">
<result property="id" column="id" />
<result property="name" column="name" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="createBy" column="create_by" />
<result property="updateBy" column="update_by" />
</resultMap>
<sql id="selectRmMonitorGroupVo">
select id, name, create_time, update_time, create_by, update_by from rm_monitor_group
</sql>
<select id="selectRmMonitorGroupList" parameterType="RmMonitorGroup" resultMap="RmMonitorGroupResult">
<include refid="selectRmMonitorGroupVo"/>
<where>
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
</where>
</select>
<select id="selectRmMonitorGroupById" parameterType="Long" resultMap="RmMonitorGroupResult">
<include refid="selectRmMonitorGroupVo"/>
where id = #{id}
</select>
<insert id="insertRmMonitorGroup" parameterType="RmMonitorGroup" useGeneratedKeys="true" keyProperty="id">
insert into rm_monitor_group
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">name,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="createBy != null">create_by,</if>
<if test="updateBy != null">update_by,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">#{name},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="createBy != null">#{createBy},</if>
<if test="updateBy != null">#{updateBy},</if>
</trim>
</insert>
<update id="updateRmMonitorGroup" parameterType="RmMonitorGroup">
update rm_monitor_group
<trim prefix="SET" suffixOverrides=",">
<if test="name != null and name != ''">name = #{name},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteRmMonitorGroupById" parameterType="Long">
delete from rm_monitor_group where id = #{id}
</delete>
<delete id="deleteRmMonitorGroupByIds" parameterType="String">
delete from rm_monitor_group where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>
@@ -0,0 +1,92 @@
package com.tongran.rocketmq.controller;
import com.tongran.common.core.web.controller.BaseController;
import com.tongran.common.core.web.domain.AjaxResult;
import com.tongran.common.log.annotation.Log;
import com.tongran.common.log.enums.BusinessType;
import com.tongran.common.security.annotation.RequiresPermissions;
import com.tongran.rocketmq.domain.RmAlarmThreshold;
import com.tongran.rocketmq.service.IRmAlarmThresholdService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 告警阈值配置Controller
*
* @author tongran
* @date 2026-01-23
*/
@RestController
@RequestMapping("/rmAlarmThreshold")
@RequiresPermissions("rocketmq:alarmLog")
public class RmAlarmThresholdController extends BaseController
{
@Autowired
private IRmAlarmThresholdService rmAlarmThresholdService;
/**
* 查询告警阈值配置列表
*/
@GetMapping("/list")
public AjaxResult list()
{
List<RmAlarmThreshold> list = rmAlarmThresholdService.selectRmAlarmThresholdList(new RmAlarmThreshold());
return success(list);
}
/**
* 获取告警阈值配置详细信息
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(rmAlarmThresholdService.selectRmAlarmThresholdById(id));
}
/**
* 新增告警阈值配置
*/
@Log(title = "告警阈值配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody RmAlarmThreshold rmAlarmThreshold)
{
int rows = rmAlarmThresholdService.insertRmAlarmThreshold(rmAlarmThreshold);
if(rows == -1){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.put(AjaxResult.CODE_TAG, 500);
ajaxResult.put(AjaxResult.MSG_TAG, "【告警类别】和【条件项】合起来不能重复");
return ajaxResult;
}
return toAjax(rows);
}
/**
* 修改告警阈值配置
*/
@Log(title = "告警阈值配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody RmAlarmThreshold rmAlarmThreshold)
{
int rows = rmAlarmThresholdService.updateRmAlarmThreshold(rmAlarmThreshold);
if(rows == -1){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.put(AjaxResult.CODE_TAG, 500);
ajaxResult.put(AjaxResult.MSG_TAG, "【告警类别】和【条件项】合起来不能重复");
return ajaxResult;
}
return toAjax(rows);
}
/**
* 删除告警阈值配置
*/
@Log(title = "告警阈值配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(rmAlarmThresholdService.deleteRmAlarmThresholdByIds(ids));
}
}
@@ -0,0 +1,38 @@
package com.tongran.rocketmq.domain;
import com.tongran.common.core.annotation.Excel;
import com.tongran.common.core.web.domain.BaseEntity;
import lombok.Data;
import java.math.BigDecimal;
/**
* 告警阈值配置对象 rm_alarm_threshold
*
* @author tongran
* @date 2026-01-23
*/
@Data
public class RmAlarmThreshold extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 告警类型 */
@Excel(name = "告警类型")
private String alarmType;
/** 条件项 */
@Excel(name = "条件项")
private String conditionItem;
/** 比较运算符1大于,2小于,3等于 */
@Excel(name = "比较运算符")
private String compareOperator;
/** 阈值 */
@Excel(name = "阈值")
private BigDecimal thresholdValue;
}
@@ -5,7 +5,9 @@ import lombok.Getter;
@Getter
public enum AlarmTypeEnum {
服务器下线("1", "服务器下线"),
交换机下线("2", "交换机下线");
交换机下线("2", "交换机下线"),
CPU使用率高("4", "CPU使用率高"),
磁盘缺失("5", "磁盘缺失");
private final String code;
private final String msg;
AlarmTypeEnum(String code, String msg){
@@ -0,0 +1,14 @@
package com.tongran.rocketmq.enums;
import lombok.Getter;
@Getter
public enum ConditionItemEnum {
服务器CPU使用率("1", "服务器CPU使用率");
private final String code;
private final String msg;
ConditionItemEnum(String code, String msg){
this.code = code;
this.msg = msg;
}
}
@@ -4,7 +4,8 @@ import lombok.Getter;
@Getter
public enum PushMethodEnum {
企业微信("1", "企业微信");
企业微信("1", "企业微信"),
手机短信("2", "手机短信");
private final String code;
private final String msg;
PushMethodEnum(String code, String msg){
@@ -18,6 +18,7 @@ import com.tongran.rocketmq.producer.MessageProducer;
import com.tongran.rocketmq.service.*;
import com.tongran.rocketmq.utils.DataProcessUtil;
import com.tongran.rocketmq.utils.JsonDataParser;
import com.tongran.rocketmq.utils.SendAlarmPushUtil;
import com.tongran.rocketmq.utils.WeChatWorkBot;
import com.tongran.system.api.RemoteRevenueConfigService;
import com.tongran.system.api.domain.*;
@@ -129,6 +130,8 @@ public class MessageHandler {
private RedissonClient redissonClient;
@Autowired
private IRmOutboundTrafficStatisticsService rmOutboundTrafficStatisticsService;
@Autowired
private SendAlarmPushUtil sendAlarmPushUtil;
/**
@@ -1019,6 +1022,15 @@ public class MessageHandler {
allDiskName.setName(diskName);
allDiskNameService.updateAllDiskName(allDiskName);
disksToRemove.add(diskName);
// 磁盘缺失,触发告警
RmAlarmLog rmAlarmLog = new RmAlarmLog();
rmAlarmLog.setClientId(clientId);
rmAlarmLog.setAlarmTime(DateUtils.getNowDate());
rmAlarmLog.setAlarmType(AlarmTypeEnum.磁盘缺失.getCode());
rmAlarmLog.setAlarmContent("服务器" + clientId + "磁盘缺失,磁盘名称:" + diskName);
rmAlarmLogService.insertRmAlarmLog(rmAlarmLog);
sendAlarmPushUtil.sendAlarmPush(rmAlarmLog);
}
} catch (NumberFormatException e) {
disksToRemove.add(diskName);
@@ -1495,17 +1507,16 @@ public class MessageHandler {
updateData.setId(rmAlarmLog.getId());
if (alarmConfigList != null && !alarmConfigList.isEmpty()) {
try {
Map<String, Object> alarmMap = new HashMap<>();
alarmMap.put("告警时间", rmAlarmLog.getAlarmTime());
alarmMap.put("IP", rmAlarmLog.getMgmPublicIp());
alarmMap.put("告警类型", alarmTypeMsg);
alarmMap.put("告警设备", rmAlarmLog.getClientId());
alarmMap.put("告警内容", rmAlarmLog.getAlarmContent());
alarmMap.put("业务名称", rmAlarmLog.getBusinessName());
for (RmAlarmPushConfig alarmPushConfig : alarmConfigList) {
String contentTemplate = alarmPushConfig.getMessageContent();
String webhookUrl = alarmPushConfig.getPushAddress();
Map<String, Object> alarmMap = new HashMap<>();
alarmMap.put("告警时间", rmAlarmLog.getAlarmTime());
alarmMap.put("IP", rmAlarmLog.getMgmPublicIp());
alarmMap.put("告警类型", alarmTypeMsg);
alarmMap.put("告警设备", rmAlarmLog.getClientId());
alarmMap.put("告警内容", rmAlarmLog.getAlarmContent());
alarmMap.put("业务名称", rmAlarmLog.getBusinessName());
if (alarmPushConfig.getContactPhones() != null) {
String[] phones = alarmPushConfig.getContactPhones().split(",");
WeChatWorkBot.sendTemplateMessage(webhookUrl, contentTemplate, alarmMap, rmAlarmLog.getAlarmContent(), phones, false);
@@ -0,0 +1,61 @@
package com.tongran.rocketmq.mapper;
import java.util.List;
import com.tongran.rocketmq.domain.RmAlarmThreshold;
/**
* 告警阈值配置Mapper接口
*
* @author tongran
* @date 2026-01-23
*/
public interface RmAlarmThresholdMapper
{
/**
* 查询告警阈值配置
*
* @param id 告警阈值配置主键
* @return 告警阈值配置
*/
public RmAlarmThreshold selectRmAlarmThresholdById(Long id);
/**
* 查询告警阈值配置列表
*
* @param rmAlarmThreshold 告警阈值配置
* @return 告警阈值配置集合
*/
public List<RmAlarmThreshold> selectRmAlarmThresholdList(RmAlarmThreshold rmAlarmThreshold);
/**
* 新增告警阈值配置
*
* @param rmAlarmThreshold 告警阈值配置
* @return 结果
*/
public int insertRmAlarmThreshold(RmAlarmThreshold rmAlarmThreshold);
/**
* 修改告警阈值配置
*
* @param rmAlarmThreshold 告警阈值配置
* @return 结果
*/
public int updateRmAlarmThreshold(RmAlarmThreshold rmAlarmThreshold);
/**
* 删除告警阈值配置
*
* @param id 告警阈值配置主键
* @return 结果
*/
public int deleteRmAlarmThresholdById(Long id);
/**
* 批量删除告警阈值配置
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteRmAlarmThresholdByIds(Long[] ids);
}
@@ -0,0 +1,61 @@
package com.tongran.rocketmq.service;
import java.util.List;
import com.tongran.rocketmq.domain.RmAlarmThreshold;
/**
* 告警阈值配置Service接口
*
* @author tongran
* @date 2026-01-23
*/
public interface IRmAlarmThresholdService
{
/**
* 查询告警阈值配置
*
* @param id 告警阈值配置主键
* @return 告警阈值配置
*/
public RmAlarmThreshold selectRmAlarmThresholdById(Long id);
/**
* 查询告警阈值配置列表
*
* @param rmAlarmThreshold 告警阈值配置
* @return 告警阈值配置集合
*/
public List<RmAlarmThreshold> selectRmAlarmThresholdList(RmAlarmThreshold rmAlarmThreshold);
/**
* 新增告警阈值配置
*
* @param rmAlarmThreshold 告警阈值配置
* @return 结果
*/
public int insertRmAlarmThreshold(RmAlarmThreshold rmAlarmThreshold);
/**
* 修改告警阈值配置
*
* @param rmAlarmThreshold 告警阈值配置
* @return 结果
*/
public int updateRmAlarmThreshold(RmAlarmThreshold rmAlarmThreshold);
/**
* 批量删除告警阈值配置
*
* @param ids 需要删除的告警阈值配置主键集合
* @return 结果
*/
public int deleteRmAlarmThresholdByIds(Long[] ids);
/**
* 删除告警阈值配置信息
*
* @param id 告警阈值配置主键
* @return 结果
*/
public int deleteRmAlarmThresholdById(Long id);
}
@@ -3,14 +3,22 @@ package com.tongran.rocketmq.service.impl;
import com.tongran.common.core.utils.DateUtils;
import com.tongran.common.core.utils.EchartsDataUtils;
import com.tongran.rocketmq.domain.InitialCpuInfo;
import com.tongran.rocketmq.domain.RmAlarmLog;
import com.tongran.rocketmq.domain.RmAlarmThreshold;
import com.tongran.rocketmq.enums.AlarmTypeEnum;
import com.tongran.rocketmq.enums.ConditionItemEnum;
import com.tongran.rocketmq.mapper.InitialCpuInfoMapper;
import com.tongran.rocketmq.mapper.RmAlarmLogMapper;
import com.tongran.rocketmq.mapper.RmAlarmThresholdMapper;
import com.tongran.rocketmq.service.IInitialCpuInfoService;
import com.tongran.rocketmq.utils.SendAlarmPushUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -31,6 +39,12 @@ public class InitialCpuInfoServiceImpl implements IInitialCpuInfoService
{
@Autowired
private InitialCpuInfoMapper initialCpuInfoMapper;
@Autowired
private RmAlarmThresholdMapper rmAlarmThresholdMapper;
@Autowired
private RmAlarmLogMapper rmAlarmLogMapper;
@Autowired
private SendAlarmPushUtil sendAlarmPushUtil;
/**
* 查询CPU监控信息
@@ -116,6 +130,48 @@ public class InitialCpuInfoServiceImpl implements IInitialCpuInfoService
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
public int batchInsertInitialCpuInfo(List<InitialCpuInfo> list) {
try {
// 查询告警阈值
RmAlarmThreshold thresholdQuery = new RmAlarmThreshold();
thresholdQuery.setAlarmType(AlarmTypeEnum.CPU使用率高.getCode());
thresholdQuery.setConditionItem(ConditionItemEnum.服务器CPU使用率.getCode());
List<RmAlarmThreshold> rmAlarmThresholdList = rmAlarmThresholdMapper.selectRmAlarmThresholdList(thresholdQuery);
if(rmAlarmThresholdList != null && !rmAlarmThresholdList.isEmpty()){
String operator = rmAlarmThresholdList.get(0).getCompareOperator();
BigDecimal threshold = rmAlarmThresholdList.get(0).getThresholdValue();
for (InitialCpuInfo initialCpuInfo : list) {
BigDecimal cpuUti = new BigDecimal(initialCpuInfo.getUti());
if(operator != null){
// 根据运算符1大于,2小于,3等于 判断是否进行告警
boolean shouldAlarm = false;
// 根据运算符判断是否进行告警
switch (operator) {
case "1": // 大于
shouldAlarm = cpuUti.compareTo(threshold) > 0;
break;
case "2": // 小于
shouldAlarm = cpuUti.compareTo(threshold) < 0;
break;
case "3": // 等于
shouldAlarm = cpuUti.compareTo(threshold) == 0;
break;
default:
log.warn("未知的运算符: {}", operator);
continue; // 跳过未知运算符的处理
}
if(shouldAlarm){
RmAlarmLog rmAlarmLog = new RmAlarmLog();
rmAlarmLog.setAlarmType(AlarmTypeEnum.CPU使用率高.getCode());
rmAlarmLog.setClientId(initialCpuInfo.getClientId());
rmAlarmLog.setAlarmTime(DateUtils.getNowDate());
rmAlarmLog.setAlarmContent("服务器" + initialCpuInfo.getClientId() + "的CPU使用率高");
rmAlarmLogMapper.insertRmAlarmLog(rmAlarmLog);
// 推送消息
sendAlarmPushUtil.sendAlarmPush(rmAlarmLog);
}
}
}
}
return initialCpuInfoMapper.batchInsertInitialCpuInfo(list);
}catch (Exception e){
log.error("批量插入CPU信息失败,失败数量:{}", list.size(), e);
@@ -0,0 +1,108 @@
package com.tongran.rocketmq.service.impl;
import com.tongran.common.core.utils.DateUtils;
import com.tongran.rocketmq.domain.RmAlarmThreshold;
import com.tongran.rocketmq.mapper.RmAlarmThresholdMapper;
import com.tongran.rocketmq.service.IRmAlarmThresholdService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 告警阈值配置Service业务层处理
*
* @author tongran
* @date 2026-01-23
*/
@Service
public class RmAlarmThresholdServiceImpl implements IRmAlarmThresholdService
{
@Autowired
private RmAlarmThresholdMapper rmAlarmThresholdMapper;
/**
* 查询告警阈值配置
*
* @param id 告警阈值配置主键
* @return 告警阈值配置
*/
@Override
public RmAlarmThreshold selectRmAlarmThresholdById(Long id)
{
return rmAlarmThresholdMapper.selectRmAlarmThresholdById(id);
}
/**
* 查询告警阈值配置列表
*
* @param rmAlarmThreshold 告警阈值配置
* @return 告警阈值配置
*/
@Override
public List<RmAlarmThreshold> selectRmAlarmThresholdList(RmAlarmThreshold rmAlarmThreshold)
{
return rmAlarmThresholdMapper.selectRmAlarmThresholdList(rmAlarmThreshold);
}
/**
* 新增告警阈值配置
*
* @param rmAlarmThreshold 告警阈值配置
* @return 结果
*/
@Override
public int insertRmAlarmThreshold(RmAlarmThreshold rmAlarmThreshold)
{
rmAlarmThreshold.setCreateTime(DateUtils.getNowDate());
try {
int rows = rmAlarmThresholdMapper.insertRmAlarmThreshold(rmAlarmThreshold);
}catch (DuplicateKeyException e){
return -1;
}
return 1;
}
/**
* 修改告警阈值配置
*
* @param rmAlarmThreshold 告警阈值配置
* @return 结果
*/
@Override
public int updateRmAlarmThreshold(RmAlarmThreshold rmAlarmThreshold)
{
rmAlarmThreshold.setUpdateTime(DateUtils.getNowDate());
try {
int rows = rmAlarmThresholdMapper.updateRmAlarmThreshold(rmAlarmThreshold);
}catch (DuplicateKeyException e){
return -1;
}
return 1;
}
/**
* 批量删除告警阈值配置
*
* @param ids 需要删除的告警阈值配置主键
* @return 结果
*/
@Override
public int deleteRmAlarmThresholdByIds(Long[] ids)
{
return rmAlarmThresholdMapper.deleteRmAlarmThresholdByIds(ids);
}
/**
* 删除告警阈值配置信息
*
* @param id 告警阈值配置主键
* @return 结果
*/
@Override
public int deleteRmAlarmThresholdById(Long id)
{
return rmAlarmThresholdMapper.deleteRmAlarmThresholdById(id);
}
}
@@ -0,0 +1,116 @@
package com.tongran.rocketmq.utils;
import com.tongran.rocketmq.domain.RmAlarmLog;
import com.tongran.rocketmq.domain.RmAlarmPushConfig;
import com.tongran.rocketmq.domain.RmNetworkInterface;
import com.tongran.rocketmq.enums.AlarmTypeEnum;
import com.tongran.rocketmq.enums.PushMethodEnum;
import com.tongran.rocketmq.service.IRmAlarmLogService;
import com.tongran.rocketmq.service.IRmAlarmPushConfigService;
import com.tongran.rocketmq.service.IRmNetworkInterfaceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
@Slf4j
public class SendAlarmPushUtil {
@Autowired
private IRmAlarmLogService rmAlarmLogService;
@Autowired
private IRmAlarmPushConfigService rmAlarmPushConfigService;
@Autowired
private IRmNetworkInterfaceService rmNetworkInterfaceService;
/**
* 发送告警推送
* @param rmAlarmLog 告警日志参数
*/
public void sendAlarmPush(RmAlarmLog rmAlarmLog) {
RmAlarmPushConfig rmAlarmPushConfig = new RmAlarmPushConfig();
rmAlarmPushConfig.setPushAlarmTypes(rmAlarmLog.getAlarmType());
List<RmAlarmPushConfig> alarmConfigList = rmAlarmPushConfigService.selectRmAlarmPushConfigList(rmAlarmPushConfig);
RmAlarmLog updateData = new RmAlarmLog();
updateData.setId(rmAlarmLog.getId());
if (alarmConfigList != null && !alarmConfigList.isEmpty()) {
try {
// 查询管理网公网ip
RmNetworkInterface rmNetworkInterface = new RmNetworkInterface();
rmNetworkInterface.setClientId(rmAlarmLog.getClientId());
rmNetworkInterface.setNewFlag(1);
List<RmNetworkInterface> interfaceList = rmNetworkInterfaceService.selectRmNetworkInterfaceList(rmNetworkInterface);
if (interfaceList != null && !interfaceList.isEmpty()) {
interfaceList.stream()
.filter(info -> "2".equals(info.getBindIp()) || "3".equals(info.getBindIp()))
.findFirst()
.ifPresent(networkInterface -> {
rmAlarmLog.setMgmPublicIp(networkInterface.getPublicIp());
});
}
Map<String, Object> alarmMap = new HashMap<>();
alarmMap.put("告警时间", rmAlarmLog.getAlarmTime());
alarmMap.put("IP", rmAlarmLog.getMgmPublicIp());
alarmMap.put("告警类型", AlarmTypeEnum.CPU使用率高.getMsg());
alarmMap.put("告警设备", rmAlarmLog.getClientId());
alarmMap.put("告警内容", rmAlarmLog.getAlarmContent());
for (RmAlarmPushConfig alarmPushConfig : alarmConfigList) {
if(PushMethodEnum.企业微信.getCode().equals(alarmPushConfig.getPushMethod())){
updateData.setPushFlag(processWeComPush(alarmPushConfig, alarmMap));
}else if(PushMethodEnum.手机短信.getCode().equals(alarmPushConfig.getPushMethod())){
updateData.setPushFlag(processTextMessagePush(alarmPushConfig, alarmMap));
}else{
log.info("暂无该推送方式:{}", alarmPushConfig.getPushMethod());
updateData.setPushFlag(2L);
}
}
} catch (Exception e){
updateData.setPushFlag(0L);
log.error("消息推送失败:{}", e.getMessage());
}
}else{
updateData.setPushFlag(2L);
}
rmAlarmLogService.updateRmAlarmLog(updateData);
}
/**
* 企业微信处理
*/
private Long processWeComPush(RmAlarmPushConfig alarmPushConfig, Map<String, Object> alarmMap){
String contentTemplate = alarmPushConfig.getMessageContent();
String webhookUrl = alarmPushConfig.getPushAddress();
if (alarmPushConfig.getContactPhones() != null) {
String[] phones = alarmPushConfig.getContactPhones().split(",");
WeChatWorkBot.sendTemplateMessage(webhookUrl, contentTemplate, alarmMap, alarmMap.get("告警内容"), phones, false);
} else {
WeChatWorkBot.sendTemplateMessage(webhookUrl, contentTemplate, alarmMap);
}
return 1L;
}
/**
* 手机短信处理
*/
private Long processTextMessagePush(RmAlarmPushConfig alarmPushConfig, Map<String, Object> alarmMap){
String contentTemplate = alarmPushConfig.getMessageContent();
String actualContent = WeChatWorkBot.processTemplate(contentTemplate, alarmMap, alarmMap.get("告警内容"));
if (alarmPushConfig.getContactPhones() != null) {
String[] phones = alarmPushConfig.getContactPhones().split(",");
SmsAlarmUtil smsAlarmUtil = new SmsAlarmUtil();
int successCount = smsAlarmUtil.sendAlarmToMultiple(alarmPushConfig.getContactPhones(), actualContent);
// 计算有效手机号数量(过滤空值)
long validPhoneCount = Arrays.stream(phones)
.map(String::trim)
.filter(phone -> !phone.isEmpty())
.count();
return validPhoneCount == successCount ? 1L : 0L;
}
return 0L;
}
}
@@ -0,0 +1,132 @@
package com.tongran.rocketmq.utils;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* 阿里云短信告警工具类
* 支持多个手机号同时发送相同内容
*/
@Slf4j
@Component
public class SmsAlarmUtil {
@Value("${aliyun.sms.access-key-id}")
private String accessKeyId;
@Value("${aliyun.sms.access-key-secret}")
private String accessKeySecret;
@Value("${aliyun.sms.sign-name}")
private String signName;
@Value("${aliyun.sms.template-code}")
private String templateCode;
private Client client;
/**
* 初始化阿里云短信客户端
*/
@PostConstruct
public void init() throws Exception {
Config config = new Config()
.setAccessKeyId(accessKeyId)
.setAccessKeySecret(accessKeySecret);
// 设置访问的域名
config.endpoint = "dysmsapi.aliyuncs.com";
this.client = new Client(config);
}
/**
* 发送短信告警(单个手机号)
* @param phoneNumber 手机号
* @param content 告警内容
* @return 是否发送成功
*/
public boolean sendAlarm(String phoneNumber, String content) {
if (!StringUtils.hasText(phoneNumber) || !StringUtils.hasText(content)) {
log.error("手机号或告警内容不能为空");
return false;
}
try {
SendSmsRequest request = new SendSmsRequest()
.setPhoneNumbers(phoneNumber)
.setSignName(signName)
.setTemplateCode(templateCode)
.setTemplateParam("{\"message_content\":\"" + content + "\"}");
SendSmsResponse response = client.sendSms(request);
if ("OK".equals(response.getBody().getCode())) {
log.info("短信发送成功,手机号:{},内容:{}", phoneNumber, content);
return true;
} else {
log.error("短信发送失败,手机号:{},错误码:{},错误信息:{}",
phoneNumber, response.getBody().getCode(), response.getBody().getMessage());
return false;
}
} catch (Exception e) {
log.error("发送短信异常,手机号:{},内容:{}", phoneNumber, content, e);
return false;
}
}
/**
* 发送短信告警(多个手机号)
* @param phoneNumbers 手机号列表
* @param content 告警内容
* @return 发送成功的手机号数量
*/
public int sendAlarmToMultiple(List<String> phoneNumbers, String content) {
if (phoneNumbers == null || phoneNumbers.isEmpty()) {
log.warn("手机号列表为空");
return 0;
}
int successCount = 0;
for (String phoneNumber : phoneNumbers) {
if (sendAlarm(phoneNumber, content)) {
successCount++;
}
}
log.info("批量发送完成,总手机号数:{},成功数:{}", phoneNumbers.size(), successCount);
return successCount;
}
/**
* 发送短信告警(多个手机号,逗号分隔)
* @param phoneNumbers 逗号分隔的手机号字符串
* @param content 告警内容
* @return 发送成功的手机号数量
*/
public int sendAlarmToMultiple(String phoneNumbers, String content) {
if (!StringUtils.hasText(phoneNumbers)) {
return 0;
}
String[] numbers = phoneNumbers.split(",");
int successCount = 0;
for (String number : numbers) {
String trimmedNumber = number.trim();
if (!trimmedNumber.isEmpty() && sendAlarm(trimmedNumber, content)) {
successCount++;
}
}
return successCount;
}
}
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tongran.rocketmq.mapper.RmAlarmThresholdMapper">
<resultMap type="RmAlarmThreshold" id="RmAlarmThresholdResult">
<result property="id" column="id" />
<result property="alarmType" column="alarm_type" />
<result property="conditionItem" column="condition_item" />
<result property="compareOperator" column="compare_operator" />
<result property="thresholdValue" column="threshold_value" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectRmAlarmThresholdVo">
select id, alarm_type, condition_item, compare_operator, threshold_value, create_by, create_time, update_by, update_time, remark from rm_alarm_threshold
</sql>
<select id="selectRmAlarmThresholdList" parameterType="RmAlarmThreshold" resultMap="RmAlarmThresholdResult">
<include refid="selectRmAlarmThresholdVo"/>
<where>
<if test="alarmType != null and alarmType != ''"> and alarm_type = #{alarmType}</if>
<if test="conditionItem != null and conditionItem != ''"> and condition_item = #{conditionItem}</if>
<if test="compareOperator != null and compareOperator != ''"> and compare_operator = #{compareOperator}</if>
<if test="thresholdValue != null "> and threshold_value = #{thresholdValue}</if>
</where>
</select>
<select id="selectRmAlarmThresholdById" parameterType="Long" resultMap="RmAlarmThresholdResult">
<include refid="selectRmAlarmThresholdVo"/>
where id = #{id}
</select>
<insert id="insertRmAlarmThreshold" parameterType="RmAlarmThreshold" useGeneratedKeys="true" keyProperty="id">
insert into rm_alarm_threshold
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="alarmType != null and alarmType != ''">alarm_type,</if>
<if test="conditionItem != null and conditionItem != ''">condition_item,</if>
<if test="compareOperator != null and compareOperator != ''">compare_operator,</if>
<if test="thresholdValue != null">threshold_value,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="alarmType != null and alarmType != ''">#{alarmType},</if>
<if test="conditionItem != null and conditionItem != ''">#{conditionItem},</if>
<if test="compareOperator != null and compareOperator != ''">#{compareOperator},</if>
<if test="thresholdValue != null">#{thresholdValue},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateRmAlarmThreshold" parameterType="RmAlarmThreshold">
update rm_alarm_threshold
<trim prefix="SET" suffixOverrides=",">
<if test="alarmType != null and alarmType != ''">alarm_type = #{alarmType},</if>
<if test="conditionItem != null and conditionItem != ''">condition_item = #{conditionItem},</if>
<if test="compareOperator != null and compareOperator != ''">compare_operator = #{compareOperator},</if>
<if test="thresholdValue != null">threshold_value = #{thresholdValue},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteRmAlarmThresholdById" parameterType="Long">
delete from rm_alarm_threshold where id = #{id}
</delete>
<delete id="deleteRmAlarmThresholdByIds" parameterType="String">
delete from rm_alarm_threshold where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>