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 18 @JsonSubTypes({
19 19 @JsonSubTypes.Type(value = IotDataSinkHttpConfig.class, name = "1"),
20 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 22 @JsonSubTypes.Type(value = IotDataSinkRocketMQConfig.class, name = "30"),
23 23 @JsonSubTypes.Type(value = IotDataSinkRabbitMQConfig.class, name = "31"),
24 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 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 5 import lombok.Data;
4 6  
5 7 /**
6   - * IoT Redis Stream 配置 {@link IotAbstractDataSinkConfig} 实现类
  8 + * IoT Redis 配置 {@link IotAbstractDataSinkConfig} 实现类
7 9 *
8 10 * @author HUIHUI
9 11 */
10 12 @Data
11   -public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig {
  13 +public class IotDataSinkRedisConfig extends IotAbstractDataSinkConfig {
12 14  
13 15 /**
14 16 * Redis 服务器地址
... ... @@ -28,7 +30,35 @@ public class IotDataSinkRedisStreamConfig extends IotAbstractDataSinkConfig {
28 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 51 private String topic;
34   -}
35 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 22 MQTT(10, "MQTT"), // TODO 待实现;
23 23  
24 24 DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。
25   - REDIS_STREAM(21, "Redis Stream"), // TODO @puhui999:改成 Redis;然后枚举不同的数据结构?这样,枚举就可以是 Redis 了
  25 + REDIS(21, "Redis"),
26 26  
27 27 ROCKETMQ(30, "RocketMQ"),
28 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 85 }
86 86  
87 87 @Test
88   - public void testRedisStreamDataBridge() throws Exception {
  88 + public void testRedisDataBridge() throws Exception {
89 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 101 // 3. 执行测试并验证缓存
101   - executeAndVerifyCache(action, config, "RedisStream");
  102 + executeAndVerifyCache(action, config, "Redis");
102 103 }
103 104  
104 105 @Test
... ...