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

This commit is contained in:
YunaiV 2025-02-02 22:08:34 +08:00
parent a4be3bb84d
commit 910bb6ca3c
4 changed files with 153 additions and 50 deletions

View File

@ -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;
}
/**
* 通过经纬度获取地球上两点之间的距离
*

View File

@ -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);
}

View File

@ -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());
}

View File

@ -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 的类型如果是 structlist 这种需要考虑下
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;
}
}
}