package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; import cn.hutool.core.text.CharPool; import cn.hutool.core.util.NumberUtil; 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.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; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** * IoT 场景规则匹配器工具类:提供通用的条件评估逻辑和工具方法,供触发器和条件匹配器使用 * * 该类包含了匹配器实现中常用的工具方法,如条件评估、参数校验、日志记录等 * * @author HUIHUI */ @Slf4j public final class IotSceneRuleMatcherHelper { /** * 私有构造函数,防止实例化 */ private IotSceneRuleMatcherHelper() { } /** * 评估条件是否匹配 * * @param sourceValue 源值(来自消息) * @param operator 操作符 * @param paramValue 参数值(来自条件配置) * @return 是否匹配 */ public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { try { // 1. 校验操作符是否合法 IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); if (operatorEnum == null) { log.warn("[evaluateCondition][operator({}) 操作符无效]", operator); return false; } // 2. 构建 Spring 表达式变量 return evaluateConditionWithOperatorEnum(sourceValue, operatorEnum, paramValue); } catch (Exception e) { log.error("[evaluateCondition][sourceValue({}) operator({}) paramValue({}) 条件评估异常]", sourceValue, operator, paramValue, e); return false; } } /** * 使用操作符枚举评估条件是否匹配 * * @param sourceValue 源值(来自消息) * @param operatorEnum 操作符枚举 * @param paramValue 参数值(来自条件配置) * @return 是否匹配 */ @SuppressWarnings("DataFlowIssue") public static boolean evaluateConditionWithOperatorEnum(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) { try { // 1. 构建 Spring 表达式变量 Map springExpressionVariables = buildSpringExpressionVariables(sourceValue, operatorEnum, paramValue); // 2. 计算 Spring 表达式 return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); } catch (Exception e) { log.error("[evaluateConditionWithOperatorEnum][sourceValue({}) operatorEnum({}) paramValue({}) 条件评估异常]", sourceValue, operatorEnum, paramValue, e); return false; } } /** * 构建 Spring 表达式变量 */ private static Map buildSpringExpressionVariables(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) { Map springExpressionVariables = new HashMap<>(); // 设置源值 springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, StrUtil.toString(sourceValue)); // 处理参数值 if (StrUtil.isNotBlank(paramValue)) { List parameterValues = StrUtil.splitTrim(paramValue, CharPool.COMMA); // 设置原始参数值 springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, paramValue); springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); // 特殊处理:解决数字比较问题 // Spring 表达式基于 compareTo 方法,对数字的比较存在问题,需要转换为数字类型 if (isNumericComparisonOperator(operatorEnum) && isNumericComparison(sourceValue, parameterValues)) { springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, NumberUtil.parseDouble(String.valueOf(sourceValue))); springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, NumberUtil.parseDouble(paramValue)); springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, convertList(parameterValues, NumberUtil::parseDouble)); } } return springExpressionVariables; } /** * 判断是否为数字比较操作符 */ private static boolean isNumericComparisonOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { return ObjectUtils.equalsAny(operatorEnum, IotSceneRuleConditionOperatorEnum.BETWEEN, IotSceneRuleConditionOperatorEnum.NOT_BETWEEN, IotSceneRuleConditionOperatorEnum.GREATER_THAN, IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS, IotSceneRuleConditionOperatorEnum.LESS_THAN, IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS); } /** * 判断是否为数字比较场景 */ private static boolean isNumericComparison(Object sourceValue, List parameterValues) { return NumberUtil.isNumber(String.valueOf(sourceValue)) && NumberUtils.isAllNumber(parameterValues); } // ========== 【触发器】相关工具方法 ========== /** * 检查基础触发器参数是否有效 * * @param trigger 触发器配置 * @return 是否有效 */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { return trigger != null && trigger.getType() != null; } /** * 检查触发器操作符和值是否有效 * * @param trigger 触发器配置 * @return 是否有效 */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); } /** * 记录触发器匹配成功日志 * * @param message 设备消息 * @param trigger 触发器配置 */ public static void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { log.debug("[isMatched][message({}) trigger({}) 匹配触发器成功]", message != null ? message.getRequestId() : null, trigger != null ? trigger.getType() : null); } /** * 记录触发器匹配失败日志 * * @param message 设备消息 * @param trigger 触发器配置 * @param reason 失败原因 */ public static void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { log.debug("[isMatched][message({}) trigger({}) reason({}) 匹配触发器失败]", message != null ? message.getRequestId() : null, trigger != null ? trigger.getType() : null, reason); } // ========== 【条件】相关工具方法 ========== /** * 检查基础条件参数是否有效 * * @param condition 触发条件 * @return 是否有效 */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { return condition != null && condition.getType() != null; } /** * 检查条件操作符和参数是否有效 * * @param condition 触发条件 * @return 是否有效 */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam()); } /** * 记录条件匹配成功日志 * * @param message 设备消息 * @param condition 触发条件 */ public static void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { log.debug("[isMatched][message({}) condition({}) 匹配条件成功]", message != null ? message.getRequestId() : null, condition != null ? condition.getType() : null); } /** * 记录条件匹配失败日志 * * @param message 设备消息 * @param condition 触发条件 * @param reason 失败原因 */ public static void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { log.debug("[isMatched][message({}) condition({}) reason({}) 匹配条件失败]", message != null ? message.getRequestId() : null, condition != null ? condition.getType() : null, reason); } // ========== 【通用】工具方法 ========== /** * 检查标识符是否匹配 * * @param expectedIdentifier 期望的标识符 * @param actualIdentifier 实际的标识符 * @return 是否匹配 */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); } }