Преглед на файлове

perf:【iot】增强 IotDeviceServiceInvokeTriggerMatcher 参数校验

puhui999 преди 8 месеца
родител
ревизия
4ad4fcf6cf

+ 51 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcher.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
 
+import cn.hutool.core.util.StrUtil;
 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;
@@ -8,6 +9,8 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
 import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
 import org.springframework.stereotype.Component;
 
+import java.util.Map;
+
 /**
  * 设备服务调用触发器匹配器:处理设备服务调用的触发器匹配逻辑
  *
@@ -42,13 +45,58 @@ public class IotDeviceServiceInvokeTriggerMatcher implements IotSceneRuleTrigger
             return false;
         }
 
-        // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可
-        // 不需要检查操作符和值,因为服务调用本身就是触发条件
-        // TODO @puhui999: 服务调用时校验输入参数是否匹配条件?
+        // 2. 检查是否配置了参数条件
+        if (hasParameterCondition(trigger)) {
+            return matchParameterCondition(message, trigger);
+        }
+
+        // 3. 无参数条件时,标识符匹配即成功
         IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
         return true;
     }
 
+    /**
+     * 判断触发器是否配置了参数条件
+     *
+     * @param trigger 触发器配置
+     * @return 是否配置了参数条件
+     */
+    private boolean hasParameterCondition(IotSceneRuleDO.Trigger trigger) {
+        return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue());
+    }
+
+    /**
+     * 匹配参数条件
+     *
+     * @param message 设备消息
+     * @param trigger 触发器配置
+     * @return 是否匹配
+     */
+    private boolean matchParameterCondition(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
+        // 从消息中提取服务调用的输入参数
+        Map<String, Object> inputParams = IotDeviceMessageUtils.extractServiceInputParams(message);
+        if (inputParams == null) {
+            IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中缺少服务输入参数");
+            return false;
+        }
+
+        // 获取要匹配的参数值(使用 identifier 作为参数名)
+        Object paramValue = inputParams.get(trigger.getIdentifier());
+        if (paramValue == null) {
+            IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "服务输入参数中缺少指定参数: " + trigger.getIdentifier());
+            return false;
+        }
+
+        // 使用条件评估器进行匹配
+        boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(paramValue, trigger.getOperator(), trigger.getValue());
+        if (matched) {
+            IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
+        } else {
+            IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "服务输入参数条件不匹配");
+        }
+        return matched;
+    }
+
     @Override
     public int getPriority() {
         return 40; // 较低优先级

+ 262 - 2
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceServiceInvokeTriggerMatcherTest.java

@@ -7,7 +7,6 @@ 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.IotBaseConditionMatcherTest;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import java.util.HashMap;
@@ -23,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.*;
  *
  * @author HUIHUI
  */
-@Disabled // TODO @puhui999:单测有报错,先屏蔽
 public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMatcherTest {
 
     private IotDeviceServiceInvokeTriggerMatcher matcher;
@@ -378,6 +376,268 @@ public class IotDeviceServiceInvokeTriggerMatcherTest extends IotBaseConditionMa
         assertFalse(result);
     }
 
+
+    // ========== 参数条件匹配测试 ==========
+
+    /**
+     * 测试无参数条件时的匹配逻辑 - 只要标识符匹配就返回 true
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.2**
+     */
+    @Test
+    public void testMatches_noParameterCondition_success() {
+        // 准备参数
+        String serviceIdentifier = "testService";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputData", MapUtil.builder(new HashMap<String, Object>())
+                        .put("level", 5)
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(null); // 无参数条件
+        trigger.setValue(null);
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言
+        assertTrue(result);
+    }
+
+    /**
+     * 测试有参数条件时的匹配逻辑 - 参数条件匹配成功
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.1**
+     */
+    @Test
+    public void testMatches_withParameterCondition_greaterThan_success() {
+        // 准备参数
+        String serviceIdentifier = "level";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputData", MapUtil.builder(new HashMap<String, Object>())
+                        .put("level", 5)
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(">"); // 大于操作符
+        trigger.setValue("3");
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言
+        assertTrue(result);
+    }
+
+    /**
+     * 测试有参数条件时的匹配逻辑 - 参数条件匹配失败
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.1**
+     */
+    @Test
+    public void testMatches_withParameterCondition_greaterThan_failure() {
+        // 准备参数
+        String serviceIdentifier = "level";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputData", MapUtil.builder(new HashMap<String, Object>())
+                        .put("level", 2)
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(">"); // 大于操作符
+        trigger.setValue("3");
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言
+        assertFalse(result);
+    }
+
+    /**
+     * 测试有参数条件时的匹配逻辑 - 等于操作符
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.1**
+     */
+    @Test
+    public void testMatches_withParameterCondition_equals_success() {
+        // 准备参数
+        String serviceIdentifier = "mode";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputData", MapUtil.builder(new HashMap<String, Object>())
+                        .put("mode", "auto")
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator("=="); // 等于操作符
+        trigger.setValue("auto");
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言
+        assertTrue(result);
+    }
+
+    /**
+     * 测试参数缺失时的处理 - 消息中缺少 inputData
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.3**
+     */
+    @Test
+    public void testMatches_withParameterCondition_missingInputData() {
+        // 准备参数
+        String serviceIdentifier = "testService";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                // 缺少 inputData 字段
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(">"); // 配置了参数条件
+        trigger.setValue("3");
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言
+        assertFalse(result);
+    }
+
+    /**
+     * 测试参数缺失时的处理 - inputData 中缺少指定参数
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.3**
+     */
+    @Test
+    public void testMatches_withParameterCondition_missingParam() {
+        // 准备参数
+        String serviceIdentifier = "level";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputData", MapUtil.builder(new HashMap<String, Object>())
+                        .put("otherParam", 5) // 不是 level 参数
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(">"); // 配置了参数条件
+        trigger.setValue("3");
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言
+        assertFalse(result);
+    }
+
+    /**
+     * 测试只有 operator 没有 value 时不触发参数条件匹配
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.2**
+     */
+    @Test
+    public void testMatches_onlyOperator_noValue() {
+        // 准备参数
+        String serviceIdentifier = "testService";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputData", MapUtil.builder(new HashMap<String, Object>())
+                        .put("level", 5)
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(">"); // 只有 operator
+        trigger.setValue(null); // 没有 value
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言:只有 operator 没有 value 时,不触发参数条件匹配,标识符匹配即成功
+        assertTrue(result);
+    }
+
+    /**
+     * 测试只有 value 没有 operator 时不触发参数条件匹配
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.2**
+     */
+    @Test
+    public void testMatches_onlyValue_noOperator() {
+        // 准备参数
+        String serviceIdentifier = "testService";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputData", MapUtil.builder(new HashMap<String, Object>())
+                        .put("level", 5)
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(null); // 没有 operator
+        trigger.setValue("3"); // 只有 value
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言:只有 value 没有 operator 时,不触发参数条件匹配,标识符匹配即成功
+        assertTrue(result);
+    }
+
+    /**
+     * 测试使用 inputParams 字段(替代 inputData)
+     * **Property 4: 服务调用触发器参数匹配逻辑**
+     * **Validates: Requirements 5.1**
+     */
+    @Test
+    public void testMatches_withInputParams_success() {
+        // 准备参数
+        String serviceIdentifier = "level";
+        Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
+                .put("identifier", serviceIdentifier)
+                .put("inputParams", MapUtil.builder(new HashMap<String, Object>()) // 使用 inputParams 而不是 inputData
+                        .put("level", 5)
+                        .build())
+                .build();
+        IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
+        IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
+        trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
+        trigger.setIdentifier(serviceIdentifier);
+        trigger.setOperator(">"); // 大于操作符
+        trigger.setValue("3");
+
+        // 调用
+        boolean result = matcher.matches(message, trigger);
+
+        // 断言
+        assertTrue(result);
+    }
+
     // ========== 辅助方法 ==========
 
     /**

+ 38 - 0
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java

@@ -207,6 +207,44 @@ public class IotDeviceMessageUtils {
         return null;
     }
 
+    /**
+     * 从服务调用消息中提取输入参数
+     * <p>
+     * 服务调用消息的 params 结构通常为:
+     * {
+     * "identifier": "serviceIdentifier",
+     * "inputData": { ... } 或 "inputParams": { ... }
+     * }
+     *
+     * @param message 设备消息
+     * @return 输入参数 Map,如果未找到则返回 null
+     */
+    @SuppressWarnings("unchecked")
+    public static Map<String, Object> extractServiceInputParams(IotDeviceMessage message) {
+        Object params = message.getParams();
+        if (params == null) {
+            return null;
+        }
+        if (!(params instanceof Map)) {
+            return null;
+        }
+        Map<String, Object> paramsMap = (Map<String, Object>) params;
+
+        // 尝试从 inputData 字段获取
+        Object inputData = paramsMap.get("inputData");
+        if (inputData instanceof Map) {
+            return (Map<String, Object>) inputData;
+        }
+
+        // 尝试从 inputParams 字段获取
+        Object inputParams = paramsMap.get("inputParams");
+        if (inputParams instanceof Map) {
+            return (Map<String, Object>) inputParams;
+        }
+
+        return null;
+    }
+
     // ========== Topic 相关 ==========
 
     public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) {