Commit 49bf744b740150c722297ed161702c44ccdf4ac3
1 parent
0d288077
feat:【IoT 物联网】新增场景规则触发器匹配策略接口和实现
Showing
10 changed files
with
913 additions
and
0 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java
| ... | ... | @@ -63,4 +63,18 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> { |
| 63 | 63 | return ARRAYS; |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * 根据类型值查找触发器类型枚举 | |
| 69 | + * | |
| 70 | + * @param typeValue 类型值 | |
| 71 | + * @return 触发器类型枚举 | |
| 72 | + */ | |
| 73 | + public static IotSceneRuleTriggerTypeEnum findTriggerTypeEnum(Integer typeValue) { | |
| 74 | + return Arrays.stream(IotSceneRuleTriggerTypeEnum.values()) | |
| 75 | + .filter(type -> type.getType().equals(typeValue)) | |
| 76 | + .findFirst() | |
| 77 | + .orElse(null); | |
| 78 | + } | |
| 79 | + | |
| 66 | 80 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.NumberUtil; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; | |
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | |
| 9 | +import lombok.extern.slf4j.Slf4j; | |
| 10 | + | |
| 11 | +import java.util.HashMap; | |
| 12 | +import java.util.List; | |
| 13 | +import java.util.Map; | |
| 14 | + | |
| 15 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 16 | + | |
| 17 | +/** | |
| 18 | + * IoT 场景规则触发器匹配器抽象基类 | |
| 19 | + * <p> | |
| 20 | + * 提供通用的条件评估逻辑和工具方法 | |
| 21 | + * | |
| 22 | + * @author HUIHUI | |
| 23 | + */ | |
| 24 | +@Slf4j | |
| 25 | +public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRuleTriggerMatcher { | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 评估条件是否匹配 | |
| 29 | + * | |
| 30 | + * @param sourceValue 源值(来自消息) | |
| 31 | + * @param operator 操作符 | |
| 32 | + * @param paramValue 参数值(来自条件配置) | |
| 33 | + * @return 是否匹配 | |
| 34 | + */ | |
| 35 | + protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { | |
| 36 | + try { | |
| 37 | + // 1. 校验操作符是否合法 | |
| 38 | + IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); | |
| 39 | + if (operatorEnum == null) { | |
| 40 | + log.warn("[evaluateCondition][存在错误的操作符({})]", operator); | |
| 41 | + return false; | |
| 42 | + } | |
| 43 | + | |
| 44 | + // 2. 构建 Spring 表达式变量 | |
| 45 | + Map<String, Object> springExpressionVariables = new HashMap<>(); | |
| 46 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); | |
| 47 | + | |
| 48 | + // 处理参数值 | |
| 49 | + if (StrUtil.isNotBlank(paramValue)) { | |
| 50 | + // 处理多值情况(如 IN、BETWEEN 操作符) | |
| 51 | + if (paramValue.contains(",")) { | |
| 52 | + List<String> paramValues = StrUtil.split(paramValue, ','); | |
| 53 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, | |
| 54 | + convertList(paramValues, NumberUtil::parseDouble)); | |
| 55 | + } else { | |
| 56 | + // 处理单值情况 | |
| 57 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, | |
| 58 | + NumberUtil.parseDouble(paramValue)); | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + // 3. 计算 Spring 表达式 | |
| 63 | + return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); | |
| 64 | + } catch (Exception e) { | |
| 65 | + log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}", | |
| 66 | + sourceValue, operator, paramValue, e); | |
| 67 | + return false; | |
| 68 | + } | |
| 69 | + } | |
| 70 | + | |
| 71 | + /** | |
| 72 | + * 检查基础触发器参数是否有效 | |
| 73 | + * | |
| 74 | + * @param trigger 触发器配置 | |
| 75 | + * @return 是否有效 | |
| 76 | + */ | |
| 77 | + protected boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { | |
| 78 | + return trigger != null && trigger.getType() != null; | |
| 79 | + } | |
| 80 | + | |
| 81 | + /** | |
| 82 | + * 检查操作符和值是否有效 | |
| 83 | + * | |
| 84 | + * @param trigger 触发器配置 | |
| 85 | + * @return 是否有效 | |
| 86 | + */ | |
| 87 | + protected boolean isOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { | |
| 88 | + return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); | |
| 89 | + } | |
| 90 | + | |
| 91 | + /** | |
| 92 | + * 检查标识符是否匹配 | |
| 93 | + * | |
| 94 | + * @param expectedIdentifier 期望的标识符 | |
| 95 | + * @param actualIdentifier 实际的标识符 | |
| 96 | + * @return 是否匹配 | |
| 97 | + */ | |
| 98 | + protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { | |
| 99 | + return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * 记录匹配成功日志 | |
| 104 | + * | |
| 105 | + * @param message 设备消息 | |
| 106 | + * @param trigger 触发器配置 | |
| 107 | + */ | |
| 108 | + protected void logMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 109 | + log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); | |
| 110 | + } | |
| 111 | + | |
| 112 | + /** | |
| 113 | + * 记录匹配失败日志 | |
| 114 | + * | |
| 115 | + * @param message 设备消息 | |
| 116 | + * @param trigger 触发器配置 | |
| 117 | + * @param reason 失败原因 | |
| 118 | + */ | |
| 119 | + protected void logMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { | |
| 120 | + log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); | |
| 121 | + } | |
| 122 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | |
| 6 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 设备事件上报触发器匹配器 | |
| 12 | + * <p> | |
| 13 | + * 处理设备事件上报的触发器匹配逻辑 | |
| 14 | + * | |
| 15 | + * @author HUIHUI | |
| 16 | + */ | |
| 17 | +@Component | |
| 18 | +public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 设备事件上报消息方法 | |
| 22 | + */ | |
| 23 | + private static final String DEVICE_EVENT_POST_METHOD = "thing.event.post"; | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | |
| 27 | + return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 32 | + // 1. 基础参数校验 | |
| 33 | + if (!isBasicTriggerValid(trigger)) { | |
| 34 | + logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 35 | + return false; | |
| 36 | + } | |
| 37 | + | |
| 38 | + // 2. 检查消息方法是否匹配 | |
| 39 | + if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { | |
| 40 | + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); | |
| 41 | + return false; | |
| 42 | + } | |
| 43 | + | |
| 44 | + // 3. 检查标识符是否匹配 | |
| 45 | + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | |
| 46 | + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | |
| 47 | + logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 48 | + return false; | |
| 49 | + } | |
| 50 | + | |
| 51 | + // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 | |
| 52 | + // 但如果配置了操作符和值,则需要进行条件匹配 | |
| 53 | + if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { | |
| 54 | + Object eventData = message.getData(); | |
| 55 | + if (eventData == null) { | |
| 56 | + logMatchFailure(message, trigger, "消息中事件数据为空"); | |
| 57 | + return false; | |
| 58 | + } | |
| 59 | + | |
| 60 | + boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); | |
| 61 | + if (!matched) { | |
| 62 | + logMatchFailure(message, trigger, "事件数据条件不匹配"); | |
| 63 | + return false; | |
| 64 | + } | |
| 65 | + } | |
| 66 | + | |
| 67 | + logMatchSuccess(message, trigger); | |
| 68 | + return true; | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Override | |
| 72 | + public int getPriority() { | |
| 73 | + return 30; // 中等优先级 | |
| 74 | + } | |
| 75 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | |
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 7 | +import org.springframework.stereotype.Component; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 设备属性上报触发器匹配器 | |
| 11 | + * <p> | |
| 12 | + * 处理设备属性数据上报的触发器匹配逻辑 | |
| 13 | + * | |
| 14 | + * @author HUIHUI | |
| 15 | + */ | |
| 16 | +@Component | |
| 17 | +public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * 设备属性上报消息方法 | |
| 21 | + */ | |
| 22 | + private static final String DEVICE_PROPERTY_POST_METHOD = "thing.property.post"; | |
| 23 | + | |
| 24 | + @Override | |
| 25 | + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | |
| 26 | + return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; | |
| 27 | + } | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 31 | + // 1. 基础参数校验 | |
| 32 | + if (!isBasicTriggerValid(trigger)) { | |
| 33 | + logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 34 | + return false; | |
| 35 | + } | |
| 36 | + | |
| 37 | + // 2. 检查消息方法是否匹配 | |
| 38 | + if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { | |
| 39 | + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); | |
| 40 | + return false; | |
| 41 | + } | |
| 42 | + | |
| 43 | + // 3. 检查标识符是否匹配 | |
| 44 | + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | |
| 45 | + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | |
| 46 | + logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 47 | + return false; | |
| 48 | + } | |
| 49 | + | |
| 50 | + // 4. 检查操作符和值是否有效 | |
| 51 | + if (!isOperatorAndValueValid(trigger)) { | |
| 52 | + logMatchFailure(message, trigger, "操作符或值无效"); | |
| 53 | + return false; | |
| 54 | + } | |
| 55 | + | |
| 56 | + // 5. 获取属性值 | |
| 57 | + Object propertyValue = message.getData(); | |
| 58 | + if (propertyValue == null) { | |
| 59 | + logMatchFailure(message, trigger, "消息中属性值为空"); | |
| 60 | + return false; | |
| 61 | + } | |
| 62 | + | |
| 63 | + // 6. 使用条件评估器进行匹配 | |
| 64 | + boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); | |
| 65 | + | |
| 66 | + if (matched) { | |
| 67 | + logMatchSuccess(message, trigger); | |
| 68 | + } else { | |
| 69 | + logMatchFailure(message, trigger, "属性值条件不匹配"); | |
| 70 | + } | |
| 71 | + | |
| 72 | + return matched; | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + public int getPriority() { | |
| 77 | + return 20; // 中等优先级 | |
| 78 | + } | |
| 79 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | |
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 7 | +import org.springframework.stereotype.Component; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 设备服务调用触发器匹配器 | |
| 11 | + * <p> | |
| 12 | + * 处理设备服务调用的触发器匹配逻辑 | |
| 13 | + * | |
| 14 | + * @author HUIHUI | |
| 15 | + */ | |
| 16 | +@Component | |
| 17 | +public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * 设备服务调用消息方法 | |
| 21 | + */ | |
| 22 | + private static final String DEVICE_SERVICE_INVOKE_METHOD = "thing.service.invoke"; | |
| 23 | + | |
| 24 | + @Override | |
| 25 | + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | |
| 26 | + return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; | |
| 27 | + } | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 31 | + // 1. 基础参数校验 | |
| 32 | + if (!isBasicTriggerValid(trigger)) { | |
| 33 | + logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 34 | + return false; | |
| 35 | + } | |
| 36 | + | |
| 37 | + // 2. 检查消息方法是否匹配 | |
| 38 | + if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { | |
| 39 | + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); | |
| 40 | + return false; | |
| 41 | + } | |
| 42 | + | |
| 43 | + // 3. 检查标识符是否匹配 | |
| 44 | + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | |
| 45 | + if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | |
| 46 | + logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 47 | + return false; | |
| 48 | + } | |
| 49 | + | |
| 50 | + // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 | |
| 51 | + // 不需要检查操作符和值,因为服务调用本身就是触发条件 | |
| 52 | + | |
| 53 | + logMatchSuccess(message, trigger); | |
| 54 | + return true; | |
| 55 | + } | |
| 56 | + | |
| 57 | + @Override | |
| 58 | + public int getPriority() { | |
| 59 | + return 40; // 较低优先级 | |
| 60 | + } | |
| 61 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 4 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 5 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 6 | +import org.springframework.stereotype.Component; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 设备状态更新触发器匹配器 | |
| 10 | + * <p> | |
| 11 | + * 处理设备上下线状态变更的触发器匹配逻辑 | |
| 12 | + * | |
| 13 | + * @author HUIHUI | |
| 14 | + */ | |
| 15 | +@Component | |
| 16 | +public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 设备状态更新消息方法 | |
| 20 | + */ | |
| 21 | + private static final String DEVICE_STATE_UPDATE_METHOD = "thing.state.update"; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | |
| 25 | + return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; | |
| 26 | + } | |
| 27 | + | |
| 28 | + @Override | |
| 29 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 30 | + // 1. 基础参数校验 | |
| 31 | + if (!isBasicTriggerValid(trigger)) { | |
| 32 | + logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 33 | + return false; | |
| 34 | + } | |
| 35 | + | |
| 36 | + // 2. 检查消息方法是否匹配 | |
| 37 | + if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { | |
| 38 | + logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); | |
| 39 | + return false; | |
| 40 | + } | |
| 41 | + | |
| 42 | + // 3. 检查操作符和值是否有效 | |
| 43 | + if (!isOperatorAndValueValid(trigger)) { | |
| 44 | + logMatchFailure(message, trigger, "操作符或值无效"); | |
| 45 | + return false; | |
| 46 | + } | |
| 47 | + | |
| 48 | + // 4. 获取设备状态值 | |
| 49 | + Object stateValue = message.getData(); | |
| 50 | + if (stateValue == null) { | |
| 51 | + logMatchFailure(message, trigger, "消息中设备状态值为空"); | |
| 52 | + return false; | |
| 53 | + } | |
| 54 | + | |
| 55 | + // 5. 使用条件评估器进行匹配 | |
| 56 | + boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); | |
| 57 | + | |
| 58 | + if (matched) { | |
| 59 | + logMatchSuccess(message, trigger); | |
| 60 | + } else { | |
| 61 | + logMatchFailure(message, trigger, "状态值条件不匹配"); | |
| 62 | + } | |
| 63 | + | |
| 64 | + return matched; | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public int getPriority() { | |
| 69 | + return 10; // 高优先级 | |
| 70 | + } | |
| 71 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 4 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 5 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * IoT 场景规则触发器匹配策略接口 | |
| 9 | + * <p> | |
| 10 | + * 用于实现不同类型触发器的匹配逻辑,遵循策略模式设计 | |
| 11 | + * | |
| 12 | + * @author HUIHUI | |
| 13 | + */ | |
| 14 | +public interface IotSceneRuleTriggerMatcher { | |
| 15 | + | |
| 16 | + /** | |
| 17 | + * 获取支持的触发器类型 | |
| 18 | + * | |
| 19 | + * @return 触发器类型枚举 | |
| 20 | + */ | |
| 21 | + IotSceneRuleTriggerTypeEnum getSupportedTriggerType(); | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * 检查触发器是否匹配消息 | |
| 25 | + * | |
| 26 | + * @param message 设备消息 | |
| 27 | + * @param trigger 触发器配置 | |
| 28 | + * @return 是否匹配 | |
| 29 | + */ | |
| 30 | + boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger); | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 获取匹配优先级(数值越小优先级越高) | |
| 34 | + * <p> | |
| 35 | + * 用于在多个匹配器支持同一触发器类型时确定优先级 | |
| 36 | + * | |
| 37 | + * @return 优先级数值 | |
| 38 | + */ | |
| 39 | + default int getPriority() { | |
| 40 | + return 100; | |
| 41 | + } | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * 获取匹配器名称,用于日志和调试 | |
| 45 | + * | |
| 46 | + * @return 匹配器名称 | |
| 47 | + */ | |
| 48 | + default String getMatcherName() { | |
| 49 | + return this.getClass().getSimpleName(); | |
| 50 | + } | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 是否启用该匹配器 | |
| 54 | + * <p> | |
| 55 | + * 可用于动态开关某些匹配器 | |
| 56 | + * | |
| 57 | + * @return 是否启用 | |
| 58 | + */ | |
| 59 | + default boolean isEnabled() { | |
| 60 | + return true; | |
| 61 | + } | |
| 62 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 7 | +import lombok.extern.slf4j.Slf4j; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +import java.util.*; | |
| 11 | +import java.util.function.Function; | |
| 12 | +import java.util.stream.Collectors; | |
| 13 | + | |
| 14 | +import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * IoT 场景规则触发器匹配管理器 | |
| 18 | + * <p> | |
| 19 | + * 负责管理所有触发器匹配器,并提供统一的匹配入口 | |
| 20 | + * | |
| 21 | + * @author HUIHUI | |
| 22 | + */ | |
| 23 | +@Component | |
| 24 | +@Slf4j | |
| 25 | +public class IotSceneRuleTriggerMatcherManager { | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 触发器匹配器映射表 | |
| 29 | + * Key: 触发器类型枚举 | |
| 30 | + * Value: 对应的匹配器实例 | |
| 31 | + */ | |
| 32 | + private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleTriggerMatcher> matcherMap; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 所有匹配器列表(按优先级排序) | |
| 36 | + */ | |
| 37 | + private final List<IotSceneRuleTriggerMatcher> allMatchers; | |
| 38 | + | |
| 39 | + public IotSceneRuleTriggerMatcherManager(List<IotSceneRuleTriggerMatcher> matchers) { | |
| 40 | + if (CollUtil.isEmpty(matchers)) { | |
| 41 | + log.warn("[IotSceneRuleTriggerMatcherManager][没有找到任何触发器匹配器]"); | |
| 42 | + this.matcherMap = new HashMap<>(); | |
| 43 | + this.allMatchers = new ArrayList<>(); | |
| 44 | + return; | |
| 45 | + } | |
| 46 | + | |
| 47 | + // 按优先级排序并过滤启用的匹配器 | |
| 48 | + this.allMatchers = matchers.stream() | |
| 49 | + .filter(IotSceneRuleTriggerMatcher::isEnabled) | |
| 50 | + .sorted(Comparator.comparing(IotSceneRuleTriggerMatcher::getPriority)) | |
| 51 | + .collect(Collectors.toList()); | |
| 52 | + | |
| 53 | + // 构建匹配器映射表 | |
| 54 | + this.matcherMap = this.allMatchers.stream() | |
| 55 | + .collect(Collectors.toMap( | |
| 56 | + IotSceneRuleTriggerMatcher::getSupportedTriggerType, | |
| 57 | + Function.identity(), | |
| 58 | + (existing, replacement) -> { | |
| 59 | + log.warn("[IotSceneRuleTriggerMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", | |
| 60 | + existing.getSupportedTriggerType(), | |
| 61 | + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); | |
| 62 | + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; | |
| 63 | + }, | |
| 64 | + LinkedHashMap::new | |
| 65 | + )); | |
| 66 | + | |
| 67 | + log.info("[IotSceneRuleTriggerMatcherManager][初始化完成,共加载 {} 个触发器匹配器]", this.matcherMap.size()); | |
| 68 | + this.matcherMap.forEach((type, matcher) -> | |
| 69 | + log.info("[IotSceneRuleTriggerMatcherManager][触发器类型: {}, 匹配器: {}, 优先级: {}]", | |
| 70 | + type, matcher.getMatcherName(), matcher.getPriority())); | |
| 71 | + } | |
| 72 | + | |
| 73 | + /** | |
| 74 | + * 检查触发器是否匹配消息 | |
| 75 | + * | |
| 76 | + * @param message 设备消息 | |
| 77 | + * @param trigger 触发器配置 | |
| 78 | + * @return 是否匹配 | |
| 79 | + */ | |
| 80 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 81 | + if (message == null || trigger == null || trigger.getType() == null) { | |
| 82 | + log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger); | |
| 83 | + return false; | |
| 84 | + } | |
| 85 | + | |
| 86 | + // 根据触发器类型查找对应的匹配器 | |
| 87 | + IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType()); | |
| 88 | + if (triggerType == null) { | |
| 89 | + log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); | |
| 90 | + return false; | |
| 91 | + } | |
| 92 | + | |
| 93 | + IotSceneRuleTriggerMatcher matcher = matcherMap.get(triggerType); | |
| 94 | + if (matcher == null) { | |
| 95 | + log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); | |
| 96 | + return false; | |
| 97 | + } | |
| 98 | + | |
| 99 | + try { | |
| 100 | + return matcher.isMatched(message, trigger); | |
| 101 | + } catch (Exception e) { | |
| 102 | + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}", | |
| 103 | + message, trigger, matcher.getMatcherName(), e); | |
| 104 | + return false; | |
| 105 | + } | |
| 106 | + } | |
| 107 | + | |
| 108 | + /** | |
| 109 | + * 获取所有支持的触发器类型 | |
| 110 | + * | |
| 111 | + * @return 支持的触发器类型列表 | |
| 112 | + */ | |
| 113 | + public Set<IotSceneRuleTriggerTypeEnum> getSupportedTriggerTypes() { | |
| 114 | + return new HashSet<>(matcherMap.keySet()); | |
| 115 | + } | |
| 116 | + | |
| 117 | + /** | |
| 118 | + * 获取指定触发器类型的匹配器 | |
| 119 | + * | |
| 120 | + * @param triggerType 触发器类型 | |
| 121 | + * @return 匹配器实例,如果不存在则返回 null | |
| 122 | + */ | |
| 123 | + public IotSceneRuleTriggerMatcher getMatcher(IotSceneRuleTriggerTypeEnum triggerType) { | |
| 124 | + return matcherMap.get(triggerType); | |
| 125 | + } | |
| 126 | + | |
| 127 | + /** | |
| 128 | + * 获取所有匹配器的统计信息 | |
| 129 | + * | |
| 130 | + * @return 统计信息映射表 | |
| 131 | + */ | |
| 132 | + public Map<String, Object> getMatcherStatistics() { | |
| 133 | + Map<String, Object> statistics = new HashMap<>(); | |
| 134 | + statistics.put("totalMatchers", allMatchers.size()); | |
| 135 | + statistics.put("enabledMatchers", matcherMap.size()); | |
| 136 | + statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); | |
| 137 | + | |
| 138 | + Map<String, Object> matcherDetails = new HashMap<>(); | |
| 139 | + matcherMap.forEach((type, matcher) -> { | |
| 140 | + Map<String, Object> detail = new HashMap<>(); | |
| 141 | + detail.put("matcherName", matcher.getMatcherName()); | |
| 142 | + detail.put("priority", matcher.getPriority()); | |
| 143 | + detail.put("enabled", matcher.isEnabled()); | |
| 144 | + matcherDetails.put(type.name(), detail); | |
| 145 | + }); | |
| 146 | + statistics.put("matcherDetails", matcherDetails); | |
| 147 | + | |
| 148 | + return statistics; | |
| 149 | + } | |
| 150 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 7 | +import org.springframework.stereotype.Component; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 定时触发器匹配器 | |
| 11 | + * <p> | |
| 12 | + * 处理定时触发的触发器匹配逻辑 | |
| 13 | + * 注意:定时触发器不依赖设备消息,主要用于定时任务场景 | |
| 14 | + * | |
| 15 | + * @author HUIHUI | |
| 16 | + */ | |
| 17 | +@Component | |
| 18 | +public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 19 | + | |
| 20 | + @Override | |
| 21 | + public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | |
| 22 | + return IotSceneRuleTriggerTypeEnum.TIMER; | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 27 | + // 1. 基础参数校验 | |
| 28 | + if (!isBasicTriggerValid(trigger)) { | |
| 29 | + logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 30 | + return false; | |
| 31 | + } | |
| 32 | + | |
| 33 | + // 2. 检查 CRON 表达式是否存在 | |
| 34 | + if (StrUtil.isBlank(trigger.getCronExpression())) { | |
| 35 | + logMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); | |
| 36 | + return false; | |
| 37 | + } | |
| 38 | + | |
| 39 | + // 3. 定时触发器通常不依赖具体的设备消息 | |
| 40 | + // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 | |
| 41 | + | |
| 42 | + // 4. 可以添加 CRON 表达式格式验证 | |
| 43 | + if (!isValidCronExpression(trigger.getCronExpression())) { | |
| 44 | + logMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); | |
| 45 | + return false; | |
| 46 | + } | |
| 47 | + | |
| 48 | + logMatchSuccess(message, trigger); | |
| 49 | + return true; | |
| 50 | + } | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 验证 CRON 表达式格式是否有效 | |
| 54 | + * | |
| 55 | + * @param cronExpression CRON 表达式 | |
| 56 | + * @return 是否有效 | |
| 57 | + */ | |
| 58 | + private boolean isValidCronExpression(String cronExpression) { | |
| 59 | + try { | |
| 60 | + // 简单的 CRON 表达式格式验证 | |
| 61 | + // 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年]) | |
| 62 | + String[] fields = cronExpression.trim().split("\\s+"); | |
| 63 | + return fields.length >= 6 && fields.length <= 7; | |
| 64 | + } catch (Exception e) { | |
| 65 | + return false; | |
| 66 | + } | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Override | |
| 70 | + public int getPriority() { | |
| 71 | + return 50; // 最低优先级,因为定时触发器不依赖消息 | |
| 72 | + } | |
| 73 | + | |
| 74 | + @Override | |
| 75 | + public boolean isEnabled() { | |
| 76 | + // 定时触发器可以根据配置动态启用/禁用 | |
| 77 | + return true; | |
| 78 | + } | |
| 79 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 7 | +import org.junit.jupiter.api.BeforeEach; | |
| 8 | +import org.junit.jupiter.api.Test; | |
| 9 | + | |
| 10 | +import java.util.Arrays; | |
| 11 | +import java.util.HashMap; | |
| 12 | +import java.util.List; | |
| 13 | + | |
| 14 | +import static org.junit.jupiter.api.Assertions.*; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * IoT 场景规则触发器匹配器测试类 | |
| 18 | + * | |
| 19 | + * @author HUIHUI | |
| 20 | + */ | |
| 21 | +public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { | |
| 22 | + | |
| 23 | + private IotSceneRuleTriggerMatcherManager matcherManager; | |
| 24 | + private List<IotSceneRuleTriggerMatcher> matchers; | |
| 25 | + | |
| 26 | + @BeforeEach | |
| 27 | + void setUp() { | |
| 28 | + // 创建所有匹配器实例 | |
| 29 | + matchers = Arrays.asList( | |
| 30 | + new DeviceStateUpdateTriggerMatcher(), | |
| 31 | + new DevicePropertyPostTriggerMatcher(), | |
| 32 | + new DeviceEventPostTriggerMatcher(), | |
| 33 | + new DeviceServiceInvokeTriggerMatcher(), | |
| 34 | + new TimerTriggerMatcher() | |
| 35 | + ); | |
| 36 | + | |
| 37 | + // 初始化匹配器管理器 | |
| 38 | + matcherManager = new IotSceneRuleTriggerMatcherManager(matchers); | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Test | |
| 42 | + void testDeviceStateUpdateTriggerMatcher() { | |
| 43 | + // 1. 准备测试数据 | |
| 44 | + IotDeviceMessage message = IotDeviceMessage.builder() | |
| 45 | + .requestId("test-001") | |
| 46 | + .method("thing.state.update") | |
| 47 | + .data(1) // 在线状态 | |
| 48 | + .build(); | |
| 49 | + | |
| 50 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | |
| 51 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); | |
| 52 | + trigger.setOperator("="); | |
| 53 | + trigger.setValue("1"); | |
| 54 | + | |
| 55 | + // 2. 执行测试 | |
| 56 | + boolean matched = matcherManager.isMatched(message, trigger); | |
| 57 | + | |
| 58 | + // 3. 验证结果 | |
| 59 | + assertTrue(matched, "设备状态更新触发器应该匹配"); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Test | |
| 63 | + void testDevicePropertyPostTriggerMatcher() { | |
| 64 | + // 1. 准备测试数据 | |
| 65 | + HashMap<String, Object> params = new HashMap<>(); | |
| 66 | + IotDeviceMessage message = IotDeviceMessage.builder() | |
| 67 | + .requestId("test-002") | |
| 68 | + .method("thing.property.post") | |
| 69 | + .data(25.5) // 温度值 | |
| 70 | + .params(params) | |
| 71 | + .build(); | |
| 72 | + // 模拟标识符 | |
| 73 | + params.put("identifier", "temperature"); | |
| 74 | + | |
| 75 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | |
| 76 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); | |
| 77 | + trigger.setIdentifier("temperature"); | |
| 78 | + trigger.setOperator(">"); | |
| 79 | + trigger.setValue("20"); | |
| 80 | + | |
| 81 | + // 2. 执行测试 | |
| 82 | + boolean matched = matcherManager.isMatched(message, trigger); | |
| 83 | + | |
| 84 | + // 3. 验证结果 | |
| 85 | + assertTrue(matched, "设备属性上报触发器应该匹配"); | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Test | |
| 89 | + void testDeviceEventPostTriggerMatcher() { | |
| 90 | + // 1. 准备测试数据 | |
| 91 | + HashMap<String, Object> params = new HashMap<>(); | |
| 92 | + IotDeviceMessage message = IotDeviceMessage.builder() | |
| 93 | + .requestId("test-003") | |
| 94 | + .method("thing.event.post") | |
| 95 | + .data("alarm_data") | |
| 96 | + .params(params) | |
| 97 | + .build(); | |
| 98 | + // 模拟标识符 | |
| 99 | + params.put("identifier", "high_temperature_alarm"); | |
| 100 | + | |
| 101 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | |
| 102 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); | |
| 103 | + trigger.setIdentifier("high_temperature_alarm"); | |
| 104 | + | |
| 105 | + // 2. 执行测试 | |
| 106 | + boolean matched = matcherManager.isMatched(message, trigger); | |
| 107 | + | |
| 108 | + // 3. 验证结果 | |
| 109 | + assertTrue(matched, "设备事件上报触发器应该匹配"); | |
| 110 | + } | |
| 111 | + | |
| 112 | + @Test | |
| 113 | + void testDeviceServiceInvokeTriggerMatcher() { | |
| 114 | + // 1. 准备测试数据 | |
| 115 | + HashMap<String, Object> params = new HashMap<>(); | |
| 116 | + IotDeviceMessage message = IotDeviceMessage.builder() | |
| 117 | + .requestId("test-004") | |
| 118 | + .method("thing.service.invoke") | |
| 119 | + .msg("alarm_data") | |
| 120 | + .params(params) | |
| 121 | + .build(); | |
| 122 | + // 模拟标识符 | |
| 123 | + params.put("identifier", "restart_device"); | |
| 124 | + | |
| 125 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | |
| 126 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); | |
| 127 | + trigger.setIdentifier("restart_device"); | |
| 128 | + | |
| 129 | + // 2. 执行测试 | |
| 130 | + boolean matched = matcherManager.isMatched(message, trigger); | |
| 131 | + | |
| 132 | + // 3. 验证结果 | |
| 133 | + assertTrue(matched, "设备服务调用触发器应该匹配"); | |
| 134 | + } | |
| 135 | + | |
| 136 | + @Test | |
| 137 | + void testTimerTriggerMatcher() { | |
| 138 | + // 1. 准备测试数据 | |
| 139 | + IotDeviceMessage message = IotDeviceMessage.builder() | |
| 140 | + .requestId("test-005") | |
| 141 | + .method("timer.trigger") // 定时触发器不依赖具体消息方法 | |
| 142 | + .build(); | |
| 143 | + | |
| 144 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | |
| 145 | + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); | |
| 146 | + trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点 | |
| 147 | + | |
| 148 | + // 2. 执行测试 | |
| 149 | + boolean matched = matcherManager.isMatched(message, trigger); | |
| 150 | + | |
| 151 | + // 3. 验证结果 | |
| 152 | + assertTrue(matched, "定时触发器应该匹配"); | |
| 153 | + } | |
| 154 | + | |
| 155 | + @Test | |
| 156 | + void testInvalidTriggerType() { | |
| 157 | + // 1. 准备测试数据 | |
| 158 | + IotDeviceMessage message = IotDeviceMessage.builder() | |
| 159 | + .requestId("test-006") | |
| 160 | + .method("unknown.method") | |
| 161 | + .build(); | |
| 162 | + | |
| 163 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | |
| 164 | + trigger.setType(999); // 无效的触发器类型 | |
| 165 | + | |
| 166 | + // 2. 执行测试 | |
| 167 | + boolean matched = matcherManager.isMatched(message, trigger); | |
| 168 | + | |
| 169 | + // 3. 验证结果 | |
| 170 | + assertFalse(matched, "无效的触发器类型应该不匹配"); | |
| 171 | + } | |
| 172 | + | |
| 173 | + @Test | |
| 174 | + void testMatcherManagerStatistics() { | |
| 175 | + // 1. 执行测试 | |
| 176 | + var statistics = matcherManager.getMatcherStatistics(); | |
| 177 | + | |
| 178 | + // 2. 验证结果 | |
| 179 | + assertNotNull(statistics); | |
| 180 | + assertEquals(5, statistics.get("totalMatchers")); | |
| 181 | + assertEquals(5, statistics.get("enabledMatchers")); | |
| 182 | + assertNotNull(statistics.get("supportedTriggerTypes")); | |
| 183 | + assertNotNull(statistics.get("matcherDetails")); | |
| 184 | + } | |
| 185 | + | |
| 186 | + @Test | |
| 187 | + void testGetSupportedTriggerTypes() { | |
| 188 | + // 1. 执行测试 | |
| 189 | + var supportedTypes = matcherManager.getSupportedTriggerTypes(); | |
| 190 | + | |
| 191 | + // 2. 验证结果 | |
| 192 | + assertNotNull(supportedTypes); | |
| 193 | + assertEquals(5, supportedTypes.size()); | |
| 194 | + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE)); | |
| 195 | + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST)); | |
| 196 | + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST)); | |
| 197 | + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); | |
| 198 | + assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); | |
| 199 | + } | |
| 200 | +} | ... | ... |