Commit a328dcf172f25cbf39efcc9dcc070c5a2af8034b

Authored by puhui999
1 parent 378cf1e9

perf:【IoT 物联网】优化 IotRedisRuleAction

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotAbstractDataSinkConfig.java
@@ -18,7 +18,7 @@ import lombok.Data; @@ -18,7 +18,7 @@ import lombok.Data;
18 @JsonSubTypes({ 18 @JsonSubTypes({
19 @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"), 19 @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
20 @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"), 20 @JsonSubTypes.Type(value = IotDataSinkMqttConfig.class, name = "10"),
21 - @JsonSubTypes.Type(value = IotDataSinkRedisStreamConfig.class, name = "21"), 21 + @JsonSubTypes.Type(value = IotDataSinkRedisConfig.class, name = "21"),
22 @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"), 22 @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
23 @JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"), 23 @JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
24 @JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"), 24 @JsonSubTypes.Type(value = IotDataSinkKafkaConfig.class, name = "32"),
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisStreamConfig.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/config/IotDataSinkRedisConfig.java
1 package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config; 1 package cn.iocoder.yudao.module.iot.dal.dataobject.rule.config;
2 2
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
3 import lombok.Data; 5 import lombok.Data;
4 6
5 /** 7 /**
6 - * IoT Redis Stream 配置 {@link IotAbstractDataSinkConfig} 实现类 8 + * IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
7 * 9 *
8 * @author HUIHUI 10 * @author HUIHUI
9 */ 11 */
10 @Data 12 @Data
11 -public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig { 13 +public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
12 14
13 /** 15 /**
14 * Redis 服务器地址 16 * Redis 服务器地址
@@ -28,7 +30,35 @@ public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig { @@ -28,7 +30,35 @@ public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig {
28 private Integer database; 30 private Integer database;
29 31
30 /** 32 /**
31 - * 主题 33 + * Redis 数据结构类型
  34 + * <p>
  35 + * 枚举 {@link IotRedisDataStructureEnum}
  36 + */
  37 + @InEnum(IotRedisDataStructureEnum.class)
  38 + private Integer dataStructure;
  39 +
  40 + /**
  41 + * 主题/键名
  42 + * <p>
  43 + * 对于不同的数据结构:
  44 + * - Stream: 流的键名
  45 + * - Hash: Hash 的键名
  46 + * - List: 列表的键名
  47 + * - Set: 集合的键名
  48 + * - ZSet: 有序集合的键名
  49 + * - String: 字符串的键名
32 */ 50 */
33 private String topic; 51 private String topic;
34 -}  
35 \ No newline at end of file 52 \ No newline at end of file
  53 +
  54 + /**
  55 + * Hash 字段名(仅当 dataStructure 为 HASH 时使用)
  56 + */
  57 + private String hashField;
  58 +
  59 + /**
  60 + * ZSet 分数字段(仅当 dataStructure 为 ZSET 时使用)
  61 + * 指定消息中哪个字段作为分数,如果不指定则使用当前时间戳
  62 + */
  63 + private String scoreField;
  64 +
  65 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
@@ -22,7 +22,7 @@ public enum IotDataSinkTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -22,7 +22,7 @@ public enum IotDataSinkTypeEnum implements ArrayValuable&lt;Integer&gt; {
22 MQTT(10, "MQTT"), // TODO 待实现; 22 MQTT(10, "MQTT"), // TODO 待实现;
23 23
24 DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。 24 DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。
25 - REDIS_STREAM(21, "Redis Stream"), // TODO @puhui999:改成 Redis;然后枚举不同的数据结构?这样,枚举就可以是 Redis 了 25 + REDIS(21, "Redis"),
26 26
27 ROCKETMQ(30, "RocketMQ"), 27 ROCKETMQ(30, "RocketMQ"),
28 RABBITMQ(31, "RabbitMQ"), 28 RABBITMQ(31, "RabbitMQ"),
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRedisDataStructureEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.enums.rule;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * IoT Redis 数据结构类型枚举
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +@RequiredArgsConstructor
  15 +@Getter
  16 +public enum IotRedisDataStructureEnum implements ArrayValuable<Integer> {
  17 +
  18 + STREAM(1, "Stream"),
  19 + HASH(2, "Hash"),
  20 + LIST(3, "List"),
  21 + SET(4, "Set"),
  22 + ZSET(5, "ZSet"),
  23 + STRING(6, "String");
  24 +
  25 + private final Integer type;
  26 +
  27 + private final String name;
  28 +
  29 + public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRedisDataStructureEnum::getType).toArray(Integer[]::new);
  30 +
  31 + @Override
  32 + public Integer[] array() {
  33 + return ARRAYS;
  34 + }
  35 +
  36 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisRuleAction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.data.action;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  5 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisConfig;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotRedisDataStructureEnum;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.redisson.Redisson;
  11 +import org.redisson.api.RedissonClient;
  12 +import org.redisson.config.Config;
  13 +import org.redisson.config.SingleServerConfig;
  14 +import org.redisson.spring.data.connection.RedissonConnectionFactory;
  15 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  16 +import org.springframework.data.redis.connection.stream.ObjectRecord;
  17 +import org.springframework.data.redis.connection.stream.StreamRecords;
  18 +import org.springframework.data.redis.core.RedisTemplate;
  19 +import org.springframework.data.redis.serializer.RedisSerializer;
  20 +import org.springframework.stereotype.Component;
  21 +
  22 +import java.util.Map;
  23 +
  24 +/**
  25 + * Redis 的 {@link IotDataRuleAction} 实现类
  26 + * 支持多种 Redis 数据结构:Stream、Hash、List、Set、ZSet、String
  27 + *
  28 + * @author HUIHUI
  29 + */
  30 +@Component
  31 +@Slf4j
  32 +public class IotRedisRuleAction extends
  33 + IotDataRuleCacheableAction<IotDataSinkRedisConfig, RedisTemplate<String, Object>> {
  34 +
  35 + @Override
  36 + public Integer getType() {
  37 + return IotDataSinkTypeEnum.REDIS.getType();
  38 + }
  39 +
  40 + @Override
  41 + public void execute(IotDeviceMessage message, IotDataSinkRedisConfig config) throws Exception {
  42 + // 1. 获取 RedisTemplate
  43 + RedisTemplate<String, Object> redisTemplate = getProducer(config);
  44 +
  45 + // 2. 根据数据结构类型执行不同的操作
  46 + String messageJson = JsonUtils.toJsonString(message);
  47 + IotRedisDataStructureEnum dataStructure = getDataStructureByType(config.getDataStructure());
  48 +
  49 + switch (dataStructure) {
  50 + case STREAM:
  51 + executeStream(redisTemplate, config, messageJson);
  52 + break;
  53 + case HASH:
  54 + executeHash(redisTemplate, config, message, messageJson);
  55 + break;
  56 + case LIST:
  57 + executeList(redisTemplate, config, messageJson);
  58 + break;
  59 + case SET:
  60 + executeSet(redisTemplate, config, messageJson);
  61 + break;
  62 + case ZSET:
  63 + executeZSet(redisTemplate, config, message, messageJson);
  64 + break;
  65 + case STRING:
  66 + executeString(redisTemplate, config, messageJson);
  67 + break;
  68 + default:
  69 + throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + dataStructure);
  70 + }
  71 +
  72 + log.info("[execute][消息发送成功] dataStructure: {}, config: {}", dataStructure.getName(), config);
  73 + }
  74 +
  75 + /**
  76 + * 执行 Stream 操作
  77 + */
  78 + private void executeStream(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  79 + ObjectRecord<String, ?> record = StreamRecords.newRecord()
  80 + .ofObject(messageJson).withStreamKey(config.getTopic());
  81 + redisTemplate.opsForStream().add(record);
  82 + }
  83 +
  84 + /**
  85 + * 执行 Hash 操作
  86 + */
  87 + private void executeHash(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config,
  88 + IotDeviceMessage message, String messageJson) {
  89 + String hashField = StrUtil.isNotBlank(config.getHashField()) ?
  90 + config.getHashField() : String.valueOf(message.getDeviceId());
  91 + redisTemplate.opsForHash().put(config.getTopic(), hashField, messageJson);
  92 + }
  93 +
  94 + /**
  95 + * 执行 List 操作
  96 + */
  97 + private void executeList(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  98 + redisTemplate.opsForList().rightPush(config.getTopic(), messageJson);
  99 + }
  100 +
  101 + /**
  102 + * 执行 Set 操作
  103 + */
  104 + private void executeSet(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  105 + redisTemplate.opsForSet().add(config.getTopic(), messageJson);
  106 + }
  107 +
  108 + /**
  109 + * 执行 ZSet 操作
  110 + */
  111 + private void executeZSet(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config,
  112 + IotDeviceMessage message, String messageJson) {
  113 + double score;
  114 + if (StrUtil.isNotBlank(config.getScoreField())) {
  115 + // 尝试从消息中获取分数字段
  116 + try {
  117 + Map<String, Object> messageMap = JsonUtils.parseObject(messageJson, Map.class);
  118 + Object scoreValue = messageMap.get(config.getScoreField());
  119 + score = scoreValue instanceof Number ? ((Number) scoreValue).doubleValue() : System.currentTimeMillis();
  120 + } catch (Exception e) {
  121 + score = System.currentTimeMillis();
  122 + }
  123 + } else {
  124 + // 使用当前时间戳作为分数
  125 + score = System.currentTimeMillis();
  126 + }
  127 + redisTemplate.opsForZSet().add(config.getTopic(), messageJson, score);
  128 + }
  129 +
  130 + /**
  131 + * 执行 String 操作
  132 + */
  133 + private void executeString(RedisTemplate<String, Object> redisTemplate, IotDataSinkRedisConfig config, String messageJson) {
  134 + redisTemplate.opsForValue().set(config.getTopic(), messageJson);
  135 + }
  136 +
  137 + @Override
  138 + protected RedisTemplate<String, Object> initProducer(IotDataSinkRedisConfig config) {
  139 + // 1.1 创建 Redisson 配置
  140 + Config redissonConfig = new Config();
  141 + SingleServerConfig serverConfig = redissonConfig.useSingleServer()
  142 + .setAddress("redis://" + config.getHost() + ":" + config.getPort())
  143 + .setDatabase(config.getDatabase());
  144 + // 1.2 设置密码(如果有)
  145 + if (StrUtil.isNotBlank(config.getPassword())) {
  146 + serverConfig.setPassword(config.getPassword());
  147 + }
  148 +
  149 + // 2.1 创建 RedisTemplate 并配置
  150 + RedissonClient redisson = Redisson.create(redissonConfig);
  151 + RedisTemplate<String, Object> template = new RedisTemplate<>();
  152 + template.setConnectionFactory(new RedissonConnectionFactory(redisson));
  153 + // 2.2 设置序列化器
  154 + template.setKeySerializer(RedisSerializer.string());
  155 + template.setHashKeySerializer(RedisSerializer.string());
  156 + template.setValueSerializer(RedisSerializer.json());
  157 + template.setHashValueSerializer(RedisSerializer.json());
  158 + template.afterPropertiesSet();
  159 + return template;
  160 + }
  161 +
  162 + @Override
  163 + protected void closeProducer(RedisTemplate<String, Object> producer) throws Exception {
  164 + RedisConnectionFactory factory = producer.getConnectionFactory();
  165 + if (factory != null) {
  166 + ((RedissonConnectionFactory) factory).destroy();
  167 + }
  168 + }
  169 +
  170 + /**
  171 + * 根据类型值获取数据结构枚举
  172 + */
  173 + private IotRedisDataStructureEnum getDataStructureByType(Integer type) {
  174 + for (IotRedisDataStructureEnum dataStructure : IotRedisDataStructureEnum.values()) {
  175 + if (dataStructure.getType().equals(type)) {
  176 + return dataStructure;
  177 + }
  178 + }
  179 + throw new IllegalArgumentException("不支持的 Redis 数据结构类型: " + type);
  180 + }
  181 +
  182 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamRuleAction.java deleted
1 -package cn.iocoder.yudao.module.iot.service.rule.data.action;  
2 -  
3 -import cn.hutool.core.util.StrUtil;  
4 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
5 -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkRedisStreamConfig;  
6 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
7 -import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;  
8 -import lombok.extern.slf4j.Slf4j;  
9 -import org.redisson.Redisson;  
10 -import org.redisson.api.RedissonClient;  
11 -import org.redisson.config.Config;  
12 -import org.redisson.config.SingleServerConfig;  
13 -import org.redisson.spring.data.connection.RedissonConnectionFactory;  
14 -import org.springframework.data.redis.connection.RedisConnectionFactory;  
15 -import org.springframework.data.redis.connection.stream.ObjectRecord;  
16 -import org.springframework.data.redis.connection.stream.StreamRecords;  
17 -import org.springframework.data.redis.core.RedisTemplate;  
18 -import org.springframework.data.redis.serializer.RedisSerializer;  
19 -import org.springframework.stereotype.Component;  
20 -  
21 -/**  
22 - * Redis Stream 的 {@link IotDataRuleAction} 实现类  
23 - *  
24 - * @author HUIHUI  
25 - */  
26 -@Component  
27 -@Slf4j  
28 -public class IotRedisStreamRuleAction extends  
29 - IotDataRuleCacheableAction<IotDataSinkRedisStreamConfig, RedisTemplate<String, Object>> {  
30 -  
31 - @Override  
32 - public Integer getType() {  
33 - return IotDataSinkTypeEnum.REDIS_STREAM.getType();  
34 - }  
35 -  
36 - @Override  
37 - public void execute(IotDeviceMessage message, IotDataSinkRedisStreamConfig config) throws Exception {  
38 - // 1. 获取 RedisTemplate  
39 - RedisTemplate<String, Object> redisTemplate = getProducer(config);  
40 -  
41 - // 2. 创建并发送 Stream 记录  
42 - ObjectRecord<String, ?> record = StreamRecords.newRecord()  
43 - .ofObject(JsonUtils.toJsonString(message)).withStreamKey(config.getTopic());  
44 - String recordId = String.valueOf(redisTemplate.opsForStream().add(record));  
45 - log.info("[execute][消息发送成功] messageId: {}, config: {}", recordId, config);  
46 - }  
47 -  
48 - @Override  
49 - protected RedisTemplate<String, Object> initProducer(IotDataSinkRedisStreamConfig config) {  
50 - // 1.1 创建 Redisson 配置  
51 - Config redissonConfig = new Config();  
52 - SingleServerConfig serverConfig = redissonConfig.useSingleServer()  
53 - .setAddress("redis://" + config.getHost() + ":" + config.getPort())  
54 - .setDatabase(config.getDatabase());  
55 - // 1.2 设置密码(如果有)  
56 - if (StrUtil.isNotBlank(config.getPassword())) {  
57 - serverConfig.setPassword(config.getPassword());  
58 - }  
59 -  
60 - // 2.1 创建 RedisTemplate 并配置  
61 - RedissonClient redisson = Redisson.create(redissonConfig);  
62 - RedisTemplate<String, Object> template = new RedisTemplate<>();  
63 - template.setConnectionFactory(new RedissonConnectionFactory(redisson));  
64 - // 2.2 设置序列化器  
65 - template.setKeySerializer(RedisSerializer.string());  
66 - template.setHashKeySerializer(RedisSerializer.string());  
67 - template.setValueSerializer(RedisSerializer.json());  
68 - template.setHashValueSerializer(RedisSerializer.json());  
69 - template.afterPropertiesSet();  
70 - return template;  
71 - }  
72 -  
73 - @Override  
74 - protected void closeProducer(RedisTemplate<String, Object> producer) throws Exception {  
75 - RedisConnectionFactory factory = producer.getConnectionFactory();  
76 - if (factory != null) {  
77 - ((RedissonConnectionFactory) factory).destroy();  
78 - }  
79 - }  
80 -  
81 -}  
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java
@@ -85,20 +85,21 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest { @@ -85,20 +85,21 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
85 } 85 }
86 86
87 @Test 87 @Test
88 - public void testRedisStreamDataBridge() throws Exception { 88 + public void testRedisDataBridge() throws Exception {
89 // 1. 创建执行器实例 89 // 1. 创建执行器实例
90 - IotRedisStreamRuleAction action = new IotRedisStreamRuleAction(); 90 + IotRedisRuleAction action = new IotRedisRuleAction();
91 91
92 - // 2. 创建配置  
93 - IotDataSinkRedisStreamConfig config = new IotDataSinkRedisStreamConfig()  
94 - .setHost("127.0.0.1")  
95 - .setPort(6379)  
96 - .setDatabase(0)  
97 - .setPassword("123456")  
98 - .setTopic("test-stream"); 92 + // 2. 创建配置 - 测试 Stream 数据结构
  93 + IotDataSinkRedisConfig config = new IotDataSinkRedisConfig();
  94 + config.setHost("127.0.0.1");
  95 + config.setPort(6379);
  96 + config.setDatabase(0);
  97 + config.setPassword("123456");
  98 + config.setTopic("test-stream");
  99 + config.setDataStructure(1); // Stream 类型
99 100
100 // 3. 执行测试并验证缓存 101 // 3. 执行测试并验证缓存
101 - executeAndVerifyCache(action, config, "RedisStream"); 102 + executeAndVerifyCache(action, config, "Redis");
102 } 103 }
103 104
104 @Test 105 @Test