From 5ab1b6e8a44294c6382f8546647638f036c548a2 Mon Sep 17 00:00:00 2001 From: gaoyutao Date: Thu, 18 Dec 2025 16:45:31 +0800 Subject: [PATCH] =?UTF-8?q?v1.3=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tongran-modules/pom.xml | 1 + tongran-modules/tongran-frp/pom.xml | 116 +++++++ .../tongran/frp/TongRanFrpApplication.java | 25 ++ .../tongran/frp/config/ConsumerConfig.java | 77 +++++ .../tongran/frp/config/ProducerConfig.java | 56 ++++ .../frp/consumer/RocketMsgListener.java | 94 ++++++ .../RocketMsgTransactionListenerImpl.java | 30 ++ .../frp/controller/RocketMqController.java | 139 +++++++++ .../com/tongran/frp/domain/DeviceMessage.java | 10 + .../frp/domain/vo/AgentUpdateMsgVo.java | 15 + .../com/tongran/frp/domain/vo/HopInfoVO.java | 16 + .../com/tongran/frp/domain/vo/MessageVo.java | 13 + .../tongran/frp/domain/vo/MtrResultVo.java | 19 ++ .../tongran/frp/domain/vo/PolicyTypeVo.java | 22 ++ .../com/tongran/frp/domain/vo/PolicyVo.java | 14 + .../frp/domain/vo/RmMtrPolicyConfigVo.java | 31 ++ .../java/com/tongran/frp/domain/vo/RspVo.java | 29 ++ .../com/tongran/frp/enums/AlarmTypeEnum.java | 16 + .../tongran/frp/enums/MessageCodeEnum.java | 67 ++++ .../com/tongran/frp/enums/MessageTopic.java | 21 ++ .../com/tongran/frp/enums/PushMethodEnum.java | 14 + .../com/tongran/frp/enums/ServerLogoEnum.java | 21 ++ .../com/tongran/frp/enums/SwitchLogo.java | 17 ++ .../tongran/frp/handler/MessageHandler.java | 114 +++++++ .../com/tongran/frp/model/ConsumerMode.java | 30 ++ .../com/tongran/frp/model/ProducerMode.java | 29 ++ .../frp/producer/ConsumeException.java | 23 ++ .../tongran/frp/producer/MessageProducer.java | 225 ++++++++++++++ .../com/tongran/frp/utils/JsonDataParser.java | 41 +++ .../frp/utils/SwitchJsonDataParser.java | 149 +++++++++ .../tongran/frp/utils/TableRouterUtil.java | 99 ++++++ .../com/tongran/frp/utils/WeChatWorkBot.java | 285 ++++++++++++++++++ .../src/main/resources/bootstrap.yml | 43 +++ .../src/main/resources/logback.xml | 72 +++++ 34 files changed, 1973 insertions(+) create mode 100644 tongran-modules/tongran-frp/pom.xml create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/TongRanFrpApplication.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ConsumerConfig.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ProducerConfig.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgListener.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgTransactionListenerImpl.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/controller/RocketMqController.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/DeviceMessage.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/AgentUpdateMsgVo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/HopInfoVO.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MessageVo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MtrResultVo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyTypeVo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyVo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RmMtrPolicyConfigVo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RspVo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/AlarmTypeEnum.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageCodeEnum.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageTopic.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/PushMethodEnum.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/ServerLogoEnum.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/SwitchLogo.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/handler/MessageHandler.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ConsumerMode.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ProducerMode.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/ConsumeException.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/MessageProducer.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/JsonDataParser.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/SwitchJsonDataParser.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/TableRouterUtil.java create mode 100644 tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/WeChatWorkBot.java create mode 100644 tongran-modules/tongran-frp/src/main/resources/bootstrap.yml create mode 100644 tongran-modules/tongran-frp/src/main/resources/logback.xml diff --git a/tongran-modules/pom.xml b/tongran-modules/pom.xml index 30b5e99..2312349 100644 --- a/tongran-modules/pom.xml +++ b/tongran-modules/pom.xml @@ -14,6 +14,7 @@ tongran-job tongran-file tongran-mtragent + tongran-frp tongran-modules diff --git a/tongran-modules/tongran-frp/pom.xml b/tongran-modules/tongran-frp/pom.xml new file mode 100644 index 0000000..05f3a47 --- /dev/null +++ b/tongran-modules/tongran-frp/pom.xml @@ -0,0 +1,116 @@ + + 4.0.0 + + com.tongran + tongran-modules + 3.6.6 + + + tongran-modules-frp + + + + UTF-8 + + + + + junit + junit + 3.8.1 + test + + + + org.apache.rocketmq + rocketmq-client + 4.9.0 + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + com.alibaba.nacos + nacos-client + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + com.mysql + mysql-connector-j + + + + + com.tongran + tongran-common-log + + + + + com.tongran + tongran-common-swagger + + + + + com.tongran + tongran-common-security + + + org.projectlombok + lombok + + + + + org.yaml + snakeyaml + 1.28 + + + + + org.springframework.boot + spring-boot-starter-web + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/TongRanFrpApplication.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/TongRanFrpApplication.java new file mode 100644 index 0000000..6516d91 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/TongRanFrpApplication.java @@ -0,0 +1,25 @@ +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模块启动成功 ლ(´ڡ`ლ)゙"); + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ConsumerConfig.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ConsumerConfig.java new file mode 100644 index 0000000..f4588f3 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ConsumerConfig.java @@ -0,0 +1,77 @@ +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 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; + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ProducerConfig.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ProducerConfig.java new file mode 100644 index 0000000..54fa8c9 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/config/ProducerConfig.java @@ -0,0 +1,56 @@ +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; + } + +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgListener.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgListener.java new file mode 100644 index 0000000..15a5485 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgListener.java @@ -0,0 +1,94 @@ +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=1,you can modify here + * 这里只设置为1,当设置为多个时,list中只要有一条消息消费失败,就会整体重试 + * @param consumeConcurrentlyContext 上下文信息 + * @return 消费状态 成功(CONSUME_SUCCESS)或者 重试 (RECONSUME_LATER) + */ + @Override + public ConsumeConcurrentlyStatus consumeMessage(List 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; + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgTransactionListenerImpl.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgTransactionListenerImpl.java new file mode 100644 index 0000000..b89c8d4 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/consumer/RocketMsgTransactionListenerImpl.java @@ -0,0 +1,30 @@ +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; // 根据实际情况返回对应的状态 + } + +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/controller/RocketMqController.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/controller/RocketMqController.java new file mode 100644 index 0000000..82a2f9e --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/controller/RocketMqController.java @@ -0,0 +1,139 @@ +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 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 result = new HashMap<>(); + result.put("msg","发送成功"); + result.put("code",200); + return result; + } + + + /** + * 批量发送消息 + */ + @PostMapping("/sendBatchMessage") + private Map sendBatchMessage(){ + // 根据实际需求创建消息列表并返回 + List 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 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 result = new HashMap<>(); + result.put("data",sendResult); + return result; + } + + + /** + * 发送有序的消息 + */ + @PostMapping("/sendOrderlyMessage") + private Map sendOrderlyMessage(){ + // 根据实际需求创建消息列表并返回 + List 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 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 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 result = new HashMap<>(); + result.put("data",sendResult); + return result; + } + +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/DeviceMessage.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/DeviceMessage.java new file mode 100644 index 0000000..9f9e747 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/DeviceMessage.java @@ -0,0 +1,10 @@ +package com.tongran.frp.domain; + +import lombok.Data; + +@Data +public class DeviceMessage { + private String clientId; + private String dataType; + private String data; +} \ No newline at end of file diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/AgentUpdateMsgVo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/AgentUpdateMsgVo.java new file mode 100644 index 0000000..e56c7b8 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/AgentUpdateMsgVo.java @@ -0,0 +1,15 @@ +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; +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/HopInfoVO.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/HopInfoVO.java new file mode 100644 index 0000000..aa99f9f --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/HopInfoVO.java @@ -0,0 +1,16 @@ +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; // 运营商 +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MessageVo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MessageVo.java new file mode 100644 index 0000000..771d78f --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MessageVo.java @@ -0,0 +1,13 @@ +package com.tongran.frp.domain.vo; + +import lombok.Data; + +@Data +public class MessageVo { + + private String clientId; + + private String dataType; + + private String data; +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MtrResultVo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MtrResultVo.java new file mode 100644 index 0000000..1acd0ab --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/MtrResultVo.java @@ -0,0 +1,19 @@ +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 hopInfos; // 每跳路由信息列表 + +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyTypeVo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyTypeVo.java new file mode 100644 index 0000000..9de648a --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyTypeVo.java @@ -0,0 +1,22 @@ +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(); +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyVo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyVo.java new file mode 100644 index 0000000..bcb9e1a --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/PolicyVo.java @@ -0,0 +1,14 @@ +package com.tongran.frp.domain.vo; + +import lombok.Data; + +import java.time.Instant; +import java.util.List; + +@Data +public class PolicyVo { + /** 更新时间戳 */ + private Long upTime = Instant.now().getEpochSecond(); + /** 更新内容 */ + private List contents; +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RmMtrPolicyConfigVo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RmMtrPolicyConfigVo.java new file mode 100644 index 0000000..70d3f8f --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RmMtrPolicyConfigVo.java @@ -0,0 +1,31 @@ +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 gyt + * @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> clientIdToIpsMap; +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RspVo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RspVo.java new file mode 100644 index 0000000..eb1a3ef --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/domain/vo/RspVo.java @@ -0,0 +1,29 @@ +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(); +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/AlarmTypeEnum.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/AlarmTypeEnum.java new file mode 100644 index 0000000..8a4c68e --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/AlarmTypeEnum.java @@ -0,0 +1,16 @@ +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; + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageCodeEnum.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageCodeEnum.java new file mode 100644 index 0000000..32f51a0 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageCodeEnum.java @@ -0,0 +1,67 @@ +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; + } + +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageTopic.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageTopic.java new file mode 100644 index 0000000..e2f9d00 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/MessageTopic.java @@ -0,0 +1,21 @@ +package com.tongran.frp.enums; + + +import java.util.ArrayList; +import java.util.List; + +/** + * 定义topic列表 + */ +public class MessageTopic { + + //在这里添加topic 用于批量订阅 + public List RocketMQTopicList(){ + List getTopicLists=new ArrayList<>(); + // agent采集消息 +// getTopicLists.add("agent_up"); + getTopicLists.add("tr_frp_up"); + return getTopicLists; + } + +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/PushMethodEnum.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/PushMethodEnum.java new file mode 100644 index 0000000..0e1b15b --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/PushMethodEnum.java @@ -0,0 +1,14 @@ +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; + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/ServerLogoEnum.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/ServerLogoEnum.java new file mode 100644 index 0000000..31a9a76 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/ServerLogoEnum.java @@ -0,0 +1,21 @@ +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; + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/SwitchLogo.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/SwitchLogo.java new file mode 100644 index 0000000..89aa563 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/enums/SwitchLogo.java @@ -0,0 +1,17 @@ +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; + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/handler/MessageHandler.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/handler/MessageHandler.java new file mode 100644 index 0000000..e1de0b0 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/handler/MessageHandler.java @@ -0,0 +1,114 @@ +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> 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 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 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 handler) { + messageHandlers.put(dataType, handler); + } + + /** + * 处理设备消息(对外暴露的主方法) + */ + public void handleMessage(DeviceMessage message) { + String dataType = message.getDataType(); + Consumer 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("开启更新资源状态========"); + } + +} \ No newline at end of file diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ConsumerMode.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ConsumerMode.java new file mode 100644 index 0000000..7123120 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ConsumerMode.java @@ -0,0 +1,30 @@ +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; +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ProducerMode.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ProducerMode.java new file mode 100644 index 0000000..9d88259 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/model/ProducerMode.java @@ -0,0 +1,29 @@ +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; +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/ConsumeException.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/ConsumeException.java new file mode 100644 index 0000000..0094dd8 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/ConsumeException.java @@ -0,0 +1,23 @@ +package com.tongran.frp.producer; + +/** + * @author 影子 + * 用于捕捉异常非受检异常(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); + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/MessageProducer.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/MessageProducer.java new file mode 100644 index 0000000..1a102c0 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/producer/MessageProducer.java @@ -0,0 +1,225 @@ +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 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 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; + } + +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/JsonDataParser.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/JsonDataParser.java new file mode 100644 index 0000000..80882af --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/JsonDataParser.java @@ -0,0 +1,41 @@ +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 List parseJsonData(String jsonStr, Class 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 result = new ArrayList<>(1); + result.add(objectMapper.readValue(jsonStr, valueType)); + return result; + } + } catch (Exception e) { + throw new RuntimeException("JSON解析失败: " + e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/SwitchJsonDataParser.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/SwitchJsonDataParser.java new file mode 100644 index 0000000..0a69933 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/SwitchJsonDataParser.java @@ -0,0 +1,149 @@ +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 List parseJsonData(String jsonStr, Class 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 result = new ArrayList<>(1); + result.add(objectMapper.treeToValue(rootNode, valueType)); + return result; + } + } catch (Exception e) { + throw new RuntimeException("JSON解析失败: " + e.getMessage(), e); + } + } + + /** + * 将JsonArray转换为List + */ + private static List convertJsonArrayToList(ArrayNode arrayNode, Class valueType) throws Exception { + List 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); + } + }); + } +} \ No newline at end of file diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/TableRouterUtil.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/TableRouterUtil.java new file mode 100644 index 0000000..9d60fb0 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/TableRouterUtil.java @@ -0,0 +1,99 @@ +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 getTableNamesBetween(String startTime, String endTime) { + LocalDateTime start = parseDateTime(startTime); + LocalDateTime end = parseDateTime(endTime); + validateTimeRange(start, end); + + Set 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("开始时间不能晚于结束时间"); + } + } +} diff --git a/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/WeChatWorkBot.java b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/WeChatWorkBot.java new file mode 100644 index 0000000..8a7a187 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/java/com/tongran/frp/utils/WeChatWorkBot.java @@ -0,0 +1,285 @@ +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 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 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 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 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 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 getTemplateFields(String template) { + java.util.List 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 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 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 message = new HashMap<>(); + message.put("msgtype", "text"); + + Map 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 message = new HashMap<>(); + message.put("msgtype", "markdown"); + + Map 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; + } + } +} \ No newline at end of file diff --git a/tongran-modules/tongran-frp/src/main/resources/bootstrap.yml b/tongran-modules/tongran-frp/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..da65df9 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/resources/bootstrap.yml @@ -0,0 +1,43 @@ +# 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 + + + diff --git a/tongran-modules/tongran-frp/src/main/resources/logback.xml b/tongran-modules/tongran-frp/src/main/resources/logback.xml new file mode 100644 index 0000000..de115b4 --- /dev/null +++ b/tongran-modules/tongran-frp/src/main/resources/logback.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + \ No newline at end of file