From 1e17e3578dca74eeb2a8a01397543f9d634e074b Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sun, 17 Aug 2025 11:22:53 +0800 Subject: [PATCH 1/5] =?UTF-8?q?perf:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8=E8=A7=84?= =?UTF-8?q?=E5=88=99=E6=A0=B9=E6=8D=AE=E7=B1=BB=E5=9E=8B=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=20trigger=E3=80=81condition=20=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/rule/scene/matcher/IotSceneRuleMatcher.java | 6 ++---- .../rule/scene/matcher/IotSceneRuleMatcherManager.java | 4 ++-- .../{ => condition}/CurrentTimeConditionMatcher.java | 7 ++++--- .../{ => condition}/DevicePropertyConditionMatcher.java | 7 ++++--- .../{ => condition}/DeviceStateConditionMatcher.java | 7 ++++--- .../{ => trigger}/DeviceEventPostTriggerMatcher.java | 7 ++++--- .../{ => trigger}/DevicePropertyPostTriggerMatcher.java | 7 ++++--- .../{ => trigger}/DeviceServiceInvokeTriggerMatcher.java | 7 ++++--- .../{ => trigger}/DeviceStateUpdateTriggerMatcher.java | 7 ++++--- .../scene/matcher/{ => trigger}/TimerTriggerMatcher.java | 7 ++++--- .../rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java | 1 + 11 files changed, 37 insertions(+), 30 deletions(-) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => condition}/CurrentTimeConditionMatcher.java (95%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => condition}/DevicePropertyConditionMatcher.java (90%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => condition}/DeviceStateConditionMatcher.java (88%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => trigger}/DeviceEventPostTriggerMatcher.java (92%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => trigger}/DevicePropertyPostTriggerMatcher.java (92%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => trigger}/DeviceServiceInvokeTriggerMatcher.java (90%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => trigger}/DeviceStateUpdateTriggerMatcher.java (90%) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{ => trigger}/TimerTriggerMatcher.java (91%) 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 b9b439c786..fb86fa9ffe 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 @@ -18,12 +18,10 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; */ public interface IotSceneRuleMatcher { - // TODO @puhui999:MatcherTypeEnum; - // TODO @puhui999:可以考虑根据类型,新建 trigger、condition 包,然后把对应的实现类放进去哈; /** * 匹配器类型枚举 */ - enum MatcherType { + enum MatcherTypeEnum { /** * 触发器匹配器 - 用于匹配主触发条件 @@ -42,7 +40,7 @@ public interface IotSceneRuleMatcher { * * @return 匹配器类型 */ - MatcherType getMatcherType(); + MatcherTypeEnum getMatcherType(); // TODO @puhui999:【重要】有个思路,IotSceneRuleMatcher 拆分成 2 种 mather 接口;然后 AbstractIotSceneRuleMatcher 是个 Helper 工具类; 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 e95e553cab..a73e915d73 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 @@ -61,10 +61,10 @@ public class IotSceneRuleMatcherManager { // 分离触发器匹配器和条件匹配器 List triggerMatchers = this.allMatchers.stream() - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER) + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherTypeEnum.TRIGGER) .toList(); List conditionMatchers = this.allMatchers.stream() - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherTypeEnum.CONDITION) .toList(); // 构建触发器匹配器映射表 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 95% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java index 94e7401b63..1ddfe71356 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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,9 +1,10 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; 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.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -33,8 +34,8 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); @Override - public MatcherType getMatcherType() { - return MatcherType.CONDITION; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.CONDITION; } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 90% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java index 37381500b9..8ba2bfbb1a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 @@ -1,9 +1,10 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; import org.springframework.stereotype.Component; /** @@ -17,8 +18,8 @@ import org.springframework.stereotype.Component; public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher { @Override - public MatcherType getMatcherType() { - return MatcherType.CONDITION; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.CONDITION; } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 88% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java index 69d3a7dcb7..8bffae9425 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 @@ -1,8 +1,9 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; 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.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; import org.springframework.stereotype.Component; /** @@ -16,8 +17,8 @@ import org.springframework.stereotype.Component; public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { @Override - public MatcherType getMatcherType() { - return MatcherType.CONDITION; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.CONDITION; } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 92% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java index 3c832f6553..edd5ea3a8e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +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; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 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.AbstractIotSceneRuleMatcher; import org.springframework.stereotype.Component; /** @@ -24,8 +25,8 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.TRIGGER; } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 92% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java index ed228fc72d..b01f7f409a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 @@ -1,10 +1,11 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; 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; 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.AbstractIotSceneRuleMatcher; import org.springframework.stereotype.Component; /** @@ -24,8 +25,8 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatche private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.TRIGGER; } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 90% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java index c2b7e4ef82..074d4afa70 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 @@ -1,10 +1,11 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; 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; 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.AbstractIotSceneRuleMatcher; import org.springframework.stereotype.Component; /** @@ -23,8 +24,8 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatch private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.TRIGGER; } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 90% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java index 3a2a0e712f..68d9ca4507 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 @@ -1,9 +1,10 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; 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.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; import org.springframework.stereotype.Component; /** @@ -23,8 +24,8 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.TRIGGER; } @Override diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 similarity index 91% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java index edc18771a3..1ae0cee66c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 @@ -1,9 +1,10 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; 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.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; import org.springframework.stereotype.Component; /** @@ -18,8 +19,8 @@ import org.springframework.stereotype.Component; public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.TRIGGER; } @Override 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 ff5e28397a..7d19fd5309 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 @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 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.trigger.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; From 34f1a2ed71d5d09f48061a56a208eeaa0b5d7edf Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sun, 17 Aug 2025 22:04:59 +0800 Subject: [PATCH 2/5] =?UTF-8?q?perf:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=8C=B9=E9=85=8D=E6=8E=A5=E5=8F=A3=E6=8B=86=E5=88=86?= =?UTF-8?q?=E4=BD=BF=E8=81=8C=E8=B4=A3=E6=9B=B4=E6=B8=85=E6=99=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scene/matcher/IotSceneRuleMatcher.java | 83 ++---------------- ...er.java => IotSceneRuleMatcherHelper.java} | 66 ++++++++------ .../matcher/IotSceneRuleMatcherManager.java | 85 +++---------------- .../CurrentTimeConditionMatcher.java | 27 +++--- .../DevicePropertyConditionMatcher.java | 29 +++---- .../DeviceStateConditionMatcher.java | 25 +++--- .../IotSceneRuleConditionMatcher.java | 38 +++++++++ .../DeviceEventPostTriggerMatcher.java | 27 +++--- .../DevicePropertyPostTriggerMatcher.java | 31 +++---- .../DeviceServiceInvokeTriggerMatcher.java | 21 ++--- .../DeviceStateUpdateTriggerMatcher.java | 27 +++--- .../trigger/IotSceneRuleTriggerMatcher.java | 38 +++++++++ .../matcher/trigger/TimerTriggerMatcher.java | 19 ++--- 13 files changed, 217 insertions(+), 299 deletions(-) rename yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/{AbstractIotSceneRuleMatcher.java => IotSceneRuleMatcherHelper.java} (65%) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java 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 fb86fa9ffe..f799a45147 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 @@ -1,91 +1,20 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; -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.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher; /** - * IoT 场景规则匹配器统一接口 + * IoT 场景规则匹配器基础接口 *

- * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计 + * 定义所有匹配器的通用行为,包括优先级、名称和启用状态 *

- * 匹配器类型说明: - * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等) - * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等) + * - {@link IotSceneRuleTriggerMatcher} 触发器匹配器 + * - {@link IotSceneRuleConditionMatcher} 条件匹配器 * * @author HUIHUI */ public interface IotSceneRuleMatcher { - /** - * 匹配器类型枚举 - */ - enum MatcherTypeEnum { - - /** - * 触发器匹配器 - 用于匹配主触发条件 - */ - TRIGGER, - - /** - * 条件匹配器 - 用于匹配子条件 - */ - CONDITION - - } - - /** - * 获取匹配器类型 - * - * @return 匹配器类型 - */ - MatcherTypeEnum getMatcherType(); - - // TODO @puhui999:【重要】有个思路,IotSceneRuleMatcher 拆分成 2 种 mather 接口;然后 AbstractIotSceneRuleMatcher 是个 Helper 工具类; - - // TODO @puhui999:是不是和 AbstractSceneRuleMatcher 一样,分下块; - - /** - * 获取支持的触发器类型(仅触发器匹配器需要实现) - * - * @return 触发器类型枚举,条件匹配器返回 null - */ - default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return null; - } - - /** - * 获取支持的条件类型(仅条件匹配器需要实现) - * - * @return 条件类型枚举,触发器匹配器返回 null - */ - default IotSceneRuleConditionTypeEnum getSupportedConditionType() { - return null; - } - - /** - * 检查触发器是否匹配消息(仅触发器匹配器需要实现) - * - * @param message 设备消息 - * @param trigger 触发器配置 - * @return 是否匹配 - */ - default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - throw new UnsupportedOperationException("触发器匹配方法仅支持触发器匹配器"); - } - - /** - * 检查条件是否匹配消息(仅条件匹配器需要实现) - * - * @param message 设备消息 - * @param condition 触发条件 - * @return 是否匹配 - */ - default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - throw new UnsupportedOperationException("条件匹配方法仅支持条件匹配器"); - } - /** * 获取匹配优先级(数值越小优先级越高) *

diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java similarity index 65% rename from yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java rename to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java index 5d48bba293..dc67237786 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java @@ -15,16 +15,22 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** - * IoT 场景规则匹配器抽象基类 + * IoT 场景规则匹配器工具类 *

- * 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型 + * 提供通用的条件评估逻辑和工具方法,供触发器和条件匹配器使用 + *

+ * 该类包含了匹配器实现中常用的工具方法,如条件评估、参数校验、日志记录等 * * @author HUIHUI */ @Slf4j -public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher { +public final class IotSceneRuleMatcherHelper { - // TODO @puhui999:这个是不是也是【通用】条件哈? + /** + * 私有构造函数,防止实例化 + */ + private IotSceneRuleMatcherHelper() { + } /** * 评估条件是否匹配 @@ -35,7 +41,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher * @return 是否匹配 */ @SuppressWarnings("DataFlowIssue") - protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { + public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { try { // 1. 校验操作符是否合法 IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); @@ -80,7 +86,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher * @param trigger 触发器配置 * @return 是否有效 */ - protected boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { + public static boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { return trigger != null && trigger.getType() != null; } @@ -90,29 +96,31 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher * @param trigger 触发器配置 * @return 是否有效 */ - protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { + public static boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); } /** * 记录触发器匹配成功日志 * - * @param message 设备消息 - * @param trigger 触发器配置 + * @param matcherName 匹配器名称 + * @param message 设备消息 + * @param trigger 触发器配置 */ - protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); + public static void logTriggerMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + log.debug("[{}][消息({}) 匹配触发器({}) 成功]", matcherName, message.getRequestId(), trigger.getType()); } /** * 记录触发器匹配失败日志 * - * @param message 设备消息 - * @param trigger 触发器配置 - * @param reason 失败原因 + * @param matcherName 匹配器名称 + * @param message 设备消息 + * @param trigger 触发器配置 + * @param reason 失败原因 */ - protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { - log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); + public static void logTriggerMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { + log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", matcherName, message.getRequestId(), trigger.getType(), reason); } // ========== 【条件】相关工具方法 ========== @@ -123,7 +131,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher * @param condition 触发条件 * @return 是否有效 */ - protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { + public static boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { return condition != null && condition.getType() != null; } @@ -133,29 +141,31 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher * @param condition 触发条件 * @return 是否有效 */ - protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { + public static boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam()); } /** * 记录条件匹配成功日志 * - * @param message 设备消息 - * @param condition 触发条件 + * @param matcherName 匹配器名称 + * @param message 设备消息 + * @param condition 触发条件 */ - protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType()); + public static void logConditionMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + log.debug("[{}][消息({}) 匹配条件({}) 成功]", matcherName, message.getRequestId(), condition.getType()); } /** * 记录条件匹配失败日志 * - * @param message 设备消息 - * @param condition 触发条件 - * @param reason 失败原因 + * @param matcherName 匹配器名称 + * @param message 设备消息 + * @param condition 触发条件 + * @param reason 失败原因 */ - protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { - log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason); + public static void logConditionMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { + log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", matcherName, message.getRequestId(), condition.getType(), reason); } // ========== 【通用】工具方法 ========== @@ -167,7 +177,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher * @param actualIdentifier 实际的标识符 * @return 是否匹配 */ - protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { + public static boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); } 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 a73e915d73..d077579f70 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 @@ -5,6 +5,8 @@ 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.IotSceneRuleConditionTypeEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -30,14 +32,14 @@ public class IotSceneRuleMatcherManager { * Key: 触发器类型枚举 * Value: 对应的匹配器实例 */ - private final Map triggerMatchers; + private final Map triggerMatchers; /** * 条件匹配器映射表 * Key: 条件类型枚举 * Value: 对应的匹配器实例 */ - private final Map conditionMatchers; + private final Map conditionMatchers; /** * 所有匹配器列表(按优先级排序) @@ -60,18 +62,20 @@ public class IotSceneRuleMatcherManager { .collect(Collectors.toList()); // 分离触发器匹配器和条件匹配器 - List triggerMatchers = this.allMatchers.stream() - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherTypeEnum.TRIGGER) + List triggerMatchers = this.allMatchers.stream() + .filter(matcher -> matcher instanceof IotSceneRuleTriggerMatcher) + .map(matcher -> (IotSceneRuleTriggerMatcher) matcher) .toList(); - List conditionMatchers = this.allMatchers.stream() - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherTypeEnum.CONDITION) + List conditionMatchers = this.allMatchers.stream() + .filter(matcher -> matcher instanceof IotSceneRuleConditionMatcher) + .map(matcher -> (IotSceneRuleConditionMatcher) matcher) .toList(); // 构建触发器匹配器映射表 // TODO @puhui999:convertMap() this.triggerMatchers = triggerMatchers.stream() .collect(Collectors.toMap( - IotSceneRuleMatcher::getSupportedTriggerType, + IotSceneRuleTriggerMatcher::getSupportedTriggerType, Function.identity(), (existing, replacement) -> { log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", @@ -84,7 +88,7 @@ public class IotSceneRuleMatcherManager { // 构建条件匹配器映射表 this.conditionMatchers = conditionMatchers.stream() .collect(Collectors.toMap( - IotSceneRuleMatcher::getSupportedConditionType, + IotSceneRuleConditionMatcher::getSupportedConditionType, Function.identity(), (existing, replacement) -> { log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", @@ -124,7 +128,7 @@ public class IotSceneRuleMatcherManager { log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); return false; } - IotSceneRuleMatcher matcher = triggerMatchers.get(triggerType); + IotSceneRuleTriggerMatcher matcher = triggerMatchers.get(triggerType); if (matcher == null) { log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); return false; @@ -159,7 +163,7 @@ public class IotSceneRuleMatcherManager { return false; } - IotSceneRuleMatcher matcher = conditionMatchers.get(conditionType); + IotSceneRuleConditionMatcher matcher = conditionMatchers.get(conditionType); if (matcher == null) { log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType); return false; @@ -205,65 +209,4 @@ public class IotSceneRuleMatcherManager { return new HashSet<>(conditionMatchers.keySet()); } - // TODO @puhui999:用不到的方法,可以去掉先哈; - - /** - * 获取指定触发器类型的匹配器 - * - * @param triggerType 触发器类型 - * @return 匹配器实例,如果不存在则返回 null - */ - public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) { - return triggerMatchers.get(triggerType); - } - - /** - * 获取指定条件类型的匹配器 - * - * @param conditionType 条件类型 - * @return 匹配器实例,如果不存在则返回 null - */ - public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) { - return conditionMatchers.get(conditionType); - } - - // TODO @puhui999:是不是不用这个哈;直接 @Getter,单测直接处理; - /** - * 获取所有匹配器的统计信息 - * - * @return 统计信息映射表 - */ - public Map getMatcherStatistics() { - Map statistics = new HashMap<>(); - statistics.put("totalMatchers", allMatchers.size()); - statistics.put("triggerMatchers", triggerMatchers.size()); - statistics.put("conditionMatchers", conditionMatchers.size()); - statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); - statistics.put("supportedConditionTypes", getSupportedConditionTypes()); - - // 触发器匹配器详情 - Map triggerMatcherDetails = new HashMap<>(); - triggerMatchers.forEach((type, matcher) -> { - Map detail = new HashMap<>(); - detail.put("matcherName", matcher.getMatcherName()); - detail.put("priority", matcher.getPriority()); - detail.put("enabled", matcher.isEnabled()); - triggerMatcherDetails.put(type.name(), detail); - }); - statistics.put("triggerMatcherDetails", triggerMatcherDetails); - - // 条件匹配器详情 - Map conditionMatcherDetails = new HashMap<>(); - conditionMatchers.forEach((type, matcher) -> { - Map detail = new HashMap<>(); - detail.put("matcherName", matcher.getMatcherName()); - detail.put("priority", matcher.getPriority()); - detail.put("enabled", matcher.isEnabled()); - conditionMatcherDetails.put(type.name(), detail); - }); - statistics.put("conditionMatcherDetails", conditionMatcherDetails); - - return statistics; - } - } 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 1ddfe71356..b282153e17 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 @@ -4,7 +4,7 @@ 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.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -21,7 +21,7 @@ import java.time.format.DateTimeFormatter; */ @Component @Slf4j -public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { +public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher { /** * 时间格式化器 - HH:mm:ss @@ -33,11 +33,6 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { */ private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.CONDITION; - } - @Override public IotSceneRuleConditionTypeEnum getSupportedConditionType() { return IotSceneRuleConditionTypeEnum.CURRENT_TIME; @@ -46,14 +41,14 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { // 1. 基础参数校验 - if (!isBasicConditionValid(condition)) { - logConditionMatchFailure(message, condition, "条件基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效"); return false; } // 2. 检查操作符和参数是否有效 - if (!isConditionOperatorAndParamValid(condition)) { - logConditionMatchFailure(message, condition, "操作符或参数无效"); + if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效"); return false; } @@ -71,17 +66,17 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { matched = matchTime(now.toLocalTime(), operator, param); } else { // 其他操作符,使用通用条件评估器 - matched = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); + matched = IotSceneRuleMatcherHelper.evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); } if (matched) { - logConditionMatchSuccess(message, condition); + IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition); } else { - logConditionMatchFailure(message, condition, "时间条件不匹配"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件不匹配"); } } catch (Exception e) { log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e); - logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage()); + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件匹配异常: " + e.getMessage()); matched = false; } return matched; @@ -92,7 +87,7 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { */ private boolean matchDateTime(LocalDateTime now, String operator, String param) { long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); - return evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param); + return IotSceneRuleMatcherHelper.evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param); } /** 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 8ba2bfbb1a..5ede1c2950 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 @@ -4,7 +4,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; /** @@ -15,12 +15,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher { - - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.CONDITION; - } +public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher { @Override public IotSceneRuleConditionTypeEnum getSupportedConditionType() { @@ -31,37 +26,37 @@ public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { // 1. 基础参数校验 - if (!isBasicConditionValid(condition)) { - logConditionMatchFailure(message, condition, "条件基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效"); return false; } // 2. 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { - logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); return false; } // 3. 检查操作符和参数是否有效 - if (!isConditionOperatorAndParamValid(condition)) { - logConditionMatchFailure(message, condition, "操作符或参数无效"); + if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效"); return false; } // 4. 获取属性值 Object propertyValue = message.getData(); if (propertyValue == null) { - logConditionMatchFailure(message, condition, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中属性值为空"); return false; } // 5. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); if (matched) { - logConditionMatchSuccess(message, condition); + IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition); } else { - logConditionMatchFailure(message, condition, "设备属性条件不匹配"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), 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 8bffae9425..56063c8141 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 @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; 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.IotSceneRuleConditionTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; /** @@ -14,12 +14,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { - - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.CONDITION; - } +public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher { @Override public IotSceneRuleConditionTypeEnum getSupportedConditionType() { @@ -29,14 +24,14 @@ public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { // 1. 基础参数校验 - if (!isBasicConditionValid(condition)) { - logConditionMatchFailure(message, condition, "条件基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效"); return false; } // 2. 检查操作符和参数是否有效 - if (!isConditionOperatorAndParamValid(condition)) { - logConditionMatchFailure(message, condition, "操作符或参数无效"); + if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效"); return false; } @@ -44,16 +39,16 @@ public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { // 设备状态通常在消息的 data 字段中 Object stateValue = message.getData(); if (stateValue == null) { - logConditionMatchFailure(message, condition, "消息中设备状态值为空"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中设备状态值为空"); return false; } // 4. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); if (matched) { - logConditionMatchSuccess(message, condition); + IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition); } else { - logConditionMatchFailure(message, condition, "设备状态条件不匹配"); + IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), 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/IotSceneRuleConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java new file mode 100644 index 0000000000..2e44b1174d --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + +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.IotSceneRuleConditionTypeEnum; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher; + +/** + * IoT 场景规则条件匹配器接口 + *

+ * 专门处理子条件的匹配逻辑,如设备状态、属性值、时间条件等 + *

+ * 条件匹配器负责判断设备消息是否满足场景规则的附加条件, + * 在触发器匹配成功后进行进一步的条件筛选 + * + * @author HUIHUI + */ +public interface IotSceneRuleConditionMatcher extends IotSceneRuleMatcher { + + /** + * 获取支持的条件类型 + * + * @return 条件类型枚举 + */ + IotSceneRuleConditionTypeEnum getSupportedConditionType(); + + /** + * 检查条件是否匹配消息 + *

+ * 判断设备消息是否满足指定的触发条件 + * + * @param message 设备消息 + * @param condition 触发条件 + * @return 是否匹配 + */ + boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition); + +} 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 edd5ea3a8e..957348e38f 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 @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 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.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; /** @@ -17,18 +17,13 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { +public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher { /** * 设备事件上报消息方法 */ private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.TRIGGER; - } - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; @@ -37,21 +32,21 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } @@ -60,18 +55,18 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { Object eventData = message.getData(); if (eventData == null) { - logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中事件数据为空"); return false; } - boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); if (!matched) { - logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "事件数据条件不匹配"); return false; } } - logTriggerMatchSuccess(message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), 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 b01f7f409a..11638877dd 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 @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 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.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; /** @@ -16,7 +16,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher { +public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher { /** * 设备属性上报消息方法 @@ -24,11 +24,6 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatche // TODO @puhui999:是不是不用枚举哈?直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.TRIGGER; - } - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; @@ -37,43 +32,43 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatche @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } // 4. 检查操作符和值是否有效 - if (!isTriggerOperatorAndValueValid(trigger)) { - logTriggerMatchFailure(message, trigger, "操作符或值无效"); + if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效"); return false; } // 5. 获取属性值 Object propertyValue = message.getData(); if (propertyValue == null) { - logTriggerMatchFailure(message, trigger, "消息中属性值为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中属性值为空"); return false; } // 6. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); if (matched) { - logTriggerMatchSuccess(message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); } else { - logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), 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 074d4afa70..c349fd211e 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 @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 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.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; /** @@ -16,18 +16,13 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher { +public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { /** * 设备服务调用消息方法 */ private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.TRIGGER; - } - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; @@ -36,28 +31,28 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatch @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查标识符是否匹配 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { - logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); return false; } // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 // 不需要检查操作符和值,因为服务调用本身就是触发条件 - logTriggerMatchSuccess(message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), 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 68d9ca4507..a435002252 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 @@ -4,7 +4,7 @@ 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.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; /** @@ -15,7 +15,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher { +public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { // TODO @puhui999:是不是不用枚举哈; /** @@ -23,11 +23,6 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher */ private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.TRIGGER; - } - @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; @@ -36,36 +31,36 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); return false; } // 2. 检查消息方法是否匹配 if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); return false; } // 3. 检查操作符和值是否有效 - if (!isTriggerOperatorAndValueValid(trigger)) { - logTriggerMatchFailure(message, trigger, "操作符或值无效"); + if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效"); return false; } // 4. 获取设备状态值 Object stateValue = message.getData(); if (stateValue == null) { - logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中设备状态值为空"); return false; } // 5. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); if (matched) { - logTriggerMatchSuccess(message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); } else { - logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), 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/IotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java new file mode 100644 index 0000000000..322421738e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +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.IotSceneRuleMatcher; + +/** + * IoT 场景规则触发器匹配器接口 + *

+ * 专门处理主触发条件的匹配逻辑,如设备消息类型、定时器等 + *

+ * 触发器匹配器负责判断设备消息是否满足场景规则的主触发条件, + * 是场景规则执行的第一道门槛 + * + * @author HUIHUI + */ +public interface IotSceneRuleTriggerMatcher extends IotSceneRuleMatcher { + + /** + * 获取支持的触发器类型 + * + * @return 触发器类型枚举 + */ + IotSceneRuleTriggerTypeEnum getSupportedTriggerType(); + + /** + * 检查触发器是否匹配消息 + *

+ * 判断设备消息是否满足指定的触发器条件 + * + * @param message 设备消息 + * @param trigger 触发器配置 + * @return 是否匹配 + */ + boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger); + +} 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 1ae0cee66c..6dfd3fc9e9 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 @@ -4,7 +4,7 @@ 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.IotSceneRuleTriggerTypeEnum; -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.AbstractIotSceneRuleMatcher; +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; import org.springframework.stereotype.Component; /** @@ -16,12 +16,7 @@ import org.springframework.stereotype.Component; * @author HUIHUI */ @Component -public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { - - @Override - public MatcherTypeEnum getMatcherType() { - return MatcherTypeEnum.TRIGGER; - } +public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher { @Override public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { @@ -31,14 +26,14 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效"); return false; } // 2. 检查 CRON 表达式是否存在 if (StrUtil.isBlank(trigger.getCronExpression())) { - logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "定时触发器缺少 CRON 表达式"); return false; } @@ -47,11 +42,11 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { // 4. 可以添加 CRON 表达式格式验证 if (!isValidCronExpression(trigger.getCronExpression())) { - logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); + IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); return false; } - logTriggerMatchSuccess(message, trigger); + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger); return true; } From fb35807ebf952001e661cb0283b6fcaeb0222991 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 18 Aug 2025 11:34:18 +0800 Subject: [PATCH 3/5] =?UTF-8?q?perf:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20review=20=E6=8F=90=E5=88=B0=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/IotSceneRuleTriggerTypeEnum.java | 5 +- .../scene/matcher/IotSceneRuleMatcher.java | 10 - .../matcher/IotSceneRuleMatcherHelper.java | 124 +++++++--- .../matcher/IotSceneRuleMatcherManager.java | 75 +++--- .../CurrentTimeConditionMatcher.java | 214 ++++++++++++------ .../DevicePropertyConditionMatcher.java | 25 +- .../DeviceStateConditionMatcher.java | 21 +- .../DeviceEventPostTriggerMatcher.java | 29 ++- .../DevicePropertyPostTriggerMatcher.java | 38 ++-- .../DeviceServiceInvokeTriggerMatcher.java | 25 +- .../DeviceStateUpdateTriggerMatcher.java | 34 ++- .../matcher/trigger/TimerTriggerMatcher.java | 43 +--- .../IotSceneRuleTriggerMatcherTest.java | 13 -- 13 files changed, 350 insertions(+), 306 deletions(-) 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. 执行测试 From 7661c7165c88c57039baee1d0db96a40de4cde31 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 18 Aug 2025 15:21:09 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=E3=80=90IoT=20=E7=89=A9=E8=81=94?= =?UTF-8?q?=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=8C=B9=E9=85=8D=E5=99=A8=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/scene/IotSceneRuleServiceImpl.java | 70 ---- .../IotSceneRuleTriggerMatcherTest.java | 190 --------- .../CurrentTimeConditionMatcherTest.java | 321 ++++++++++++++ .../DevicePropertyConditionMatcherTest.java | 391 ++++++++++++++++++ .../DeviceStateConditionMatcherTest.java | 334 +++++++++++++++ .../DeviceEventPostTriggerMatcherTest.java | 341 +++++++++++++++ .../DevicePropertyPostTriggerMatcherTest.java | 298 +++++++++++++ ...DeviceServiceInvokeTriggerMatcherTest.java | 362 ++++++++++++++++ .../DeviceStateUpdateTriggerMatcherTest.java | 245 +++++++++++ .../trigger/TimerTriggerMatcherTest.java | 240 +++++++++++ .../test/resources/application-unit-test.yaml | 9 + .../src/test/resources/logback.xml | 33 ++ .../src/main/resources/application-local.yaml | 1 + 13 files changed, 2575 insertions(+), 260 deletions(-) delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java index ee56310fba..ba48afc5c2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java @@ -2,18 +2,11 @@ package cn.iocoder.yudao.module.iot.service.rule.scene; 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.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -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; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; @@ -23,7 +16,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; @@ -36,12 +28,9 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS; @@ -353,65 +342,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { } } - // TODO @puhui999:下面还需要么? - /** - * 判断触发器的条件参数是否匹配 - * - * @param message 设备消息 - * @param condition 触发条件 - * @param sceneRule 规则场景(用于日志,无其它作用) - * @param trigger 触发器(用于日志,无其它作用) - * @return 是否匹配 - */ - @SuppressWarnings({"unchecked", "DataFlowIssue"}) - private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, - IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { - // 1.1 校验操作符是否合法 - IotSceneRuleConditionOperatorEnum operator = - IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator()); - if (operator == null) { - log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]", - sceneRule.getId(), trigger, condition.getOperator()); - return false; - } - // 1.2 校验消息是否包含对应的值 - String messageValue = MapUtil.getStr((Map) message.getData(), condition.getIdentifier()); - if (messageValue == null) { - return false; - } - - // 2.1 构建 Spring 表达式的变量 - Map springExpressionVariables = new HashMap<>(); - try { - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam()); - List parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); - // 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题! - if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN, - IotSceneRuleConditionOperatorEnum.NOT_BETWEEN, - IotSceneRuleConditionOperatorEnum.GREATER_THAN, - IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS, - IotSceneRuleConditionOperatorEnum.LESS_THAN, - IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS) - && NumberUtil.isNumber(messageValue) - && NumberUtils.isAllNumber(parameterValues)) { - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, - NumberUtil.parseDouble(messageValue)); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, - NumberUtil.parseDouble(condition.getParam())); - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, - convertList(parameterValues, NumberUtil::parseDouble)); - } - // 2.2 计算 Spring 表达式 - return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables); - } catch (Exception e) { - log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]", - message, sceneRule.getId(), trigger, operator, springExpressionVariables, e); - return false; - } - } - /** * 执行规则场景的动作 * 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 deleted file mode 100644 index f97fd5f3c2..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -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.trigger.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * IoT 场景规则触发器匹配器测试类 - * - * @author HUIHUI - */ -public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { - - // TODO @puhui999:public 都加下哈; - - private IotSceneRuleMatcherManager matcherManager; - - @BeforeEach - void setUp() { - // 创建所有匹配器实例 - List matchers = Arrays.asList( - new DeviceStateUpdateTriggerMatcher(), - new DevicePropertyPostTriggerMatcher(), - new DeviceEventPostTriggerMatcher(), - new DeviceServiceInvokeTriggerMatcher(), - new TimerTriggerMatcher() - ); - - // 初始化匹配器管理器 - matcherManager = new IotSceneRuleMatcherManager(matchers); - } - - @Test - void testDeviceStateUpdateTriggerMatcher() { - // 1. 准备测试数据 - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-001") - .method("thing.state.update") // TODO @puhui999:这里的枚举; - .data(1) // 在线状态 TODO @puhui999:这里的枚举; - .build(); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); - trigger.setOperator("="); // TODO @puhui999:这里的枚举;下面也是类似; - trigger.setValue("1"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备状态更新触发器应该匹配"); - } - - @Test - void testDevicePropertyPostTriggerMatcher() { - // 1. 准备测试数据 - HashMap params = new HashMap<>(); - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-002") - .method("thing.property.post") - .data(25.5) // 温度值 - .params(params) - .build(); - // 模拟标识符 - params.put("identifier", "temperature"); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); - trigger.setIdentifier("temperature"); - trigger.setOperator(">"); - trigger.setValue("20"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备属性上报触发器应该匹配"); - } - - @Test - void testDeviceEventPostTriggerMatcher() { - // 1. 准备测试数据 - HashMap params = new HashMap<>(); - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-003") - .method("thing.event.post") - .data("alarm_data") - .params(params) - .build(); - // 模拟标识符 - params.put("identifier", "high_temperature_alarm"); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); - trigger.setIdentifier("high_temperature_alarm"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备事件上报触发器应该匹配"); - } - - @Test - void testDeviceServiceInvokeTriggerMatcher() { - // 1. 准备测试数据 - HashMap params = new HashMap<>(); - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-004") - .method("thing.service.invoke") - .msg("alarm_data") - .params(params) - .build(); - // 模拟标识符 - params.put("identifier", "restart_device"); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); - trigger.setIdentifier("restart_device"); - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "设备服务调用触发器应该匹配"); - } - - @Test - void testTimerTriggerMatcher() { - // 1. 准备测试数据 - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-005") - .method("timer.trigger") // 定时触发器不依赖具体消息方法 - .build(); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); - trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点 - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertTrue(matched, "定时触发器应该匹配"); - } - - @Test - void testInvalidTriggerType() { - // 1. 准备测试数据 - IotDeviceMessage message = IotDeviceMessage.builder() - .requestId("test-006") - .method("unknown.method") - .build(); - - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); - trigger.setType(999); // 无效的触发器类型 - - // 2. 执行测试 - boolean matched = matcherManager.isMatched(message, trigger); - - // 3. 验证结果 - assertFalse(matched, "无效的触发器类型应该不匹配"); - } - - @Test - void testGetSupportedTriggerTypes() { - // 1. 执行测试 - var supportedTypes = matcherManager.getSupportedTriggerTypes(); - - // 2. 验证结果 - assertNotNull(supportedTypes); - assertEquals(5, supportedTypes.size()); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java new file mode 100644 index 0000000000..88e948ea0f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java @@ -0,0 +1,321 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link CurrentTimeConditionMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest { + + private CurrentTimeConditionMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new CurrentTimeConditionMatcher(); + } + + @Test + public void testGetSupportedConditionType() { + // when & then + assertEquals(IotSceneRuleConditionTypeEnum.CURRENT_TIME, matcher.getSupportedConditionType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(40, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + // ========== 时间戳条件测试 ========== + + @Test + public void testIsMatched_DateTimeGreaterThan_Success() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), + String.valueOf(pastTimestamp) + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_DateTimeGreaterThan_Failure() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), + String.valueOf(futureTimestamp) + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_DateTimeLessThan_Success() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN.getOperator(), + String.valueOf(futureTimestamp) + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_DateTimeBetween_Success() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long startTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); + long endTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), + startTimestamp + "," + endTimestamp + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_DateTimeBetween_Failure() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + long startTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); + long endTimestamp = LocalDateTime.now().plusHours(2).toEpochSecond(ZoneOffset.of("+8")); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), + startTimestamp + "," + endTimestamp + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + // ========== 当日时间条件测试 ========== + + @Test + public void testIsMatched_TimeGreaterThan_EarlyMorning() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), + "06:00:00" // 早上6点 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + // 结果取决于当前时间,如果当前时间大于6点则为true + assertNotNull(result); + } + + @Test + public void testIsMatched_TimeLessThan_LateNight() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN.getOperator(), + "23:59:59" // 晚上11点59分59秒 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + // 大部分情况下应该为true,除非在午夜前1秒运行测试 + assertNotNull(result); + } + + @Test + public void testIsMatched_TimeBetween_AllDay() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), + "00:00:00,23:59:59" // 全天 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); // 全天范围应该总是匹配 + } + + @Test + public void testIsMatched_TimeBetween_WorkingHours() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), + "09:00:00,17:00:00" // 工作时间 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + // 结果取决于当前时间是否在工作时间内 + assertNotNull(result); + } + + // ========== 异常情况测试 ========== + + @Test + public void testIsMatched_NullCondition() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_NullConditionType() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidOperator() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); + condition.setOperator("invalid_operator"); + condition.setParam("12:00:00"); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidTimeFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), + "invalid-time-format" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidTimestampFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), + "invalid-timestamp" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_InvalidBetweenFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), + "09:00:00" // 缺少结束时间 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建日期时间条件 + */ + private IotSceneRuleDO.TriggerCondition createDateTimeCondition(String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } + + /** + * 创建当日时间条件 + */ + private IotSceneRuleDO.TriggerCondition createTimeCondition(String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java new file mode 100644 index 0000000000..209893d1c8 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java @@ -0,0 +1,391 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DevicePropertyConditionMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { + + private DevicePropertyConditionMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DevicePropertyConditionMatcher(); + } + + @Test + public void testGetSupportedConditionType() { + // when & then + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY, matcher.getSupportedConditionType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(20, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_TemperatureEquals() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "25.5" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_HumidityGreaterThan() { + // given + Map properties = MapUtil.of("humidity", 75); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "humidity", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "70" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_PressureLessThan() { + // given + Map properties = MapUtil.of("pressure", 1010.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "pressure", + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), + "1020" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_StatusNotEquals() { + // given + Map properties = MapUtil.of("status", "active"); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "status", + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + "inactive" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_PropertyMismatch() { + // given + Map properties = MapUtil.of("temperature", 15.0); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_PropertyNotFound() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "humidity", // 不存在的属性 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "50" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullCondition() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullConditionType() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier(null); // 缺少标识符 + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); + condition.setParam("20"); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingOperator() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier("temperature"); + condition.setOperator(null); // 缺少操作符 + condition.setParam("20"); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingParam() { + // given + Map properties = MapUtil.of("temperature", 25.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier("temperature"); + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); + condition.setParam(null); // 缺少参数 + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessage() { + // given + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(null, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullDeviceProperties() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(null); + + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOrEquals() { + // given + Map properties = MapUtil.of("voltage", 12.0); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "voltage", + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), + "12.0" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_LessThanOrEquals() { + // given + Map properties = MapUtil.of("current", 2.5); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "current", + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), + "3.0" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_StringProperty() { + // given + Map properties = MapUtil.of("mode", "auto"); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "mode", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "auto" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_BooleanProperty() { + // given + Map properties = MapUtil.of("enabled", true); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "enabled", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "true" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_MultipleProperties() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .put("humidity", 60) + .put("status", "active") + .put("enabled", true) + .build(); + IotDeviceMessage message = createDeviceMessage(properties); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + "humidity", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "60" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建设备消息 + */ + private IotDeviceMessage createDeviceMessage(Map properties) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(properties); + return message; + } + + /** + * 创建有效的条件 + */ + private IotSceneRuleDO.TriggerCondition createValidCondition(String identifier, String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); + condition.setIdentifier(identifier); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java new file mode 100644 index 0000000000..8eaf3c4af5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java @@ -0,0 +1,334 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceStateConditionMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest { + + private DeviceStateConditionMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceStateConditionMatcher(); + } + + @Test + public void testGetSupportedConditionType() { + // when & then + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_STATE, matcher.getSupportedConditionType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(30, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_OnlineState() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_OfflineState() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_InactiveState() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.INACTIVE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_StateMismatch() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_NotEqualsOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); // 2 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() // 1 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_LessThanOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); // 0 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() // 1 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_NullCondition() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullConditionType() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator(null); + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingParam() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); + condition.setParam(null); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessage() { + // given + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(null, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullDeviceState() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(null); + + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOrEqualsOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() // 1 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_LessThanOrEqualsOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1 + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() // 2 + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_InvalidOperator() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator("invalid_operator"); + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidParamFormat() { + // given + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.TriggerCondition condition = createValidCondition( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "invalid_state_value" + ); + + // when + boolean result = matcher.isMatched(message, condition); + + // then + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建设备消息 + */ + private IotDeviceMessage createDeviceMessage(Integer deviceState) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setParams(deviceState); + return message; + } + + /** + * 创建有效的条件 + */ + private IotSceneRuleDO.TriggerCondition createValidCondition(String operator, String param) { + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); + condition.setOperator(operator); + condition.setParam(param); + return condition; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java new file mode 100644 index 0000000000..acba2332c1 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java @@ -0,0 +1,341 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +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.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceEventPostTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest { + + private DeviceEventPostTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceEventPostTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(30, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_AlarmEvent() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .put("message", "Temperature too high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_ErrorEvent() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "error") + .put("value", MapUtil.builder(new HashMap()) + .put("code", 500) + .put("description", "System error") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_InfoEvent() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "info") + .put("value", MapUtil.builder(new HashMap()) + .put("status", "normal") + .put("timestamp", System.currentTimeMillis()) + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("info"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_EventIdentifierMismatch() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); // 不匹配的事件标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 + message.setParams(eventParams); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); + trigger.setIdentifier(null); // 缺少标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); + message.setParams("invalid-params"); // 不是 Map 类型 + + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingEventIdentifierInParams() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) // 缺少 identifier 字段 + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "alarm") + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + trigger.setIdentifier("alarm"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_ComplexEventValue() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "maintenance") + .put("value", MapUtil.builder(new HashMap()) + .put("type", "scheduled") + .put("duration", 120) + .put("components", new String[]{"motor", "sensor"}) + .put("priority", "medium") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("maintenance"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_EmptyEventValue() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "heartbeat") + .put("value", MapUtil.of()) // 空的事件值 + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("heartbeat"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_CaseInsensitiveIdentifier() { + // given + Map eventParams = MapUtil.builder(new HashMap()) + .put("identifier", "ALARM") // 大写 + .put("value", MapUtil.builder(new HashMap()) + .put("level", "high") + .build()) + .build(); + IotDeviceMessage message = createEventPostMessage(eventParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); // 小写 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + // 根据实际实现,这里可能需要调整期望结果 + // 如果实现是大小写敏感的,则应该为 false + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建事件上报消息 + */ + private IotDeviceMessage createEventPostMessage(Map eventParams) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); + message.setParams(eventParams); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); + trigger.setIdentifier(identifier); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java new file mode 100644 index 0000000000..0744c9a272 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java @@ -0,0 +1,298 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +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.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DevicePropertyPostTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest { + + private DevicePropertyPostTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DevicePropertyPostTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(20, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_TemperatureProperty() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_HumidityProperty() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("humidity", 60) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "humidity", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "60" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_PropertyMismatch() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 15.0) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_PropertyNotFound() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "humidity", // 不存在的属性 + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "50" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); + message.setParams(properties); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); + trigger.setIdentifier(null); // 缺少标识符 + trigger.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); + trigger.setValue("20"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams("invalid-params"); // 不是 Map 类型 + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_LessThanOperator() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 15.0) + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "temperature", + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), + "20" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_NotEqualsOperator() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("status", "active") + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "status", + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + "inactive" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_MultipleProperties() { + // given + Map properties = MapUtil.builder(new HashMap()) + .put("temperature", 25.5) + .put("humidity", 60) + .put("status", "active") + .build(); + IotDeviceMessage message = createPropertyPostMessage(properties); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + "humidity", + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + "60" + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建属性上报消息 + */ + private IotDeviceMessage createPropertyPostMessage(Map properties) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams(properties); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String identifier, String operator, String value) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); + trigger.setIdentifier(identifier); + trigger.setOperator(operator); + trigger.setValue(value); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java new file mode 100644 index 0000000000..addb1c5277 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java @@ -0,0 +1,362 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +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.dal.dataobject.rule.IotSceneRuleDO; +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest { + + private DeviceServiceInvokeTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceServiceInvokeTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(40, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_RestartService() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_ConfigService() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "config") + .put("inputData", MapUtil.builder(new HashMap()) + .put("interval", 30) + .put("enabled", true) + .put("threshold", 75.5) + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_UpdateService() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "update") + .put("inputData", MapUtil.builder(new HashMap()) + .put("version", "1.2.3") + .put("url", "http://example.com/firmware.bin") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("update"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_ServiceIdentifierMismatch() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); // 不匹配的服务标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 + message.setParams(serviceParams); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingIdentifier() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); + trigger.setIdentifier(null); // 缺少标识符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_InvalidMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); + message.setParams("invalid-params"); // 不是 Map 类型 + + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingServiceIdentifierInParams() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) // 缺少 identifier 字段 + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "restart") + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + trigger.setIdentifier("restart"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_EmptyInputData() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "ping") + .put("inputData", MapUtil.of()) // 空的输入数据 + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("ping"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_NoInputData() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "status") + // 没有 inputData 字段 + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("status"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_ComplexInputData() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "calibrate") + .put("inputData", MapUtil.builder(new HashMap()) + .put("sensors", new String[]{"temperature", "humidity", "pressure"}) + .put("precision", 0.01) + .put("duration", 300) + .put("autoSave", true) + .put("config", MapUtil.builder(new HashMap()) + .put("mode", "auto") + .put("level", "high") + .build()) + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("calibrate"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_CaseInsensitiveIdentifier() { + // given + Map serviceParams = MapUtil.builder(new HashMap()) + .put("identifier", "RESTART") // 大写 + .put("inputData", MapUtil.builder(new HashMap()) + .put("mode", "soft") + .build()) + .build(); + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); // 小写 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + // 根据实际实现,这里可能需要调整期望结果 + // 如果实现是大小写敏感的,则应该为 false + assertFalse(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建服务调用消息 + */ + private IotDeviceMessage createServiceInvokeMessage(Map serviceParams) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); + message.setParams(serviceParams); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); + trigger.setIdentifier(identifier); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java new file mode 100644 index 0000000000..2f101b2b08 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java @@ -0,0 +1,245 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; +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.IotSceneRuleTriggerTypeEnum; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link DeviceStateUpdateTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest { + + private DeviceStateUpdateTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new DeviceStateUpdateTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(10, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_OnlineState() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_OfflineState() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.OFFLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_StateMismatch() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_WrongMessageMethod() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); + message.setParams(IotDeviceStateEnum.ONLINE.getState()); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingOperator() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); + trigger.setOperator(null); + trigger.setValue(IotDeviceStateEnum.ONLINE.getState().toString()); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_MissingValue() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); + trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); + trigger.setValue(null); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullMessageParams() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); + message.setParams(null); + + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), + IotDeviceStateEnum.ONLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_GreaterThanOperator() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), + IotDeviceStateEnum.INACTIVE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_NotEqualsOperator() { + // given + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); + IotSceneRuleDO.Trigger trigger = createValidTrigger( + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), + IotDeviceStateEnum.OFFLINE.getState().toString() + ); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建设备状态更新消息 + */ + private IotDeviceMessage createStateUpdateMessage(Integer state) { + IotDeviceMessage message = new IotDeviceMessage(); + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); + message.setParams(state); + return message; + } + + /** + * 创建有效的触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String operator, String value) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); + trigger.setOperator(operator); + trigger.setValue(value); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java new file mode 100644 index 0000000000..13fe587e14 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java @@ -0,0 +1,240 @@ +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; + +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link TimerTriggerMatcher} 的单元测试类 + * + * @author HUIHUI + */ +public class TimerTriggerMatcherTest extends BaseMockitoUnitTest { + + private TimerTriggerMatcher matcher; + + @BeforeEach + public void setUp() { + matcher = new TimerTriggerMatcher(); + } + + @Test + public void testGetSupportedTriggerType() { + // when & then + assertEquals(IotSceneRuleTriggerTypeEnum.TIMER, matcher.getSupportedTriggerType()); + } + + @Test + public void testGetPriority() { + // when & then + assertEquals(50, matcher.getPriority()); + } + + @Test + public void testIsEnabled() { + // when & then + assertTrue(matcher.isEnabled()); + } + + @Test + public void testIsMatched_Success_ValidCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * ?"); // 每天中午12点 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_EveryMinuteCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 * * * * ?"); // 每分钟 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_WeekdaysCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9 ? * MON-FRI"); // 工作日上午9点 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_InvalidCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("invalid-cron-expression"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_EmptyCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger(""); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); + trigger.setCronExpression(null); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTrigger() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + + // when + boolean result = matcher.isMatched(message, null); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Failure_NullTriggerType() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(null); + trigger.setCronExpression("0 0 12 * * ?"); + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_ComplexCronExpression() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 15 10 ? * 6#3"); // 每月第三个星期五上午10:15 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_IncorrectCronFormat() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * *"); // 缺少字段 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_SpecificDateCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 0 1 1 ? 2025"); // 2025年1月1日午夜 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Success_EverySecondCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("* * * * * ?"); // 每秒 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + @Test + public void testIsMatched_Failure_InvalidCharactersCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * @ #"); // 包含无效字符 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertFalse(result); + } + + @Test + public void testIsMatched_Success_RangeCron() { + // given + IotDeviceMessage message = new IotDeviceMessage(); + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点 + + // when + boolean result = matcher.isMatched(message, trigger); + + // then + assertTrue(result); + } + + // ========== 辅助方法 ========== + + /** + * 创建有效的定时触发器 + */ + private IotSceneRuleDO.Trigger createValidTrigger(String cronExpression) { + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); + trigger.setCronExpression(cronExpression); + return trigger; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml index 7eecc88a4b..3966a274d4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml @@ -20,6 +20,15 @@ mybatis-plus: lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject +# 日志配置 +logging: + level: + cn.iocoder.yudao.module.iot.service.rule.scene.matcher: DEBUG + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager: INFO + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition: DEBUG + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger: DEBUG + root: WARN + --- #################### 定时任务相关配置 #################### --- #################### 配置中心相关配置 #################### diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml index 1d071e4799..b68931dc1c 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml @@ -1,4 +1,37 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index b12c0bbd04..6d97229e9b 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -185,6 +185,7 @@ logging: cn.iocoder.yudao.module.erp.dal.mysql: debug cn.iocoder.yudao.module.iot.dal.mysql: debug cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG + cn.iocoder.yudao.module.iot.service.rule: debug cn.iocoder.yudao.module.ai.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 From 887bf175affa1c88ebad5870f87a3ade899bd259 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 19 Aug 2025 23:49:17 +0800 Subject: [PATCH 5/5] =?UTF-8?q?review=EF=BC=9A=E3=80=90iot=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E5=9C=BA=E6=99=AF=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/enums/rule/IotSceneRuleTriggerTypeEnum.java | 12 ------------ .../scene/matcher/IotSceneRuleMatcherManager.java | 10 +++++----- .../condition/CurrentTimeConditionMatcher.java | 1 + .../condition/DevicePropertyConditionMatcher.java | 1 + .../DeviceServiceInvokeTriggerMatcherTest.java | 1 + 5 files changed, 8 insertions(+), 17 deletions(-) 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 a0f268902d..bfc84c9f60 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 @@ -64,17 +64,5 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable { public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) { return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); } - /** - * 根据类型值查找触发器类型枚举 - * - * @param typeValue 类型值 - * @return 触发器类型枚举 - */ - public static IotSceneRuleTriggerTypeEnum findTriggerTypeEnum(Integer typeValue) { - return Arrays.stream(IotSceneRuleTriggerTypeEnum.values()) - .filter(type -> type.getType().equals(typeValue)) - .findFirst() - .orElse(null); - } } 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 2f6ace2616..103c09a1eb 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 @@ -29,21 +29,18 @@ public class IotSceneRuleMatcherManager { /** * 触发器匹配器映射表 - * Key: 触发器类型枚举 - * Value: 对应的匹配器实例 */ private final Map triggerMatchers; /** * 条件匹配器映射表 - * Key: 条件类型枚举 - * Value: 对应的匹配器实例 */ private final Map conditionMatchers; /** * 所有匹配器列表(按优先级排序) */ + // TODO @puhui999:貌似 local variable 也可以 private final List allMatchers; public IotSceneRuleMatcherManager(List matchers) { @@ -152,13 +149,13 @@ public class IotSceneRuleMatcherManager { log.warn("[isConditionMatched][conditionType({}) 未知的条件类型]", condition.getType()); return false; } - IotSceneRuleConditionMatcher matcher = conditionMatchers.get(conditionType); if (matcher == null) { log.warn("[isConditionMatched][conditionType({}) 没有对应的匹配器]", conditionType); return false; } + // 执行匹配逻辑 try { return matcher.isMatched(message, condition); } catch (Exception e) { @@ -174,12 +171,15 @@ public class IotSceneRuleMatcherManager { * @return 条件类型枚举 */ private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) { + // TODO @puhui999:是不是搞到枚举类里? return Arrays.stream(IotSceneRuleConditionTypeEnum.values()) .filter(type -> type.getType().equals(typeValue)) .findFirst() .orElse(null); } + // TODO @puhui999:下面两个方法,是不是也可以删除哈? + /** * 获取所有支持的触发器类型 * 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 0756c86ac3..0daf4eefd5 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 @@ -123,6 +123,7 @@ public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher isDateTimeOperator(operatorEnum); } + // TODO @puhui999:switch 兼容下 jdk8 /** * 匹配日期时间(时间戳) * 直接实现时间戳比较逻辑 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 0e16df7019..e6fe043d0a 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,6 +22,7 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; } + // TODO @puhui999:matches 会不会更好?参考的 org.hamcrest.Matcher jdk 接口 @Override public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { // 1.1 基础参数校验 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java index addb1c5277..6aef51cf71 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java @@ -31,6 +31,7 @@ public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest { @Test public void testGetSupportedTriggerType() { // when & then + // TODO @puhui999:单测按照现有项目的注释风格哈;类似 // 调用;// 断言 assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, matcher.getSupportedTriggerType()); }