Commit ea1f0cb462520cc0a3bd1633e02672a6c0ebac97

Authored by YunaiV
1 parent 2a04bdc3

feat:【IoT 物联网】实现“数据流转”功能的执行(80%)

Showing 19 changed files with 272 additions and 148 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java
... ... @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor;
7 7 /**
8 8 * IoT 设备消息标识符枚举
9 9 */
  10 +@Deprecated
10 11 @Getter
11 12 @RequiredArgsConstructor
12 13 public enum IotDeviceMessageIdentifierEnum {
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java
... ... @@ -40,9 +40,7 @@ public enum IotRuleSceneActionTypeEnum implements ArrayValuable<Integer> {
40 40  
41 41 @Deprecated
42 42 ALERT(2), // 告警执行
43   -
44   - @Deprecated
45   - DATA_BRIDGE(3); // 桥接执行
  43 + ;
46 44  
47 45 private final Integer type;
48 46  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/rule/IotDataRuleMapper.java
... ... @@ -31,4 +31,8 @@ public interface IotDataRuleMapper extends BaseMapperX<IotDataRuleDO> {
31 31 .apply(MyBatisUtils.findInSet("sink_ids", sinkId)));
32 32 }
33 33  
  34 + default List<IotDataRuleDO> selectListByStatus(Integer status) {
  35 + return selectList(IotDataRuleDO::getStatus, status);
  36 + }
  37 +
34 38 }
35 39 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
... ... @@ -60,4 +60,20 @@ public interface RedisKeyConstants {
60 60 */
61 61 String THING_MODEL_LIST = "iot:thing_model_list";
62 62  
  63 + /**
  64 + * 数据流转规则的数据缓存,使用 Spring Cache 操作
  65 + *
  66 + * KEY 格式:data_rule_list_${deviceId}_${method}_${identifier}
  67 + * VALUE 数据类型:String 数组(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO} 列表
  68 + */
  69 + String DATA_RULE_LIST = "iot:data_rule_list";
  70 +
  71 + /**
  72 + * 数据目的的数据缓存,使用 Spring Cache 操作
  73 + *
  74 + * KEY 格式:data_sink_${id}
  75 + * VALUE 数据类型:String(JSON),即 {@link cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO}
  76 + */
  77 + String DATA_SINK = "iot:data_sink";
  78 +
63 79 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotDataRuleMessageHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mq.consumer.rule;
  2 +
  3 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  4 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  5 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.service.rule.data.IotDataRuleService;
  8 +import jakarta.annotation.PostConstruct;
  9 +import jakarta.annotation.Resource;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.stereotype.Component;
  12 +
  13 +// TODO @puhui999:后面重构哈
  14 +
  15 +/**
  16 + * 针对 {@link IotDeviceMessage} 的消费者,处理数据流转
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@Component
  21 +@Slf4j
  22 +public class IotDataRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
  23 +
  24 + @Resource
  25 + private IotDataRuleService dataRuleService;
  26 +
  27 + @Resource
  28 + private IotMessageBus messageBus;
  29 +
  30 + @PostConstruct
  31 + public void init() {
  32 + messageBus.register(this);
  33 + }
  34 +
  35 + @Override
  36 + public String getTopic() {
  37 + return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
  38 + }
  39 +
  40 + @Override
  41 + public String getGroup() {
  42 + return "iot_data_rule_consumer";
  43 + }
  44 +
  45 + @Override
  46 + public void onMessage(IotDeviceMessage message) {
  47 + TenantUtils.execute(message.getTenantId(), () -> dataRuleService.executeDataRule(message));
  48 + }
  49 +
  50 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleService.java
1 1 package cn.iocoder.yudao.module.iot.service.rule.data;
2 2  
3   -import java.util.List;
4   -
5 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 4 import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
7 5 import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 7 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
9 8 import jakarta.validation.Valid;
10 9  
  10 +import java.util.List;
  11 +
11 12 /**
12 13 * IoT 数据流转规则 Service 接口
13 14 *
... ... @@ -59,6 +60,13 @@ public interface IotDataRuleService {
59 60 * @param sinkId 数据目的编号
60 61 * @return 是否被使用
61 62 */
62   - List<IotDataRuleDO> getDataRuleBySinkId(Long sinkId);
  63 + List<IotDataRuleDO> getDataRuleListBySinkId(Long sinkId);
  64 +
  65 + /**
  66 + * 执行数据流转规则
  67 + *
  68 + * @param message 消息
  69 + */
  70 + void executeDataRule(IotDeviceMessage message);
63 71  
64 72 }
65 73 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleServiceImpl.java
... ... @@ -3,17 +3,28 @@ package cn.iocoder.yudao.module.iot.service.rule.data;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  10 +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
8 11 import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRulePageReqVO;
9 12 import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.rule.IotDataRuleSaveReqVO;
  13 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  14 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
10 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
11 16 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataRuleDO;
  17 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
12 18 import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotDataRuleMapper;
  19 +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
13 20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
14 21 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
  22 +import cn.iocoder.yudao.module.iot.service.rule.data.action.IotDataRuleAction;
15 23 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
16 24 import jakarta.annotation.Resource;
  25 +import lombok.extern.slf4j.Slf4j;
  26 +import org.springframework.cache.annotation.CacheEvict;
  27 +import org.springframework.cache.annotation.Cacheable;
17 28 import org.springframework.stereotype.Service;
18 29 import org.springframework.validation.annotation.Validated;
19 30  
... ... @@ -30,6 +41,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_RULE_NOT
30 41 */
31 42 @Service
32 43 @Validated
  44 +@Slf4j
33 45 public class IotDataRuleServiceImpl implements IotDataRuleService {
34 46  
35 47 @Resource
... ... @@ -44,7 +56,11 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
44 56 @Resource
45 57 private IotDataSinkService dataSinkService;
46 58  
  59 + @Resource
  60 + private List<IotDataRuleAction> dataRuleActions;
  61 +
47 62 @Override
  63 + @CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
48 64 public Long createDataRule(IotDataRuleSaveReqVO createReqVO) {
49 65 // 校验数据源配置和数据目的
50 66 validateDataRuleConfig(createReqVO);
... ... @@ -55,6 +71,7 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
55 71 }
56 72  
57 73 @Override
  74 + @CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
58 75 public void updateDataRule(IotDataRuleSaveReqVO updateReqVO) {
59 76 // 校验存在
60 77 validateDataRuleExists(updateReqVO.getId());
... ... @@ -67,6 +84,7 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
67 84 }
68 85  
69 86 @Override
  87 + @CacheEvict(value = RedisKeyConstants.DATA_RULE_LIST, allEntries = true)
70 88 public void deleteDataRule(Long id) {
71 89 // 校验存在
72 90 validateDataRuleExists(id);
... ... @@ -116,15 +134,15 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
116 134 * @param sourceConfigs 数据源配置列表
117 135 */
118 136 private void validateThingModelsExist(List<IotDataRuleDO.SourceConfig> sourceConfigs) {
119   - Map<Long, Set<String>> productIdToIdentifiers = new HashMap<>();
  137 + Map<Long, Set<String>> productIdIdentifiers = new HashMap<>();
120 138 for (IotDataRuleDO.SourceConfig config : sourceConfigs) {
121 139 if (StrUtil.isEmpty(config.getIdentifier())) {
122 140 continue;
123 141 }
124   - productIdToIdentifiers.computeIfAbsent(config.getProductId(),
  142 + productIdIdentifiers.computeIfAbsent(config.getProductId(),
125 143 productId -> new HashSet<>()).add(config.getIdentifier());
126 144 }
127   - for (Map.Entry<Long, Set<String>> entry : productIdToIdentifiers.entrySet()) {
  145 + for (Map.Entry<Long, Set<String>> entry : productIdIdentifiers.entrySet()) {
128 146 thingModelService.validateThingModelsExist(entry.getKey(), entry.getValue());
129 147 }
130 148 }
... ... @@ -140,8 +158,102 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
140 158 }
141 159  
142 160 @Override
143   - public List<IotDataRuleDO> getDataRuleBySinkId(Long sinkId) {
  161 + public List<IotDataRuleDO> getDataRuleListBySinkId(Long sinkId) {
144 162 return dataRuleMapper.selectListBySinkId(sinkId);
145 163 }
146 164  
  165 + @Cacheable(value = RedisKeyConstants.DATA_RULE_LIST,
  166 + key = "#deviceId + '_' + #method + '_' + (#identifier ?: '')")
  167 + public List<IotDataRuleDO> getDataRuleListByConditionFromCache(Long deviceId, String method, String identifier) {
  168 + // 1. 查询所有开启的数据流转规则
  169 + List<IotDataRuleDO> rules = dataRuleMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
  170 + // 2. 内存里过滤匹配的规则
  171 + List<IotDataRuleDO> matchedRules = new ArrayList<>();
  172 + for (IotDataRuleDO rule : rules) {
  173 + IotDataRuleDO.SourceConfig found = CollUtil.findOne(rule.getSourceConfigs(),
  174 + config -> ObjectUtils.equalsAny(config.getDeviceId(), deviceId, IotDeviceDO.DEVICE_ID_ALL)
  175 + && (StrUtil.isNotEmpty(config.getMethod()) || ObjUtil.equal(config.getMethod(), method))
  176 + && (StrUtil.isEmpty(config.getIdentifier()) || ObjUtil.equal(config.getIdentifier(), identifier)));
  177 + if (found != null) {
  178 + matchedRules.add(new IotDataRuleDO().setId(rule.getId()).setSinkIds(rule.getSinkIds()));
  179 + }
  180 + }
  181 + return matchedRules;
  182 + }
  183 +
  184 + @Override
  185 + public void executeDataRule(IotDeviceMessage message) {
  186 + try {
  187 + // 1. 获取匹配的数据流转规则
  188 + Long deviceId = message.getDeviceId();
  189 + String method = message.getMethod();
  190 + String identifier = IotDeviceMessageUtils.getIdentifier(message);
  191 + List<IotDataRuleDO> rules = getSelf().getDataRuleListByConditionFromCache(deviceId, method, identifier);
  192 + if (CollUtil.isEmpty(rules)) {
  193 + log.debug("[executeDataRule][设备({}) 方法({}) 标识符({}) 没有匹配的数据流转规则]",
  194 + deviceId, method, identifier);
  195 + return;
  196 + }
  197 + log.info("[executeDataRule][设备({}) 方法({}) 标识符({}) 匹配到 {} 条数据流转规则]",
  198 + deviceId, method, identifier, rules.size());
  199 +
  200 + // 2. 遍历规则,执行数据流转
  201 + rules.forEach(rule -> executeDataRule(message, rule));
  202 + } catch (Exception e) {
  203 + log.error("[executeDataRule][消息({}) 执行数据流转规则异常]", message, e);
  204 + }
  205 + }
  206 +
  207 + /**
  208 + * 为指定规则的所有数据目的执行数据流转
  209 + *
  210 + * @param message 设备消息
  211 + * @param rule 数据流转规则
  212 + */
  213 + private void executeDataRule(IotDeviceMessage message, IotDataRuleDO rule) {
  214 + rule.getSinkIds().forEach(sinkId -> {
  215 + try {
  216 + // 获取数据目的配置
  217 + IotDataSinkDO dataSink = dataSinkService.getDataSinkFromCache(sinkId);
  218 + if (dataSink == null) {
  219 + log.error("[executeDataRule][规则({}) 对应的数据目的({}) 不存在]", rule.getId(), sinkId);
  220 + return;
  221 + }
  222 + if (CommonStatusEnum.isDisable(dataSink.getStatus())) {
  223 + log.info("[executeDataRule][规则({}) 对应的数据目的({}) 状态为禁用]", rule.getId(), sinkId);
  224 + return;
  225 + }
  226 +
  227 + // 执行数据桥接操作
  228 + executeDataRuleAction(message, dataSink);
  229 + } catch (Exception e) {
  230 + log.error("[executeDataRule][规则({}) 数据目的({}) 执行异常]", rule.getId(), sinkId, e);
  231 + }
  232 + });
  233 + }
  234 +
  235 + /**
  236 + * 执行数据流转操作
  237 + *
  238 + * @param message 设备消息
  239 + * @param dataSink 数据目的
  240 + */
  241 + private void executeDataRuleAction(IotDeviceMessage message, IotDataSinkDO dataSink) {
  242 + dataRuleActions.forEach(action -> {
  243 + if (ObjUtil.notEqual(action.getType(), dataSink.getType())) {
  244 + return;
  245 + }
  246 + try {
  247 + action.execute(message, dataSink);
  248 + log.info("[executeDataRuleAction][消息({}) 数据目的({}) 执行成功]", message.getId(), dataSink.getId());
  249 + } catch (Exception e) {
  250 + log.error("[executeDataRuleAction][消息({}) 数据目的({}) 执行异常]", message.getId(), dataSink.getId(), e);
  251 + }
  252 + });
  253 + }
  254 +
  255 + private IotDataRuleServiceImpl getSelf() {
  256 + return SpringUtils.getBean(IotDataRuleServiceImpl.class);
  257 + }
  258 +
147 259 }
148 260 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkService.java
... ... @@ -47,6 +47,14 @@ public interface IotDataSinkService {
47 47 IotDataSinkDO getDataSink(Long id);
48 48  
49 49 /**
  50 + * 从缓存中获得数据流转目的
  51 + *
  52 + * @param id 编号
  53 + * @return 数据流转目的
  54 + */
  55 + IotDataSinkDO getDataSinkFromCache(Long id);
  56 +
  57 + /**
50 58 * 获得数据流转目的分页
51 59 *
52 60 * @param pageReqVO 分页查询
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataSinkServiceImpl.java
... ... @@ -7,7 +7,9 @@ import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSin
7 7 import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.data.sink.IotDataSinkSaveReqVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
9 9 import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotDataSinkMapper;
  10 +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
10 11 import jakarta.annotation.Resource;
  12 +import org.springframework.cache.annotation.Cacheable;
11 13 import org.springframework.context.annotation.Lazy;
12 14 import org.springframework.stereotype.Service;
13 15 import org.springframework.validation.annotation.Validated;
... ... @@ -16,8 +18,8 @@ import java.util.Collection;
16 18 import java.util.List;
17 19  
18 20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
19   -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_NOT_EXISTS;
20 21 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_DELETE_FAIL_USED_BY_RULE;
  22 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_NOT_EXISTS;
21 23  
22 24 /**
23 25 * IoT 数据流转目的 Service 实现类
... ... @@ -56,7 +58,7 @@ public class IotDataSinkServiceImpl implements IotDataSinkService {
56 58 // 校验存在
57 59 validateDataBridgeExists(id);
58 60 // 校验是否被数据流转规则使用
59   - if (CollUtil.isNotEmpty(dataRuleService.getDataRuleBySinkId(id))) {
  61 + if (CollUtil.isNotEmpty(dataRuleService.getDataRuleListBySinkId(id))) {
60 62 throw exception(DATA_SINK_DELETE_FAIL_USED_BY_RULE);
61 63 }
62 64 // 删除
... ... @@ -75,6 +77,12 @@ public class IotDataSinkServiceImpl implements IotDataSinkService {
75 77 }
76 78  
77 79 @Override
  80 + @Cacheable(value = RedisKeyConstants.DATA_SINK, key = "#id")
  81 + public IotDataSinkDO getDataSinkFromCache(Long id) {
  82 + return dataSinkMapper.selectById(id);
  83 + }
  84 +
  85 + @Override
78 86 public PageResult<IotDataSinkDO> getDataSinkPage(IotDataSinkPageReqVO pageReqVO) {
79 87 return dataSinkMapper.selectPage(pageReqVO);
80 88 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotRuleSceneDataBridgeAction.java deleted
1   -package cn.iocoder.yudao.module.iot.service.rule.data;
2   -
3   -import cn.hutool.core.lang.Assert;
4   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
7   -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
8   -import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
9   -import cn.iocoder.yudao.module.iot.service.rule.data.action.IotDataBridgeExecute;
10   -import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotRuleSceneAction;
11   -import jakarta.annotation.Resource;
12   -import lombok.extern.slf4j.Slf4j;
13   -import org.springframework.stereotype.Component;
14   -
15   -import java.util.List;
16   -
17   -/**
18   - * IoT 数据流转目的的 {@link IotRuleSceneAction} 实现类
19   - *
20   - * @author 芋道源码
21   - */
22   -@Deprecated
23   -@Component
24   -@Slf4j
25   -public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction {
26   -
27   - @Resource
28   - private IotDataSinkService dataBridgeService;
29   - @Resource
30   - private List<IotDataBridgeExecute<?>> dataBridgeExecutes;
31   -
32   - @Override
33   - public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) throws Exception {
34   - // 1.1 如果消息为空,直接返回
35   - if (message == null) {
36   - return;
37   - }
38   - // 1.2 获得数据流转目的
39   - Assert.notNull(config.getDataBridgeId(), "数据流转目的编号不能为空");
40   - IotDataSinkDO dataBridge = dataBridgeService.getDataSink(config.getDataBridgeId());
41   - if (dataBridge == null || dataBridge.getConfig() == null) {
42   - log.error("[execute][message({}) config({}) 对应的数据流转目的不存在]", message, config);
43   - return;
44   - }
45   - if (CommonStatusEnum.isDisable(dataBridge.getStatus())) {
46   - log.info("[execute][message({}) config({}) 对应的数据流转目的({}) 状态为禁用]", message, config, dataBridge);
47   - return;
48   - }
49   -
50   - // 2. 执行数据桥接操作
51   - for (IotDataBridgeExecute<?> execute : dataBridgeExecutes) {
52   - execute.execute(message, dataBridge);
53   - }
54   - }
55   -
56   - @Override
57   - public IotRuleSceneActionTypeEnum getType() {
58   - return IotRuleSceneActionTypeEnum.DATA_BRIDGE;
59   - }
60   -
61   -}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataBridgeExecute.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleAction.java
... ... @@ -4,11 +4,11 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 4 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
5 5  
6 6 /**
7   - * IoT 数据流转目的的执行器 execute 接口
  7 + * IoT 数据流转目的的执行器 action 接口
8 8 *
9 9 * @author HUIHUI
10 10 */
11   -public interface IotDataBridgeExecute<Config> {
  11 +public interface IotDataRuleAction {
12 12  
13 13 /**
14 14 * 获取数据流转目的类型
... ... @@ -21,25 +21,8 @@ public interface IotDataBridgeExecute&lt;Config&gt; {
21 21 * 执行数据流转目的操作
22 22 *
23 23 * @param message 设备消息
24   - * @param dataBridge 数据流转目的
  24 + * @param dataSink 数据流转目的
25 25 */
26   - @SuppressWarnings({"unchecked"})
27   - default void execute(IotDeviceMessage message, IotDataSinkDO dataBridge) throws Exception {
28   - // 1.1 校验数据流转目的类型
29   - if (!getType().equals(dataBridge.getType())) {
30   - return;
31   - }
32   -
33   - // 1.2 执行对应的数据流转目的发送消息
34   - execute0(message, (Config) dataBridge.getConfig());
35   - }
36   -
37   - /**
38   - * 【真正】执行数据流转目的操作
39   - *
40   - * @param message 设备消息
41   - * @param config 桥梁配置
42   - */
43   - void execute0(IotDeviceMessage message, Config config) throws Exception;
  26 + void execute(IotDeviceMessage message, IotDataSinkDO dataSink);
44 27  
45 28 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/AbstractCacheableDataBridgeExecute.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
1 1 package cn.iocoder.yudao.module.iot.service.rule.data.action;
2 2  
  3 +import cn.hutool.core.lang.Assert;
3 4 import cn.hutool.core.util.ObjUtil;
4 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
... ... @@ -17,14 +18,14 @@ import java.time.Duration;
17 18 // TODO @芋艿:websocket
18 19  
19 20 /**
20   - * 带缓存功能的数据流转目的执行器抽象类
  21 + * 可缓存的 {@link IotDataRuleAction} 抽象实现
21 22 *
22 23 * 该类提供了一个通用的缓存机制,用于管理各类数据桥接的生产者(Producer)实例。
23 24 *
24 25 * 主要特点:
25 26 * - 基于Guava Cache实现高效的生产者实例缓存管理
26 27 * - 自动处理生产者的生命周期(创建、获取、关闭)
27   - * - 支持30分钟未访问自动过期清理机制
  28 + * - 支持 30 分钟未访问自动过期清理机制
28 29 * - 异常处理与日志记录,便于问题排查
29 30 *
30 31 * 子类需要实现:
... ... @@ -36,7 +37,7 @@ import java.time.Duration;
36 37 * @author HUIHUI
37 38 */
38 39 @Slf4j
39   -public abstract class AbstractCacheableDataBridgeExecute<Config, Producer> implements IotDataBridgeExecute<Config> {
  40 +public abstract class IotDataRuleCacheableAction<Config, Producer> implements IotDataRuleAction {
40 41  
41 42 /**
42 43 * Producer 缓存
... ... @@ -45,10 +46,6 @@ public abstract class AbstractCacheableDataBridgeExecute&lt;Config, Producer&gt; imple
45 46 .expireAfterAccess(Duration.ofMinutes(30)) // 30 分钟未访问就提前过期
46 47 .removalListener((RemovalListener<Config, Producer>) notification -> {
47 48 Producer producer = notification.getValue();
48   - if (producer == null) {
49   - return;
50   - }
51   -
52 49 try {
53 50 closeProducer(producer);
54 51 log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已关闭]", notification.getKey());
... ... @@ -100,15 +97,21 @@ public abstract class AbstractCacheableDataBridgeExecute&lt;Config, Producer&gt; imple
100 97  
101 98 @Override
102 99 @SuppressWarnings({"unchecked"})
103   - public void execute(IotDeviceMessage message, IotDataSinkDO dataBridge) {
104   - if (ObjUtil.notEqual(dataBridge.getType(), getType())) {
105   - return;
106   - }
  100 + public void execute(IotDeviceMessage message, IotDataSinkDO dataSink) {
  101 + Assert.isTrue(ObjUtil.equal(dataSink.getType(), getType()), "类型({})不匹配", dataSink.getType());
107 102 try {
108   - execute0(message, (Config) dataBridge.getConfig());
  103 + execute(message, (Config) dataSink.getConfig());
109 104 } catch (Exception e) {
110   - log.error("[execute][桥梁配置 config({}) 对应的 message({}) 发送异常]", dataBridge.getConfig(), message, e);
  105 + log.error("[execute][桥梁配置 config({}) 对应的 message({}) 发送异常]", dataSink.getConfig(), message, e);
111 106 }
112 107 }
113 108  
  109 + /**
  110 + * 执行数据流转
  111 + *
  112 + * @param message 设备消息
  113 + * @param config 配置信息
  114 + */
  115 + protected abstract void execute(IotDeviceMessage message, Config config) throws Exception;
  116 +
114 117 }
115 118 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotHttpDataBridgeExecute.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotHttpDataSinkAction.java
1 1 package cn.iocoder.yudao.module.iot.service.rule.data.action;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
4 5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
5 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6   -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkHttpConfig;
7 7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  8 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataSinkDO;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkHttpConfig;
8 10 import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
9 11 import jakarta.annotation.Resource;
10 12 import lombok.extern.slf4j.Slf4j;
... ... @@ -17,13 +19,13 @@ import java.util.HashMap;
17 19 import java.util.Map;
18 20  
19 21 /**
20   - * Http 的 {@link IotDataBridgeExecute} 实现类
  22 + * HTTP 的 {@link IotDataRuleAction} 实现类
21 23 *
22 24 * @author HUIHUI
23 25 */
24 26 @Component
25 27 @Slf4j
26   -public class IotHttpDataBridgeExecute implements IotDataBridgeExecute<IotDataSinkHttpConfig> {
  28 +public class IotHttpDataSinkAction implements IotDataRuleAction {
27 29  
28 30 @Resource
29 31 private RestTemplate restTemplate;
... ... @@ -34,8 +36,9 @@ public class IotHttpDataBridgeExecute implements IotDataBridgeExecute&lt;IotDataSin
34 36 }
35 37  
36 38 @Override
37   - @SuppressWarnings({"unchecked", "deprecation"})
38   - public void execute0(IotDeviceMessage message, IotDataSinkHttpConfig config) {
  39 + public void execute(IotDeviceMessage message, IotDataSinkDO dataSink) {
  40 + IotDataSinkHttpConfig config = (IotDataSinkHttpConfig) dataSink.getConfig();
  41 + Assert.notNull(config, "配置({})不能为空", dataSink.getId());
39 42 String url = null;
40 43 HttpMethod method = HttpMethod.valueOf(config.getMethod().toUpperCase());
41 44 HttpEntity<String> requestEntity = null;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotKafkaMQDataBridgeExecute.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotKafkaDataRuleAction.java
... ... @@ -17,17 +17,17 @@ import java.util.Map;
17 17 import java.util.concurrent.TimeUnit;
18 18  
19 19 /**
20   - * Kafka 的 {@link IotDataBridgeExecute} 实现类
  20 + * Kafka 的 {@link IotDataRuleAction} 实现类
21 21 *
22 22 * @author HUIHUI
23 23 */
24 24 @ConditionalOnClass(name = "org.springframework.kafka.core.KafkaTemplate")
25 25 @Component
26 26 @Slf4j
27   -public class IotKafkaMQDataBridgeExecute extends
28   - AbstractCacheableDataBridgeExecute<IotDataSinkKafkaConfig, KafkaTemplate<String, String>> {
  27 +public class IotKafkaDataRuleAction extends
  28 + IotDataRuleCacheableAction<IotDataSinkKafkaConfig, KafkaTemplate<String, String>> {
29 29  
30   - private static final Duration SEND_TIMEOUT = Duration.ofMillis(10000); // 10 秒超时时间
  30 + private static final Duration SEND_TIMEOUT = Duration.ofSeconds(10);
31 31  
32 32 @Override
33 33 public Integer getType() {
... ... @@ -35,7 +35,7 @@ public class IotKafkaMQDataBridgeExecute extends
35 35 }
36 36  
37 37 @Override
38   - public void execute0(IotDeviceMessage message, IotDataSinkKafkaConfig config) throws Exception {
  38 + public void execute(IotDeviceMessage message, IotDataSinkKafkaConfig config) throws Exception {
39 39 // 1. 获取或创建 KafkaTemplate
40 40 KafkaTemplate<String, String> kafkaTemplate = getProducer(config);
41 41  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRabbitMQDataBridgeExecute.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRabbitMQDataRuleAction.java
... ... @@ -13,16 +13,15 @@ import org.springframework.stereotype.Component;
13 13 import java.nio.charset.StandardCharsets;
14 14  
15 15 /**
16   - * RabbitMQ 的 {@link IotDataBridgeExecute} 实现类
  16 + * RabbitMQ 的 {@link IotDataRuleAction} 实现类
17 17 *
18 18 * @author HUIHUI
19 19 */
20 20 @ConditionalOnClass(name = "com.rabbitmq.client.Channel")
21 21 @Component
22 22 @Slf4j
23   -public class IotRabbitMQDataBridgeExecute extends
24   - AbstractCacheableDataBridgeExecute<IotDataSinkRabbitMQConfig, Channel> {
25   -
  23 +public class IotRabbitMQDataRuleAction extends
  24 + IotDataRuleCacheableAction<IotDataSinkRabbitMQConfig, Channel> {
26 25  
27 26 @Override
28 27 public Integer getType() {
... ... @@ -30,16 +29,15 @@ public class IotRabbitMQDataBridgeExecute extends
30 29 }
31 30  
32 31 @Override
33   - public void execute0(IotDeviceMessage message, IotDataSinkRabbitMQConfig config) throws Exception {
34   - // 1. 获取或创建 Channel
  32 + public void execute(IotDeviceMessage message, IotDataSinkRabbitMQConfig config) throws Exception {
  33 + // 1.1 获取或创建 Channel
35 34 Channel channel = getProducer(config);
36   -
37   - // 2.1 声明交换机、队列和绑定关系
  35 + // 1.2 声明交换机、队列和绑定关系
38 36 channel.exchangeDeclare(config.getExchange(), "direct", true);
39 37 channel.queueDeclare(config.getQueue(), true, false, false, null);
40 38 channel.queueBind(config.getQueue(), config.getExchange(), config.getRoutingKey());
41 39  
42   - // 2.2 发送消息
  40 + // 2. 发送消息
43 41 channel.basicPublish(config.getExchange(), config.getRoutingKey(), null,
44 42 message.toString().getBytes(StandardCharsets.UTF_8));
45 43 log.info("[executeRabbitMQ][message({}) config({}) 发送成功]", message, config);
... ... @@ -55,10 +53,8 @@ public class IotRabbitMQDataBridgeExecute extends
55 53 factory.setVirtualHost(config.getVirtualHost());
56 54 factory.setUsername(config.getUsername());
57 55 factory.setPassword(config.getPassword());
58   -
59 56 // 2. 创建连接
60 57 Connection connection = factory.newConnection();
61   -
62 58 // 3. 创建信道
63 59 return connection.createChannel();
64 60 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamDataBridgeExecute.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRedisStreamRuleAction.java
... ... @@ -18,14 +18,14 @@ import org.springframework.data.redis.serializer.RedisSerializer;
18 18 import org.springframework.stereotype.Component;
19 19  
20 20 /**
21   - * Redis Stream 的 {@link IotDataBridgeExecute} 实现类
  21 + * Redis Stream 的 {@link IotDataRuleAction} 实现类
22 22 *
23 23 * @author HUIHUI
24 24 */
25 25 @Component
26 26 @Slf4j
27   -public class IotRedisStreamDataBridgeExecute extends
28   - AbstractCacheableDataBridgeExecute<IotDataSinkRedisStreamConfig, RedisTemplate<String, Object>> {
  27 +public class IotRedisStreamRuleAction extends
  28 + IotDataRuleCacheableAction<IotDataSinkRedisStreamConfig, RedisTemplate<String, Object>> {
29 29  
30 30 @Override
31 31 public Integer getType() {
... ... @@ -33,7 +33,7 @@ public class IotRedisStreamDataBridgeExecute extends
33 33 }
34 34  
35 35 @Override
36   - public void execute0(IotDeviceMessage message, IotDataSinkRedisStreamConfig config) throws Exception {
  36 + public void execute(IotDeviceMessage message, IotDataSinkRedisStreamConfig config) throws Exception {
37 37 // 1. 获取 RedisTemplate
38 38 RedisTemplate<String, Object> redisTemplate = getProducer(config);
39 39  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRocketMQDataBridgeExecute.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotRocketMQDataRuleAction.java
... ... @@ -13,15 +13,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
13 13 import org.springframework.stereotype.Component;
14 14  
15 15 /**
16   - * RocketMQ 的 {@link IotDataBridgeExecute} 实现类
  16 + * RocketMQ 的 {@link IotDataRuleAction} 实现类
17 17 *
18 18 * @author HUIHUI
19 19 */
20 20 @ConditionalOnClass(name = "org.apache.rocketmq.client.producer.DefaultMQProducer")
21 21 @Component
22 22 @Slf4j
23   -public class IotRocketMQDataBridgeExecute extends
24   - AbstractCacheableDataBridgeExecute<IotDataSinkRocketMQConfig, DefaultMQProducer> {
  23 +public class IotRocketMQDataRuleAction extends
  24 + IotDataRuleCacheableAction<IotDataSinkRocketMQConfig, DefaultMQProducer> {
25 25  
26 26 @Override
27 27 public Integer getType() {
... ... @@ -29,7 +29,7 @@ public class IotRocketMQDataBridgeExecute extends
29 29 }
30 30  
31 31 @Override
32   - public void execute0(IotDeviceMessage message, IotDataSinkRocketMQConfig config) throws Exception {
  32 + public void execute(IotDeviceMessage message, IotDataSinkRocketMQConfig config) throws Exception {
33 33 // 1. 获取或创建 Producer
34 34 DefaultMQProducer producer = getProducer(config);
35 35  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotRuleSceneServiceImpl.java
... ... @@ -214,11 +214,6 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
214 214 .build());
215 215 action01.setDeviceControl(actionDeviceControl01);
216 216 // ruleScene01.getActions().add(action01); // TODO 芋艿:先不测试了
217   - // 数据桥接(http)
218   - IotRuleSceneDO.ActionConfig action02 = new IotRuleSceneDO.ActionConfig();
219   - action02.setType(IotRuleSceneActionTypeEnum.DATA_BRIDGE.getType());
220   - action02.setDataBridgeId(1L);
221   - ruleScene01.getActions().add(action02);
222 217 return ListUtil.toList(ruleScene01);
223 218 }
224 219  
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecuteTest.java
... ... @@ -23,7 +23,7 @@ import static org.mockito.ArgumentMatchers.anyString;
23 23 import static org.mockito.Mockito.when;
24 24  
25 25 /**
26   - * {@link IotDataBridgeExecute} 实现类的单元测试
  26 + * {@link IotDataRuleAction} 实现类的单元测试
27 27 *
28 28 * @author HUIHUI
29 29 */
... ... @@ -37,7 +37,7 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
37 37 private RestTemplate restTemplate;
38 38  
39 39 @InjectMocks
40   - private IotHttpDataBridgeExecute httpDataBridgeExecute;
  40 + private IotHttpDataSinkAction httpDataBridgeExecute;
41 41  
42 42 @BeforeEach
43 43 public void setUp() {
... ... @@ -51,7 +51,7 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
51 51 @Test
52 52 public void testKafkaMQDataBridge() throws Exception {
53 53 // 1. 创建执行器实例
54   - IotKafkaMQDataBridgeExecute action = new IotKafkaMQDataBridgeExecute();
  54 + IotKafkaDataRuleAction action = new IotKafkaDataRuleAction();
55 55  
56 56 // 2. 创建配置
57 57 IotDataSinkKafkaConfig config = new IotDataSinkKafkaConfig()
... ... @@ -68,7 +68,7 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
68 68 @Test
69 69 public void testRabbitMQDataBridge() throws Exception {
70 70 // 1. 创建执行器实例
71   - IotRabbitMQDataBridgeExecute action = new IotRabbitMQDataBridgeExecute();
  71 + IotRabbitMQDataRuleAction action = new IotRabbitMQDataRuleAction();
72 72  
73 73 // 2. 创建配置
74 74 IotDataSinkRabbitMQConfig config = new IotDataSinkRabbitMQConfig()
... ... @@ -88,7 +88,7 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
88 88 @Test
89 89 public void testRedisStreamDataBridge() throws Exception {
90 90 // 1. 创建执行器实例
91   - IotRedisStreamDataBridgeExecute action = new IotRedisStreamDataBridgeExecute();
  91 + IotRedisStreamRuleAction action = new IotRedisStreamRuleAction();
92 92  
93 93 // 2. 创建配置
94 94 IotDataSinkRedisStreamConfig config = new IotDataSinkRedisStreamConfig()
... ... @@ -105,7 +105,7 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
105 105 @Test
106 106 public void testRocketMQDataBridge() throws Exception {
107 107 // 1. 创建执行器实例
108   - IotRocketMQDataBridgeExecute action = new IotRocketMQDataBridgeExecute();
  108 + IotRocketMQDataRuleAction action = new IotRocketMQDataRuleAction();
109 109  
110 110 // 2. 创建配置
111 111 IotDataSinkRocketMQConfig config = new IotDataSinkRocketMQConfig()
... ... @@ -142,7 +142,7 @@ public class IotDataBridgeExecuteTest extends BaseMockitoUnitTest {
142 142 * @param type MQ 类型
143 143 * @throws Exception 如果执行过程中发生异常
144 144 */
145   - private void executeAndVerifyCache(IotDataBridgeExecute<?> action, IotAbstractDataSinkConfig config, String type)
  145 + private void executeAndVerifyCache(IotDataRuleAction<?> action, IotAbstractDataSinkConfig config, String type)
146 146 throws Exception {
147 147 log.info("[test{}DataBridge][第一次执行,应该会创建新的 producer]", type);
148 148 action.execute(message, new IotDataSinkDO().setType(action.getType()).setConfig(config));
... ...