diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java new file mode 100644 index 0000000000..c83b72c1f5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.iot.enums.rule; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * IoT 场景规则条件层级枚举 + *
+ * 用于区分主条件(触发器级别)和子条件(条件分组级别)
+ *
+ * @author HUIHUI
+ */
+@AllArgsConstructor
+@Getter
+public enum IotSceneRuleConditionLevelEnum {
+
+ /**
+ * 主条件 - 触发器级别的条件
+ * 用于判断触发器本身是否匹配(如消息类型、设备标识等)
+ */
+ PRIMARY(1, "主条件"),
+
+ /**
+ * 子条件 - 条件分组级别的条件
+ * 用于判断具体的业务条件(如设备状态、属性值、时间条件等)
+ */
+ SECONDARY(2, "子条件");
+
+ /**
+ * 条件层级
+ */
+ private final Integer level;
+
+ /**
+ * 条件层级名称
+ */
+ private final String name;
+
+ /**
+ * 根据层级值获取枚举
+ *
+ * @param level 层级值
+ * @return 条件层级枚举
+ */
+ public static IotSceneRuleConditionLevelEnum levelOf(Integer level) {
+ if (level == null) {
+ return null;
+ }
+ for (IotSceneRuleConditionLevelEnum levelEnum : values()) {
+ if (levelEnum.getLevel().equals(level)) {
+ return levelEnum;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 判断是否为主条件
+ *
+ * @return 是否为主条件
+ */
+ public boolean isPrimary() {
+ return this == PRIMARY;
+ }
+
+ /**
+ * 判断是否为子条件
+ *
+ * @return 是否为子条件
+ */
+ public boolean isSecondary() {
+ return this == SECONDARY;
+ }
+}
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 295a796d4c..fc3e96798f 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
@@ -19,19 +19,17 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO;
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.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.IotSceneRuleConditionTypeEnum;
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;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
-import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleTriggerMatcherManager;
+import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -67,7 +65,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
@Resource
private IotDeviceService deviceService;
@Resource
- private IotSceneRuleTriggerMatcherManager triggerMatcherManager;
+ private IotSceneRuleMatcherManager matcherManager;
@Resource
private List
- * 提供通用的条件评估逻辑和工具方法
+ * 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型
*
* @author HUIHUI
*/
@Slf4j
-public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRuleTriggerMatcher {
+public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher {
/**
* 评估条件是否匹配
@@ -68,6 +68,8 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule
}
}
+ // ========== 触发器相关工具方法 ==========
+
/**
* 检查基础触发器参数是否有效
*
@@ -79,15 +81,81 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule
}
/**
- * 检查操作符和值是否有效
+ * 检查触发器操作符和值是否有效
*
* @param trigger 触发器配置
* @return 是否有效
*/
- protected boolean isOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) {
+ protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) {
return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue());
}
+ /**
+ * 记录触发器匹配成功日志
+ *
+ * @param message 设备消息
+ * @param trigger 触发器配置
+ */
+ protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
+ log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType());
+ }
+
+ /**
+ * 记录触发器匹配失败日志
+ *
+ * @param message 设备消息
+ * @param trigger 触发器配置
+ * @param reason 失败原因
+ */
+ protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
+ log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason);
+ }
+
+ // ========== 条件相关工具方法 ==========
+
+ /**
+ * 检查基础条件参数是否有效
+ *
+ * @param condition 触发条件
+ * @return 是否有效
+ */
+ protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) {
+ return condition != null && condition.getType() != null;
+ }
+
+ /**
+ * 检查条件操作符和参数是否有效
+ *
+ * @param condition 触发条件
+ * @return 是否有效
+ */
+ protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) {
+ return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam());
+ }
+
+ /**
+ * 记录条件匹配成功日志
+ *
+ * @param message 设备消息
+ * @param condition 触发条件
+ */
+ protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
+ log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType());
+ }
+
+ /**
+ * 记录条件匹配失败日志
+ *
+ * @param message 设备消息
+ * @param condition 触发条件
+ * @param reason 失败原因
+ */
+ protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
+ log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason);
+ }
+
+ // ========== 通用工具方法 ==========
+
/**
* 检查标识符是否匹配
*
@@ -99,25 +167,4 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule
return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier);
}
- /**
- * 记录匹配成功日志
- *
- * @param message 设备消息
- * @param trigger 触发器配置
- */
- protected void logMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
- log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType());
- }
-
- /**
- * 记录匹配失败日志
- *
- * @param message 设备消息
- * @param trigger 触发器配置
- * @param reason 失败原因
- */
- protected void logMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
- log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason);
- }
-
}
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
new file mode 100644
index 0000000000..df11c666da
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java
@@ -0,0 +1,177 @@
+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.IotSceneRuleConditionLevelEnum;
+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 IotSceneRuleConditionLevelEnum getSupportedConditionLevel() {
+ return IotSceneRuleConditionLevelEnum.SECONDARY;
+ }
+
+ @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();
+
+ // 4. 根据操作符类型进行不同的时间匹配
+ String operator = condition.getOperator();
+ String param = condition.getParam();
+
+ boolean matched = false;
+
+ 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());
+
+ 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);
+
+ 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) {
+ log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e);
+ return false;
+ }
+ }
+
+ /**
+ * 解析时间字符串
+ */
+ private LocalTime parseTime(String timeStr) {
+ 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
index 1ee0cdda81..3c832f6553 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/DeviceEventPostTriggerMatcher.java
@@ -16,13 +16,18 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+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;
@@ -32,20 +37,20 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
// 1. 基础参数校验
if (!isBasicTriggerValid(trigger)) {
- logMatchFailure(message, trigger, "触发器基础参数无效");
+ logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
return false;
}
// 2. 检查消息方法是否匹配
if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) {
- logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod());
+ logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod());
return false;
}
// 3. 检查标识符是否匹配
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
- logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
+ logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
return false;
}
@@ -54,18 +59,18 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa
if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) {
Object eventData = message.getData();
if (eventData == null) {
- logMatchFailure(message, trigger, "消息中事件数据为空");
+ logTriggerMatchFailure(message, trigger, "消息中事件数据为空");
return false;
}
boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue());
if (!matched) {
- logMatchFailure(message, trigger, "事件数据条件不匹配");
+ logTriggerMatchFailure(message, trigger, "事件数据条件不匹配");
return false;
}
}
- logMatchSuccess(message, trigger);
+ 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/DevicePropertyConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java
new file mode 100644
index 0000000000..70c789edcd
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java
@@ -0,0 +1,80 @@
+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.IotSceneRuleConditionLevelEnum;
+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;
+ }
+
+ @Override
+ public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() {
+ return IotSceneRuleConditionLevelEnum.SECONDARY;
+ }
+
+ @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
index 7ed00519ec..0953453ed2 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/DevicePropertyPostTriggerMatcher.java
@@ -15,13 +15,18 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
/**
* 设备属性上报消息方法
*/
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;
@@ -31,33 +36,33 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
// 1. 基础参数校验
if (!isBasicTriggerValid(trigger)) {
- logMatchFailure(message, trigger, "触发器基础参数无效");
+ logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
return false;
}
// 2. 检查消息方法是否匹配
if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) {
- logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod());
+ logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod());
return false;
}
// 3. 检查标识符是否匹配
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
- logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
+ logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
return false;
}
// 4. 检查操作符和值是否有效
- if (!isOperatorAndValueValid(trigger)) {
- logMatchFailure(message, trigger, "操作符或值无效");
+ if (!isTriggerOperatorAndValueValid(trigger)) {
+ logTriggerMatchFailure(message, trigger, "操作符或值无效");
return false;
}
// 5. 获取属性值
Object propertyValue = message.getData();
if (propertyValue == null) {
- logMatchFailure(message, trigger, "消息中属性值为空");
+ logTriggerMatchFailure(message, trigger, "消息中属性值为空");
return false;
}
@@ -65,9 +70,9 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge
boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
if (matched) {
- logMatchSuccess(message, trigger);
+ logTriggerMatchSuccess(message, trigger);
} else {
- logMatchFailure(message, trigger, "属性值条件不匹配");
+ 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/DeviceServiceInvokeTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java
index 996fe173f9..c2b7e4ef82 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/DeviceServiceInvokeTriggerMatcher.java
@@ -15,13 +15,18 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+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;
@@ -31,27 +36,27 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
// 1. 基础参数校验
if (!isBasicTriggerValid(trigger)) {
- logMatchFailure(message, trigger, "触发器基础参数无效");
+ logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
return false;
}
// 2. 检查消息方法是否匹配
if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) {
- logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod());
+ logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod());
return false;
}
// 3. 检查标识符是否匹配
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
- logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
+ logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
return false;
}
// 4. 对于服务调用触发器,通常只需要匹配服务标识符即可
// 不需要检查操作符和值,因为服务调用本身就是触发条件
- logMatchSuccess(message, trigger);
+ 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/DeviceStateConditionMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java
new file mode 100644
index 0000000000..aa3acab2a1
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java
@@ -0,0 +1,73 @@
+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.IotSceneRuleConditionLevelEnum;
+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 IotSceneRuleConditionLevelEnum getSupportedConditionLevel() {
+ return IotSceneRuleConditionLevelEnum.SECONDARY;
+ }
+
+ @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
index aec372c51b..a505e0d393 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/DeviceStateUpdateTriggerMatcher.java
@@ -14,13 +14,18 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher {
/**
* 设备状态更新消息方法
*/
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;
@@ -30,26 +35,26 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
// 1. 基础参数校验
if (!isBasicTriggerValid(trigger)) {
- logMatchFailure(message, trigger, "触发器基础参数无效");
+ logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
return false;
}
// 2. 检查消息方法是否匹配
if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) {
- logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod());
+ logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod());
return false;
}
// 3. 检查操作符和值是否有效
- if (!isOperatorAndValueValid(trigger)) {
- logMatchFailure(message, trigger, "操作符或值无效");
+ if (!isTriggerOperatorAndValueValid(trigger)) {
+ logTriggerMatchFailure(message, trigger, "操作符或值无效");
return false;
}
// 4. 获取设备状态值
Object stateValue = message.getData();
if (stateValue == null) {
- logMatchFailure(message, trigger, "消息中设备状态值为空");
+ logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
return false;
}
@@ -57,9 +62,9 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger
boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
if (matched) {
- logMatchSuccess(message, trigger);
+ logTriggerMatchSuccess(message, trigger);
} else {
- logMatchFailure(message, trigger, "状态值条件不匹配");
+ 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/IotSceneRuleMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java
new file mode 100644
index 0000000000..5e5c35baf5
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java
@@ -0,0 +1,123 @@
+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.IotSceneRuleConditionLevelEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
+
+/**
+ * IoT 场景规则匹配器统一接口
+ *
+ * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计
+ *
+ * 匹配器类型说明:
+ * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等)
+ * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等)
+ *
+ * @author HUIHUI
+ */
+public interface IotSceneRuleMatcher {
+
+ /**
+ * 匹配器类型枚举
+ */
+ enum MatcherType {
+ /**
+ * 触发器匹配器 - 用于匹配主触发条件
+ */
+ TRIGGER,
+ /**
+ * 条件匹配器 - 用于匹配子条件
+ */
+ CONDITION
+ }
+
+ /**
+ * 获取匹配器类型
+ *
+ * @return 匹配器类型
+ */
+ MatcherType getMatcherType();
+
+ /**
+ * 获取支持的触发器类型(仅触发器匹配器需要实现)
+ *
+ * @return 触发器类型枚举,条件匹配器返回 null
+ */
+ default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
+ return null;
+ }
+
+ /**
+ * 获取支持的条件类型(仅条件匹配器需要实现)
+ *
+ * @return 条件类型枚举,触发器匹配器返回 null
+ */
+ default IotSceneRuleConditionTypeEnum getSupportedConditionType() {
+ return null;
+ }
+
+ /**
+ * 获取支持的条件层级(仅条件匹配器需要实现)
+ *
+ * @return 条件层级枚举,触发器匹配器返回 null
+ */
+ default IotSceneRuleConditionLevelEnum getSupportedConditionLevel() {
+ 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("条件匹配方法仅支持条件匹配器");
+ }
+
+ /**
+ * 获取匹配优先级(数值越小优先级越高)
+ *
+ * 用于在多个匹配器支持同一类型时确定优先级
+ *
+ * @return 优先级数值
+ */
+ default int getPriority() {
+ return 100;
+ }
+
+ /**
+ * 获取匹配器名称,用于日志和调试
+ *
+ * @return 匹配器名称
+ */
+ default String getMatcherName() {
+ return this.getClass().getSimpleName();
+ }
+
+ /**
+ * 是否启用该匹配器
+ *
+ * 可用于动态开关某些匹配器
+ *
+ * @return 是否启用
+ */
+ default 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/IotSceneRuleMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java
new file mode 100644
index 0000000000..9852e4acdb
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java
@@ -0,0 +1,296 @@
+package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
+
+import cn.hutool.core.collection.CollUtil;
+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.IotSceneRuleConditionLevelEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
+import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum;
+
+/**
+ * IoT 场景规则匹配器统一管理器
+ *
+ * 负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口
+ *
+ * @author HUIHUI
+ */
+@Component
+@Slf4j
+public class IotSceneRuleMatcherManager {
+
+ /**
+ * 触发器匹配器映射表
+ * Key: 触发器类型枚举
+ * Value: 对应的匹配器实例
+ */
+ private final Map
- * 用于实现不同类型触发器的匹配逻辑,遵循策略模式设计
- *
- * @author HUIHUI
- */
-public interface IotSceneRuleTriggerMatcher {
-
- /**
- * 获取支持的触发器类型
- *
- * @return 触发器类型枚举
- */
- IotSceneRuleTriggerTypeEnum getSupportedTriggerType();
-
- /**
- * 检查触发器是否匹配消息
- *
- * @param message 设备消息
- * @param trigger 触发器配置
- * @return 是否匹配
- */
- boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger);
-
- /**
- * 获取匹配优先级(数值越小优先级越高)
- *
- * 用于在多个匹配器支持同一触发器类型时确定优先级
- *
- * @return 优先级数值
- */
- default int getPriority() {
- return 100;
- }
-
- /**
- * 获取匹配器名称,用于日志和调试
- *
- * @return 匹配器名称
- */
- default String getMatcherName() {
- return this.getClass().getSimpleName();
- }
-
- /**
- * 是否启用该匹配器
- *
- * 可用于动态开关某些匹配器
- *
- * @return 是否启用
- */
- default 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/IotSceneRuleTriggerMatcherManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java
deleted file mode 100644
index 6e6c383a95..0000000000
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
-
-import cn.hutool.core.collection.CollUtil;
-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 lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.util.*;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum;
-
-/**
- * IoT 场景规则触发器匹配管理器
- *
- * 负责管理所有触发器匹配器,并提供统一的匹配入口
- *
- * @author HUIHUI
- */
-@Component
-@Slf4j
-public class IotSceneRuleTriggerMatcherManager {
-
- /**
- * 触发器匹配器映射表
- * Key: 触发器类型枚举
- * Value: 对应的匹配器实例
- */
- private final Map