From 1e17e3578dca74eeb2a8a01397543f9d634e074b Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sun, 17 Aug 2025 11:22:53 +0800 Subject: [PATCH] perf:【IoT 物联网】场景联动规则根据类型,新建 trigger、condition 包 --- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java | 165 --------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java | 82 ---------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java | 73 ------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java | 85 ------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java | 68 -------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java | 65 ----------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java | 77 ----------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java | 6 ++---- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java | 4 ++-- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java | 86 -------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java | 1 + 19 files changed, 714 insertions(+), 707 deletions(-) delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java 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/CurrentTimeConditionMatcher.java deleted file mode 100644 index 94e7401..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java +++ /dev/null @@ -1,165 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -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 lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -/** - * 当前时间条件匹配器 - *

- * 处理时间相关的子条件匹配逻辑 - * - * @author HUIHUI - */ -@Component -@Slf4j -public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { - - /** - * 时间格式化器 - HH:mm:ss - */ - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); - - /** - * 时间格式化器 - HH:mm - */ - private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); - - @Override - public MatcherType getMatcherType() { - return MatcherType.CONDITION; - } - - @Override - public IotSceneRuleConditionTypeEnum getSupportedConditionType() { - return IotSceneRuleConditionTypeEnum.CURRENT_TIME; - } - - @Override - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1. 基础参数校验 - if (!isBasicConditionValid(condition)) { - logConditionMatchFailure(message, condition, "条件基础参数无效"); - return false; - } - - // 2. 检查操作符和参数是否有效 - if (!isConditionOperatorAndParamValid(condition)) { - logConditionMatchFailure(message, condition, "操作符或参数无效"); - return false; - } - - // 3. 根据操作符类型进行不同的时间匹配 - LocalDateTime now = LocalDateTime.now(); - 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 = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); - } - - if (matched) { - logConditionMatchSuccess(message, condition); - } else { - logConditionMatchFailure(message, condition, "时间条件不匹配"); - } - } catch (Exception e) { - log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e); - logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage()); - matched = false; - } - return matched; - } - - /** - * 匹配日期时间(时间戳) - */ - 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); - } - - /** - * 匹配当日时间(HH:mm:ss) - */ - private boolean matchTime(LocalTime currentTime, String operator, String param) { - try { - String actualOperator = operator.substring("time_".length()); - - // 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); - } 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; - } - } - } catch (Exception e) { - // TODO @puhui999:1)日志格式 [][];2)方法名不对哈; - log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e); - return false; - } - } - - /** - * 解析时间字符串 - */ - private LocalTime parseTime(String timeStr) { - // TODO @puhui999:可以用 hutool Assert 类简化 - if (StrUtil.isBlank(timeStr)) { - throw new IllegalArgumentException("时间字符串不能为空"); - } - // 尝试不同的时间格式 - try { - if (timeStr.length() == 5) { // HH:mm - return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); - } else { // HH:mm:ss - return LocalTime.parse(timeStr, TIME_FORMATTER); - } - } catch (Exception e) { - throw new IllegalArgumentException("时间格式无效: " + timeStr, e); - } - } - - @Override - public int getPriority() { - return 40; // 较低优先级 - } - -} 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/DeviceEventPostTriggerMatcher.java deleted file mode 100644 index 3c832f6..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -import cn.hutool.core.util.StrUtil; -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 org.springframework.stereotype.Component; - -/** - * 设备事件上报触发器匹配器 - *

- * 处理设备事件上报的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { - - /** - * 设备事件上报消息方法 - */ - private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); - - @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; - } - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; - } - - @Override - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 2. 检查消息方法是否匹配 - if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(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); - return false; - } - - // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 - // 但如果配置了操作符和值,则需要进行条件匹配 - if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { - Object eventData = message.getData(); - if (eventData == null) { - logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); - return false; - } - - boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); - if (!matched) { - logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); - return false; - } - } - - logTriggerMatchSuccess(message, trigger); - return true; - } - - @Override - public int getPriority() { - return 30; // 中等优先级 - } - -} 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/DevicePropertyConditionMatcher.java deleted file mode 100644 index 3738150..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java +++ /dev/null @@ -1,73 +0,0 @@ -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.core.util.IotDeviceMessageUtils; -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; -import org.springframework.stereotype.Component; - -/** - * 设备属性条件匹配器 - *

- * 处理设备属性相关的子条件匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher { - - @Override - public MatcherType getMatcherType() { - return MatcherType.CONDITION; - } - - @Override - public IotSceneRuleConditionTypeEnum getSupportedConditionType() { - 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. 基础参数校验 - if (!isBasicConditionValid(condition)) { - logConditionMatchFailure(message, condition, "条件基础参数无效"); - return false; - } - - // 2. 检查标识符是否匹配 - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); - if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { - logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); - return false; - } - - // 3. 检查操作符和参数是否有效 - if (!isConditionOperatorAndParamValid(condition)) { - logConditionMatchFailure(message, condition, "操作符或参数无效"); - return false; - } - - // 4. 获取属性值 - Object propertyValue = message.getData(); - if (propertyValue == null) { - logConditionMatchFailure(message, condition, "消息中属性值为空"); - return false; - } - - // 5. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); - if (matched) { - logConditionMatchSuccess(message, condition); - } else { - logConditionMatchFailure(message, condition, "设备属性条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 25; // 中等优先级 - } - -} 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/DevicePropertyPostTriggerMatcher.java deleted file mode 100644 index ed228fc..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java +++ /dev/null @@ -1,85 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -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 org.springframework.stereotype.Component; - -/** - * 设备属性上报触发器匹配器 - *

- * 处理设备属性数据上报的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher { - - /** - * 设备属性上报消息方法 - */ - // TODO @puhui999:是不是不用枚举哈?直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() - private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); - - @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; - } - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; - } - - @Override - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 2. 检查消息方法是否匹配 - if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(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); - return false; - } - - // 4. 检查操作符和值是否有效 - if (!isTriggerOperatorAndValueValid(trigger)) { - logTriggerMatchFailure(message, trigger, "操作符或值无效"); - return false; - } - - // 5. 获取属性值 - Object propertyValue = message.getData(); - if (propertyValue == null) { - logTriggerMatchFailure(message, trigger, "消息中属性值为空"); - return false; - } - - // 6. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); - if (matched) { - logTriggerMatchSuccess(message, trigger); - } else { - logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 20; // 中等优先级 - } - -} 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/DeviceServiceInvokeTriggerMatcher.java deleted file mode 100644 index c2b7e4e..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java +++ /dev/null @@ -1,68 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -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 org.springframework.stereotype.Component; - -/** - * 设备服务调用触发器匹配器 - *

- * 处理设备服务调用的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher { - - /** - * 设备服务调用消息方法 - */ - private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); - - @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; - } - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; - } - - @Override - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 2. 检查消息方法是否匹配 - if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(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); - return false; - } - - // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 - // 不需要检查操作符和值,因为服务调用本身就是触发条件 - - logTriggerMatchSuccess(message, trigger); - return true; - } - - @Override - public int getPriority() { - return 40; // 较低优先级 - } - -} 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/DeviceStateConditionMatcher.java deleted file mode 100644 index 69d3a7d..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java +++ /dev/null @@ -1,65 +0,0 @@ -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 org.springframework.stereotype.Component; - -/** - * 设备状态条件匹配器 - *

- * 处理设备状态相关的子条件匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { - - @Override - public MatcherType getMatcherType() { - return MatcherType.CONDITION; - } - - @Override - public IotSceneRuleConditionTypeEnum getSupportedConditionType() { - return IotSceneRuleConditionTypeEnum.DEVICE_STATE; - } - - @Override - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { - // 1. 基础参数校验 - if (!isBasicConditionValid(condition)) { - logConditionMatchFailure(message, condition, "条件基础参数无效"); - return false; - } - - // 2. 检查操作符和参数是否有效 - if (!isConditionOperatorAndParamValid(condition)) { - logConditionMatchFailure(message, condition, "操作符或参数无效"); - return false; - } - - // 3. 获取设备状态值 - // 设备状态通常在消息的 data 字段中 - Object stateValue = message.getData(); - if (stateValue == null) { - logConditionMatchFailure(message, condition, "消息中设备状态值为空"); - return false; - } - - // 4. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); - if (matched) { - logConditionMatchSuccess(message, condition); - } else { - logConditionMatchFailure(message, condition, "设备状态条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 30; // 中等优先级 - } - -} 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/DeviceStateUpdateTriggerMatcher.java deleted file mode 100644 index 3a2a0e7..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -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.springframework.stereotype.Component; - -/** - * 设备状态更新触发器匹配器 - *

- * 处理设备上下线状态变更的触发器匹配逻辑 - * - * @author HUIHUI - */ -@Component -public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher { - - // TODO @puhui999:是不是不用枚举哈; - /** - * 设备状态更新消息方法 - */ - private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); - - @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; - } - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; - } - - @Override - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 2. 检查消息方法是否匹配 - if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); - return false; - } - - // 3. 检查操作符和值是否有效 - if (!isTriggerOperatorAndValueValid(trigger)) { - logTriggerMatchFailure(message, trigger, "操作符或值无效"); - return false; - } - - // 4. 获取设备状态值 - Object stateValue = message.getData(); - if (stateValue == null) { - logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); - return false; - } - - // 5. 使用条件评估器进行匹配 - boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); - if (matched) { - logTriggerMatchSuccess(message, trigger); - } else { - logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); - } - return matched; - } - - @Override - public int getPriority() { - return 10; // 高优先级 - } - -} 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 b9b439c..fb86fa9 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 e95e553..a73e915 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/TimerTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java deleted file mode 100644 index edc1877..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java +++ /dev/null @@ -1,86 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; - -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 org.springframework.stereotype.Component; - -/** - * 定时触发器匹配器 - *

- * 处理定时触发的触发器匹配逻辑 - * 注意:定时触发器不依赖设备消息,主要用于定时任务场景 - * - * @author HUIHUI - */ -@Component -public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { - - @Override - public MatcherType getMatcherType() { - return MatcherType.TRIGGER; - } - - @Override - public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { - return IotSceneRuleTriggerTypeEnum.TIMER; - } - - @Override - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { - // 1. 基础参数校验 - if (!isBasicTriggerValid(trigger)) { - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); - return false; - } - - // 2. 检查 CRON 表达式是否存在 - if (StrUtil.isBlank(trigger.getCronExpression())) { - logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); - return false; - } - - // 3. 定时触发器通常不依赖具体的设备消息 - // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 - - // 4. 可以添加 CRON 表达式格式验证 - if (!isValidCronExpression(trigger.getCronExpression())) { - logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); - return false; - } - - 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/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 new file mode 100644 index 0000000..1ddfe71 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java @@ -0,0 +1,166 @@ +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; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * 当前时间条件匹配器 + *

+ * 处理时间相关的子条件匹配逻辑 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { + + /** + * 时间格式化器 - HH:mm:ss + */ + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); + + /** + * 时间格式化器 - HH:mm + */ + 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; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + // 1. 基础参数校验 + if (!isBasicConditionValid(condition)) { + logConditionMatchFailure(message, condition, "条件基础参数无效"); + return false; + } + + // 2. 检查操作符和参数是否有效 + if (!isConditionOperatorAndParamValid(condition)) { + logConditionMatchFailure(message, condition, "操作符或参数无效"); + return false; + } + + // 3. 根据操作符类型进行不同的时间匹配 + LocalDateTime now = LocalDateTime.now(); + 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 = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); + } + + if (matched) { + logConditionMatchSuccess(message, condition); + } else { + logConditionMatchFailure(message, condition, "时间条件不匹配"); + } + } catch (Exception e) { + log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e); + logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage()); + matched = false; + } + return matched; + } + + /** + * 匹配日期时间(时间戳) + */ + 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); + } + + /** + * 匹配当日时间(HH:mm:ss) + */ + private boolean matchTime(LocalTime currentTime, String operator, String param) { + try { + String actualOperator = operator.substring("time_".length()); + + // 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); + } 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; + } + } + } catch (Exception e) { + // TODO @puhui999:1)日志格式 [][];2)方法名不对哈; + log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e); + return false; + } + } + + /** + * 解析时间字符串 + */ + private LocalTime parseTime(String timeStr) { + // TODO @puhui999:可以用 hutool Assert 类简化 + if (StrUtil.isBlank(timeStr)) { + throw new IllegalArgumentException("时间字符串不能为空"); + } + // 尝试不同的时间格式 + try { + if (timeStr.length() == 5) { // HH:mm + return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); + } else { // HH:mm:ss + return LocalTime.parse(timeStr, TIME_FORMATTER); + } + } catch (Exception e) { + throw new IllegalArgumentException("时间格式无效: " + timeStr, e); + } + } + + @Override + public int getPriority() { + return 40; // 较低优先级 + } + +} 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 new file mode 100644 index 0000000..8ba2bfb --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java @@ -0,0 +1,74 @@ +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; + +/** + * 设备属性条件匹配器 + *

+ * 处理设备属性相关的子条件匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher { + + @Override + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.CONDITION; + } + + @Override + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { + 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. 基础参数校验 + if (!isBasicConditionValid(condition)) { + logConditionMatchFailure(message, condition, "条件基础参数无效"); + return false; + } + + // 2. 检查标识符是否匹配 + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); + if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { + logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); + return false; + } + + // 3. 检查操作符和参数是否有效 + if (!isConditionOperatorAndParamValid(condition)) { + logConditionMatchFailure(message, condition, "操作符或参数无效"); + return false; + } + + // 4. 获取属性值 + Object propertyValue = message.getData(); + if (propertyValue == null) { + logConditionMatchFailure(message, condition, "消息中属性值为空"); + return false; + } + + // 5. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); + if (matched) { + logConditionMatchSuccess(message, condition); + } else { + logConditionMatchFailure(message, condition, "设备属性条件不匹配"); + } + return matched; + } + + @Override + public int getPriority() { + return 25; // 中等优先级 + } + +} 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 new file mode 100644 index 0000000..8bffae9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java @@ -0,0 +1,66 @@ +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; + +/** + * 设备状态条件匹配器 + *

+ * 处理设备状态相关的子条件匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { + + @Override + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.CONDITION; + } + + @Override + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { + return IotSceneRuleConditionTypeEnum.DEVICE_STATE; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { + // 1. 基础参数校验 + if (!isBasicConditionValid(condition)) { + logConditionMatchFailure(message, condition, "条件基础参数无效"); + return false; + } + + // 2. 检查操作符和参数是否有效 + if (!isConditionOperatorAndParamValid(condition)) { + logConditionMatchFailure(message, condition, "操作符或参数无效"); + return false; + } + + // 3. 获取设备状态值 + // 设备状态通常在消息的 data 字段中 + Object stateValue = message.getData(); + if (stateValue == null) { + logConditionMatchFailure(message, condition, "消息中设备状态值为空"); + return false; + } + + // 4. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); + if (matched) { + logConditionMatchSuccess(message, condition); + } else { + logConditionMatchFailure(message, condition, "设备状态条件不匹配"); + } + return matched; + } + + @Override + public int getPriority() { + return 30; // 中等优先级 + } + +} 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 new file mode 100644 index 0000000..edd5ea3 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java @@ -0,0 +1,83 @@ +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; +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; + +/** + * 设备事件上报触发器匹配器 + *

+ * 处理设备事件上报的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { + + /** + * 设备事件上报消息方法 + */ + 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; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { + logTriggerMatchFailure(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); + return false; + } + + // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 + // 但如果配置了操作符和值,则需要进行条件匹配 + if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { + Object eventData = message.getData(); + if (eventData == null) { + logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); + return false; + } + + boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); + if (!matched) { + logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); + return false; + } + } + + logTriggerMatchSuccess(message, trigger); + return true; + } + + @Override + public int getPriority() { + return 30; // 中等优先级 + } + +} 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 new file mode 100644 index 0000000..b01f7f4 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java @@ -0,0 +1,86 @@ +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; + +/** + * 设备属性上报触发器匹配器 + *

+ * 处理设备属性数据上报的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher { + + /** + * 设备属性上报消息方法 + */ + // 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; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { + logTriggerMatchFailure(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); + return false; + } + + // 4. 检查操作符和值是否有效 + if (!isTriggerOperatorAndValueValid(trigger)) { + logTriggerMatchFailure(message, trigger, "操作符或值无效"); + return false; + } + + // 5. 获取属性值 + Object propertyValue = message.getData(); + if (propertyValue == null) { + logTriggerMatchFailure(message, trigger, "消息中属性值为空"); + return false; + } + + // 6. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); + if (matched) { + logTriggerMatchSuccess(message, trigger); + } else { + logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); + } + return matched; + } + + @Override + public int getPriority() { + return 20; // 中等优先级 + } + +} 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 new file mode 100644 index 0000000..074d4af --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java @@ -0,0 +1,69 @@ +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; + +/** + * 设备服务调用触发器匹配器 + *

+ * 处理设备服务调用的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher { + + /** + * 设备服务调用消息方法 + */ + 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; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { + logTriggerMatchFailure(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); + return false; + } + + // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 + // 不需要检查操作符和值,因为服务调用本身就是触发条件 + + logTriggerMatchSuccess(message, trigger); + return true; + } + + @Override + public int getPriority() { + return 40; // 较低优先级 + } + +} 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 new file mode 100644 index 0000000..68d9ca4 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java @@ -0,0 +1,78 @@ +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; + +/** + * 设备状态更新触发器匹配器 + *

+ * 处理设备上下线状态变更的触发器匹配逻辑 + * + * @author HUIHUI + */ +@Component +public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher { + + // TODO @puhui999:是不是不用枚举哈; + /** + * 设备状态更新消息方法 + */ + 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; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查消息方法是否匹配 + if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); + return false; + } + + // 3. 检查操作符和值是否有效 + if (!isTriggerOperatorAndValueValid(trigger)) { + logTriggerMatchFailure(message, trigger, "操作符或值无效"); + return false; + } + + // 4. 获取设备状态值 + Object stateValue = message.getData(); + if (stateValue == null) { + logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); + return false; + } + + // 5. 使用条件评估器进行匹配 + boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); + if (matched) { + logTriggerMatchSuccess(message, trigger); + } else { + logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); + } + return matched; + } + + @Override + public int getPriority() { + return 10; // 高优先级 + } + +} 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 new file mode 100644 index 0000000..1ae0cee --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java @@ -0,0 +1,87 @@ +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; + +/** + * 定时触发器匹配器 + *

+ * 处理定时触发的触发器匹配逻辑 + * 注意:定时触发器不依赖设备消息,主要用于定时任务场景 + * + * @author HUIHUI + */ +@Component +public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { + + @Override + public MatcherTypeEnum getMatcherType() { + return MatcherTypeEnum.TRIGGER; + } + + @Override + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { + return IotSceneRuleTriggerTypeEnum.TIMER; + } + + @Override + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { + // 1. 基础参数校验 + if (!isBasicTriggerValid(trigger)) { + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); + return false; + } + + // 2. 检查 CRON 表达式是否存在 + if (StrUtil.isBlank(trigger.getCronExpression())) { + logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); + return false; + } + + // 3. 定时触发器通常不依赖具体的设备消息 + // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 + + // 4. 可以添加 CRON 表达式格式验证 + if (!isValidCronExpression(trigger.getCronExpression())) { + logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); + return false; + } + + 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 ff5e283..7d19fd5 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; -- libgit2 0.21.4