diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java index 216584ec20..a0f268902d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.enums.rule; +import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.common.core.ArrayValuable; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -60,7 +61,9 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable { return ARRAYS; } - // TODO @puhui999:可以参考下别的枚举哈,方法名,和实现都可以更简洁;of(String type) { firstMatch + public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } /** * 根据类型值查找触发器类型枚举 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java index f799a45147..84795d9fe5 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java @@ -26,16 +26,6 @@ public interface IotSceneRuleMatcher { return 100; } - // TODO @puhui999:如果目前没自定义,体感可以删除哈; - /** - * 获取匹配器名称,用于日志和调试 - * - * @return 匹配器名称 - */ - default String getMatcherName() { - return this.getClass().getSimpleName(); - } - /** * 是否启用该匹配器 *

diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java index dc67237786..7175e37a7e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java @@ -1,7 +1,10 @@ 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; @@ -40,44 +43,99 @@ public final class IotSceneRuleMatcherHelper { * @param paramValue 参数值(来自条件配置) * @return 是否匹配 */ - @SuppressWarnings("DataFlowIssue") public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { try { // 1. 校验操作符是否合法 IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); if (operatorEnum == null) { - log.warn("[evaluateCondition][存在错误的操作符({})]", operator); + log.warn("[evaluateCondition][operator({}) 操作符无效]", operator); return false; } // 2. 构建 Spring 表达式变量 - Map springExpressionVariables = new HashMap<>(); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); - - // 处理参数值 - if (StrUtil.isNotBlank(paramValue)) { - // 处理多值情况(如 IN、BETWEEN 操作符) - // TODO @puhui999:使用这个,会不会有问题?例如说:string 恰好有 , 分隔? - if (paramValue.contains(",")) { - List paramValues = StrUtil.split(paramValue, ','); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, - convertList(paramValues, NumberUtil::parseDouble)); - } else { - // 处理单值情况 - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, - NumberUtil.parseDouble(paramValue)); - } - } - - // 3. 计算 Spring 表达式 - return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); + return evaluateConditionWithOperatorEnum(sourceValue, operatorEnum, paramValue); } catch (Exception e) { - log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}", + 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, 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); + } + // ========== 【触发器】相关工具方法 ========== /** @@ -103,24 +161,22 @@ public final class IotSceneRuleMatcherHelper { /** * 记录触发器匹配成功日志 * - * @param matcherName 匹配器名称 * @param message 设备消息 * @param trigger 触发器配置 */ - public static void logTriggerMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - log.debug("[{}][消息({}) 匹配触发器({}) 成功]", matcherName, message.getRequestId(), trigger.getType()); + public static void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + log.debug("[isMatched][message({}) trigger({}) 匹配触发器成功]", message.getRequestId(), trigger.getType()); } /** * 记录触发器匹配失败日志 * - * @param matcherName 匹配器名称 * @param message 设备消息 * @param trigger 触发器配置 * @param reason 失败原因 */ - public static void logTriggerMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { - log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", matcherName, message.getRequestId(), trigger.getType(), reason); + public static void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { + log.debug("[isMatched][message({}) trigger({}) reason({}) 匹配触发器失败]", message.getRequestId(), trigger.getType(), reason); } // ========== 【条件】相关工具方法 ========== @@ -148,24 +204,22 @@ public final class IotSceneRuleMatcherHelper { /** * 记录条件匹配成功日志 * - * @param matcherName 匹配器名称 * @param message 设备消息 * @param condition 触发条件 */ - public static void logConditionMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - log.debug("[{}][消息({}) 匹配条件({}) 成功]", matcherName, message.getRequestId(), condition.getType()); + public static void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + log.debug("[isMatched][message({}) condition({}) 匹配条件成功]", message.getRequestId(), condition.getType()); } /** * 记录条件匹配失败日志 * - * @param matcherName 匹配器名称 * @param message 设备消息 * @param condition 触发条件 * @param reason 失败原因 */ - public static void logConditionMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { - log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", matcherName, message.getRequestId(), condition.getType(), reason); + public static void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { + log.debug("[isMatched][message({}) condition({}) reason({}) 匹配条件失败]", message.getRequestId(), condition.getType(), reason); } // ========== 【通用】工具方法 ========== diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java index d077579f70..2f6ace2616 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java @@ -14,7 +14,7 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * IoT 场景规则匹配器统一管理器 @@ -72,42 +72,34 @@ public class IotSceneRuleMatcherManager { .toList(); // 构建触发器匹配器映射表 - // TODO @puhui999:convertMap() - this.triggerMatchers = triggerMatchers.stream() - .collect(Collectors.toMap( - IotSceneRuleTriggerMatcher::getSupportedTriggerType, - Function.identity(), - (existing, replacement) -> { - log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", - existing.getSupportedTriggerType(), - existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); - return existing.getPriority() <= replacement.getPriority() ? existing : replacement; - }, - LinkedHashMap::new - )); + this.triggerMatchers = convertMap(triggerMatchers, IotSceneRuleTriggerMatcher::getSupportedTriggerType, + Function.identity(), + (existing, replacement) -> { + log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", + existing.getSupportedTriggerType(), + existing.getPriority() <= replacement.getPriority() ? + existing.getSupportedTriggerType() : replacement.getSupportedTriggerType()); + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; + }, LinkedHashMap::new); // 构建条件匹配器映射表 - this.conditionMatchers = conditionMatchers.stream() - .collect(Collectors.toMap( - IotSceneRuleConditionMatcher::getSupportedConditionType, - Function.identity(), - (existing, replacement) -> { - log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", - existing.getSupportedConditionType(), - existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); - return existing.getPriority() <= replacement.getPriority() ? existing : replacement; - }, - LinkedHashMap::new - )); + this.conditionMatchers = convertMap(conditionMatchers, IotSceneRuleConditionMatcher::getSupportedConditionType, + Function.identity(), + (existing, replacement) -> { + log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", + existing.getSupportedConditionType(), + existing.getPriority() <= replacement.getPriority() ? + existing.getSupportedConditionType() : replacement.getSupportedConditionType()); + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; + }, + LinkedHashMap::new); // 日志输出初始化信息 - log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]", + log.info("[IotSceneRuleMatcherManager][初始化完成,共加载({})个匹配器,其中触发器匹配器({})个,条件匹配器({})个]", this.allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size()); this.triggerMatchers.forEach((type, matcher) -> - log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}", - type, matcher.getMatcherName(), matcher.getPriority())); + log.info("[IotSceneRuleMatcherManager][触发器匹配器类型: ({}), 优先级: ({})] ", type, matcher.getPriority())); this.conditionMatchers.forEach((type, matcher) -> - log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}", - type, matcher.getMatcherName(), matcher.getPriority())); + log.info("[IotSceneRuleMatcherManager][条件匹配器类型: ({}), 优先级: ({})]", type, matcher.getPriority())); } /** @@ -118,27 +110,25 @@ public class IotSceneRuleMatcherManager { * @return 是否匹配 */ public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // TODO @puhui999:日志优化下;claude 打出来的日志风格,和项目有点不一样哈; if (message == null || trigger == null || trigger.getType() == null) { - log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger); + log.debug("[isMatched][message({}) trigger({}) 参数无效]", message, trigger); return false; } - IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType()); + IotSceneRuleTriggerTypeEnum triggerType = IotSceneRuleTriggerTypeEnum.typeOf(trigger.getType()); if (triggerType == null) { - log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); + log.warn("[isMatched][triggerType({}) 未知的触发器类型]", trigger.getType()); return false; } IotSceneRuleTriggerMatcher matcher = triggerMatchers.get(triggerType); if (matcher == null) { - log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); + log.warn("[isMatched][triggerType({}) 没有对应的匹配器]", triggerType); return false; } try { return matcher.isMatched(message, trigger); } catch (Exception e) { - log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}", - message, trigger, matcher.getMatcherName(), e); + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}", message, trigger, e); return false; } } @@ -152,28 +142,27 @@ public class IotSceneRuleMatcherManager { */ public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { if (message == null || condition == null || condition.getType() == null) { - log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition); + log.debug("[isConditionMatched][message({}) condition({}) 参数无效]", message, condition); return false; } // 根据条件类型查找对应的匹配器 IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType()); if (conditionType == null) { - log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType()); + log.warn("[isConditionMatched][conditionType({}) 未知的条件类型]", condition.getType()); return false; } IotSceneRuleConditionMatcher matcher = conditionMatchers.get(conditionType); if (matcher == null) { - log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType); + log.warn("[isConditionMatched][conditionType({}) 没有对应的匹配器]", conditionType); return false; } try { return matcher.isMatched(message, condition); } catch (Exception e) { - log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}", - message, condition, matcher.getMatcherName(), e); + log.error("[isConditionMatched][message({}) condition({}) 条件匹配异常]", message, condition, e); return false; } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java index b282153e17..0756c86ac3 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java @@ -1,8 +1,11 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.CharPool; import cn.hutool.core.util.StrUtil; 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 cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import lombok.extern.slf4j.Slf4j; @@ -11,6 +14,7 @@ import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.List; /** * 当前时间条件匹配器 @@ -40,115 +44,175 @@ public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); return false; } - // 2. 检查操作符和参数是否有效 + // 1.2 检查操作符和参数是否有效 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); return false; } - // 3. 根据操作符类型进行不同的时间匹配 - LocalDateTime now = LocalDateTime.now(); + // 1.3 验证操作符是否为支持的时间操作符 String operator = condition.getOperator(); - String param = condition.getParam(); - boolean matched; - try { - if (operator.startsWith("date_time_")) { - // 日期时间匹配(时间戳) - matched = matchDateTime(now, operator, param); - } else if (operator.startsWith("time_")) { - // 当日时间匹配(HH:mm:ss) - matched = matchTime(now.toLocalTime(), operator, param); - } else { - // 其他操作符,使用通用条件评估器 - matched = IotSceneRuleMatcherHelper.evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); - } - - if (matched) { - IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition); - } else { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件不匹配"); - } - } catch (Exception e) { - log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e); - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件匹配异常: " + e.getMessage()); - matched = false; + IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); + if (operatorEnum == null) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "无效的操作符: " + operator); + return false; } + + if (!isTimeOperator(operatorEnum)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "不支持的时间操作符: " + operator); + return false; + } + + // 2.1 执行时间匹配 + boolean matched = executeTimeMatching(operatorEnum, condition.getParam()); + + // 2.2 记录匹配结果 + if (matched) { + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); + } else { + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "时间条件不匹配"); + } + return matched; } /** - * 匹配日期时间(时间戳) + * 执行时间匹配逻辑 + * 直接实现时间条件匹配,不使用 Spring EL 表达式 */ - private boolean matchDateTime(LocalDateTime now, String operator, String param) { - long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); - return IotSceneRuleMatcherHelper.evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param); - } - - /** - * 匹配当日时间(HH:mm:ss) - */ - private boolean matchTime(LocalTime currentTime, String operator, String param) { + private boolean executeTimeMatching(IotSceneRuleConditionOperatorEnum operatorEnum, String param) { try { - String actualOperator = operator.substring("time_".length()); + LocalDateTime now = LocalDateTime.now(); - // TODO @puhui999:if return 简化; - if ("between".equals(actualOperator)) { - // 时间区间匹配 - String[] timeRange = param.split(","); - if (timeRange.length != 2) { - return false; - } - LocalTime startTime = parseTime(timeRange[0].trim()); - LocalTime endTime = parseTime(timeRange[1].trim()); - return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime); + if (isDateTimeOperator(operatorEnum)) { + // 日期时间匹配(时间戳) + long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); + return matchDateTime(currentTimestamp, operatorEnum, param); } else { - // 单个时间比较 - LocalTime targetTime = parseTime(param); - // TODO @puhui999:枚举类; - switch (actualOperator) { - case ">": - return currentTime.isAfter(targetTime); - case "<": - return currentTime.isBefore(targetTime); - case ">=": - return !currentTime.isBefore(targetTime); - case "<=": - return !currentTime.isAfter(targetTime); - case "=": - return currentTime.equals(targetTime); - default: - return false; - } + // 当日时间匹配(HH:mm:ss) + return matchTime(now.toLocalTime(), operatorEnum, param); } } catch (Exception e) { - // TODO @puhui999:1)日志格式 [][];2)方法名不对哈; - log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e); + log.error("[executeTimeMatching][operatorEnum({}) param({}) 时间匹配异常]", operatorEnum, param, e); return false; } } + /** + * 判断是否为日期时间操作符 + */ + private boolean isDateTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { + return operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN || + operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN || + operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN; + } + + /** + * 判断是否为时间操作符 + */ + private boolean isTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { + return operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN || + operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN || + operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_BETWEEN || + isDateTimeOperator(operatorEnum); + } + + /** + * 匹配日期时间(时间戳) + * 直接实现时间戳比较逻辑 + */ + private boolean matchDateTime(long currentTimestamp, IotSceneRuleConditionOperatorEnum operatorEnum, String param) { + try { + long targetTimestamp = Long.parseLong(param); + return switch (operatorEnum) { + case DATE_TIME_GREATER_THAN -> currentTimestamp > targetTimestamp; + case DATE_TIME_LESS_THAN -> currentTimestamp < targetTimestamp; + case DATE_TIME_BETWEEN -> matchDateTimeBetween(currentTimestamp, param); + default -> { + log.warn("[matchDateTime][operatorEnum({}) 不支持的日期时间操作符]", operatorEnum); + yield false; + } + }; + } catch (Exception e) { + log.error("[matchDateTime][operatorEnum({}) param({}) 日期时间匹配异常]", operatorEnum, param, e); + return false; + } + } + + /** + * 匹配日期时间区间 + */ + private boolean matchDateTimeBetween(long currentTimestamp, String param) { + List timestampRange = StrUtil.splitTrim(param, CharPool.COMMA); + if (timestampRange.size() != 2) { + log.warn("[matchDateTimeBetween][param({}) 时间戳区间参数格式错误]", param); + return false; + } + long startTimestamp = Long.parseLong(timestampRange.get(0).trim()); + long endTimestamp = Long.parseLong(timestampRange.get(1).trim()); + return currentTimestamp >= startTimestamp && currentTimestamp <= endTimestamp; + } + + /** + * 匹配当日时间(HH:mm:ss) + * 直接实现时间比较逻辑 + */ + private boolean matchTime(LocalTime currentTime, IotSceneRuleConditionOperatorEnum operatorEnum, String param) { + try { + LocalTime targetTime = parseTime(param); + return switch (operatorEnum) { + case TIME_GREATER_THAN -> currentTime.isAfter(targetTime); + case TIME_LESS_THAN -> currentTime.isBefore(targetTime); + case TIME_BETWEEN -> matchTimeBetween(currentTime, param); + default -> { + log.warn("[matchTime][operatorEnum({}) 不支持的时间操作符]", operatorEnum); + yield false; + } + }; + } catch (Exception e) { + log.error("[matchTime][][operatorEnum({}) param({}) 时间解析异常]", operatorEnum, param, e); + return false; + } + } + + /** + * 匹配时间区间 + */ + private boolean matchTimeBetween(LocalTime currentTime, String param) { + List timeRange = StrUtil.splitTrim(param, CharPool.COMMA); + if (timeRange.size() != 2) { + log.warn("[matchTimeBetween][param({}) 时间区间参数格式错误]", param); + return false; + } + LocalTime startTime = parseTime(timeRange.get(0).trim()); + LocalTime endTime = parseTime(timeRange.get(1).trim()); + return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime); + } + /** * 解析时间字符串 + * 支持 HH:mm 和 HH:mm:ss 两种格式 */ private LocalTime parseTime(String timeStr) { - // TODO @puhui999:可以用 hutool Assert 类简化 - if (StrUtil.isBlank(timeStr)) { - throw new IllegalArgumentException("时间字符串不能为空"); - } - // 尝试不同的时间格式 + Assert.isFalse(StrUtil.isBlank(timeStr), "时间字符串不能为空"); + try { + // 尝试不同的时间格式 if (timeStr.length() == 5) { // HH:mm return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); - } else { // HH:mm:ss + } else if (timeStr.length() == 8) { // HH:mm:ss return LocalTime.parse(timeStr, TIME_FORMATTER); + } else { + throw new IllegalArgumentException("时间格式长度不正确,期望 HH:mm 或 HH:mm:ss 格式"); } } catch (Exception e) { + log.error("[parseTime][timeStr({}) 时间格式解析失败]", timeStr, e); throw new IllegalArgumentException("时间格式无效: " + timeStr, e); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java index 5ede1c2950..0e16df7019 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java @@ -22,41 +22,40 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; } - // TODO @puhui999:参数校验的,要不要 1.1 1.2 1.3 1.4 ?这样最终看到 2. 3. 就是核心逻辑列; @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); return false; } - // 2. 检查标识符是否匹配 + // 1.2 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!IotSceneRuleMatcherHelper.isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); return false; } - // 3. 检查操作符和参数是否有效 + // 1.3 检查操作符和参数是否有效 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); return false; } - // 4. 获取属性值 - Object propertyValue = message.getData(); + // 2.1. 获取属性值 + Object propertyValue = message.getParams(); if (propertyValue == null) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空"); return false; } - // 5. 使用条件评估器进行匹配 + // 2.2 使用条件评估器进行匹配 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); if (matched) { - IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition); + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); } else { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "设备属性条件不匹配"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备属性条件不匹配"); } return matched; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java index 56063c8141..a25bef467f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java @@ -23,32 +23,31 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); return false; } - // 2. 检查操作符和参数是否有效 + // 1.2 检查操作符和参数是否有效 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); return false; } - // 3. 获取设备状态值 - // 设备状态通常在消息的 data 字段中 - Object stateValue = message.getData(); + // 2.1 获取设备状态值 + Object stateValue = message.getParams(); if (stateValue == null) { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中设备状态值为空"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空"); return false; } - // 4. 使用条件评估器进行匹配 + // 2.2 使用条件评估器进行匹配 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); if (matched) { - IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition); + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); } else { - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "设备状态条件不匹配"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备状态条件不匹配"); } return matched; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java index 957348e38f..8d0d156851 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java @@ -19,11 +19,6 @@ import org.springframework.stereotype.Component; @Component public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher { - /** - * 设备事件上报消息方法 - */ - private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; @@ -31,42 +26,44 @@ public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } - // 2. 检查消息方法是否匹配 - if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); + // 1.2 检查消息方法是否匹配 + if (!IotDeviceMessageMethodEnum.EVENT_POST.getMethod().equals(message.getMethod())) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + + IotDeviceMessageMethodEnum.EVENT_POST.getMethod() + ", 实际: " + message.getMethod()); return false; } - // 3. 检查标识符是否匹配 + // 1.3 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } - // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 + // 2. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 // 但如果配置了操作符和值,则需要进行条件匹配 if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { Object eventData = message.getData(); if (eventData == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中事件数据为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); return false; } boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); if (!matched) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "事件数据条件不匹配"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); return false; } } - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); return true; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java index 11638877dd..654305c858 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java @@ -18,12 +18,6 @@ import org.springframework.stereotype.Component; @Component public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher { - /** - * 设备属性上报消息方法 - */ - // TODO @puhui999:是不是不用枚举哈?直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() - private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; @@ -31,44 +25,46 @@ public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatc @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } - // 2. 检查消息方法是否匹配 - if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); + // 1.2 检查消息方法是否匹配 + if (!IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod().equals(message.getMethod())) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() + ", 实际: " + message.getMethod()); return false; } - // 3. 检查标识符是否匹配 + // 1.3 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } - // 4. 检查操作符和值是否有效 + // 1.4 检查操作符和值是否有效 if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效"); return false; } - // 5. 获取属性值 - Object propertyValue = message.getData(); + // 2.1 获取属性值 + Object propertyValue = message.getParams(); if (propertyValue == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空"); return false; } - // 6. 使用条件评估器进行匹配 + // 2.2 使用条件评估器进行匹配 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); if (matched) { - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); } else { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "属性值条件不匹配"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); } return matched; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java index c349fd211e..da72bdaf3c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java @@ -18,11 +18,6 @@ import org.springframework.stereotype.Component; @Component public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { - /** - * 设备服务调用消息方法 - */ - private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; @@ -30,29 +25,29 @@ public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMat @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } - // 2. 检查消息方法是否匹配 - if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); + // 1.2 检查消息方法是否匹配 + if (!IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod().equals(message.getMethod())) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod() + ", 实际: " + message.getMethod()); return false; } - // 3. 检查标识符是否匹配 + // 1.3 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } - // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 + // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可 // 不需要检查操作符和值,因为服务调用本身就是触发条件 - - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); + // TODO @puhui999: 服务调用时校验输入参数是否匹配条件 + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); return true; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java index a435002252..139b47ac7c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java @@ -17,12 +17,6 @@ import org.springframework.stereotype.Component; @Component public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { - // TODO @puhui999:是不是不用枚举哈; - /** - * 设备状态更新消息方法 - */ - private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; @@ -30,37 +24,39 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } - // 2. 检查消息方法是否匹配 - if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); + // 1.2 检查消息方法是否匹配 + if (!IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod().equals(message.getMethod())) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + + IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod() + ", 实际: " + message.getMethod()); return false; } - // 3. 检查操作符和值是否有效 + // 1.3 检查操作符和值是否有效 if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效"); return false; } - // 4. 获取设备状态值 - Object stateValue = message.getData(); + // 2.1 获取设备状态值 + Object stateValue = message.getParams(); if (stateValue == null) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中设备状态值为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); return false; } - // 5. 使用条件评估器进行匹配 + // 2.2 使用条件评估器进行匹配 + // TODO @puhui999: 状态匹配重新实现 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); if (matched) { - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); } else { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "状态值条件不匹配"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); } return matched; } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java index 6dfd3fc9e9..5c9ac13cf4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java @@ -5,6 +5,7 @@ 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.IotSceneRuleTriggerTypeEnum; import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; +import org.quartz.CronExpression; import org.springframework.stereotype.Component; /** @@ -25,58 +26,32 @@ public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher { @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 + // 1.1 基础参数校验 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); return false; } - // 2. 检查 CRON 表达式是否存在 + // 1.2 检查 CRON 表达式是否存在 if (StrUtil.isBlank(trigger.getCronExpression())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "定时触发器缺少 CRON 表达式"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); return false; } - // 3. 定时触发器通常不依赖具体的设备消息 + // 1.3 定时触发器通常不依赖具体的设备消息 // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 - - // 4. 可以添加 CRON 表达式格式验证 - if (!isValidCronExpression(trigger.getCronExpression())) { - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); + if (!CronExpression.isValidExpression(trigger.getCronExpression())) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); return false; } - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); return true; } - /** - * 验证 CRON 表达式格式是否有效 - * - * @param cronExpression CRON 表达式 - * @return 是否有效 - */ - private boolean isValidCronExpression(String cronExpression) { - // TODO @puhui999:CronExpression.isValidExpression(cronExpression); - try { - // 简单的 CRON 表达式格式验证 - // 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年]) - String[] fields = cronExpression.trim().split("\\s+"); - return fields.length >= 6 && fields.length <= 7; - } catch (Exception e) { - return false; - } - } - @Override public int getPriority() { return 50; // 最低优先级,因为定时触发器不依赖消息 } - @Override - public boolean isEnabled() { - // 定时触发器可以根据配置动态启用/禁用 - return true; - } - } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java index 7d19fd5309..f97fd5f3c2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java @@ -172,19 +172,6 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { assertFalse(matched, "无效的触发器类型应该不匹配"); } - @Test - void testMatcherManagerStatistics() { - // 1. 执行测试 - var statistics = matcherManager.getMatcherStatistics(); - - // 2. 验证结果 - assertNotNull(statistics); - assertEquals(5, statistics.get("totalMatchers")); - assertEquals(5, statistics.get("enabledMatchers")); - assertNotNull(statistics.get("supportedTriggerTypes")); - assertNotNull(statistics.get("matcherDetails")); - } - @Test void testGetSupportedTriggerTypes() { // 1. 执行测试