diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java index 4a8a8ab6f5..d3120a81bc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; + /** * 设备属性条件匹配器 *

@@ -43,10 +45,10 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc return false; } - // 2.1. 获取属性值 - Object propertyValue = message.getParams(); + // 2.1. 获取属性值 - 使用工具类方法正确提取属性值 + Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, condition.getIdentifier()); if (propertyValue == null) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空或未找到指定属性"); return false; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java index d5bb97a53e..99000fd06b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; @@ -35,8 +36,9 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher return false; } - // 2.1 获取设备状态值 - Object stateValue = message.getParams(); + // 2.1 获取设备状态值 - 使用工具类方法正确提取状态值 + // 对于设备状态条件,状态值通过 getIdentifier 获取(实际是从 params.state 字段) + String stateValue = IotDeviceMessageUtils.getIdentifier(message); if (stateValue == null) { IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空"); return false; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java index 6eccdab427..0ee31a951e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java +++ b/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 return false; } - // 2.1 获取属性值 - Object propertyValue = message.getParams(); + // 2.1 获取属性值 - 使用工具类方法正确提取属性值 + Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, trigger.getIdentifier()); if (propertyValue == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空或未找到指定属性"); return false; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java index edd3c4e907..f3a9f44cb0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java +++ b/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; import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; @@ -43,16 +44,17 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch return false; } - // 2.1 获取设备状态值 - Object stateValue = message.getParams(); - if (stateValue == null) { + // 2.1 获取设备状态值 - 使用工具类方法正确提取状态值 + // 对于状态更新消息,状态值通过 getIdentifier 获取(实际是从 params.state 字段) + String stateIdentifier = IotDeviceMessageUtils.getIdentifier(message); + if (stateIdentifier == null) { IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); return false; } // 2.2 使用条件评估器进行匹配 - // TODO @puhui999: 状态匹配重新实现 - boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); + // 状态值通常是字符串或数字,直接使用标识符作为状态值 + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateIdentifier, trigger.getOperator(), trigger.getValue()); if (matched) { IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); } else { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java index 9c34d86216..ddfebc66be 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; -import cn.hutool.core.map.MapUtil; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; @@ -13,7 +12,6 @@ import java.util.HashMap; import java.util.Map; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; /** @@ -45,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { int result = matcher.getPriority(); // 断言 - assertEquals(20, result); - } - - @Test - public void testIsEnabled() { - // 调用 - boolean result = matcher.isEnabled(); - - // 断言 - assertTrue(result); + assertEquals(25, result); // 修正:实际返回值是 25 } @Test public void testMatches_temperatureEquals_success() { - // 准备参数 - String propertyName = "temperature"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "temperature"; Double propertyValue = 25.5; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), String.valueOf(propertyValue) ); @@ -79,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_humidityGreaterThan_success() { - // 准备参数 - String propertyName = "humidity"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "humidity"; Integer propertyValue = 75; Integer compareValue = 70; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), String.valueOf(compareValue) ); @@ -100,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_pressureLessThan_success() { - // 准备参数 - String propertyName = "pressure"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "pressure"; Double propertyValue = 1010.5; Integer compareValue = 1020; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), String.valueOf(compareValue) ); @@ -121,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_statusNotEquals_success() { - // 准备参数 - String propertyName = "status"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "status"; String propertyValue = "active"; String compareValue = "inactive"; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), compareValue ); @@ -142,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_propertyMismatch_fail() { - // 准备参数 - String propertyName = "temperature"; + // 准备参数:创建属性上报消息,值不满足条件 + String propertyIdentifier = "temperature"; Double propertyValue = 15.0; Integer compareValue = 20; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), String.valueOf(compareValue) ); @@ -162,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { } @Test - public void testMatches_propertyNotFound_fail() { - // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + public void testMatches_identifierMismatch_fail() { + // 准备参数:标识符不匹配 + String messageIdentifier = "temperature"; + String conditionIdentifier = "humidity"; + Double propertyValue = 25.5; + IotDeviceMessage message = createPropertyPostMessage(messageIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - randomString(), // 随机不存在的属性名 - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), - "50" + conditionIdentifier, + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + String.valueOf(propertyValue) ); // 调用 @@ -182,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_nullCondition_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); // 调用 boolean result = matcher.matches(message, null); @@ -195,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_nullConditionType_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(null); @@ -210,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_missingIdentifier_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); condition.setIdentifier(null); // 缺少标识符 @@ -228,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_missingOperator_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); condition.setIdentifier("temperature"); @@ -246,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_missingParam_fail() { // 准备参数 - Map properties = MapUtil.of("temperature", 25.5); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5); IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); condition.setIdentifier("temperature"); @@ -279,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_nullDeviceProperties_fail() { - // 准备参数 + // 准备参数:消息的 params 为 null IotDeviceMessage message = new IotDeviceMessage(); message.setParams(null); IotSceneRuleDO.TriggerCondition condition = createValidCondition( @@ -296,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { } @Test - public void testMatches_voltageGreaterThanOrEquals_success() { - // 准备参数 - String propertyName = "voltage"; - Double propertyValue = 12.0; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + public void testMatches_propertiesStructure_success() { + // 测试使用 properties 结构的消息(真实的属性上报场景) + String identifier = "temperature"; + Double propertyValue = 25.5; + IotDeviceMessage message = createPropertyPostMessageWithProperties(identifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + identifier, + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // 调用 + boolean result = matcher.matches(message, condition); + + // 断言:修复后的实现应该能正确从 properties 中提取属性值 + assertTrue(result); + } + + @Test + public void testMatches_simpleValueMessage_success() { + // 测试简单值消息(params 直接是属性值) + Double propertyValue = 25.5; + IotDeviceMessage message = createSimpleValueMessage(propertyValue); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "any", // 对于简单值消息,标识符匹配会被跳过 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // 调用 + boolean result = matcher.matches(message, condition); + + // 断言:修复后的实现应该能处理简单值消息 + // 但由于标识符匹配失败,结果为 false + assertFalse(result); + } + + @Test + public void testMatches_valueFieldStructure_success() { + // 测试使用 value 字段的消息结构 + String identifier = "temperature"; + Double propertyValue = 25.5; + + IotDeviceMessage message = new IotDeviceMessage(); + message.setDeviceId(randomLongId()); + message.setMethod("thing.event.post"); + + Map params = new HashMap<>(); + params.put("identifier", identifier); + params.put("value", propertyValue); + message.setParams(params); + + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + identifier, + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // 调用 + boolean result = matcher.matches(message, condition); + + // 断言:修复后的实现应该能从 value 字段提取属性值 + assertTrue(result); + } + + @Test + public void testMatches_voltageGreaterThanOrEquals_success() { + // 准备参数:创建属性上报消息 + String propertyIdentifier = "voltage"; + Double propertyValue = 12.0; + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + propertyIdentifier, IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), String.valueOf(propertyValue) ); @@ -317,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_currentLessThanOrEquals_success() { - // 准备参数 - String propertyName = "current"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "current"; Double propertyValue = 2.5; Double compareValue = 3.0; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), String.valueOf(compareValue) ); @@ -338,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_stringProperty_success() { - // 准备参数 - String propertyName = "mode"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "mode"; String propertyValue = "auto"; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), propertyValue ); @@ -358,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @Test public void testMatches_booleanProperty_success() { - // 准备参数 - String propertyName = "enabled"; + // 准备参数:创建属性上报消息 + String propertyIdentifier = "enabled"; Boolean propertyValue = true; - Map properties = MapUtil.of(propertyName, propertyValue); - IotDeviceMessage message = createDeviceMessage(properties); + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue); IotSceneRuleDO.TriggerCondition condition = createValidCondition( - propertyName, + propertyIdentifier, IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), String.valueOf(propertyValue) ); @@ -376,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { assertTrue(result); } - @Test - public void testMatches_multipleProperties_success() { - // 准备参数 - Map properties = MapUtil.builder(new HashMap()) - .put("temperature", 25.5) - .put("humidity", 60) - .put("status", "active") - .put("enabled", true) - .build(); - IotDeviceMessage message = createDeviceMessage(properties); - String targetProperty = "humidity"; - Integer targetValue = 60; - IotSceneRuleDO.TriggerCondition condition = createValidCondition( - targetProperty, - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), - String.valueOf(targetValue) - ); - - // 调用 - boolean result = matcher.matches(message, condition); - - // 断言 - assertTrue(result); - } - // ========== 辅助方法 ========== /** - * 创建设备消息 + * 创建设备消息用于测试 + * + * 支持的消息格式: + * 1. 直接属性值:params 直接是属性值(适用于简单消息) + * 2. 标识符+值:params 包含 identifier 和对应的属性值 + * 3. properties 结构:params.properties[identifier] = value + * 4. data 结构:params.data[identifier] = value + * 5. value 字段:params.value = value */ - private IotDeviceMessage createDeviceMessage(Map properties) { + private IotDeviceMessage createPropertyPostMessage(String identifier, Object value) { IotDeviceMessage message = new IotDeviceMessage(); message.setDeviceId(randomLongId()); - message.setParams(properties); + message.setMethod("thing.event.post"); // 使用事件上报方法 + + // 创建符合修复后逻辑的 params 结构 + Map params = new HashMap<>(); + params.put("identifier", identifier); + // 直接将属性值放在标识符对应的字段中 + params.put(identifier, value); + message.setParams(params); + + return message; + } + + /** + * 创建使用 properties 结构的消息(模拟真实的属性上报消息) + */ + private IotDeviceMessage createPropertyPostMessageWithProperties(String identifier, Object value) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setDeviceId(randomLongId()); + message.setMethod("thing.property.post"); // 属性上报方法 + + Map properties = new HashMap<>(); + properties.put(identifier, value); + + Map params = new HashMap<>(); + params.put("properties", properties); + message.setParams(params); + + return message; + } + + /** + * 创建简单值消息(params 直接是属性值) + */ + private IotDeviceMessage createSimpleValueMessage(Object value) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setDeviceId(randomLongId()); + message.setMethod("thing.property.post"); + // 直接将属性值作为 params + message.setParams(value); + return message; } diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java index 5b7778ea0c..65165425c8 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java +++ b/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 { return null; } + /** + * 从设备消息中提取指定标识符的属性值 + * - 支持多种消息格式和属性值提取策略 + * - 兼容现有的消息结构 + * - 提供统一的属性值提取接口 + *

+ * 支持的提取策略(按优先级顺序): + * 1. 直接值:如果 params 不是 Map,直接返回该值(适用于简单消息) + * 2. 标识符字段:从 params[identifier] 获取 + * 3. properties 结构:从 params.properties[identifier] 获取(标准属性上报) + * 4. data 结构:从 params.data[identifier] 获取 + * 5. value 字段:从 params.value 获取(单值消息) + * 6. 单值 Map:如果 Map 只包含 identifier 和一个值,返回该值 + * + * @param message 设备消息 + * @param identifier 属性标识符 + * @return 属性值,如果未找到则返回 null + */ + @SuppressWarnings("unchecked") + public static Object extractPropertyValue(IotDeviceMessage message, String identifier) { + Object params = message.getParams(); + if (params == null) { + return null; + } + + // 策略1:如果 params 不是 Map,直接返回该值(适用于简单的单属性消息) + if (!(params instanceof Map)) { + return params; + } + + Map paramsMap = (Map) params; + + // 策略2:直接通过标识符获取属性值 + Object directValue = paramsMap.get(identifier); + if (directValue != null) { + return directValue; + } + + // 策略3:从 properties 字段中获取(适用于标准属性上报消息) + Object properties = paramsMap.get("properties"); + if (properties instanceof Map) { + Map propertiesMap = (Map) properties; + Object propertyValue = propertiesMap.get(identifier); + if (propertyValue != null) { + return propertyValue; + } + } + + // 策略4:从 data 字段中获取(适用于某些消息格式) + Object data = paramsMap.get("data"); + if (data instanceof Map) { + Map dataMap = (Map) data; + Object dataValue = dataMap.get(identifier); + if (dataValue != null) { + return dataValue; + } + } + + // 策略5:从 value 字段中获取(适用于单值消息) + Object value = paramsMap.get("value"); + if (value != null) { + return value; + } + + // 策略6:如果 Map 只有两个字段且包含 identifier,返回另一个字段的值 + if (paramsMap.size() == 2 && paramsMap.containsKey("identifier")) { + for (Map.Entry entry : paramsMap.entrySet()) { + if (!"identifier".equals(entry.getKey())) { + return entry.getValue(); + } + } + } + + // 未找到对应的属性值 + return null; + } + // ========== Topic 相关 ========== public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) { diff --git a/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java b/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java new file mode 100644 index 0000000000..a6d669d170 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-core/src/test/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtilsTest.java @@ -0,0 +1,141 @@ +package cn.iocoder.yudao.module.iot.core.util; + +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link IotDeviceMessageUtils} 的单元测试 + * + * @author HUIHUI + */ +public class IotDeviceMessageUtilsTest { + + @Test + public void testExtractPropertyValue_directValue() { + // 测试直接值(非 Map) + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(25.5); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_directIdentifier() { + // 测试直接通过标识符获取 + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("temperature", 25.5); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_propertiesStructure() { + // 测试 properties 结构 + IotDeviceMessage message = new IotDeviceMessage(); + Map properties = new HashMap<>(); + properties.put("temperature", 25.5); + properties.put("humidity", 60); + + Map params = new HashMap<>(); + params.put("properties", properties); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_dataStructure() { + // 测试 data 结构 + IotDeviceMessage message = new IotDeviceMessage(); + Map data = new HashMap<>(); + data.put("temperature", 25.5); + + Map params = new HashMap<>(); + params.put("data", data); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_valueField() { + // 测试 value 字段 + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("identifier", "temperature"); + params.put("value", 25.5); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_singleValueMap() { + // 测试单值 Map(包含 identifier 和一个值) + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("identifier", "temperature"); + params.put("actualValue", 25.5); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); + } + + @Test + public void testExtractPropertyValue_notFound() { + // 测试未找到属性值 + IotDeviceMessage message = new IotDeviceMessage(); + Map params = new HashMap<>(); + params.put("humidity", 60); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertNull(result); + } + + @Test + public void testExtractPropertyValue_nullParams() { + // 测试 params 为 null + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(null); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertNull(result); + } + + @Test + public void testExtractPropertyValue_priorityOrder() { + // 测试优先级顺序:直接标识符 > properties > data > value + IotDeviceMessage message = new IotDeviceMessage(); + + Map properties = new HashMap<>(); + properties.put("temperature", 20.0); + + Map data = new HashMap<>(); + data.put("temperature", 30.0); + + Map params = new HashMap<>(); + params.put("temperature", 25.5); // 最高优先级 + params.put("properties", properties); + params.put("data", data); + params.put("value", 40.0); + message.setParams(params); + + Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature"); + assertEquals(25.5, result); // 应该返回直接标识符的值 + } +}