Commit b945970ce6b5a6d7e37a29261ecd78b355791a81
Committed by
Gitee
!1417 feat: 【IoT 物联网】实现场景规则定时触发器的注册、重构设备属性设置执行器、新增设备服务调用实现
Merge pull request !1417 from puhui999/feature/iot
Showing
20 changed files
with
964 additions
and
170 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java
| @@ -17,11 +17,11 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | @@ -17,11 +17,11 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | ||
| 17 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 17 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 18 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; | 18 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; |
| 19 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 19 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 20 | -import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; | ||
| 21 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | 20 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| 22 | import cn.iocoder.yudao.module.iot.service.product.IotProductService; | 21 | import cn.iocoder.yudao.module.iot.service.product.IotProductService; |
| 23 | import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction; | 22 | import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction; |
| 24 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager; | 23 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager; |
| 24 | +import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler; | ||
| 25 | import jakarta.annotation.Resource; | 25 | import jakarta.annotation.Resource; |
| 26 | import lombok.extern.slf4j.Slf4j; | 26 | import lombok.extern.slf4j.Slf4j; |
| 27 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
| @@ -47,9 +47,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -47,9 +47,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 47 | @Resource | 47 | @Resource |
| 48 | private IotSceneRuleMapper sceneRuleMapper; | 48 | private IotSceneRuleMapper sceneRuleMapper; |
| 49 | 49 | ||
| 50 | - // TODO @puhui999:定时任务,基于它调度; | ||
| 51 | - @Resource(name = "iotSchedulerManager") | ||
| 52 | - private IotSchedulerManager schedulerManager; | ||
| 53 | @Resource | 50 | @Resource |
| 54 | private IotProductService productService; | 51 | private IotProductService productService; |
| 55 | @Resource | 52 | @Resource |
| @@ -59,11 +56,17 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -59,11 +56,17 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 59 | private IotSceneRuleMatcherManager sceneRuleMatcherManager; | 56 | private IotSceneRuleMatcherManager sceneRuleMatcherManager; |
| 60 | @Resource | 57 | @Resource |
| 61 | private List<IotSceneRuleAction> sceneRuleActions; | 58 | private List<IotSceneRuleAction> sceneRuleActions; |
| 59 | + @Resource | ||
| 60 | + private IotSceneRuleTimerHandler timerHandler; | ||
| 62 | 61 | ||
| 63 | @Override | 62 | @Override |
| 64 | public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { | 63 | public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) { |
| 65 | IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); | 64 | IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class); |
| 66 | sceneRuleMapper.insert(sceneRule); | 65 | sceneRuleMapper.insert(sceneRule); |
| 66 | + | ||
| 67 | + // 注册定时触发器 | ||
| 68 | + timerHandler.registerTimerTriggers(sceneRule); | ||
| 69 | + | ||
| 67 | return sceneRule.getId(); | 70 | return sceneRule.getId(); |
| 68 | } | 71 | } |
| 69 | 72 | ||
| @@ -74,6 +77,9 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -74,6 +77,9 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 74 | // 更新 | 77 | // 更新 |
| 75 | IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class); | 78 | IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class); |
| 76 | sceneRuleMapper.updateById(updateObj); | 79 | sceneRuleMapper.updateById(updateObj); |
| 80 | + | ||
| 81 | + // 更新定时触发器 | ||
| 82 | + timerHandler.updateTimerTriggers(updateObj); | ||
| 77 | } | 83 | } |
| 78 | 84 | ||
| 79 | @Override | 85 | @Override |
| @@ -83,12 +89,26 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -83,12 +89,26 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 83 | // 更新状态 | 89 | // 更新状态 |
| 84 | IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status); | 90 | IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status); |
| 85 | sceneRuleMapper.updateById(updateObj); | 91 | sceneRuleMapper.updateById(updateObj); |
| 92 | + | ||
| 93 | + // 根据状态管理定时触发器 | ||
| 94 | + if (CommonStatusEnum.isEnable(status)) { | ||
| 95 | + // 启用时,获取完整的场景规则信息并注册定时触发器 | ||
| 96 | + IotSceneRuleDO sceneRule = sceneRuleMapper.selectById(id); | ||
| 97 | + if (sceneRule != null) { | ||
| 98 | + timerHandler.registerTimerTriggers(sceneRule); | ||
| 99 | + } | ||
| 100 | + } else { | ||
| 101 | + // 禁用时,暂停定时触发器 | ||
| 102 | + timerHandler.pauseTimerTriggers(id); | ||
| 103 | + } | ||
| 86 | } | 104 | } |
| 87 | 105 | ||
| 88 | @Override | 106 | @Override |
| 89 | public void deleteSceneRule(Long id) { | 107 | public void deleteSceneRule(Long id) { |
| 90 | // 校验存在 | 108 | // 校验存在 |
| 91 | validateSceneRuleExists(id); | 109 | validateSceneRuleExists(id); |
| 110 | + // 删除定时触发器 | ||
| 111 | + timerHandler.unregisterTimerTriggers(id); | ||
| 92 | // 删除 | 112 | // 删除 |
| 93 | sceneRuleMapper.deleteById(id); | 113 | sceneRuleMapper.deleteById(id); |
| 94 | } | 114 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.action; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.action; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.util.StrUtil; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | ||
| 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 8 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 5 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; | 9 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; |
| 6 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | 10 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| @@ -9,8 +13,11 @@ import jakarta.annotation.Resource; | @@ -9,8 +13,11 @@ import jakarta.annotation.Resource; | ||
| 9 | import lombok.extern.slf4j.Slf4j; | 13 | import lombok.extern.slf4j.Slf4j; |
| 10 | import org.springframework.stereotype.Component; | 14 | import org.springframework.stereotype.Component; |
| 11 | 15 | ||
| 16 | +import java.util.List; | ||
| 17 | +import java.util.Map; | ||
| 18 | + | ||
| 12 | /** | 19 | /** |
| 13 | - * IoT 设备控制的 {@link IotSceneRuleAction} 实现类 | 20 | + * IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类 |
| 14 | * | 21 | * |
| 15 | * @author 芋道源码 | 22 | * @author 芋道源码 |
| 16 | */ | 23 | */ |
| @@ -23,28 +30,108 @@ public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction { | @@ -23,28 +30,108 @@ public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction { | ||
| 23 | @Resource | 30 | @Resource |
| 24 | private IotDeviceMessageService deviceMessageService; | 31 | private IotDeviceMessageService deviceMessageService; |
| 25 | 32 | ||
| 26 | - // TODO @puhui999:这里 | ||
| 27 | @Override | 33 | @Override |
| 28 | public void execute(IotDeviceMessage message, | 34 | public void execute(IotDeviceMessage message, |
| 29 | IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { | 35 | IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { |
| 30 | - //IotSceneRuleDO.ActionDeviceControl control = actionConfig.getDeviceControl(); | ||
| 31 | - //Assert.notNull(control, "设备控制配置不能为空"); | ||
| 32 | - //// 遍历每个设备,下发消息 | ||
| 33 | - //control.getDeviceNames().forEach(deviceName -> { | ||
| 34 | - // IotDeviceDO device = deviceService.getDeviceFromCache(control.getProductKey(), deviceName); | ||
| 35 | - // if (device == null) { | ||
| 36 | - // log.error("[execute][message({}) actionConfig({}) 对应的设备不存在]", message, actionConfig); | ||
| 37 | - // return; | ||
| 38 | - // } | ||
| 39 | - // try { | ||
| 40 | - // // TODO @芋艿:@puhui999:这块可能要改,从 type => method | ||
| 41 | - // IotDeviceMessage downstreamMessage = deviceMessageService.sendDeviceMessage(IotDeviceMessage.requestOf( | ||
| 42 | - // control.getType() + control.getIdentifier(), control.getData()).setDeviceId(device.getId())); | ||
| 43 | - // log.info("[execute][message({}) actionConfig({}) 下发消息({})成功]", message, actionConfig, downstreamMessage); | ||
| 44 | - // } catch (Exception e) { | ||
| 45 | - // log.error("[execute][message({}) actionConfig({}) 下发消息失败]", message, actionConfig, e); | ||
| 46 | - // } | ||
| 47 | - //}); | 36 | + // 1. 参数校验 |
| 37 | + if (actionConfig.getDeviceId() == null) { | ||
| 38 | + log.error("[execute][规则场景({}) 动作配置({}) 设备编号不能为空]", rule.getId(), actionConfig); | ||
| 39 | + return; | ||
| 40 | + } | ||
| 41 | + if (StrUtil.isEmpty(actionConfig.getIdentifier())) { | ||
| 42 | + log.error("[execute][规则场景({}) 动作配置({}) 属性标识符不能为空]", rule.getId(), actionConfig); | ||
| 43 | + return; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + // 2. 判断是否为全部设备 | ||
| 47 | + if (IotDeviceDO.DEVICE_ID_ALL.equals(actionConfig.getDeviceId())) { | ||
| 48 | + executeForAllDevices(message, rule, actionConfig); | ||
| 49 | + } else { | ||
| 50 | + executeForSingleDevice(message, rule, actionConfig); | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * 为单个设备执行属性设置 | ||
| 56 | + */ | ||
| 57 | + private void executeForSingleDevice(IotDeviceMessage message, | ||
| 58 | + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { | ||
| 59 | + // 1. 获取设备信息 | ||
| 60 | + IotDeviceDO device = deviceService.getDeviceFromCache(actionConfig.getDeviceId()); | ||
| 61 | + if (device == null) { | ||
| 62 | + log.error("[executeForSingleDevice][规则场景({}) 动作配置({}) 对应的设备({}) 不存在]", | ||
| 63 | + rule.getId(), actionConfig, actionConfig.getDeviceId()); | ||
| 64 | + return; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + // 2. 执行属性设置 | ||
| 68 | + executePropertySetForDevice(rule, actionConfig, device); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 为产品下的所有设备执行属性设置 | ||
| 73 | + */ | ||
| 74 | + private void executeForAllDevices(IotDeviceMessage message, | ||
| 75 | + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { | ||
| 76 | + // 1. 参数校验 | ||
| 77 | + if (actionConfig.getProductId() == null) { | ||
| 78 | + log.error("[executeForAllDevices][规则场景({}) 动作配置({}) 产品编号不能为空]", rule.getId(), actionConfig); | ||
| 79 | + return; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + // 2. 获取产品下的所有设备 | ||
| 83 | + List<IotDeviceDO> devices = deviceService.getDeviceListByProductId(actionConfig.getProductId()); | ||
| 84 | + if (CollUtil.isEmpty(devices)) { | ||
| 85 | + log.warn("[executeForAllDevices][规则场景({}) 动作配置({}) 产品({}) 下没有设备]", | ||
| 86 | + rule.getId(), actionConfig, actionConfig.getProductId()); | ||
| 87 | + return; | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + // 3. 遍历所有设备执行属性设置 | ||
| 91 | + for (IotDeviceDO device : devices) { | ||
| 92 | + executePropertySetForDevice(rule, actionConfig, device); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + /** | ||
| 97 | + * 为指定设备执行属性设置 | ||
| 98 | + */ | ||
| 99 | + private void executePropertySetForDevice(IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { | ||
| 100 | + // 1. 构建属性设置消息 | ||
| 101 | + IotDeviceMessage downstreamMessage = buildPropertySetMessage(actionConfig, device); | ||
| 102 | + if (downstreamMessage == null) { | ||
| 103 | + log.error("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 构建属性设置消息失败]", | ||
| 104 | + rule.getId(), actionConfig, device.getId()); | ||
| 105 | + return; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + // 2. 发送设备消息 | ||
| 109 | + try { | ||
| 110 | + IotDeviceMessage result = deviceMessageService.sendDeviceMessage(downstreamMessage, device); | ||
| 111 | + log.info("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 属性设置消息({}) 发送成功]", | ||
| 112 | + rule.getId(), actionConfig, device.getId(), result.getId()); | ||
| 113 | + } catch (Exception e) { | ||
| 114 | + log.error("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 属性设置消息发送失败]", | ||
| 115 | + rule.getId(), actionConfig, device.getId(), e); | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + /** | ||
| 120 | + * 构建属性设置消息 | ||
| 121 | + * | ||
| 122 | + * @param actionConfig 动作配置 | ||
| 123 | + * @param device 设备信息 | ||
| 124 | + * @return 设备消息 | ||
| 125 | + */ | ||
| 126 | + private IotDeviceMessage buildPropertySetMessage(IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { | ||
| 127 | + try { | ||
| 128 | + // 属性设置参数格式: {"properties": {"identifier": value}} | ||
| 129 | + Object params = Map.of("properties", Map.of(actionConfig.getIdentifier(), actionConfig.getParams())); | ||
| 130 | + return IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod(), params); | ||
| 131 | + } catch (Exception e) { | ||
| 132 | + log.error("[buildPropertySetMessage][构建属性设置消息异常]", e); | ||
| 133 | + return null; | ||
| 134 | + } | ||
| 48 | } | 135 | } |
| 49 | 136 | ||
| 50 | @Override | 137 | @Override |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceServiceInvokeSceneRuleAction.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.action; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.util.StrUtil; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 9 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum; | ||
| 10 | +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | ||
| 11 | +import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService; | ||
| 12 | +import jakarta.annotation.Resource; | ||
| 13 | +import lombok.extern.slf4j.Slf4j; | ||
| 14 | +import org.springframework.stereotype.Component; | ||
| 15 | + | ||
| 16 | +import java.util.List; | ||
| 17 | +import java.util.Map; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * IoT 设备服务调用的 {@link IotSceneRuleAction} 实现类 | ||
| 21 | + * | ||
| 22 | + * @author HUIHUI | ||
| 23 | + */ | ||
| 24 | +@Component | ||
| 25 | +@Slf4j | ||
| 26 | +public class IotDeviceServiceInvokeSceneRuleAction implements IotSceneRuleAction { | ||
| 27 | + | ||
| 28 | + @Resource | ||
| 29 | + private IotDeviceService deviceService; | ||
| 30 | + @Resource | ||
| 31 | + private IotDeviceMessageService deviceMessageService; | ||
| 32 | + | ||
| 33 | + @Override | ||
| 34 | + public void execute(IotDeviceMessage message, | ||
| 35 | + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { | ||
| 36 | + // 1. 参数校验 | ||
| 37 | + if (actionConfig.getDeviceId() == null) { | ||
| 38 | + log.error("[execute][规则场景({}) 动作配置({}) 设备编号不能为空]", rule.getId(), actionConfig); | ||
| 39 | + return; | ||
| 40 | + } | ||
| 41 | + if (StrUtil.isEmpty(actionConfig.getIdentifier())) { | ||
| 42 | + log.error("[execute][规则场景({}) 动作配置({}) 服务标识符不能为空]", rule.getId(), actionConfig); | ||
| 43 | + return; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + // 2. 判断是否为全部设备 | ||
| 47 | + if (IotDeviceDO.DEVICE_ID_ALL.equals(actionConfig.getDeviceId())) { | ||
| 48 | + executeForAllDevices(message, rule, actionConfig); | ||
| 49 | + } else { | ||
| 50 | + executeForSingleDevice(message, rule, actionConfig); | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * 为单个设备执行服务调用 | ||
| 56 | + */ | ||
| 57 | + private void executeForSingleDevice(IotDeviceMessage message, | ||
| 58 | + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { | ||
| 59 | + // 1. 获取设备信息 | ||
| 60 | + IotDeviceDO device = deviceService.getDeviceFromCache(actionConfig.getDeviceId()); | ||
| 61 | + if (device == null) { | ||
| 62 | + log.error("[executeForSingleDevice][规则场景({}) 动作配置({}) 对应的设备({}) 不存在]", | ||
| 63 | + rule.getId(), actionConfig, actionConfig.getDeviceId()); | ||
| 64 | + return; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + // 2. 执行服务调用 | ||
| 68 | + executeServiceInvokeForDevice(rule, actionConfig, device); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 为产品下的所有设备执行服务调用 | ||
| 73 | + */ | ||
| 74 | + private void executeForAllDevices(IotDeviceMessage message, | ||
| 75 | + IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) { | ||
| 76 | + // 1. 参数校验 | ||
| 77 | + if (actionConfig.getProductId() == null) { | ||
| 78 | + log.error("[executeForAllDevices][规则场景({}) 动作配置({}) 产品编号不能为空]", rule.getId(), actionConfig); | ||
| 79 | + return; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + // 2. 获取产品下的所有设备 | ||
| 83 | + List<IotDeviceDO> devices = deviceService.getDeviceListByProductId(actionConfig.getProductId()); | ||
| 84 | + if (CollUtil.isEmpty(devices)) { | ||
| 85 | + log.warn("[executeForAllDevices][规则场景({}) 动作配置({}) 产品({}) 下没有设备]", | ||
| 86 | + rule.getId(), actionConfig, actionConfig.getProductId()); | ||
| 87 | + return; | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + // 3. 遍历所有设备执行服务调用 | ||
| 91 | + for (IotDeviceDO device : devices) { | ||
| 92 | + executeServiceInvokeForDevice(rule, actionConfig, device); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + /** | ||
| 97 | + * 为指定设备执行服务调用 | ||
| 98 | + */ | ||
| 99 | + private void executeServiceInvokeForDevice(IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { | ||
| 100 | + // 1. 构建服务调用消息 | ||
| 101 | + IotDeviceMessage downstreamMessage = buildServiceInvokeMessage(actionConfig, device); | ||
| 102 | + if (downstreamMessage == null) { | ||
| 103 | + log.error("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 构建服务调用消息失败]", | ||
| 104 | + rule.getId(), actionConfig, device.getId()); | ||
| 105 | + return; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + // 2. 发送设备消息 | ||
| 109 | + try { | ||
| 110 | + IotDeviceMessage result = deviceMessageService.sendDeviceMessage(downstreamMessage, device); | ||
| 111 | + log.info("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 服务调用消息({}) 发送成功]", | ||
| 112 | + rule.getId(), actionConfig, device.getId(), result.getId()); | ||
| 113 | + } catch (Exception e) { | ||
| 114 | + log.error("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 服务调用消息发送失败]", | ||
| 115 | + rule.getId(), actionConfig, device.getId(), e); | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + /** | ||
| 120 | + * 构建服务调用消息 | ||
| 121 | + * | ||
| 122 | + * @param actionConfig 动作配置 | ||
| 123 | + * @param device 设备信息 | ||
| 124 | + * @return 设备消息 | ||
| 125 | + */ | ||
| 126 | + private IotDeviceMessage buildServiceInvokeMessage(IotSceneRuleDO.Action actionConfig, IotDeviceDO device) { | ||
| 127 | + try { | ||
| 128 | + // 服务调用参数格式: {"identifier": "serviceId", "params": {...}} | ||
| 129 | + Object params = Map.of( | ||
| 130 | + "identifier", actionConfig.getIdentifier(), | ||
| 131 | + "params", actionConfig.getParams() != null ? actionConfig.getParams() : Map.of() | ||
| 132 | + ); | ||
| 133 | + return IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(), params); | ||
| 134 | + } catch (Exception e) { | ||
| 135 | + log.error("[buildServiceInvokeMessage][构建服务调用消息异常]", e); | ||
| 136 | + return null; | ||
| 137 | + } | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + @Override | ||
| 141 | + public IotSceneRuleActionTypeEnum getType() { | ||
| 142 | + return IotSceneRuleActionTypeEnum.DEVICE_SERVICE_INVOKE; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | +} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java
| @@ -91,7 +91,7 @@ public final class IotSceneRuleMatcherHelper { | @@ -91,7 +91,7 @@ public final class IotSceneRuleMatcherHelper { | ||
| 91 | Map<String, Object> springExpressionVariables = new HashMap<>(); | 91 | Map<String, Object> springExpressionVariables = new HashMap<>(); |
| 92 | 92 | ||
| 93 | // 设置源值 | 93 | // 设置源值 |
| 94 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); | 94 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, StrUtil.toString(sourceValue)); |
| 95 | 95 | ||
| 96 | // 处理参数值 | 96 | // 处理参数值 |
| 97 | if (StrUtil.isNotBlank(paramValue)) { | 97 | if (StrUtil.isNotBlank(paramValue)) { |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; |
| 2 | 2 | ||
| 3 | + | ||
| 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 4 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 5 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | ||
| 7 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | 8 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; |
| 8 | import org.springframework.stereotype.Component; | 9 | import org.springframework.stereotype.Component; |
| 9 | 10 | ||
| 11 | + | ||
| 10 | /** | 12 | /** |
| 11 | * 设备属性条件匹配器 | 13 | * 设备属性条件匹配器 |
| 12 | * <p> | 14 | * <p> |
| @@ -43,10 +45,10 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc | @@ -43,10 +45,10 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc | ||
| 43 | return false; | 45 | return false; |
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | - // 2.1. 获取属性值 | ||
| 47 | - Object propertyValue = message.getParams(); | 48 | + // 2.1. 获取属性值 - 使用工具类方法正确提取属性值 |
| 49 | + Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, condition.getIdentifier()); | ||
| 48 | if (propertyValue == null) { | 50 | if (propertyValue == null) { |
| 49 | - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空"); | 51 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空或未找到指定属性"); |
| 50 | return false; | 52 | return false; |
| 51 | } | 53 | } |
| 52 | 54 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 4 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | ||
| 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 5 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; |
| 6 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | 7 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; |
| @@ -35,8 +36,9 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher | @@ -35,8 +36,9 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher | ||
| 35 | return false; | 36 | return false; |
| 36 | } | 37 | } |
| 37 | 38 | ||
| 38 | - // 2.1 获取设备状态值 | ||
| 39 | - Object stateValue = message.getParams(); | 39 | + // 2.1 获取设备状态值 - 使用工具类方法正确提取状态值 |
| 40 | + // 对于设备状态条件,状态值通过 getIdentifier 获取(实际是从 params.state 字段) | ||
| 41 | + String stateValue = IotDeviceMessageUtils.getIdentifier(message); | ||
| 40 | if (stateValue == null) { | 42 | if (stateValue == null) { |
| 41 | IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空"); | 43 | IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空"); |
| 42 | return false; | 44 | return false; |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java
| @@ -52,10 +52,10 @@ public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatc | @@ -52,10 +52,10 @@ public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatc | ||
| 52 | return false; | 52 | return false; |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | - // 2.1 获取属性值 | ||
| 56 | - Object propertyValue = message.getParams(); | 55 | + // 2.1 获取属性值 - 使用工具类方法正确提取属性值 |
| 56 | + Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, trigger.getIdentifier()); | ||
| 57 | if (propertyValue == null) { | 57 | if (propertyValue == null) { |
| 58 | - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空"); | 58 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空或未找到指定属性"); |
| 59 | return false; | 59 | return false; |
| 60 | } | 60 | } |
| 61 | 61 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | ||
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 7 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | 8 | import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; |
| @@ -43,16 +44,17 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch | @@ -43,16 +44,17 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch | ||
| 43 | return false; | 44 | return false; |
| 44 | } | 45 | } |
| 45 | 46 | ||
| 46 | - // 2.1 获取设备状态值 | ||
| 47 | - Object stateValue = message.getParams(); | ||
| 48 | - if (stateValue == null) { | 47 | + // 2.1 获取设备状态值 - 使用工具类方法正确提取状态值 |
| 48 | + // 对于状态更新消息,状态值通过 getIdentifier 获取(实际是从 params.state 字段) | ||
| 49 | + String stateIdentifier = IotDeviceMessageUtils.getIdentifier(message); | ||
| 50 | + if (stateIdentifier == null) { | ||
| 49 | IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); | 51 | IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); |
| 50 | return false; | 52 | return false; |
| 51 | } | 53 | } |
| 52 | 54 | ||
| 53 | // 2.2 使用条件评估器进行匹配 | 55 | // 2.2 使用条件评估器进行匹配 |
| 54 | - // TODO @puhui999: 状态匹配重新实现 | ||
| 55 | - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); | 56 | + // 状态值通常是字符串或数字,直接使用标识符作为状态值 |
| 57 | + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateIdentifier, trigger.getOperator(), trigger.getValue()); | ||
| 56 | if (matched) { | 58 | if (matched) { |
| 57 | IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); | 59 | IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); |
| 58 | } else { | 60 | } else { |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/timer/IotSceneRuleTimerHandler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.timer; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.util.ObjUtil; | ||
| 5 | +import cn.hutool.core.util.StrUtil; | ||
| 6 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 9 | +import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; | ||
| 10 | +import cn.iocoder.yudao.module.iot.job.rule.IotSceneRuleJob; | ||
| 11 | +import jakarta.annotation.Resource; | ||
| 12 | +import lombok.extern.slf4j.Slf4j; | ||
| 13 | +import org.quartz.SchedulerException; | ||
| 14 | +import org.springframework.stereotype.Component; | ||
| 15 | + | ||
| 16 | +import java.util.List; | ||
| 17 | + | ||
| 18 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + * IoT 场景规则定时触发器处理器 | ||
| 22 | + * <p> | ||
| 23 | + * 负责管理定时触发器的注册、更新、删除等操作 | ||
| 24 | + * | ||
| 25 | + * @author HUIHUI | ||
| 26 | + */ | ||
| 27 | +@Component | ||
| 28 | +@Slf4j | ||
| 29 | +public class IotSceneRuleTimerHandler { | ||
| 30 | + | ||
| 31 | + @Resource(name = "iotSchedulerManager") | ||
| 32 | + private IotSchedulerManager schedulerManager; | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 注册场景规则的定时触发器 | ||
| 36 | + * | ||
| 37 | + * @param sceneRule 场景规则 | ||
| 38 | + */ | ||
| 39 | + public void registerTimerTriggers(IotSceneRuleDO sceneRule) { | ||
| 40 | + if (sceneRule == null || CollUtil.isEmpty(sceneRule.getTriggers())) { | ||
| 41 | + return; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + // 过滤出定时触发器 | ||
| 45 | + List<IotSceneRuleDO.Trigger> timerTriggers = filterList(sceneRule.getTriggers(), | ||
| 46 | + trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType())); | ||
| 47 | + | ||
| 48 | + if (CollUtil.isEmpty(timerTriggers)) { | ||
| 49 | + return; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + // 注册每个定时触发器 | ||
| 53 | + timerTriggers.forEach(trigger -> registerSingleTimerTrigger(sceneRule, trigger)); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 更新场景规则的定时触发器 | ||
| 58 | + * | ||
| 59 | + * @param sceneRule 场景规则 | ||
| 60 | + */ | ||
| 61 | + public void updateTimerTriggers(IotSceneRuleDO sceneRule) { | ||
| 62 | + if (sceneRule == null) { | ||
| 63 | + return; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + // 先删除旧的定时任务 | ||
| 67 | + unregisterTimerTriggers(sceneRule.getId()); | ||
| 68 | + | ||
| 69 | + // 如果场景规则已禁用,则不重新注册 | ||
| 70 | + if (CommonStatusEnum.isDisable(sceneRule.getStatus())) { | ||
| 71 | + log.info("[updateTimerTriggers][场景规则({}) 已禁用,不注册定时触发器]", sceneRule.getId()); | ||
| 72 | + return; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + // 重新注册定时触发器 | ||
| 76 | + registerTimerTriggers(sceneRule); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * 注销场景规则的定时触发器 | ||
| 81 | + * | ||
| 82 | + * @param sceneRuleId 场景规则ID | ||
| 83 | + */ | ||
| 84 | + public void unregisterTimerTriggers(Long sceneRuleId) { | ||
| 85 | + if (sceneRuleId == null) { | ||
| 86 | + return; | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + String jobName = buildJobName(sceneRuleId); | ||
| 90 | + try { | ||
| 91 | + schedulerManager.deleteJob(jobName); | ||
| 92 | + log.info("[unregisterTimerTriggers][场景规则({}) 定时触发器注销成功]", sceneRuleId); | ||
| 93 | + } catch (SchedulerException e) { | ||
| 94 | + log.error("[unregisterTimerTriggers][场景规则({}) 定时触发器注销失败]", sceneRuleId, e); | ||
| 95 | + } | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + /** | ||
| 99 | + * 暂停场景规则的定时触发器 | ||
| 100 | + * | ||
| 101 | + * @param sceneRuleId 场景规则ID | ||
| 102 | + */ | ||
| 103 | + public void pauseTimerTriggers(Long sceneRuleId) { | ||
| 104 | + if (sceneRuleId == null) { | ||
| 105 | + return; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + String jobName = buildJobName(sceneRuleId); | ||
| 109 | + try { | ||
| 110 | + schedulerManager.pauseJob(jobName); | ||
| 111 | + log.info("[pauseTimerTriggers][场景规则({}) 定时触发器暂停成功]", sceneRuleId); | ||
| 112 | + } catch (SchedulerException e) { | ||
| 113 | + log.error("[pauseTimerTriggers][场景规则({}) 定时触发器暂停失败]", sceneRuleId, e); | ||
| 114 | + } | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + /** | ||
| 118 | + * 恢复场景规则的定时触发器 | ||
| 119 | + * | ||
| 120 | + * @param sceneRuleId 场景规则ID | ||
| 121 | + */ | ||
| 122 | + public void resumeTimerTriggers(Long sceneRuleId) { | ||
| 123 | + if (sceneRuleId == null) { | ||
| 124 | + return; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + String jobName = buildJobName(sceneRuleId); | ||
| 128 | + try { | ||
| 129 | + schedulerManager.resumeJob(jobName); | ||
| 130 | + log.info("[resumeTimerTriggers][场景规则({}) 定时触发器恢复成功]", sceneRuleId); | ||
| 131 | + } catch (SchedulerException e) { | ||
| 132 | + log.error("[resumeTimerTriggers][场景规则({}) 定时触发器恢复失败]", sceneRuleId, e); | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + /** | ||
| 137 | + * 注册单个定时触发器 | ||
| 138 | + * | ||
| 139 | + * @param sceneRule 场景规则 | ||
| 140 | + * @param trigger 定时触发器配置 | ||
| 141 | + */ | ||
| 142 | + private void registerSingleTimerTrigger(IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { | ||
| 143 | + // 1. 参数校验 | ||
| 144 | + if (StrUtil.isBlank(trigger.getCronExpression())) { | ||
| 145 | + log.error("[registerSingleTimerTrigger][场景规则({}) 定时触发器缺少 CRON 表达式]", sceneRule.getId()); | ||
| 146 | + return; | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + // 2. 构建任务名称和数据 | ||
| 150 | + String jobName = buildJobName(sceneRule.getId()); | ||
| 151 | + | ||
| 152 | + try { | ||
| 153 | + // 3. 注册定时任务 | ||
| 154 | + schedulerManager.addOrUpdateJob( | ||
| 155 | + IotSceneRuleJob.class, | ||
| 156 | + jobName, | ||
| 157 | + trigger.getCronExpression(), | ||
| 158 | + IotSceneRuleJob.buildJobDataMap(sceneRule.getId()) | ||
| 159 | + ); | ||
| 160 | + | ||
| 161 | + log.info("[registerSingleTimerTrigger][场景规则({}) 定时触发器注册成功,CRON: {}]", | ||
| 162 | + sceneRule.getId(), trigger.getCronExpression()); | ||
| 163 | + } catch (SchedulerException e) { | ||
| 164 | + log.error("[registerSingleTimerTrigger][场景规则({}) 定时触发器注册失败,CRON: {}]", | ||
| 165 | + sceneRule.getId(), trigger.getCronExpression(), e); | ||
| 166 | + } | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + /** | ||
| 170 | + * 构建任务名称 | ||
| 171 | + * | ||
| 172 | + * @param sceneRuleId 场景规则ID | ||
| 173 | + * @return 任务名称 | ||
| 174 | + */ | ||
| 175 | + private String buildJobName(Long sceneRuleId) { | ||
| 176 | + return "iot_scene_rule_timer_" + sceneRuleId; | ||
| 177 | + } | ||
| 178 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/BaseMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | ||
| 2 | + | ||
| 3 | +import cn.hutool.extra.spring.SpringUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; | ||
| 5 | +import org.springframework.context.annotation.Bean; | ||
| 6 | +import org.springframework.context.annotation.Configuration; | ||
| 7 | +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * Matcher 测试基类 | ||
| 11 | + * 提供通用的 Spring 测试配置 | ||
| 12 | + * | ||
| 13 | + * @author HUIHUI | ||
| 14 | + */ | ||
| 15 | +@SpringJUnitConfig | ||
| 16 | +public abstract class BaseMatcherTest { | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 注入一下 SpringUtil,解析 EL 表达式时需要 | ||
| 20 | + * {@link SpringExpressionUtils#parseExpression} | ||
| 21 | + */ | ||
| 22 | + @Configuration | ||
| 23 | + static class TestConfig { | ||
| 24 | + | ||
| 25 | + @Bean | ||
| 26 | + public SpringUtil springUtil() { | ||
| 27 | + return new SpringUtil(); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | 5 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; |
| 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; |
| 7 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; | ||
| 8 | +import org.junit.jupiter.api.BeforeEach; | ||
| 8 | import org.junit.jupiter.api.Test; | 9 | import org.junit.jupiter.api.Test; |
| 9 | -import org.mockito.InjectMocks; | ||
| 10 | 10 | ||
| 11 | import java.time.LocalDateTime; | 11 | import java.time.LocalDateTime; |
| 12 | import java.time.ZoneOffset; | 12 | import java.time.ZoneOffset; |
| @@ -20,11 +20,15 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -20,11 +20,15 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 20 | * | 20 | * |
| 21 | * @author HUIHUI | 21 | * @author HUIHUI |
| 22 | */ | 22 | */ |
| 23 | -public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest { | 23 | +public class CurrentTimeConditionMatcherTest extends BaseMatcherTest { |
| 24 | 24 | ||
| 25 | - @InjectMocks | ||
| 26 | private CurrentTimeConditionMatcher matcher; | 25 | private CurrentTimeConditionMatcher matcher; |
| 27 | 26 | ||
| 27 | + @BeforeEach | ||
| 28 | + public void setUp() { | ||
| 29 | + matcher = new CurrentTimeConditionMatcher(); | ||
| 30 | + } | ||
| 31 | + | ||
| 28 | @Test | 32 | @Test |
| 29 | public void testGetSupportedConditionType() { | 33 | public void testGetSupportedConditionType() { |
| 30 | // 调用 | 34 | // 调用 |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.map.MapUtil; | ||
| 4 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | 5 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; |
| 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; |
| 7 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; | ||
| 8 | +import org.junit.jupiter.api.BeforeEach; | ||
| 9 | import org.junit.jupiter.api.Test; | 9 | import org.junit.jupiter.api.Test; |
| 10 | -import org.mockito.InjectMocks; | ||
| 11 | 10 | ||
| 12 | import java.util.HashMap; | 11 | import java.util.HashMap; |
| 13 | import java.util.Map; | 12 | import java.util.Map; |
| 14 | 13 | ||
| 15 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | 14 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 16 | -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | ||
| 17 | import static org.junit.jupiter.api.Assertions.*; | 15 | import static org.junit.jupiter.api.Assertions.*; |
| 18 | 16 | ||
| 19 | /** | 17 | /** |
| @@ -21,11 +19,15 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -21,11 +19,15 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 21 | * | 19 | * |
| 22 | * @author HUIHUI | 20 | * @author HUIHUI |
| 23 | */ | 21 | */ |
| 24 | -public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | 22 | +public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { |
| 25 | 23 | ||
| 26 | - @InjectMocks | ||
| 27 | private DevicePropertyConditionMatcher matcher; | 24 | private DevicePropertyConditionMatcher matcher; |
| 28 | 25 | ||
| 26 | + @BeforeEach | ||
| 27 | + public void setUp() { | ||
| 28 | + matcher = new DevicePropertyConditionMatcher(); | ||
| 29 | + } | ||
| 30 | + | ||
| 29 | @Test | 31 | @Test |
| 30 | public void testGetSupportedConditionType() { | 32 | public void testGetSupportedConditionType() { |
| 31 | // 调用 | 33 | // 调用 |
| @@ -41,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -41,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 41 | int result = matcher.getPriority(); | 43 | int result = matcher.getPriority(); |
| 42 | 44 | ||
| 43 | // 断言 | 45 | // 断言 |
| 44 | - assertEquals(20, result); | ||
| 45 | - } | ||
| 46 | - | ||
| 47 | - @Test | ||
| 48 | - public void testIsEnabled() { | ||
| 49 | - // 调用 | ||
| 50 | - boolean result = matcher.isEnabled(); | ||
| 51 | - | ||
| 52 | - // 断言 | ||
| 53 | - assertTrue(result); | 46 | + assertEquals(25, result); // 修正:实际返回值是 25 |
| 54 | } | 47 | } |
| 55 | 48 | ||
| 56 | @Test | 49 | @Test |
| 57 | public void testMatches_temperatureEquals_success() { | 50 | public void testMatches_temperatureEquals_success() { |
| 58 | - // 准备参数 | ||
| 59 | - String propertyName = "temperature"; | 51 | + // 准备参数:创建属性上报消息 |
| 52 | + String propertyIdentifier = "temperature"; | ||
| 60 | Double propertyValue = 25.5; | 53 | Double propertyValue = 25.5; |
| 61 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 62 | - IotDeviceMessage message = createDeviceMessage(properties); | 54 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 63 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 55 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 64 | - propertyName, | 56 | + propertyIdentifier, |
| 65 | IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | 57 | IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), |
| 66 | String.valueOf(propertyValue) | 58 | String.valueOf(propertyValue) |
| 67 | ); | 59 | ); |
| @@ -75,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -75,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 75 | 67 | ||
| 76 | @Test | 68 | @Test |
| 77 | public void testMatches_humidityGreaterThan_success() { | 69 | public void testMatches_humidityGreaterThan_success() { |
| 78 | - // 准备参数 | ||
| 79 | - String propertyName = "humidity"; | 70 | + // 准备参数:创建属性上报消息 |
| 71 | + String propertyIdentifier = "humidity"; | ||
| 80 | Integer propertyValue = 75; | 72 | Integer propertyValue = 75; |
| 81 | Integer compareValue = 70; | 73 | Integer compareValue = 70; |
| 82 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 83 | - IotDeviceMessage message = createDeviceMessage(properties); | 74 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 84 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 75 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 85 | - propertyName, | 76 | + propertyIdentifier, |
| 86 | IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | 77 | IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), |
| 87 | String.valueOf(compareValue) | 78 | String.valueOf(compareValue) |
| 88 | ); | 79 | ); |
| @@ -96,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -96,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 96 | 87 | ||
| 97 | @Test | 88 | @Test |
| 98 | public void testMatches_pressureLessThan_success() { | 89 | public void testMatches_pressureLessThan_success() { |
| 99 | - // 准备参数 | ||
| 100 | - String propertyName = "pressure"; | 90 | + // 准备参数:创建属性上报消息 |
| 91 | + String propertyIdentifier = "pressure"; | ||
| 101 | Double propertyValue = 1010.5; | 92 | Double propertyValue = 1010.5; |
| 102 | Integer compareValue = 1020; | 93 | Integer compareValue = 1020; |
| 103 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 104 | - IotDeviceMessage message = createDeviceMessage(properties); | 94 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 105 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 95 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 106 | - propertyName, | 96 | + propertyIdentifier, |
| 107 | IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), | 97 | IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), |
| 108 | String.valueOf(compareValue) | 98 | String.valueOf(compareValue) |
| 109 | ); | 99 | ); |
| @@ -117,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -117,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 117 | 107 | ||
| 118 | @Test | 108 | @Test |
| 119 | public void testMatches_statusNotEquals_success() { | 109 | public void testMatches_statusNotEquals_success() { |
| 120 | - // 准备参数 | ||
| 121 | - String propertyName = "status"; | 110 | + // 准备参数:创建属性上报消息 |
| 111 | + String propertyIdentifier = "status"; | ||
| 122 | String propertyValue = "active"; | 112 | String propertyValue = "active"; |
| 123 | String compareValue = "inactive"; | 113 | String compareValue = "inactive"; |
| 124 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 125 | - IotDeviceMessage message = createDeviceMessage(properties); | 114 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 126 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 115 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 127 | - propertyName, | 116 | + propertyIdentifier, |
| 128 | IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), | 117 | IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), |
| 129 | compareValue | 118 | compareValue |
| 130 | ); | 119 | ); |
| @@ -138,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -138,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 138 | 127 | ||
| 139 | @Test | 128 | @Test |
| 140 | public void testMatches_propertyMismatch_fail() { | 129 | public void testMatches_propertyMismatch_fail() { |
| 141 | - // 准备参数 | ||
| 142 | - String propertyName = "temperature"; | 130 | + // 准备参数:创建属性上报消息,值不满足条件 |
| 131 | + String propertyIdentifier = "temperature"; | ||
| 143 | Double propertyValue = 15.0; | 132 | Double propertyValue = 15.0; |
| 144 | Integer compareValue = 20; | 133 | Integer compareValue = 20; |
| 145 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 146 | - IotDeviceMessage message = createDeviceMessage(properties); | 134 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 147 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 135 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 148 | - propertyName, | 136 | + propertyIdentifier, |
| 149 | IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | 137 | IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), |
| 150 | String.valueOf(compareValue) | 138 | String.valueOf(compareValue) |
| 151 | ); | 139 | ); |
| @@ -158,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -158,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 158 | } | 146 | } |
| 159 | 147 | ||
| 160 | @Test | 148 | @Test |
| 161 | - public void testMatches_propertyNotFound_fail() { | ||
| 162 | - // 准备参数 | ||
| 163 | - Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 164 | - IotDeviceMessage message = createDeviceMessage(properties); | 149 | + public void testMatches_identifierMismatch_fail() { |
| 150 | + // 准备参数:标识符不匹配 | ||
| 151 | + String messageIdentifier = "temperature"; | ||
| 152 | + String conditionIdentifier = "humidity"; | ||
| 153 | + Double propertyValue = 25.5; | ||
| 154 | + IotDeviceMessage message = createPropertyPostMessage(messageIdentifier, propertyValue); | ||
| 165 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 155 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 166 | - randomString(), // 随机不存在的属性名 | ||
| 167 | - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 168 | - "50" | 156 | + conditionIdentifier, |
| 157 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 158 | + String.valueOf(propertyValue) | ||
| 169 | ); | 159 | ); |
| 170 | 160 | ||
| 171 | // 调用 | 161 | // 调用 |
| @@ -178,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -178,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 178 | @Test | 168 | @Test |
| 179 | public void testMatches_nullCondition_fail() { | 169 | public void testMatches_nullCondition_fail() { |
| 180 | // 准备参数 | 170 | // 准备参数 |
| 181 | - Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 182 | - IotDeviceMessage message = createDeviceMessage(properties); | 171 | + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); |
| 183 | 172 | ||
| 184 | // 调用 | 173 | // 调用 |
| 185 | boolean result = matcher.matches(message, null); | 174 | boolean result = matcher.matches(message, null); |
| @@ -191,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -191,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 191 | @Test | 180 | @Test |
| 192 | public void testMatches_nullConditionType_fail() { | 181 | public void testMatches_nullConditionType_fail() { |
| 193 | // 准备参数 | 182 | // 准备参数 |
| 194 | - Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 195 | - IotDeviceMessage message = createDeviceMessage(properties); | 183 | + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); |
| 196 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | 184 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); |
| 197 | condition.setType(null); | 185 | condition.setType(null); |
| 198 | 186 | ||
| @@ -206,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -206,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 206 | @Test | 194 | @Test |
| 207 | public void testMatches_missingIdentifier_fail() { | 195 | public void testMatches_missingIdentifier_fail() { |
| 208 | // 准备参数 | 196 | // 准备参数 |
| 209 | - Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 210 | - IotDeviceMessage message = createDeviceMessage(properties); | 197 | + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); |
| 211 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | 198 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); |
| 212 | condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); | 199 | condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); |
| 213 | condition.setIdentifier(null); // 缺少标识符 | 200 | condition.setIdentifier(null); // 缺少标识符 |
| @@ -224,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -224,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 224 | @Test | 211 | @Test |
| 225 | public void testMatches_missingOperator_fail() { | 212 | public void testMatches_missingOperator_fail() { |
| 226 | // 准备参数 | 213 | // 准备参数 |
| 227 | - Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 228 | - IotDeviceMessage message = createDeviceMessage(properties); | 214 | + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); |
| 229 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | 215 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); |
| 230 | condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); | 216 | condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); |
| 231 | condition.setIdentifier("temperature"); | 217 | condition.setIdentifier("temperature"); |
| @@ -242,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -242,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 242 | @Test | 228 | @Test |
| 243 | public void testMatches_missingParam_fail() { | 229 | public void testMatches_missingParam_fail() { |
| 244 | // 准备参数 | 230 | // 准备参数 |
| 245 | - Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 246 | - IotDeviceMessage message = createDeviceMessage(properties); | 231 | + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); |
| 247 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | 232 | IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); |
| 248 | condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); | 233 | condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); |
| 249 | condition.setIdentifier("temperature"); | 234 | condition.setIdentifier("temperature"); |
| @@ -275,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -275,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 275 | 260 | ||
| 276 | @Test | 261 | @Test |
| 277 | public void testMatches_nullDeviceProperties_fail() { | 262 | public void testMatches_nullDeviceProperties_fail() { |
| 278 | - // 准备参数 | 263 | + // 准备参数:消息的 params 为 null |
| 279 | IotDeviceMessage message = new IotDeviceMessage(); | 264 | IotDeviceMessage message = new IotDeviceMessage(); |
| 280 | message.setParams(null); | 265 | message.setParams(null); |
| 281 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 266 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| @@ -292,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -292,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 292 | } | 277 | } |
| 293 | 278 | ||
| 294 | @Test | 279 | @Test |
| 280 | + public void testMatches_propertiesStructure_success() { | ||
| 281 | + // 测试使用 properties 结构的消息(真实的属性上报场景) | ||
| 282 | + String identifier = "temperature"; | ||
| 283 | + Double propertyValue = 25.5; | ||
| 284 | + IotDeviceMessage message = createPropertyPostMessageWithProperties(identifier, propertyValue); | ||
| 285 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 286 | + identifier, | ||
| 287 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 288 | + "20" | ||
| 289 | + ); | ||
| 290 | + | ||
| 291 | + // 调用 | ||
| 292 | + boolean result = matcher.matches(message, condition); | ||
| 293 | + | ||
| 294 | + // 断言:修复后的实现应该能正确从 properties 中提取属性值 | ||
| 295 | + assertTrue(result); | ||
| 296 | + } | ||
| 297 | + | ||
| 298 | + @Test | ||
| 299 | + public void testMatches_simpleValueMessage_success() { | ||
| 300 | + // 测试简单值消息(params 直接是属性值) | ||
| 301 | + Double propertyValue = 25.5; | ||
| 302 | + IotDeviceMessage message = createSimpleValueMessage(propertyValue); | ||
| 303 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 304 | + "any", // 对于简单值消息,标识符匹配会被跳过 | ||
| 305 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 306 | + "20" | ||
| 307 | + ); | ||
| 308 | + | ||
| 309 | + // 调用 | ||
| 310 | + boolean result = matcher.matches(message, condition); | ||
| 311 | + | ||
| 312 | + // 断言:修复后的实现应该能处理简单值消息 | ||
| 313 | + // 但由于标识符匹配失败,结果为 false | ||
| 314 | + assertFalse(result); | ||
| 315 | + } | ||
| 316 | + | ||
| 317 | + @Test | ||
| 318 | + public void testMatches_valueFieldStructure_success() { | ||
| 319 | + // 测试使用 value 字段的消息结构 | ||
| 320 | + String identifier = "temperature"; | ||
| 321 | + Double propertyValue = 25.5; | ||
| 322 | + | ||
| 323 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 324 | + message.setDeviceId(randomLongId()); | ||
| 325 | + message.setMethod("thing.event.post"); | ||
| 326 | + | ||
| 327 | + Map<String, Object> params = new HashMap<>(); | ||
| 328 | + params.put("identifier", identifier); | ||
| 329 | + params.put("value", propertyValue); | ||
| 330 | + message.setParams(params); | ||
| 331 | + | ||
| 332 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 333 | + identifier, | ||
| 334 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 335 | + "20" | ||
| 336 | + ); | ||
| 337 | + | ||
| 338 | + // 调用 | ||
| 339 | + boolean result = matcher.matches(message, condition); | ||
| 340 | + | ||
| 341 | + // 断言:修复后的实现应该能从 value 字段提取属性值 | ||
| 342 | + assertTrue(result); | ||
| 343 | + } | ||
| 344 | + | ||
| 345 | + @Test | ||
| 295 | public void testMatches_voltageGreaterThanOrEquals_success() { | 346 | public void testMatches_voltageGreaterThanOrEquals_success() { |
| 296 | - // 准备参数 | ||
| 297 | - String propertyName = "voltage"; | 347 | + // 准备参数:创建属性上报消息 |
| 348 | + String propertyIdentifier = "voltage"; | ||
| 298 | Double propertyValue = 12.0; | 349 | Double propertyValue = 12.0; |
| 299 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 300 | - IotDeviceMessage message = createDeviceMessage(properties); | 350 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 301 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 351 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 302 | - propertyName, | 352 | + propertyIdentifier, |
| 303 | IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), | 353 | IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), |
| 304 | String.valueOf(propertyValue) | 354 | String.valueOf(propertyValue) |
| 305 | ); | 355 | ); |
| @@ -313,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -313,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 313 | 363 | ||
| 314 | @Test | 364 | @Test |
| 315 | public void testMatches_currentLessThanOrEquals_success() { | 365 | public void testMatches_currentLessThanOrEquals_success() { |
| 316 | - // 准备参数 | ||
| 317 | - String propertyName = "current"; | 366 | + // 准备参数:创建属性上报消息 |
| 367 | + String propertyIdentifier = "current"; | ||
| 318 | Double propertyValue = 2.5; | 368 | Double propertyValue = 2.5; |
| 319 | Double compareValue = 3.0; | 369 | Double compareValue = 3.0; |
| 320 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 321 | - IotDeviceMessage message = createDeviceMessage(properties); | 370 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 322 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 371 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 323 | - propertyName, | 372 | + propertyIdentifier, |
| 324 | IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), | 373 | IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), |
| 325 | String.valueOf(compareValue) | 374 | String.valueOf(compareValue) |
| 326 | ); | 375 | ); |
| @@ -334,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -334,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 334 | 383 | ||
| 335 | @Test | 384 | @Test |
| 336 | public void testMatches_stringProperty_success() { | 385 | public void testMatches_stringProperty_success() { |
| 337 | - // 准备参数 | ||
| 338 | - String propertyName = "mode"; | 386 | + // 准备参数:创建属性上报消息 |
| 387 | + String propertyIdentifier = "mode"; | ||
| 339 | String propertyValue = "auto"; | 388 | String propertyValue = "auto"; |
| 340 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 341 | - IotDeviceMessage message = createDeviceMessage(properties); | 389 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 342 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 390 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 343 | - propertyName, | 391 | + propertyIdentifier, |
| 344 | IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | 392 | IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), |
| 345 | propertyValue | 393 | propertyValue |
| 346 | ); | 394 | ); |
| @@ -354,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -354,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 354 | 402 | ||
| 355 | @Test | 403 | @Test |
| 356 | public void testMatches_booleanProperty_success() { | 404 | public void testMatches_booleanProperty_success() { |
| 357 | - // 准备参数 | ||
| 358 | - String propertyName = "enabled"; | 405 | + // 准备参数:创建属性上报消息 |
| 406 | + String propertyIdentifier = "enabled"; | ||
| 359 | Boolean propertyValue = true; | 407 | Boolean propertyValue = true; |
| 360 | - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue); | ||
| 361 | - IotDeviceMessage message = createDeviceMessage(properties); | 408 | + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); |
| 362 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( | 409 | IotSceneRuleDO.TriggerCondition condition = createValidCondition( |
| 363 | - propertyName, | 410 | + propertyIdentifier, |
| 364 | IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | 411 | IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), |
| 365 | String.valueOf(propertyValue) | 412 | String.valueOf(propertyValue) |
| 366 | ); | 413 | ); |
| @@ -372,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | @@ -372,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 372 | assertTrue(result); | 419 | assertTrue(result); |
| 373 | } | 420 | } |
| 374 | 421 | ||
| 375 | - @Test | ||
| 376 | - public void testMatches_multipleProperties_success() { | ||
| 377 | - // 准备参数 | ||
| 378 | - Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 379 | - .put("temperature", 25.5) | ||
| 380 | - .put("humidity", 60) | ||
| 381 | - .put("status", "active") | ||
| 382 | - .put("enabled", true) | ||
| 383 | - .build(); | ||
| 384 | - IotDeviceMessage message = createDeviceMessage(properties); | ||
| 385 | - String targetProperty = "humidity"; | ||
| 386 | - Integer targetValue = 60; | ||
| 387 | - IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 388 | - targetProperty, | ||
| 389 | - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 390 | - String.valueOf(targetValue) | ||
| 391 | - ); | 422 | + // ========== 辅助方法 ========== |
| 392 | 423 | ||
| 393 | - // 调用 | ||
| 394 | - boolean result = matcher.matches(message, condition); | 424 | + /** |
| 425 | + * 创建设备消息用于测试 | ||
| 426 | + * | ||
| 427 | + * 支持的消息格式: | ||
| 428 | + * 1. 直接属性值:params 直接是属性值(适用于简单消息) | ||
| 429 | + * 2. 标识符+值:params 包含 identifier 和对应的属性值 | ||
| 430 | + * 3. properties 结构:params.properties[identifier] = value | ||
| 431 | + * 4. data 结构:params.data[identifier] = value | ||
| 432 | + * 5. value 字段:params.value = value | ||
| 433 | + */ | ||
| 434 | + private IotDeviceMessage createPropertyPostMessage(String identifier, Object value) { | ||
| 435 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 436 | + message.setDeviceId(randomLongId()); | ||
| 437 | + message.setMethod("thing.event.post"); // 使用事件上报方法 | ||
| 395 | 438 | ||
| 396 | - // 断言 | ||
| 397 | - assertTrue(result); | 439 | + // 创建符合修复后逻辑的 params 结构 |
| 440 | + Map<String, Object> params = new HashMap<>(); | ||
| 441 | + params.put("identifier", identifier); | ||
| 442 | + // 直接将属性值放在标识符对应的字段中 | ||
| 443 | + params.put(identifier, value); | ||
| 444 | + message.setParams(params); | ||
| 445 | + | ||
| 446 | + return message; | ||
| 398 | } | 447 | } |
| 399 | 448 | ||
| 400 | - // ========== 辅助方法 ========== | 449 | + /** |
| 450 | + * 创建使用 properties 结构的消息(模拟真实的属性上报消息) | ||
| 451 | + */ | ||
| 452 | + private IotDeviceMessage createPropertyPostMessageWithProperties(String identifier, Object value) { | ||
| 453 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 454 | + message.setDeviceId(randomLongId()); | ||
| 455 | + message.setMethod("thing.property.post"); // 属性上报方法 | ||
| 456 | + | ||
| 457 | + Map<String, Object> properties = new HashMap<>(); | ||
| 458 | + properties.put(identifier, value); | ||
| 459 | + | ||
| 460 | + Map<String, Object> params = new HashMap<>(); | ||
| 461 | + params.put("properties", properties); | ||
| 462 | + message.setParams(params); | ||
| 463 | + | ||
| 464 | + return message; | ||
| 465 | + } | ||
| 401 | 466 | ||
| 402 | /** | 467 | /** |
| 403 | - * 创建设备消息 | 468 | + * 创建简单值消息(params 直接是属性值) |
| 404 | */ | 469 | */ |
| 405 | - private IotDeviceMessage createDeviceMessage(Map<String, Object> properties) { | 470 | + private IotDeviceMessage createSimpleValueMessage(Object value) { |
| 406 | IotDeviceMessage message = new IotDeviceMessage(); | 471 | IotDeviceMessage message = new IotDeviceMessage(); |
| 407 | message.setDeviceId(randomLongId()); | 472 | message.setDeviceId(randomLongId()); |
| 408 | - message.setParams(properties); | 473 | + message.setMethod("thing.property.post"); |
| 474 | + // 直接将属性值作为 params | ||
| 475 | + message.setParams(value); | ||
| 476 | + | ||
| 409 | return message; | 477 | return message; |
| 410 | } | 478 | } |
| 411 | 479 |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; |
| 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; |
| 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; | ||
| 9 | +import org.junit.jupiter.api.BeforeEach; | ||
| 9 | import org.junit.jupiter.api.Test; | 10 | import org.junit.jupiter.api.Test; |
| 10 | -import org.mockito.InjectMocks; | ||
| 11 | 11 | ||
| 12 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | 12 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 13 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | 13 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; |
| @@ -18,11 +18,15 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -18,11 +18,15 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 18 | * | 18 | * |
| 19 | * @author HUIHUI | 19 | * @author HUIHUI |
| 20 | */ | 20 | */ |
| 21 | -public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest { | 21 | +public class DeviceStateConditionMatcherTest extends BaseMatcherTest { |
| 22 | 22 | ||
| 23 | - @InjectMocks | ||
| 24 | private DeviceStateConditionMatcher matcher; | 23 | private DeviceStateConditionMatcher matcher; |
| 25 | 24 | ||
| 25 | + @BeforeEach | ||
| 26 | + public void setUp() { | ||
| 27 | + matcher = new DeviceStateConditionMatcher(); | ||
| 28 | + } | ||
| 29 | + | ||
| 26 | @Test | 30 | @Test |
| 27 | public void testGetSupportedConditionType() { | 31 | public void testGetSupportedConditionType() { |
| 28 | // 调用 | 32 | // 调用 |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.map.MapUtil; | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; | ||
| 9 | +import org.junit.jupiter.api.BeforeEach; | ||
| 9 | import org.junit.jupiter.api.Test; | 10 | import org.junit.jupiter.api.Test; |
| 10 | -import org.mockito.InjectMocks; | ||
| 11 | 11 | ||
| 12 | import java.util.HashMap; | 12 | import java.util.HashMap; |
| 13 | import java.util.Map; | 13 | import java.util.Map; |
| @@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 22 | * | 22 | * |
| 23 | * @author HUIHUI | 23 | * @author HUIHUI |
| 24 | */ | 24 | */ |
| 25 | -public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest { | 25 | +public class DeviceEventPostTriggerMatcherTest extends BaseMatcherTest { |
| 26 | 26 | ||
| 27 | - @InjectMocks | ||
| 28 | private DeviceEventPostTriggerMatcher matcher; | 27 | private DeviceEventPostTriggerMatcher matcher; |
| 29 | 28 | ||
| 29 | + @BeforeEach | ||
| 30 | + public void setUp() { | ||
| 31 | + matcher = new DeviceEventPostTriggerMatcher(); | ||
| 32 | + } | ||
| 33 | + | ||
| 30 | @Test | 34 | @Test |
| 31 | public void testGetSupportedTriggerType_success() { | 35 | public void testGetSupportedTriggerType_success() { |
| 32 | // 准备参数 | 36 | // 准备参数 |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.map.MapUtil; | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; |
| 9 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 9 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; | ||
| 10 | +import org.junit.jupiter.api.BeforeEach; | ||
| 10 | import org.junit.jupiter.api.Test; | 11 | import org.junit.jupiter.api.Test; |
| 11 | -import org.mockito.InjectMocks; | ||
| 12 | 12 | ||
| 13 | import java.util.HashMap; | 13 | import java.util.HashMap; |
| 14 | import java.util.Map; | 14 | import java.util.Map; |
| @@ -24,11 +24,15 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -24,11 +24,15 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 24 | * | 24 | * |
| 25 | * @author HUIHUI | 25 | * @author HUIHUI |
| 26 | */ | 26 | */ |
| 27 | -public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest { | 27 | +public class DevicePropertyPostTriggerMatcherTest extends BaseMatcherTest { |
| 28 | 28 | ||
| 29 | - @InjectMocks | ||
| 30 | private DevicePropertyPostTriggerMatcher matcher; | 29 | private DevicePropertyPostTriggerMatcher matcher; |
| 31 | 30 | ||
| 31 | + @BeforeEach | ||
| 32 | + public void setUp() { | ||
| 33 | + matcher = new DevicePropertyPostTriggerMatcher(); | ||
| 34 | + } | ||
| 35 | + | ||
| 32 | @Test | 36 | @Test |
| 33 | public void testGetSupportedTriggerType_success() { | 37 | public void testGetSupportedTriggerType_success() { |
| 34 | // 准备参数 | 38 | // 准备参数 |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.map.MapUtil; | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; | ||
| 9 | +import org.junit.jupiter.api.BeforeEach; | ||
| 9 | import org.junit.jupiter.api.Test; | 10 | import org.junit.jupiter.api.Test; |
| 10 | -import org.mockito.InjectMocks; | ||
| 11 | 11 | ||
| 12 | import java.util.HashMap; | 12 | import java.util.HashMap; |
| 13 | import java.util.Map; | 13 | import java.util.Map; |
| @@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 22 | * | 22 | * |
| 23 | * @author HUIHUI | 23 | * @author HUIHUI |
| 24 | */ | 24 | */ |
| 25 | -public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest { | 25 | +public class DeviceServiceInvokeTriggerMatcherTest extends BaseMatcherTest { |
| 26 | 26 | ||
| 27 | - @InjectMocks | ||
| 28 | private DeviceServiceInvokeTriggerMatcher matcher; | 27 | private DeviceServiceInvokeTriggerMatcher matcher; |
| 29 | 28 | ||
| 29 | + @BeforeEach | ||
| 30 | + public void setUp() { | ||
| 31 | + matcher = new DeviceServiceInvokeTriggerMatcher(); | ||
| 32 | + } | ||
| 33 | + | ||
| 30 | @Test | 34 | @Test |
| 31 | public void testGetSupportedTriggerType_success() { | 35 | public void testGetSupportedTriggerType_success() { |
| 32 | // 准备参数 | 36 | // 准备参数 |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | 3 | +import cn.hutool.extra.spring.SpringUtil; |
| 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 5 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | 5 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; |
| 9 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 9 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 10 | +import org.junit.jupiter.api.BeforeEach; | ||
| 10 | import org.junit.jupiter.api.Test; | 11 | import org.junit.jupiter.api.Test; |
| 11 | -import org.mockito.InjectMocks; | 12 | +import org.springframework.context.annotation.Bean; |
| 13 | +import org.springframework.context.annotation.Configuration; | ||
| 14 | +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; | ||
| 12 | 15 | ||
| 13 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | 16 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 14 | import static org.junit.jupiter.api.Assertions.*; | 17 | import static org.junit.jupiter.api.Assertions.*; |
| @@ -18,11 +21,24 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -18,11 +21,24 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 18 | * | 21 | * |
| 19 | * @author HUIHUI | 22 | * @author HUIHUI |
| 20 | */ | 23 | */ |
| 21 | -public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest { | 24 | +@SpringJUnitConfig(DeviceStateUpdateTriggerMatcherTest.TestConfig.class) |
| 25 | +public class DeviceStateUpdateTriggerMatcherTest { | ||
| 26 | + | ||
| 27 | + @Configuration | ||
| 28 | + static class TestConfig { | ||
| 29 | + @Bean | ||
| 30 | + public SpringUtil springUtil() { | ||
| 31 | + return new SpringUtil(); | ||
| 32 | + } | ||
| 33 | + } | ||
| 22 | 34 | ||
| 23 | - @InjectMocks | ||
| 24 | private DeviceStateUpdateTriggerMatcher matcher; | 35 | private DeviceStateUpdateTriggerMatcher matcher; |
| 25 | 36 | ||
| 37 | + @BeforeEach | ||
| 38 | + public void setUp() { | ||
| 39 | + matcher = new DeviceStateUpdateTriggerMatcher(); | ||
| 40 | + } | ||
| 41 | + | ||
| 26 | @Test | 42 | @Test |
| 27 | public void testGetSupportedTriggerType_success() { | 43 | public void testGetSupportedTriggerType_success() { |
| 28 | // 准备参数 | 44 | // 准备参数 |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 5 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 6 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest; | ||
| 7 | +import org.junit.jupiter.api.BeforeEach; | ||
| 7 | import org.junit.jupiter.api.Test; | 8 | import org.junit.jupiter.api.Test; |
| 8 | -import org.mockito.InjectMocks; | ||
| 9 | 9 | ||
| 10 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; | 10 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; |
| 11 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | 11 | import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; |
| @@ -16,11 +16,15 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -16,11 +16,15 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 16 | * | 16 | * |
| 17 | * @author HUIHUI | 17 | * @author HUIHUI |
| 18 | */ | 18 | */ |
| 19 | -public class TimerTriggerMatcherTest extends BaseMockitoUnitTest { | 19 | +public class TimerTriggerMatcherTest extends BaseMatcherTest { |
| 20 | 20 | ||
| 21 | - @InjectMocks | ||
| 22 | private TimerTriggerMatcher matcher; | 21 | private TimerTriggerMatcher matcher; |
| 23 | 22 | ||
| 23 | + @BeforeEach | ||
| 24 | + public void setUp() { | ||
| 25 | + matcher = new TimerTriggerMatcher(); | ||
| 26 | + } | ||
| 27 | + | ||
| 24 | @Test | 28 | @Test |
| 25 | public void testGetSupportedTriggerType_success() { | 29 | public void testGetSupportedTriggerType_success() { |
| 26 | // 准备参数 | 30 | // 准备参数 |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java
| @@ -69,6 +69,83 @@ public class IotDeviceMessageUtils { | @@ -69,6 +69,83 @@ public class IotDeviceMessageUtils { | ||
| 69 | return null; | 69 | return null; |
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | + /** | ||
| 73 | + * 从设备消息中提取指定标识符的属性值 | ||
| 74 | + * - 支持多种消息格式和属性值提取策略 | ||
| 75 | + * - 兼容现有的消息结构 | ||
| 76 | + * - 提供统一的属性值提取接口 | ||
| 77 | + * <p> | ||
| 78 | + * 支持的提取策略(按优先级顺序): | ||
| 79 | + * 1. 直接值:如果 params 不是 Map,直接返回该值(适用于简单消息) | ||
| 80 | + * 2. 标识符字段:从 params[identifier] 获取 | ||
| 81 | + * 3. properties 结构:从 params.properties[identifier] 获取(标准属性上报) | ||
| 82 | + * 4. data 结构:从 params.data[identifier] 获取 | ||
| 83 | + * 5. value 字段:从 params.value 获取(单值消息) | ||
| 84 | + * 6. 单值 Map:如果 Map 只包含 identifier 和一个值,返回该值 | ||
| 85 | + * | ||
| 86 | + * @param message 设备消息 | ||
| 87 | + * @param identifier 属性标识符 | ||
| 88 | + * @return 属性值,如果未找到则返回 null | ||
| 89 | + */ | ||
| 90 | + @SuppressWarnings("unchecked") | ||
| 91 | + public static Object extractPropertyValue(IotDeviceMessage message, String identifier) { | ||
| 92 | + Object params = message.getParams(); | ||
| 93 | + if (params == null) { | ||
| 94 | + return null; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + // 策略1:如果 params 不是 Map,直接返回该值(适用于简单的单属性消息) | ||
| 98 | + if (!(params instanceof Map)) { | ||
| 99 | + return params; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + Map<String, Object> paramsMap = (Map<String, Object>) params; | ||
| 103 | + | ||
| 104 | + // 策略2:直接通过标识符获取属性值 | ||
| 105 | + Object directValue = paramsMap.get(identifier); | ||
| 106 | + if (directValue != null) { | ||
| 107 | + return directValue; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + // 策略3:从 properties 字段中获取(适用于标准属性上报消息) | ||
| 111 | + Object properties = paramsMap.get("properties"); | ||
| 112 | + if (properties instanceof Map) { | ||
| 113 | + Map<String, Object> propertiesMap = (Map<String, Object>) properties; | ||
| 114 | + Object propertyValue = propertiesMap.get(identifier); | ||
| 115 | + if (propertyValue != null) { | ||
| 116 | + return propertyValue; | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + // 策略4:从 data 字段中获取(适用于某些消息格式) | ||
| 121 | + Object data = paramsMap.get("data"); | ||
| 122 | + if (data instanceof Map) { | ||
| 123 | + Map<String, Object> dataMap = (Map<String, Object>) data; | ||
| 124 | + Object dataValue = dataMap.get(identifier); | ||
| 125 | + if (dataValue != null) { | ||
| 126 | + return dataValue; | ||
| 127 | + } | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + // 策略5:从 value 字段中获取(适用于单值消息) | ||
| 131 | + Object value = paramsMap.get("value"); | ||
| 132 | + if (value != null) { | ||
| 133 | + return value; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + // 策略6:如果 Map 只有两个字段且包含 identifier,返回另一个字段的值 | ||
| 137 | + if (paramsMap.size() == 2 && paramsMap.containsKey("identifier")) { | ||
| 138 | + for (Map.Entry<String, Object> entry : paramsMap.entrySet()) { | ||
| 139 | + if (!"identifier".equals(entry.getKey())) { | ||
| 140 | + return entry.getValue(); | ||
| 141 | + } | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + // 未找到对应的属性值 | ||
| 146 | + return null; | ||
| 147 | + } | ||
| 148 | + | ||
| 72 | // ========== Topic 相关 ========== | 149 | // ========== Topic 相关 ========== |
| 73 | 150 | ||
| 74 | public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) { | 151 | public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) { |
yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.util; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 4 | +import org.junit.jupiter.api.Test; | ||
| 5 | + | ||
| 6 | +import java.util.HashMap; | ||
| 7 | +import java.util.Map; | ||
| 8 | + | ||
| 9 | +import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 10 | +import static org.junit.jupiter.api.Assertions.assertNull; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * {@link IotDeviceMessageUtils} 的单元测试 | ||
| 14 | + * | ||
| 15 | + * @author HUIHUI | ||
| 16 | + */ | ||
| 17 | +public class IotDeviceMessageUtilsTest { | ||
| 18 | + | ||
| 19 | + @Test | ||
| 20 | + public void testExtractPropertyValue_directValue() { | ||
| 21 | + // 测试直接值(非 Map) | ||
| 22 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 23 | + message.setParams(25.5); | ||
| 24 | + | ||
| 25 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 26 | + assertEquals(25.5, result); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + @Test | ||
| 30 | + public void testExtractPropertyValue_directIdentifier() { | ||
| 31 | + // 测试直接通过标识符获取 | ||
| 32 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 33 | + Map<String, Object> params = new HashMap<>(); | ||
| 34 | + params.put("temperature", 25.5); | ||
| 35 | + message.setParams(params); | ||
| 36 | + | ||
| 37 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 38 | + assertEquals(25.5, result); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + @Test | ||
| 42 | + public void testExtractPropertyValue_propertiesStructure() { | ||
| 43 | + // 测试 properties 结构 | ||
| 44 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 45 | + Map<String, Object> properties = new HashMap<>(); | ||
| 46 | + properties.put("temperature", 25.5); | ||
| 47 | + properties.put("humidity", 60); | ||
| 48 | + | ||
| 49 | + Map<String, Object> params = new HashMap<>(); | ||
| 50 | + params.put("properties", properties); | ||
| 51 | + message.setParams(params); | ||
| 52 | + | ||
| 53 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 54 | + assertEquals(25.5, result); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + @Test | ||
| 58 | + public void testExtractPropertyValue_dataStructure() { | ||
| 59 | + // 测试 data 结构 | ||
| 60 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 61 | + Map<String, Object> data = new HashMap<>(); | ||
| 62 | + data.put("temperature", 25.5); | ||
| 63 | + | ||
| 64 | + Map<String, Object> params = new HashMap<>(); | ||
| 65 | + params.put("data", data); | ||
| 66 | + message.setParams(params); | ||
| 67 | + | ||
| 68 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 69 | + assertEquals(25.5, result); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + @Test | ||
| 73 | + public void testExtractPropertyValue_valueField() { | ||
| 74 | + // 测试 value 字段 | ||
| 75 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 76 | + Map<String, Object> params = new HashMap<>(); | ||
| 77 | + params.put("identifier", "temperature"); | ||
| 78 | + params.put("value", 25.5); | ||
| 79 | + message.setParams(params); | ||
| 80 | + | ||
| 81 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 82 | + assertEquals(25.5, result); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Test | ||
| 86 | + public void testExtractPropertyValue_singleValueMap() { | ||
| 87 | + // 测试单值 Map(包含 identifier 和一个值) | ||
| 88 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 89 | + Map<String, Object> params = new HashMap<>(); | ||
| 90 | + params.put("identifier", "temperature"); | ||
| 91 | + params.put("actualValue", 25.5); | ||
| 92 | + message.setParams(params); | ||
| 93 | + | ||
| 94 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 95 | + assertEquals(25.5, result); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + @Test | ||
| 99 | + public void testExtractPropertyValue_notFound() { | ||
| 100 | + // 测试未找到属性值 | ||
| 101 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 102 | + Map<String, Object> params = new HashMap<>(); | ||
| 103 | + params.put("humidity", 60); | ||
| 104 | + message.setParams(params); | ||
| 105 | + | ||
| 106 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 107 | + assertNull(result); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + @Test | ||
| 111 | + public void testExtractPropertyValue_nullParams() { | ||
| 112 | + // 测试 params 为 null | ||
| 113 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 114 | + message.setParams(null); | ||
| 115 | + | ||
| 116 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 117 | + assertNull(result); | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + @Test | ||
| 121 | + public void testExtractPropertyValue_priorityOrder() { | ||
| 122 | + // 测试优先级顺序:直接标识符 > properties > data > value | ||
| 123 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 124 | + | ||
| 125 | + Map<String, Object> properties = new HashMap<>(); | ||
| 126 | + properties.put("temperature", 20.0); | ||
| 127 | + | ||
| 128 | + Map<String, Object> data = new HashMap<>(); | ||
| 129 | + data.put("temperature", 30.0); | ||
| 130 | + | ||
| 131 | + Map<String, Object> params = new HashMap<>(); | ||
| 132 | + params.put("temperature", 25.5); // 最高优先级 | ||
| 133 | + params.put("properties", properties); | ||
| 134 | + params.put("data", data); | ||
| 135 | + params.put("value", 40.0); | ||
| 136 | + message.setParams(params); | ||
| 137 | + | ||
| 138 | + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); | ||
| 139 | + assertEquals(25.5, result); // 应该返回直接标识符的值 | ||
| 140 | + } | ||
| 141 | +} |