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 0000000..c83b72c --- /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 295a796..fc3e967 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,45 +81,90 @@ 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 expectedIdentifier 期望的标识符
- * @param actualIdentifier 实际的标识符
- * @return 是否匹配
- */
- protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) {
- return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier);
- }
-
- /**
- * 记录匹配成功日志
+ * 记录触发器匹配成功日志
*
* @param message 设备消息
* @param trigger 触发器配置
*/
- protected void logMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
+ protected void logTriggerMatchSuccess(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) {
+ 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);
+ }
+
+ // ========== 通用工具方法 ==========
+
+ /**
+ * 检查标识符是否匹配
+ *
+ * @param expectedIdentifier 期望的标识符
+ * @param actualIdentifier 实际的标识符
+ * @return 是否匹配
+ */
+ protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) {
+ return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier);
+ }
+
}
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/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 0000000..df11c66
--- /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 1ee0cdd..3c832f6 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,7 +16,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
/**
* 设备事件上报消息方法
@@ -24,6 +24,11 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa
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 0000000..70c789e
--- /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 7ed0051..0953453 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,7 +15,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
/**
* 设备属性上报消息方法
@@ -23,6 +23,11 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge
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 996fe17..c2b7e4e 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,7 +15,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher {
/**
* 设备服务调用消息方法
@@ -23,6 +23,11 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg
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 0000000..aa3acab
--- /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 aec372c..a505e0d 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,7 +14,7 @@ import org.springframework.stereotype.Component;
* @author HUIHUI
*/
@Component
-public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher {
+public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher {
/**
* 设备状态更新消息方法
@@ -22,6 +22,11 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger
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/IotSceneRuleTriggerMatcher.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java
index bf111a2..5e5c35b 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java
@@ -2,37 +2,97 @@ 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 场景规则触发器匹配策略接口
+ * IoT 场景规则匹配器统一接口
*
- * 用于实现不同类型触发器的匹配逻辑,遵循策略模式设计
+ * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计
+ *
+ * 匹配器类型说明:
+ * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等)
+ * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等)
*
* @author HUIHUI
*/
-public interface IotSceneRuleTriggerMatcher {
+public interface IotSceneRuleMatcher {
+
+ /**
+ * 匹配器类型枚举
+ */
+ enum MatcherType {
+ /**
+ * 触发器匹配器 - 用于匹配主触发条件
+ */
+ TRIGGER,
+ /**
+ * 条件匹配器 - 用于匹配子条件
+ */
+ CONDITION
+ }
+
+ /**
+ * 获取匹配器类型
+ *
+ * @return 匹配器类型
+ */
+ MatcherType getMatcherType();
/**
- * 获取支持的触发器类型
+ * 获取支持的触发器类型(仅触发器匹配器需要实现)
*
- * @return 触发器类型枚举
+ * @return 触发器类型枚举,条件匹配器返回 null
*/
- IotSceneRuleTriggerTypeEnum getSupportedTriggerType();
+ default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
+ return null;
+ }
/**
- * 检查触发器是否匹配消息
+ * 获取支持的条件类型(仅条件匹配器需要实现)
+ *
+ * @return 条件类型枚举,触发器匹配器返回 null
+ */
+ default IotSceneRuleConditionTypeEnum getSupportedConditionType() {
+ return null;
+ }
+
+ /**
+ * 获取支持的条件层级(仅条件匹配器需要实现)
+ *
+ * @return 条件层级枚举,触发器匹配器返回 null
+ */
+ default IotSceneRuleConditionLevelEnum getSupportedConditionLevel() {
+ return null;
+ }
+
+ /**
+ * 检查触发器是否匹配消息(仅触发器匹配器需要实现)
*
* @param message 设备消息
* @param trigger 触发器配置
* @return 是否匹配
*/
- boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger);
+ 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 优先级数值
*/
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 0000000..9852e4a
--- /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
- */
-@Component
-@Slf4j
-public class IotSceneRuleTriggerMatcherManager {
-
- /**
- * 触发器匹配器映射表
- * Key: 触发器类型枚举
- * Value: 对应的匹配器实例
- */
- private final Map