Commit 61ea09488ed624d1f15fc01b8ffa98aa717484b7
1 parent
3c998597
【代码优化】IoT: 优化数据桥梁执行器抽象类增加泛型,减少子类类型强转
Showing
5 changed files
with
81 additions
and
84 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/AbstractCacheableDataBridgeExecute.java
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.action.databridge; |
| 3 | 3 | import com.google.common.cache.CacheBuilder; |
| 4 | 4 | import com.google.common.cache.CacheLoader; |
| 5 | 5 | import com.google.common.cache.LoadingCache; |
| 6 | +import com.google.common.cache.RemovalListener; | |
| 6 | 7 | import lombok.extern.slf4j.Slf4j; |
| 7 | 8 | |
| 8 | 9 | import java.time.Duration; |
| ... | ... | @@ -15,22 +16,36 @@ import java.time.Duration; |
| 15 | 16 | /** |
| 16 | 17 | * 带缓存功能的数据桥梁执行器抽象类 |
| 17 | 18 | * |
| 19 | + * 该类提供了一个通用的缓存机制,用于管理各类数据桥接的生产者(Producer)实例。 | |
| 20 | + * | |
| 21 | + * 主要特点: | |
| 22 | + * - 基于Guava Cache实现高效的生产者实例缓存管理 | |
| 23 | + * - 自动处理生产者的生命周期(创建、获取、关闭) | |
| 24 | + * - 支持30分钟未访问自动过期清理机制 | |
| 25 | + * - 异常处理与日志记录,便于问题排查 | |
| 26 | + * | |
| 27 | + * 子类需要实现: | |
| 28 | + * - initProducer(Config) - 初始化特定类型的生产者实例 | |
| 29 | + * - closeProducer(Producer) - 关闭生产者实例并释放资源 | |
| 30 | + * | |
| 31 | + * @param <Config> 配置信息类型,用于初始化生产者 | |
| 32 | + * @param <Producer> 生产者类型,负责将数据发送到目标系统 | |
| 18 | 33 | * @author HUIHUI |
| 19 | 34 | */ |
| 20 | 35 | @Slf4j |
| 21 | -public abstract class AbstractCacheableDataBridgeExecute implements IotDataBridgeExecute { | |
| 36 | +public abstract class AbstractCacheableDataBridgeExecute<Config, Producer> implements IotDataBridgeExecute { | |
| 22 | 37 | |
| 23 | - // TODO @huihui:AbstractCacheableDataBridgeExecute<Producer> 这样,下面的 Object, Object 就有了类型;另外 IotDataBridgeDO.Config 可以替代一个 Object 哇, | |
| 24 | 38 | /** |
| 25 | 39 | * Producer 缓存 |
| 26 | 40 | */ |
| 27 | - private final LoadingCache<Object, Object> PRODUCER_CACHE = CacheBuilder.newBuilder() | |
| 28 | - .expireAfterAccess(Duration.ofMinutes(30)) | |
| 29 | - .removalListener(notification -> { | |
| 30 | - Object producer = notification.getValue(); | |
| 41 | + private final LoadingCache<Config, Producer> PRODUCER_CACHE = CacheBuilder.newBuilder() | |
| 42 | + .expireAfterAccess(Duration.ofMinutes(30)) // 30 分钟未访问就提前过期 | |
| 43 | + .removalListener((RemovalListener<Config, Producer>) notification -> { | |
| 44 | + Producer producer = notification.getValue(); | |
| 31 | 45 | if (producer == null) { |
| 32 | 46 | return; |
| 33 | 47 | } |
| 48 | + | |
| 34 | 49 | try { |
| 35 | 50 | closeProducer(producer); |
| 36 | 51 | log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已关闭]", notification.getKey()); |
| ... | ... | @@ -38,15 +53,18 @@ public abstract class AbstractCacheableDataBridgeExecute implements IotDataBridg |
| 38 | 53 | log.error("[PRODUCER_CACHE][配置({}) 对应的 producer 关闭失败]", notification.getKey(), e); |
| 39 | 54 | } |
| 40 | 55 | }) |
| 41 | - .build(new CacheLoader<Object, Object>() { | |
| 42 | - | |
| 56 | + .build(new CacheLoader<Config, Producer>() { | |
| 43 | 57 | @Override |
| 44 | - public Object load(Object config) throws Exception { | |
| 45 | - Object producer = initProducer(config); | |
| 46 | - log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已创建并启动]", config); | |
| 47 | - return producer; | |
| 58 | + public Producer load(Config config) throws Exception { | |
| 59 | + try { | |
| 60 | + Producer producer = initProducer(config); | |
| 61 | + log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已创建并启动]", config); | |
| 62 | + return producer; | |
| 63 | + } catch (Exception e) { | |
| 64 | + log.error("[PRODUCER_CACHE][配置({}) 对应的 producer 创建启动失败]", config, e); | |
| 65 | + throw e; // 抛出异常,触发缓存加载失败机制 | |
| 66 | + } | |
| 48 | 67 | } |
| 49 | - | |
| 50 | 68 | }); |
| 51 | 69 | |
| 52 | 70 | /** |
| ... | ... | @@ -55,7 +73,7 @@ public abstract class AbstractCacheableDataBridgeExecute implements IotDataBridg |
| 55 | 73 | * @param config 配置信息 |
| 56 | 74 | * @return 生产者对象 |
| 57 | 75 | */ |
| 58 | - protected Object getProducer(Object config) throws Exception { | |
| 76 | + protected Producer getProducer(Config config) throws Exception { | |
| 59 | 77 | return PRODUCER_CACHE.get(config); |
| 60 | 78 | } |
| 61 | 79 | |
| ... | ... | @@ -66,13 +84,13 @@ public abstract class AbstractCacheableDataBridgeExecute implements IotDataBridg |
| 66 | 84 | * @return 生产者对象 |
| 67 | 85 | * @throws Exception 如果初始化失败 |
| 68 | 86 | */ |
| 69 | - protected abstract Object initProducer(Object config) throws Exception; | |
| 87 | + protected abstract Producer initProducer(Config config) throws Exception; | |
| 70 | 88 | |
| 71 | 89 | /** |
| 72 | 90 | * 关闭生产者 |
| 73 | 91 | * |
| 74 | 92 | * @param producer 生产者对象 |
| 75 | 93 | */ |
| 76 | - protected abstract void closeProducer(Object producer); | |
| 94 | + protected abstract void closeProducer(Producer producer) throws Exception; | |
| 77 | 95 | |
| 78 | 96 | } |
| 79 | 97 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotKafkaMQDataBridgeExecute.java
| ... | ... | @@ -24,7 +24,8 @@ import java.util.concurrent.TimeoutException; |
| 24 | 24 | */ |
| 25 | 25 | @Component |
| 26 | 26 | @Slf4j |
| 27 | -public class IotKafkaMQDataBridgeExecute extends AbstractCacheableDataBridgeExecute { | |
| 27 | +public class IotKafkaMQDataBridgeExecute extends | |
| 28 | + AbstractCacheableDataBridgeExecute<IotDataBridgeDO.KafkaMQConfig, KafkaTemplate<String, String>> { | |
| 28 | 29 | |
| 29 | 30 | private static final Duration SEND_TIMEOUT = Duration.ofMillis(10); |
| 30 | 31 | |
| ... | ... | @@ -38,11 +39,10 @@ public class IotKafkaMQDataBridgeExecute extends AbstractCacheableDataBridgeExec |
| 38 | 39 | executeKafka(message, (IotDataBridgeDO.KafkaMQConfig) dataBridge.getConfig()); |
| 39 | 40 | } |
| 40 | 41 | |
| 41 | - @SuppressWarnings("unchecked") | |
| 42 | 42 | private void executeKafka(IotDeviceMessage message, IotDataBridgeDO.KafkaMQConfig config) { |
| 43 | 43 | try { |
| 44 | 44 | // 1. 获取或创建 KafkaTemplate |
| 45 | - KafkaTemplate<String, String> kafkaTemplate = (KafkaTemplate<String, String>) getProducer(config); | |
| 45 | + KafkaTemplate<String, String> kafkaTemplate = getProducer(config); | |
| 46 | 46 | |
| 47 | 47 | // 2. 发送消息并等待结果 |
| 48 | 48 | kafkaTemplate.send(config.getTopic(), message.toString()) |
| ... | ... | @@ -56,24 +56,22 @@ public class IotKafkaMQDataBridgeExecute extends AbstractCacheableDataBridgeExec |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | 58 | @Override |
| 59 | - protected Object initProducer(Object config) { | |
| 60 | - IotDataBridgeDO.KafkaMQConfig kafkaConfig = (IotDataBridgeDO.KafkaMQConfig) config; | |
| 61 | - | |
| 59 | + protected KafkaTemplate<String, String> initProducer(IotDataBridgeDO.KafkaMQConfig config) { | |
| 62 | 60 | // 1.1 构建生产者配置 |
| 63 | 61 | Map<String, Object> props = new HashMap<>(); |
| 64 | - props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServers()); | |
| 62 | + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers()); | |
| 65 | 63 | props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); |
| 66 | 64 | props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); |
| 67 | 65 | // 1.2 如果配置了认证信息 |
| 68 | - if (kafkaConfig.getUsername() != null && kafkaConfig.getPassword() != null) { | |
| 66 | + if (config.getUsername() != null && config.getPassword() != null) { | |
| 69 | 67 | props.put("security.protocol", "SASL_PLAINTEXT"); |
| 70 | 68 | props.put("sasl.mechanism", "PLAIN"); |
| 71 | 69 | props.put("sasl.jaas.config", |
| 72 | 70 | "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"" |
| 73 | - + kafkaConfig.getUsername() + "\" password=\"" + kafkaConfig.getPassword() + "\";"); | |
| 71 | + + config.getUsername() + "\" password=\"" + config.getPassword() + "\";"); | |
| 74 | 72 | } |
| 75 | 73 | // 1.3 如果启用 SSL |
| 76 | - if (Boolean.TRUE.equals(kafkaConfig.getSsl())) { | |
| 74 | + if (Boolean.TRUE.equals(config.getSsl())) { | |
| 77 | 75 | props.put("security.protocol", "SSL"); |
| 78 | 76 | } |
| 79 | 77 | |
| ... | ... | @@ -83,10 +81,8 @@ public class IotKafkaMQDataBridgeExecute extends AbstractCacheableDataBridgeExec |
| 83 | 81 | } |
| 84 | 82 | |
| 85 | 83 | @Override |
| 86 | - protected void closeProducer(Object producer) { | |
| 87 | - if (producer instanceof KafkaTemplate) { | |
| 88 | - ((KafkaTemplate<?, ?>) producer).destroy(); | |
| 89 | - } | |
| 84 | + protected void closeProducer(KafkaTemplate<String, String> producer) { | |
| 85 | + producer.destroy(); | |
| 90 | 86 | } |
| 91 | 87 | |
| 92 | 88 | // TODO @芋艿:测试代码,后续清理 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotRabbitMQDataBridgeExecute.java
| ... | ... | @@ -19,7 +19,8 @@ import java.time.LocalDateTime; |
| 19 | 19 | */ |
| 20 | 20 | @Component |
| 21 | 21 | @Slf4j |
| 22 | -public class IotRabbitMQDataBridgeExecute extends AbstractCacheableDataBridgeExecute { | |
| 22 | +public class IotRabbitMQDataBridgeExecute extends | |
| 23 | + AbstractCacheableDataBridgeExecute<IotDataBridgeDO.RabbitMQConfig, Channel> { | |
| 23 | 24 | |
| 24 | 25 | @Override |
| 25 | 26 | public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) { |
| ... | ... | @@ -34,7 +35,7 @@ public class IotRabbitMQDataBridgeExecute extends AbstractCacheableDataBridgeExe |
| 34 | 35 | private void executeRabbitMQ(IotDeviceMessage message, IotDataBridgeDO.RabbitMQConfig config) { |
| 35 | 36 | try { |
| 36 | 37 | // 1. 获取或创建 Channel |
| 37 | - Channel channel = (Channel) getProducer(config); | |
| 38 | + Channel channel = getProducer(config); | |
| 38 | 39 | |
| 39 | 40 | // 2.1 声明交换机、队列和绑定关系 |
| 40 | 41 | channel.exchangeDeclare(config.getExchange(), "direct", true); |
| ... | ... | @@ -52,16 +53,14 @@ public class IotRabbitMQDataBridgeExecute extends AbstractCacheableDataBridgeExe |
| 52 | 53 | |
| 53 | 54 | @Override |
| 54 | 55 | @SuppressWarnings("resource") |
| 55 | - protected Object initProducer(Object config) throws Exception { | |
| 56 | - IotDataBridgeDO.RabbitMQConfig rabbitConfig = (IotDataBridgeDO.RabbitMQConfig) config; | |
| 57 | - | |
| 56 | + protected Channel initProducer(IotDataBridgeDO.RabbitMQConfig config) throws Exception { | |
| 58 | 57 | // 1. 创建连接工厂 |
| 59 | 58 | ConnectionFactory factory = new ConnectionFactory(); |
| 60 | - factory.setHost(rabbitConfig.getHost()); | |
| 61 | - factory.setPort(rabbitConfig.getPort()); | |
| 62 | - factory.setVirtualHost(rabbitConfig.getVirtualHost()); | |
| 63 | - factory.setUsername(rabbitConfig.getUsername()); | |
| 64 | - factory.setPassword(rabbitConfig.getPassword()); | |
| 59 | + factory.setHost(config.getHost()); | |
| 60 | + factory.setPort(config.getPort()); | |
| 61 | + factory.setVirtualHost(config.getVirtualHost()); | |
| 62 | + factory.setUsername(config.getUsername()); | |
| 63 | + factory.setPassword(config.getPassword()); | |
| 65 | 64 | |
| 66 | 65 | // 2. 创建连接 |
| 67 | 66 | Connection connection = factory.newConnection(); |
| ... | ... | @@ -71,20 +70,13 @@ public class IotRabbitMQDataBridgeExecute extends AbstractCacheableDataBridgeExe |
| 71 | 70 | } |
| 72 | 71 | |
| 73 | 72 | @Override |
| 74 | - protected void closeProducer(Object producer) { | |
| 75 | - if (producer instanceof Channel) { | |
| 76 | - try { | |
| 77 | - Channel channel = (Channel) producer; | |
| 78 | - if (channel.isOpen()) { | |
| 79 | - channel.close(); | |
| 80 | - } | |
| 81 | - Connection connection = channel.getConnection(); | |
| 82 | - if (connection.isOpen()) { | |
| 83 | - connection.close(); | |
| 84 | - } | |
| 85 | - } catch (Exception e) { | |
| 86 | - log.error("[closeProducer][关闭 RabbitMQ 连接异常]", e); | |
| 87 | - } | |
| 73 | + protected void closeProducer(Channel channel) throws Exception { | |
| 74 | + if (channel.isOpen()) { | |
| 75 | + channel.close(); | |
| 76 | + } | |
| 77 | + Connection connection = channel.getConnection(); | |
| 78 | + if (connection.isOpen()) { | |
| 79 | + connection.close(); | |
| 88 | 80 | } |
| 89 | 81 | } |
| 90 | 82 | |
| ... | ... | @@ -124,4 +116,5 @@ public class IotRabbitMQDataBridgeExecute extends AbstractCacheableDataBridgeExe |
| 124 | 116 | log.info("[main][第二次执行,应该会复用缓存的 channel]"); |
| 125 | 117 | action.executeRabbitMQ(message, config); |
| 126 | 118 | } |
| 119 | + | |
| 127 | 120 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotRedisStreamMQDataBridgeExecute.java
| ... | ... | @@ -29,7 +29,8 @@ import java.time.LocalDateTime; |
| 29 | 29 | */ |
| 30 | 30 | @Component |
| 31 | 31 | @Slf4j |
| 32 | -public class IotRedisStreamMQDataBridgeExecute extends AbstractCacheableDataBridgeExecute { | |
| 32 | +public class IotRedisStreamMQDataBridgeExecute extends | |
| 33 | + AbstractCacheableDataBridgeExecute<IotDataBridgeDO.RedisStreamMQConfig, RedisTemplate<String, Object>> { | |
| 33 | 34 | |
| 34 | 35 | @Override |
| 35 | 36 | public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) { |
| ... | ... | @@ -46,7 +47,7 @@ public class IotRedisStreamMQDataBridgeExecute extends AbstractCacheableDataBrid |
| 46 | 47 | private void executeRedisStream(IotDeviceMessage message, IotDataBridgeDO.RedisStreamMQConfig config) { |
| 47 | 48 | try { |
| 48 | 49 | // 1. 获取 RedisTemplate |
| 49 | - RedisTemplate<String, Object> redisTemplate = (RedisTemplate<String, Object>) getProducer(config); | |
| 50 | + RedisTemplate<String, Object> redisTemplate = getProducer(config); | |
| 50 | 51 | |
| 51 | 52 | // 2. 创建并发送 Stream 记录 |
| 52 | 53 | ObjectRecord<String, IotDeviceMessage> record = StreamRecords.newRecord() |
| ... | ... | @@ -59,17 +60,15 @@ public class IotRedisStreamMQDataBridgeExecute extends AbstractCacheableDataBrid |
| 59 | 60 | } |
| 60 | 61 | |
| 61 | 62 | @Override |
| 62 | - protected Object initProducer(Object config) { | |
| 63 | - IotDataBridgeDO.RedisStreamMQConfig redisConfig = (IotDataBridgeDO.RedisStreamMQConfig) config; | |
| 64 | - | |
| 63 | + protected RedisTemplate<String, Object> initProducer(IotDataBridgeDO.RedisStreamMQConfig config) { | |
| 65 | 64 | // 1.1 创建 Redisson 配置 |
| 66 | 65 | Config redissonConfig = new Config(); |
| 67 | 66 | SingleServerConfig serverConfig = redissonConfig.useSingleServer() |
| 68 | - .setAddress("redis://" + redisConfig.getHost() + ":" + redisConfig.getPort()) | |
| 69 | - .setDatabase(redisConfig.getDatabase()); | |
| 67 | + .setAddress("redis://" + config.getHost() + ":" + config.getPort()) | |
| 68 | + .setDatabase(config.getDatabase()); | |
| 70 | 69 | // 1.2 设置密码(如果有) |
| 71 | - if (StrUtil.isNotBlank(redisConfig.getPassword())) { | |
| 72 | - serverConfig.setPassword(redisConfig.getPassword()); | |
| 70 | + if (StrUtil.isNotBlank(config.getPassword())) { | |
| 71 | + serverConfig.setPassword(config.getPassword()); | |
| 73 | 72 | } |
| 74 | 73 | |
| 75 | 74 | // TODO @huihui:看看能不能简化一些。按道理说,不用这么多的哈。 |
| ... | ... | @@ -90,17 +89,10 @@ public class IotRedisStreamMQDataBridgeExecute extends AbstractCacheableDataBrid |
| 90 | 89 | } |
| 91 | 90 | |
| 92 | 91 | @Override |
| 93 | - protected void closeProducer(Object producer) { | |
| 94 | - // TODO @huihui:try catch 交给父类来做。子类不处理异常 | |
| 95 | - if (producer instanceof RedisTemplate) { | |
| 96 | - RedisConnectionFactory factory = ((RedisTemplate<?, ?>) producer).getConnectionFactory(); | |
| 97 | - try { | |
| 98 | - if (factory != null) { | |
| 99 | - ((RedissonConnectionFactory) factory).destroy(); | |
| 100 | - } | |
| 101 | - } catch (Exception e) { | |
| 102 | - log.error("[closeProducer][关闭 redisson 连接异常]", e); | |
| 103 | - } | |
| 92 | + protected void closeProducer(RedisTemplate<String, Object> producer) throws Exception { | |
| 93 | + RedisConnectionFactory factory = producer.getConnectionFactory(); | |
| 94 | + if (factory != null) { | |
| 95 | + ((RedissonConnectionFactory) factory).destroy(); | |
| 104 | 96 | } |
| 105 | 97 | } |
| 106 | 98 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotRocketMQDataBridgeExecute.java
| ... | ... | @@ -20,7 +20,8 @@ import java.time.LocalDateTime; |
| 20 | 20 | */ |
| 21 | 21 | @Component |
| 22 | 22 | @Slf4j |
| 23 | -public class IotRocketMQDataBridgeExecute extends AbstractCacheableDataBridgeExecute { | |
| 23 | +public class IotRocketMQDataBridgeExecute extends | |
| 24 | + AbstractCacheableDataBridgeExecute<IotDataBridgeDO.RocketMQConfig, DefaultMQProducer> { | |
| 24 | 25 | |
| 25 | 26 | @Override |
| 26 | 27 | public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) { |
| ... | ... | @@ -35,7 +36,7 @@ public class IotRocketMQDataBridgeExecute extends AbstractCacheableDataBridgeExe |
| 35 | 36 | private void executeRocketMQ(IotDeviceMessage message, IotDataBridgeDO.RocketMQConfig config) { |
| 36 | 37 | try { |
| 37 | 38 | // 1. 获取或创建 Producer |
| 38 | - DefaultMQProducer producer = (DefaultMQProducer) getProducer(config); | |
| 39 | + DefaultMQProducer producer = getProducer(config); | |
| 39 | 40 | |
| 40 | 41 | // 2.1 创建消息对象,指定Topic、Tag和消息体 |
| 41 | 42 | Message msg = new Message( |
| ... | ... | @@ -57,19 +58,16 @@ public class IotRocketMQDataBridgeExecute extends AbstractCacheableDataBridgeExe |
| 57 | 58 | } |
| 58 | 59 | |
| 59 | 60 | @Override |
| 60 | - protected Object initProducer(Object config) throws Exception { | |
| 61 | - IotDataBridgeDO.RocketMQConfig rocketMQConfig = (IotDataBridgeDO.RocketMQConfig) config; | |
| 62 | - DefaultMQProducer producer = new DefaultMQProducer(rocketMQConfig.getGroup()); | |
| 63 | - producer.setNamesrvAddr(rocketMQConfig.getNameServer()); | |
| 61 | + protected DefaultMQProducer initProducer(IotDataBridgeDO.RocketMQConfig config) throws Exception { | |
| 62 | + DefaultMQProducer producer = new DefaultMQProducer(config.getGroup()); | |
| 63 | + producer.setNamesrvAddr(config.getNameServer()); | |
| 64 | 64 | producer.start(); |
| 65 | 65 | return producer; |
| 66 | 66 | } |
| 67 | 67 | |
| 68 | 68 | @Override |
| 69 | - protected void closeProducer(Object producer) { | |
| 70 | - if (producer instanceof DefaultMQProducer) { | |
| 71 | - ((DefaultMQProducer) producer).shutdown(); | |
| 72 | - } | |
| 69 | + protected void closeProducer(DefaultMQProducer producer) { | |
| 70 | + producer.shutdown(); | |
| 73 | 71 | } |
| 74 | 72 | |
| 75 | 73 | // TODO @芋艿:测试代码,后续清理 | ... | ... |