瀏覽代碼

【代码新增】IoT:完善 IotRuleSceneServiceImpl 的规则匹配计算,isTriggerConditionParameterMatched 函数有点长,= = 捉摸咋优化下

YunaiV 6 月之前
父節點
當前提交
910bb6ca3c

+ 14 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java

@@ -1,9 +1,11 @@
 package cn.iocoder.yudao.framework.common.util.number;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 /**
  * 数字的工具类,补全 {@link cn.hutool.core.util.NumberUtil} 的功能
@@ -20,6 +22,18 @@ public class NumberUtils {
         return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null;
     }
 
+    public static boolean isAllNumber(List<String> values) {
+        if (CollUtil.isEmpty(values)) {
+            return false;
+        }
+        for (String value : values) {
+            if (!NumberUtil.isNumber(value)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * 通过经纬度获取地球上两点之间的距离
      *

+ 14 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringExpressionUtils.java

@@ -97,12 +97,26 @@ public class SpringExpressionUtils {
      * @return 执行界面
      */
     public static Object parseExpression(String expressionString) {
+        return parseExpression(expressionString, null);
+    }
+
+    /**
+     * 从 Bean 工厂,解析 EL 表达式的结果
+     *
+     * @param expressionString EL 表达式
+     * @param variables        变量
+     * @return 执行界面
+     */
+    public static Object parseExpression(String expressionString, Map<String, Object> variables) {
         if (StrUtil.isBlank(expressionString)) {
             return null;
         }
         Expression expression = EXPRESSION_PARSER.parseExpression(expressionString);
         StandardEvaluationContext context = new StandardEvaluationContext();
         context.setBeanResolver(new BeanFactoryResolver(SpringUtil.getApplicationContext()));
+        if (MapUtil.isNotEmpty(variables)) {
+            context.setVariables(variables);
+        }
         return expression.getValue(context);
     }
 

+ 25 - 12
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneTriggerConditionParameterOperatorEnum.java

@@ -16,29 +16,42 @@ import java.util.Arrays;
 @Getter
 public enum IotRuleSceneTriggerConditionParameterOperatorEnum implements ArrayValuable<String> {
 
-    EQUALS("=", "%s == %s"),
-    NOT_EQUALS("!=", "%s != %s"),
+    EQUALS("=", "#source == #value"),
+    NOT_EQUALS("!=", "!(#source == #value)"),
 
-    GREATER_THAN(">", "%s > %s"),
-    GREATER_THAN_OR_EQUALS(">=", "%s >= %s"),
+    GREATER_THAN(">", "#source > #value"),
+    GREATER_THAN_OR_EQUALS(">=", "#source >= #value"),
 
-    LESS_THAN("<", "%s < %s"),
-    LESS_THAN_OR_EQUALS("<=", "%s <= %s"),
+    LESS_THAN("<", "#source < #value"),
+    LESS_THAN_OR_EQUALS("<=", "#source <= #value"),
 
-    IN("in", "%s in { %s }"),
-    NOT_IN("not in", "%s not in { %s }"),
+    IN("in", "#values.contains(#source)"),
+    NOT_IN("not in", "!(#values.contains(#source))"),
 
-    BETWEEN("between", "(%s >= %s) && (%s <= %s)"),
-    NOT_BETWEEN("not between", "!(%s between %s and %s)"),
+    BETWEEN("between", "(#source >= #values.get(0)) && (#source <= #values.get(1))"),
+    NOT_BETWEEN("not between", "(#source < #values.get(0)) || (#source > #values.get(1))"),
 
-    LIKE("like", "%s like %s"), // 字符串匹配
-    NOT_NULL("not null", ""); // 非空
+    LIKE("like", "#source.contains(#value)"), // 字符串匹配
+    NOT_NULL("not null", "#source != null && #source.length() > 0"); // 非空
 
     private final String operator;
     private final String springExpression;
 
     public static final String[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneTriggerConditionParameterOperatorEnum::getOperator).toArray(String[]::new);
 
+    /**
+     * Spring 表达式 - 原始值
+     */
+    public static final String SPRING_EXPRESSION_SOURCE = "source";
+    /**
+     * Spring 表达式 - 目标值
+     */
+    public static final String SPRING_EXPRESSION_VALUE = "value";
+    /**
+     * Spring 表达式 - 目标值数组
+     */
+    public static final String SPRING_EXPRESSION_VALUE_List = "values";
+
     public static IotRuleSceneTriggerConditionParameterOperatorEnum operatorOf(String operator) {
         return ArrayUtil.firstMatch(item -> item.getOperator().equals(operator), values());
     }

+ 100 - 38
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneServiceImpl.java

@@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.iot.service.rule;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.text.CharPool;
+import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
@@ -20,9 +23,11 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
 
 /**
@@ -48,10 +53,16 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
             IotRuleSceneDO.Trigger trigger01 = new IotRuleSceneDO.Trigger();
             trigger01.setType(IotRuleSceneTriggerTypeEnum.DEVICE.getType());
             trigger01.setConditions(CollUtil.newArrayList());
+            // 属性
             IotRuleSceneDO.TriggerCondition condition01 = new IotRuleSceneDO.TriggerCondition();
             condition01.setType(IotDeviceMessageTypeEnum.PROPERTY.getType());
             condition01.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_REPORT.getIdentifier());
             condition01.setParameters(CollUtil.newArrayList());
+//            IotRuleSceneDO.TriggerConditionParameter parameter010 = new IotRuleSceneDO.TriggerConditionParameter();
+//            parameter010.setIdentifier("width");
+//            parameter010.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.getOperator());
+//            parameter010.setValue("abc");
+//            condition01.getParameters().add(parameter010);
             IotRuleSceneDO.TriggerConditionParameter parameter011 = new IotRuleSceneDO.TriggerConditionParameter();
             parameter011.setIdentifier("width");
             parameter011.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.getOperator());
@@ -103,8 +114,23 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
             parameter020.setValue("2,3");
             condition01.getParameters().add(parameter020);
             trigger01.getConditions().add(condition01);
+            // 状态
+            IotRuleSceneDO.TriggerCondition condition02 = new IotRuleSceneDO.TriggerCondition();
+            condition02.setType(IotDeviceMessageTypeEnum.STATE.getType());
+            condition02.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier());
+            condition02.setParameters(CollUtil.newArrayList());
+            trigger01.getConditions().add(condition02);
+            // TODO 芋艿:事件
+            IotRuleSceneDO.TriggerCondition condition03 = new IotRuleSceneDO.TriggerCondition();
+            condition03.setType(IotDeviceMessageTypeEnum.EVENT.getType());
+            condition03.setIdentifier("xxx");
+            condition03.setParameters(CollUtil.newArrayList());
+            IotRuleSceneDO.TriggerConditionParameter parameter030 = new IotRuleSceneDO.TriggerConditionParameter();
+            parameter030.setIdentifier("width");
+            parameter030.setOperator(IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS.getOperator());
+            parameter030.setValue("1");
+            trigger01.getConditions().add(condition03);
             ruleScene01.getTriggers().add(trigger01);
-
             return ListUtil.toList(ruleScene01);
         }
 
@@ -139,7 +165,6 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
      * @param message 设备消息
      * @return 规则场景列表
      */
-    @SuppressWarnings("unchecked")
     private List<IotRuleSceneDO> getMatchedRuleSceneList(IotDeviceMessage message) {
         // 1. 匹配设备
         // TODO @芋艿:可能需要 getSelf(); 缓存
@@ -152,54 +177,27 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
         // 2. 匹配 trigger 触发器的条件
         return filterList(ruleScenes, ruleScene -> {
             for (IotRuleSceneDO.Trigger trigger : ruleScene.getTriggers()) {
-                // 非设备触发,不匹配
+                // 2.1 非设备触发,不匹配
                 if (ObjUtil.notEqual(trigger.getType(), IotRuleSceneTriggerTypeEnum.DEVICE.getType())) {
                     return false;
                 }
                 // TODO 芋艿:产品、设备的匹配,要不要这里在做一次???貌似和 1. 部分重复了
-                // 条件为空,说明没有匹配的条件,因此不匹配
+                // 2.2 条件为空,说明没有匹配的条件,因此不匹配
                 if (CollUtil.isEmpty(trigger.getConditions())) {
                     return false;
                 }
-                IotRuleSceneDO.TriggerCondition found = CollUtil.findOne(trigger.getConditions(), condition -> {
+                // 2.3 多个条件,只需要满足一个即可
+                IotRuleSceneDO.TriggerCondition matchedCondition = CollUtil.findOne(trigger.getConditions(), condition -> {
                     if (ObjUtil.notEqual(message.getType(), condition.getType())
                             || ObjUtil.notEqual(message.getIdentifier(), condition.getIdentifier())) {
                         return false;
                     }
-                    // TODO @芋艿:设备上线,需要测试下。
-                    for (IotRuleSceneDO.TriggerConditionParameter parameter : condition.getParameters()) {
-                        // 计算是否匹配
-                        IotRuleSceneTriggerConditionParameterOperatorEnum operator =
-                                IotRuleSceneTriggerConditionParameterOperatorEnum.operatorOf(parameter.getOperator());
-                        if (operator == null) {
-                            log.error("[getMatchedRuleSceneList][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
-                                    ruleScene.getId(), trigger, parameter.getOperator());
-                            return false;
-                        }
-                        Object messageValue = ((Map<String, Object>) message.getData()).get(parameter.getIdentifier());
-                        if (messageValue == null) {
-                            return false;
-                        }
-                        String springExpression;
-                        if (ObjectUtils.equalsAny(operator, IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN,
-                                IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN)) {
-                            String[] parameterValues = StrUtil.splitToArray(parameter.getValue(), CharPool.COMMA);
-                            springExpression = String.format(operator.getSpringExpression(), messageValue, parameterValues[0],
-                                    messageValue, parameterValues[1]);
-                        } else {
-                            springExpression = String.format(operator.getSpringExpression(), messageValue, parameter.getValue());
-                        }
-                        // TODO @芋艿:【需优化】需要考虑 struct、时间等参数的比较
-                        try {
-                            System.out.println(SpringExpressionUtils.parseExpression(springExpression));
-                        } catch (Exception e) {
-                            log.error("[getMatchedRuleSceneList][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}) 计算异常]",
-                                    message, ruleScene.getId(), trigger, springExpression, e);
-                        }
-                    }
-                    return true;
+                    // 多个条件参数,必须全部满足。所以,下面的逻辑就是找到一个不满足的条件参数
+                    IotRuleSceneDO.TriggerConditionParameter notMatchedParameter = CollUtil.findOne(condition.getParameters(),
+                            parameter -> !isTriggerConditionParameterMatched(message, parameter, ruleScene, trigger));
+                    return notMatchedParameter == null;
                 });
-                if (found == null) {
+                if (matchedCondition == null) {
                     return false;
                 }
                 log.info("[getMatchedRuleSceneList][消息({}) 匹配到规则场景编号({}) 的触发器({})]", message, ruleScene.getId(), trigger);
@@ -209,4 +207,68 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
         });
     }
 
+    // TODO @芋艿:【可优化】可以考虑增加下单测,边界太多了。
+    /**
+     * 判断触发器的条件参数是否匹配
+     *
+     * @param message   设备消息
+     * @param parameter 触发器条件参数
+     * @param ruleScene 规则场景(用于日志,无其它作用)
+     * @param trigger   触发器(用于日志,无其它作用)
+     * @return 是否匹配
+     */
+    @SuppressWarnings({"unchecked", "DataFlowIssue"})
+    private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotRuleSceneDO.TriggerConditionParameter parameter,
+                                                       IotRuleSceneDO ruleScene, IotRuleSceneDO.Trigger trigger) {
+        // 计算是否匹配
+        IotRuleSceneTriggerConditionParameterOperatorEnum operator =
+                IotRuleSceneTriggerConditionParameterOperatorEnum.operatorOf(parameter.getOperator());
+        if (operator == null) {
+            log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
+                    ruleScene.getId(), trigger, parameter.getOperator());
+            return false;
+        }
+        // TODO @芋艿:目前只支持方便转换成 string 的类型,如果是 struct、list 这种,需要考虑下
+        String messageValue = MapUtil.getStr((Map<String, Object>) message.getData(), parameter.getIdentifier());
+        if (messageValue == null) {
+            return false;
+        }
+        Map<String, Object> springExpressionVariables = new HashMap<>();
+        try {
+            springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue);
+            springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_VALUE, parameter.getValue());
+            if (ObjectUtils.equalsAny(operator, IotRuleSceneTriggerConditionParameterOperatorEnum.IN,
+                    IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN)) {
+                springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_VALUE_List,
+                        StrUtil.split(parameter.getValue(), CharPool.COMMA));
+            } else if (ObjectUtils.equalsAny(operator, IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN,
+                    IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN)) {
+                List<String> parameterValues = StrUtil.splitTrim(parameter.getValue(), CharPool.COMMA);
+                if (NumberUtil.isNumber(messageValue) && NumberUtils.isAllNumber(parameterValues)) {  // 特殊:解决数字的比较
+                    springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_SOURCE,
+                            NumberUtil.parseDouble(messageValue));
+                    springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_VALUE_List,
+                            convertList(parameterValues, NumberUtil::parseDouble));
+                } else {
+                    springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_VALUE_List, parameterValues);
+                }
+            } else if (ObjectUtils.equalsAny(operator, IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN,
+                    IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS,
+                    IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN,
+                    IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS)) {
+                if (NumberUtil.isNumber(messageValue) && NumberUtil.isNumber(parameter.getValue())) {  // 特殊:解决数字的比较
+                    springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_SOURCE,
+                            NumberUtil.parseDouble(messageValue));
+                    springExpressionVariables.put(IotRuleSceneTriggerConditionParameterOperatorEnum.SPRING_EXPRESSION_VALUE,
+                            NumberUtil.parseDouble(parameter.getValue()));
+                }
+            }
+            return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables);
+        } catch (Exception e) {
+            log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]",
+                    message, ruleScene.getId(), trigger, operator, springExpressionVariables, e);
+            return false;
+        }
+    }
+
 }