Commit cb16539b663b2e510f8e67aa9f37b78378c68599

Authored by puhui999
1 parent 7b449b81

【功能新增】IoT: 新增 Redis Stream MQ 数据桥梁实现

yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataBridgTypeEnum.java
... ... @@ -22,7 +22,7 @@ public enum IotDataBridgTypeEnum implements ArrayValuable<Integer> {
22 22 MQTT(10),
23 23  
24 24 DATABASE(20),
25   - REDIS(21),
  25 + REDIS_STREAM(21),
26 26  
27 27 ROCKETMQ(30),
28 28 RABBITMQ(31),
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotDataBridgeDO.java
... ... @@ -194,10 +194,6 @@ public class IotDataBridgeDO extends BaseDO {
194 194 private Boolean ssl;
195 195  
196 196 /**
197   - * 生产者组 ID
198   - */
199   - private String groupId;
200   - /**
201 197 * 主题
202 198 */
203 199 private String topic;
... ... @@ -245,4 +241,33 @@ public class IotDataBridgeDO extends BaseDO {
245 241 private String queue;
246 242 }
247 243  
  244 + /**
  245 + * Redis Stream MQ 配置
  246 + */
  247 + @Data
  248 + public static class RedisStreamMQConfig implements Config {
  249 +
  250 + /**
  251 + * Redis 服务器地址
  252 + */
  253 + private String host;
  254 + /**
  255 + * 端口
  256 + */
  257 + private Integer port;
  258 + /**
  259 + * 密码
  260 + */
  261 + private String password;
  262 + /**
  263 + * 数据库索引
  264 + */
  265 + private Integer database;
  266 +
  267 + /**
  268 + * 主题
  269 + */
  270 + private String topic;
  271 + }
  272 +
248 273 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/AbstractCacheableDataBridgeExecute.java
... ... @@ -7,6 +7,16 @@ import lombok.extern.slf4j.Slf4j;
7 7  
8 8 import java.time.Duration;
9 9  
  10 +// TODO @芋艿:因为下面的,都是有状态的,所以通过 guava 缓存连接,然后通过 RemovalNotification 实现关闭。例如说,一次新建有效期是 10 分钟;
  11 +// TODO @芋艿:mq-redis
  12 +// TODO @芋艿:mq-数据库
  13 +// TODO @芋艿:kafka
  14 +// TODO @芋艿:rocketmq
  15 +// TODO @芋艿:rabbitmq
  16 +// TODO @芋艿:mqtt
  17 +// TODO @芋艿:tcp
  18 +// TODO @芋艿:websocket
  19 +
10 20 /**
11 21 * 带缓存功能的数据桥梁执行器抽象类
12 22 *
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecute.java
... ... @@ -19,14 +19,4 @@ public interface IotDataBridgeExecute {
19 19 */
20 20 void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge);
21 21  
22   - // TODO @芋艿:因为下面的,都是有状态的,所以通过 guava 缓存连接,然后通过 RemovalNotification 实现关闭。例如说,一次新建有效期是 10 分钟;
23   - // TODO @芋艿:mq-redis
24   - // TODO @芋艿:mq-数据库
25   - // TODO @芋艿:kafka
26   - // TODO @芋艿:rocketmq
27   - // TODO @芋艿:rabbitmq
28   - // TODO @芋艿:mqtt
29   - // TODO @芋艿:tcp
30   - // TODO @芋艿:websocket
31   -
32 22 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotKafkaMQDataBridgeExecute.java
... ... @@ -35,6 +35,7 @@ public class IotKafkaMQDataBridgeExecute extends AbstractCacheableDataBridgeExec
35 35 executeKafka(message, (IotDataBridgeDO.KafkaMQConfig) dataBridge.getConfig());
36 36 }
37 37  
  38 + @SuppressWarnings("unchecked")
38 39 private void executeKafka(IotDeviceMessage message, IotDataBridgeDO.KafkaMQConfig config) {
39 40 try {
40 41 // 1. 获取或创建 KafkaTemplate
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotRabbitMQDataBridgeExecute.java
... ... @@ -87,7 +87,7 @@ public class IotRabbitMQDataBridgeExecute extends AbstractCacheableDataBridgeExe
87 87 }
88 88 }
89 89  
90   - // TODO @芋道源码:测试代码,后续清理
  90 + // TODO @芋艿:测试代码,后续清理
91 91 public static void main(String[] args) {
92 92 // 1. 创建一个共享的实例
93 93 IotRabbitMQDataBridgeExecute action = new IotRabbitMQDataBridgeExecute();
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotRedisStreamMQDataBridgeExecute.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.action.databridge;
  2 +
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO;
  6 +import cn.iocoder.yudao.module.iot.enums.rule.IotDataBridgTypeEnum;
  7 +import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
  8 +import com.fasterxml.jackson.databind.ObjectMapper;
  9 +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.redisson.Redisson;
  12 +import org.redisson.api.RedissonClient;
  13 +import org.redisson.config.Config;
  14 +import org.redisson.config.SingleServerConfig;
  15 +import org.redisson.spring.data.connection.RedissonConnectionFactory;
  16 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  17 +import org.springframework.data.redis.connection.stream.ObjectRecord;
  18 +import org.springframework.data.redis.connection.stream.StreamRecords;
  19 +import org.springframework.data.redis.core.RedisTemplate;
  20 +import org.springframework.data.redis.serializer.RedisSerializer;
  21 +import org.springframework.stereotype.Component;
  22 +
  23 +import java.time.LocalDateTime;
  24 +
  25 +/**
  26 + * Redis Stream MQ 的 {@link IotDataBridgeExecute} 实现类
  27 + *
  28 + * @author HUIHUI
  29 + */
  30 +@Component
  31 +@Slf4j
  32 +public class IotRedisStreamMQDataBridgeExecute extends AbstractCacheableDataBridgeExecute {
  33 +
  34 + @Override
  35 + public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) {
  36 + // 1.1 校验数据桥梁类型
  37 + if (!IotDataBridgTypeEnum.REDIS_STREAM.getType().equals(dataBridge.getType())) {
  38 + return;
  39 + }
  40 + // 1.2 执行消息发送
  41 + executeRedisStream(message, (IotDataBridgeDO.RedisStreamMQConfig) dataBridge.getConfig());
  42 + }
  43 +
  44 + @SuppressWarnings("unchecked")
  45 + private void executeRedisStream(IotDeviceMessage message, IotDataBridgeDO.RedisStreamMQConfig config) {
  46 + try {
  47 + // 1. 获取 RedisTemplate
  48 + RedisTemplate<String, Object> redisTemplate = (RedisTemplate<String, Object>) getProducer(config);
  49 +
  50 + // 2. 创建并发送 Stream 记录
  51 + ObjectRecord<String, IotDeviceMessage> record = StreamRecords.newRecord()
  52 + .ofObject(message).withStreamKey(config.getTopic());
  53 + String recordId = String.valueOf(redisTemplate.opsForStream().add(record));
  54 + log.info("[executeRedisStream][消息发送成功] messageId: {}, config: {}", recordId, config);
  55 + } catch (Exception e) {
  56 + log.error("[executeRedisStream][消息发送失败] message: {}, config: {}", message, config, e);
  57 + }
  58 + }
  59 +
  60 + @Override
  61 + protected Object initProducer(Object config) {
  62 + IotDataBridgeDO.RedisStreamMQConfig redisConfig = (IotDataBridgeDO.RedisStreamMQConfig) config;
  63 +
  64 + // 1.1 创建 Redisson 配置
  65 + Config redissonConfig = new Config();
  66 + SingleServerConfig serverConfig = redissonConfig.useSingleServer()
  67 + .setAddress("redis://" + redisConfig.getHost() + ":" + redisConfig.getPort())
  68 + .setDatabase(redisConfig.getDatabase());
  69 + // 1.2 设置密码(如果有)
  70 + if (StrUtil.isNotBlank(redisConfig.getPassword())) {
  71 + serverConfig.setPassword(redisConfig.getPassword());
  72 + }
  73 +
  74 + // 2.1 创建 RedissonClient
  75 + RedissonClient redisson = Redisson.create(redissonConfig);
  76 + // 2.2 创建并配置 RedisTemplate
  77 + RedisTemplate<String, Object> template = new RedisTemplate<>();
  78 + // 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友,可以自己去撸下。
  79 + template.setConnectionFactory(new RedissonConnectionFactory(redisson));
  80 + // 使用 String 序列化方式,序列化 KEY 。
  81 + template.setKeySerializer(RedisSerializer.string());
  82 + template.setHashKeySerializer(RedisSerializer.string());
  83 + // 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
  84 + template.setValueSerializer(buildRedisSerializer());
  85 + template.setHashValueSerializer(buildRedisSerializer());
  86 + template.afterPropertiesSet();// 初始化
  87 + return template;
  88 + }
  89 +
  90 + @Override
  91 + protected void closeProducer(Object producer) {
  92 + if (producer instanceof RedisTemplate) {
  93 + RedisConnectionFactory factory = ((RedisTemplate<?, ?>) producer).getConnectionFactory();
  94 + try {
  95 + if (factory != null) {
  96 + ((RedissonConnectionFactory) factory).destroy();
  97 + }
  98 + } catch (Exception e) {
  99 + log.error("[closeProducer][关闭 redisson 连接异常]", e);
  100 + }
  101 + }
  102 + }
  103 +
  104 +
  105 + public static RedisSerializer<?> buildRedisSerializer() {
  106 + RedisSerializer<Object> json = RedisSerializer.json();
  107 + // 解决 LocalDateTime 的序列化
  108 + ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
  109 + objectMapper.registerModules(new JavaTimeModule());
  110 + return json;
  111 + }
  112 +
  113 + // TODO @芋艿:测试代码,后续清理
  114 + public static void main(String[] args) {
  115 + // 1. 创建一个共享的实例
  116 + IotRedisStreamMQDataBridgeExecute action = new IotRedisStreamMQDataBridgeExecute();
  117 +
  118 + // 2. 创建共享的配置
  119 + IotDataBridgeDO.RedisStreamMQConfig config = new IotDataBridgeDO.RedisStreamMQConfig();
  120 + config.setHost("127.0.0.1");
  121 + config.setPort(6379);
  122 + config.setDatabase(0);
  123 + config.setPassword("123456");
  124 + config.setTopic("test-stream");
  125 +
  126 + // 3. 创建共享的消息
  127 + IotDeviceMessage message = IotDeviceMessage.builder()
  128 + .requestId("TEST-001")
  129 + .productKey("testProduct")
  130 + .deviceName("testDevice")
  131 + .deviceKey("testDeviceKey")
  132 + .type("property")
  133 + .identifier("temperature")
  134 + .data("{\"value\": 60}")
  135 + .reportTime(LocalDateTime.now())
  136 + .tenantId(1L)
  137 + .build();
  138 +
  139 + // 4. 执行两次测试,验证缓存
  140 + log.info("[main][第一次执行,应该会创建新的 RedisTemplate]");
  141 + action.executeRedisStream(message, config);
  142 +
  143 + log.info("[main][第二次执行,应该会复用缓存的 RedisTemplate]");
  144 + action.executeRedisStream(message, config);
  145 + }
  146 +
  147 +}
0 148 \ No newline at end of file
... ...