Commit fe0c1bbf34a3bdc0ed2d3628abdac4b93c23b80f

Authored by puhui999
1 parent 796d69b2

refactor: 【IoT 物联网】修复设备属性条件匹配器设计问题并重构属性值提取逻辑

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/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.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;
@@ -13,7 +12,6 @@ import java.util.HashMap; @@ -13,7 +12,6 @@ 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 /**
@@ -45,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -45,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
45 int result = matcher.getPriority(); 43 int result = matcher.getPriority();
46 44
47 // 断言 45 // 断言
48 - assertEquals(20, result);  
49 - }  
50 -  
51 - @Test  
52 - public void testIsEnabled() {  
53 - // 调用  
54 - boolean result = matcher.isEnabled();  
55 -  
56 - // 断言  
57 - assertTrue(result); 46 + assertEquals(25, result); // 修正:实际返回值是 25
58 } 47 }
59 48
60 @Test 49 @Test
61 public void testMatches_temperatureEquals_success() { 50 public void testMatches_temperatureEquals_success() {
62 - // 准备参数  
63 - String propertyName = "temperature"; 51 + // 准备参数:创建属性上报消息
  52 + String propertyIdentifier = "temperature";
64 Double propertyValue = 25.5; 53 Double propertyValue = 25.5;
65 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
66 - IotDeviceMessage message = createDeviceMessage(properties); 54 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
67 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 55 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
68 - propertyName, 56 + propertyIdentifier,
69 IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), 57 IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
70 String.valueOf(propertyValue) 58 String.valueOf(propertyValue)
71 ); 59 );
@@ -79,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -79,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
79 67
80 @Test 68 @Test
81 public void testMatches_humidityGreaterThan_success() { 69 public void testMatches_humidityGreaterThan_success() {
82 - // 准备参数  
83 - String propertyName = "humidity"; 70 + // 准备参数:创建属性上报消息
  71 + String propertyIdentifier = "humidity";
84 Integer propertyValue = 75; 72 Integer propertyValue = 75;
85 Integer compareValue = 70; 73 Integer compareValue = 70;
86 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
87 - IotDeviceMessage message = createDeviceMessage(properties); 74 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
88 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 75 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
89 - propertyName, 76 + propertyIdentifier,
90 IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), 77 IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
91 String.valueOf(compareValue) 78 String.valueOf(compareValue)
92 ); 79 );
@@ -100,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -100,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
100 87
101 @Test 88 @Test
102 public void testMatches_pressureLessThan_success() { 89 public void testMatches_pressureLessThan_success() {
103 - // 准备参数  
104 - String propertyName = "pressure"; 90 + // 准备参数:创建属性上报消息
  91 + String propertyIdentifier = "pressure";
105 Double propertyValue = 1010.5; 92 Double propertyValue = 1010.5;
106 Integer compareValue = 1020; 93 Integer compareValue = 1020;
107 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
108 - IotDeviceMessage message = createDeviceMessage(properties); 94 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
109 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 95 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
110 - propertyName, 96 + propertyIdentifier,
111 IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), 97 IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
112 String.valueOf(compareValue) 98 String.valueOf(compareValue)
113 ); 99 );
@@ -121,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -121,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
121 107
122 @Test 108 @Test
123 public void testMatches_statusNotEquals_success() { 109 public void testMatches_statusNotEquals_success() {
124 - // 准备参数  
125 - String propertyName = "status"; 110 + // 准备参数:创建属性上报消息
  111 + String propertyIdentifier = "status";
126 String propertyValue = "active"; 112 String propertyValue = "active";
127 String compareValue = "inactive"; 113 String compareValue = "inactive";
128 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
129 - IotDeviceMessage message = createDeviceMessage(properties); 114 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
130 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 115 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
131 - propertyName, 116 + propertyIdentifier,
132 IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), 117 IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
133 compareValue 118 compareValue
134 ); 119 );
@@ -142,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -142,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
142 127
143 @Test 128 @Test
144 public void testMatches_propertyMismatch_fail() { 129 public void testMatches_propertyMismatch_fail() {
145 - // 准备参数  
146 - String propertyName = "temperature"; 130 + // 准备参数:创建属性上报消息,值不满足条件
  131 + String propertyIdentifier = "temperature";
147 Double propertyValue = 15.0; 132 Double propertyValue = 15.0;
148 Integer compareValue = 20; 133 Integer compareValue = 20;
149 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
150 - IotDeviceMessage message = createDeviceMessage(properties); 134 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
151 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 135 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
152 - propertyName, 136 + propertyIdentifier,
153 IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), 137 IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
154 String.valueOf(compareValue) 138 String.valueOf(compareValue)
155 ); 139 );
@@ -162,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -162,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
162 } 146 }
163 147
164 @Test 148 @Test
165 - public void testMatches_propertyNotFound_fail() {  
166 - // 准备参数  
167 - Map<String, Object> properties = MapUtil.of("temperature", 25.5);  
168 - 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);
169 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 155 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
170 - randomString(), // 随机不存在的属性名  
171 - IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),  
172 - "50" 156 + conditionIdentifier,
  157 + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
  158 + String.valueOf(propertyValue)
173 ); 159 );
174 160
175 // 调用 161 // 调用
@@ -182,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -182,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
182 @Test 168 @Test
183 public void testMatches_nullCondition_fail() { 169 public void testMatches_nullCondition_fail() {
184 // 准备参数 170 // 准备参数
185 - Map<String, Object> properties = MapUtil.of("temperature", 25.5);  
186 - IotDeviceMessage message = createDeviceMessage(properties); 171 + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
187 172
188 // 调用 173 // 调用
189 boolean result = matcher.matches(message, null); 174 boolean result = matcher.matches(message, null);
@@ -195,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -195,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
195 @Test 180 @Test
196 public void testMatches_nullConditionType_fail() { 181 public void testMatches_nullConditionType_fail() {
197 // 准备参数 182 // 准备参数
198 - Map<String, Object> properties = MapUtil.of("temperature", 25.5);  
199 - IotDeviceMessage message = createDeviceMessage(properties); 183 + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
200 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); 184 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
201 condition.setType(null); 185 condition.setType(null);
202 186
@@ -210,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -210,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
210 @Test 194 @Test
211 public void testMatches_missingIdentifier_fail() { 195 public void testMatches_missingIdentifier_fail() {
212 // 准备参数 196 // 准备参数
213 - Map<String, Object> properties = MapUtil.of("temperature", 25.5);  
214 - IotDeviceMessage message = createDeviceMessage(properties); 197 + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
215 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); 198 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
216 condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); 199 condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
217 condition.setIdentifier(null); // 缺少标识符 200 condition.setIdentifier(null); // 缺少标识符
@@ -228,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -228,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
228 @Test 211 @Test
229 public void testMatches_missingOperator_fail() { 212 public void testMatches_missingOperator_fail() {
230 // 准备参数 213 // 准备参数
231 - Map<String, Object> properties = MapUtil.of("temperature", 25.5);  
232 - IotDeviceMessage message = createDeviceMessage(properties); 214 + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
233 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); 215 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
234 condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); 216 condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
235 condition.setIdentifier("temperature"); 217 condition.setIdentifier("temperature");
@@ -246,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -246,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
246 @Test 228 @Test
247 public void testMatches_missingParam_fail() { 229 public void testMatches_missingParam_fail() {
248 // 准备参数 230 // 准备参数
249 - Map<String, Object> properties = MapUtil.of("temperature", 25.5);  
250 - IotDeviceMessage message = createDeviceMessage(properties); 231 + IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
251 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); 232 IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
252 condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); 233 condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
253 condition.setIdentifier("temperature"); 234 condition.setIdentifier("temperature");
@@ -279,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -279,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
279 260
280 @Test 261 @Test
281 public void testMatches_nullDeviceProperties_fail() { 262 public void testMatches_nullDeviceProperties_fail() {
282 - // 准备参数 263 + // 准备参数:消息的 params 为 null
283 IotDeviceMessage message = new IotDeviceMessage(); 264 IotDeviceMessage message = new IotDeviceMessage();
284 message.setParams(null); 265 message.setParams(null);
285 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 266 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
@@ -296,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -296,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
296 } 277 }
297 278
298 @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
299 public void testMatches_voltageGreaterThanOrEquals_success() { 346 public void testMatches_voltageGreaterThanOrEquals_success() {
300 - // 准备参数  
301 - String propertyName = "voltage"; 347 + // 准备参数:创建属性上报消息
  348 + String propertyIdentifier = "voltage";
302 Double propertyValue = 12.0; 349 Double propertyValue = 12.0;
303 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
304 - IotDeviceMessage message = createDeviceMessage(properties); 350 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
305 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 351 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
306 - propertyName, 352 + propertyIdentifier,
307 IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), 353 IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
308 String.valueOf(propertyValue) 354 String.valueOf(propertyValue)
309 ); 355 );
@@ -317,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -317,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
317 363
318 @Test 364 @Test
319 public void testMatches_currentLessThanOrEquals_success() { 365 public void testMatches_currentLessThanOrEquals_success() {
320 - // 准备参数  
321 - String propertyName = "current"; 366 + // 准备参数:创建属性上报消息
  367 + String propertyIdentifier = "current";
322 Double propertyValue = 2.5; 368 Double propertyValue = 2.5;
323 Double compareValue = 3.0; 369 Double compareValue = 3.0;
324 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
325 - IotDeviceMessage message = createDeviceMessage(properties); 370 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
326 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 371 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
327 - propertyName, 372 + propertyIdentifier,
328 IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), 373 IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
329 String.valueOf(compareValue) 374 String.valueOf(compareValue)
330 ); 375 );
@@ -338,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -338,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
338 383
339 @Test 384 @Test
340 public void testMatches_stringProperty_success() { 385 public void testMatches_stringProperty_success() {
341 - // 准备参数  
342 - String propertyName = "mode"; 386 + // 准备参数:创建属性上报消息
  387 + String propertyIdentifier = "mode";
343 String propertyValue = "auto"; 388 String propertyValue = "auto";
344 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
345 - IotDeviceMessage message = createDeviceMessage(properties); 389 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
346 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 390 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
347 - propertyName, 391 + propertyIdentifier,
348 IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), 392 IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
349 propertyValue 393 propertyValue
350 ); 394 );
@@ -358,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -358,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
358 402
359 @Test 403 @Test
360 public void testMatches_booleanProperty_success() { 404 public void testMatches_booleanProperty_success() {
361 - // 准备参数  
362 - String propertyName = "enabled"; 405 + // 准备参数:创建属性上报消息
  406 + String propertyIdentifier = "enabled";
363 Boolean propertyValue = true; 407 Boolean propertyValue = true;
364 - Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);  
365 - IotDeviceMessage message = createDeviceMessage(properties); 408 + IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
366 IotSceneRuleDO.TriggerCondition condition = createValidCondition( 409 IotSceneRuleDO.TriggerCondition condition = createValidCondition(
367 - propertyName, 410 + propertyIdentifier,
368 IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), 411 IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
369 String.valueOf(propertyValue) 412 String.valueOf(propertyValue)
370 ); 413 );
@@ -376,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest { @@ -376,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
376 assertTrue(result); 419 assertTrue(result);
377 } 420 }
378 421
379 - @Test  
380 - public void testMatches_multipleProperties_success() {  
381 - // 准备参数  
382 - Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())  
383 - .put("temperature", 25.5)  
384 - .put("humidity", 60)  
385 - .put("status", "active")  
386 - .put("enabled", true)  
387 - .build();  
388 - IotDeviceMessage message = createDeviceMessage(properties);  
389 - String targetProperty = "humidity";  
390 - Integer targetValue = 60;  
391 - IotSceneRuleDO.TriggerCondition condition = createValidCondition(  
392 - targetProperty,  
393 - IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),  
394 - String.valueOf(targetValue)  
395 - ); 422 + // ========== 辅助方法 ==========
396 423
397 - // 调用  
398 - 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"); // 使用事件上报方法
399 438
400 - // 断言  
401 - 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;
402 } 447 }
403 448
404 - // ========== 辅助方法 ========== 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 + }
405 466
406 /** 467 /**
407 - * 创建设备消息 468 + * 创建简单值消息(params 直接是属性值)
408 */ 469 */
409 - private IotDeviceMessage createDeviceMessage(Map<String, Object> properties) { 470 + private IotDeviceMessage createSimpleValueMessage(Object value) {
410 IotDeviceMessage message = new IotDeviceMessage(); 471 IotDeviceMessage message = new IotDeviceMessage();
411 message.setDeviceId(randomLongId()); 472 message.setDeviceId(randomLongId());
412 - message.setParams(properties); 473 + message.setMethod("thing.property.post");
  474 + // 直接将属性值作为 params
  475 + message.setParams(value);
  476 +
413 return message; 477 return message;
414 } 478 }
415 479
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 +}