Commit 93aaffddfefad7ecc2039a2da448dcbe8cb7be96
1 parent
cfb5230c
feat:【IoT 物联网】新增场景规则触发器匹配子条件策略接口和实现
Showing
15 changed files
with
905 additions
and
338 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleConditionLevelEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.enums.rule; | |
| 2 | + | |
| 3 | +import lombok.AllArgsConstructor; | |
| 4 | +import lombok.Getter; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * IoT 场景规则条件层级枚举 | |
| 8 | + * <p> | |
| 9 | + * 用于区分主条件(触发器级别)和子条件(条件分组级别) | |
| 10 | + * | |
| 11 | + * @author HUIHUI | |
| 12 | + */ | |
| 13 | +@AllArgsConstructor | |
| 14 | +@Getter | |
| 15 | +public enum IotSceneRuleConditionLevelEnum { | |
| 16 | + | |
| 17 | + /** | |
| 18 | + * 主条件 - 触发器级别的条件 | |
| 19 | + * 用于判断触发器本身是否匹配(如消息类型、设备标识等) | |
| 20 | + */ | |
| 21 | + PRIMARY(1, "主条件"), | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * 子条件 - 条件分组级别的条件 | |
| 25 | + * 用于判断具体的业务条件(如设备状态、属性值、时间条件等) | |
| 26 | + */ | |
| 27 | + SECONDARY(2, "子条件"); | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 条件层级 | |
| 31 | + */ | |
| 32 | + private final Integer level; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 条件层级名称 | |
| 36 | + */ | |
| 37 | + private final String name; | |
| 38 | + | |
| 39 | + /** | |
| 40 | + * 根据层级值获取枚举 | |
| 41 | + * | |
| 42 | + * @param level 层级值 | |
| 43 | + * @return 条件层级枚举 | |
| 44 | + */ | |
| 45 | + public static IotSceneRuleConditionLevelEnum levelOf(Integer level) { | |
| 46 | + if (level == null) { | |
| 47 | + return null; | |
| 48 | + } | |
| 49 | + for (IotSceneRuleConditionLevelEnum levelEnum : values()) { | |
| 50 | + if (levelEnum.getLevel().equals(level)) { | |
| 51 | + return levelEnum; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + return null; | |
| 55 | + } | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * 判断是否为主条件 | |
| 59 | + * | |
| 60 | + * @return 是否为主条件 | |
| 61 | + */ | |
| 62 | + public boolean isPrimary() { | |
| 63 | + return this == PRIMARY; | |
| 64 | + } | |
| 65 | + | |
| 66 | + /** | |
| 67 | + * 判断是否为子条件 | |
| 68 | + * | |
| 69 | + * @return 是否为子条件 | |
| 70 | + */ | |
| 71 | + public boolean isSecondary() { | |
| 72 | + return this == SECONDARY; | |
| 73 | + } | |
| 74 | +} | ... | ... |
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; |
| 19 | 19 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; |
| 20 | 20 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; |
| 21 | 21 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 22 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | |
| 23 | 22 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 24 | 23 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 25 | 24 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 26 | 25 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; |
| 27 | 26 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; |
| 28 | -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | |
| 29 | 27 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 30 | 28 | import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; |
| 31 | 29 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| 32 | 30 | import cn.iocoder.yudao.module.iot.service.product.IotProductService; |
| 33 | 31 | import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction; |
| 34 | -import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleTriggerMatcherManager; | |
| 32 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager; | |
| 35 | 33 | import jakarta.annotation.Resource; |
| 36 | 34 | import lombok.extern.slf4j.Slf4j; |
| 37 | 35 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -67,7 +65,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { |
| 67 | 65 | @Resource |
| 68 | 66 | private IotDeviceService deviceService; |
| 69 | 67 | @Resource |
| 70 | - private IotSceneRuleTriggerMatcherManager triggerMatcherManager; | |
| 68 | + private IotSceneRuleMatcherManager matcherManager; | |
| 71 | 69 | @Resource |
| 72 | 70 | private List<IotSceneRuleAction> sceneRuleActions; |
| 73 | 71 | |
| ... | ... | @@ -285,7 +283,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { |
| 285 | 283 | private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { |
| 286 | 284 | try { |
| 287 | 285 | // 2. 检查触发器的条件分组 |
| 288 | - return triggerMatcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); | |
| 286 | + return matcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); | |
| 289 | 287 | } catch (Exception e) { |
| 290 | 288 | log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}", |
| 291 | 289 | sceneRule.getId(), trigger.getType(), message, e); |
| ... | ... | @@ -333,7 +331,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { |
| 333 | 331 | } |
| 334 | 332 | |
| 335 | 333 | /** |
| 336 | - * 基于消息,判断触发器的条件是否匹配 | |
| 334 | + * 基于消息,判断触发器的子条件是否匹配 | |
| 337 | 335 | * |
| 338 | 336 | * @param message 设备消息 |
| 339 | 337 | * @param condition 触发条件 |
| ... | ... | @@ -344,21 +342,8 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { |
| 344 | 342 | private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, |
| 345 | 343 | IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { |
| 346 | 344 | try { |
| 347 | - // 1. 根据条件类型进行匹配 | |
| 348 | - if (IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType().equals(condition.getType())) { | |
| 349 | - // 设备状态条件匹配 | |
| 350 | - return matchDeviceStateCondition(message, condition); | |
| 351 | - } else if (IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType().equals(condition.getType())) { | |
| 352 | - // 设备属性条件匹配 | |
| 353 | - return matchDevicePropertyCondition(message, condition); | |
| 354 | - } else if (IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType().equals(condition.getType())) { | |
| 355 | - // 当前时间条件匹配 | |
| 356 | - return matchCurrentTimeCondition(condition); | |
| 357 | - } else { | |
| 358 | - log.warn("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 存在未知的条件类型({})]", | |
| 359 | - sceneRule.getId(), trigger, condition.getType()); | |
| 360 | - return false; | |
| 361 | - } | |
| 345 | + // 使用重构后的条件匹配管理器进行匹配 | |
| 346 | + return matcherManager.isConditionMatched(message, condition); | |
| 362 | 347 | } catch (Exception e) { |
| 363 | 348 | log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]", |
| 364 | 349 | sceneRule.getId(), trigger, e); |
| ... | ... | @@ -366,105 +351,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { |
| 366 | 351 | } |
| 367 | 352 | } |
| 368 | 353 | |
| 369 | - /** | |
| 370 | - * 匹配设备状态条件 | |
| 371 | - * | |
| 372 | - * @param message 设备消息 | |
| 373 | - * @param condition 触发条件 | |
| 374 | - * @return 是否匹配 | |
| 375 | - */ | |
| 376 | - private boolean matchDeviceStateCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 377 | - // TODO @芋艿:需要根据设备状态进行匹配 | |
| 378 | - // 这里需要检查消息中的设备状态是否符合条件中定义的状态 | |
| 379 | - log.debug("[matchDeviceStateCondition][设备状态条件匹配逻辑待实现] condition: {}", condition); | |
| 380 | - return false; | |
| 381 | - } | |
| 382 | - | |
| 383 | - /** | |
| 384 | - * 匹配设备属性条件 | |
| 385 | - * | |
| 386 | - * @param message 设备消息 | |
| 387 | - * @param condition 触发条件 | |
| 388 | - * @return 是否匹配 | |
| 389 | - */ | |
| 390 | - private boolean matchDevicePropertyCondition(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 391 | - // 1. 检查标识符是否匹配 | |
| 392 | - String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | |
| 393 | - if (StrUtil.isBlank(condition.getIdentifier()) || !condition.getIdentifier().equals(messageIdentifier)) { | |
| 394 | - return false; | |
| 395 | - } | |
| 396 | - | |
| 397 | - // 2. 获取消息中的属性值 | |
| 398 | - Object messageValue = message.getData(); | |
| 399 | - if (messageValue == null) { | |
| 400 | - return false; | |
| 401 | - } | |
| 402 | - | |
| 403 | - // 3. 根据操作符进行匹配 | |
| 404 | - return evaluateCondition(messageValue, condition.getOperator(), condition.getParam()); | |
| 405 | - } | |
| 406 | - | |
| 407 | - /** | |
| 408 | - * 匹配当前时间条件 | |
| 409 | - * | |
| 410 | - * @param condition 触发条件 | |
| 411 | - * @return 是否匹配 | |
| 412 | - */ | |
| 413 | - private boolean matchCurrentTimeCondition(IotSceneRuleDO.TriggerCondition condition) { | |
| 414 | - // TODO @芋艿:需要根据当前时间进行匹配 | |
| 415 | - // 这里需要检查当前时间是否符合条件中定义的时间范围 | |
| 416 | - log.debug("[matchCurrentTimeCondition][当前时间条件匹配逻辑待实现] condition: {}", condition); | |
| 417 | - return false; | |
| 418 | - } | |
| 419 | - | |
| 420 | - /** | |
| 421 | - * 评估条件是否匹配 | |
| 422 | - * | |
| 423 | - * @param sourceValue 源值(来自消息) | |
| 424 | - * @param operator 操作符 | |
| 425 | - * @param paramValue 参数值(来自条件配置) | |
| 426 | - * @return 是否匹配 | |
| 427 | - */ | |
| 428 | - private boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { | |
| 429 | - try { | |
| 430 | - // 1. 校验操作符是否合法 | |
| 431 | - IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); | |
| 432 | - if (operatorEnum == null) { | |
| 433 | - log.warn("[evaluateCondition][存在错误的操作符({})]", operator); | |
| 434 | - return false; | |
| 435 | - } | |
| 436 | - | |
| 437 | - // 2.1 构建 Spring 表达式的变量 | |
| 438 | - Map<String, Object> springExpressionVariables = MapUtil.<String, Object>builder() | |
| 439 | - .put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue) | |
| 440 | - .build(); | |
| 441 | - // 2.2 根据操作符类型处理参数值 | |
| 442 | - if (StrUtil.isNotBlank(paramValue)) { | |
| 443 | - // TODO @puhui999:这里是不是在 IotSceneRuleConditionOperatorEnum 加个属性; | |
| 444 | - if (operatorEnum == IotSceneRuleConditionOperatorEnum.IN | |
| 445 | - || operatorEnum == IotSceneRuleConditionOperatorEnum.NOT_IN | |
| 446 | - || operatorEnum == IotSceneRuleConditionOperatorEnum.BETWEEN | |
| 447 | - || operatorEnum == IotSceneRuleConditionOperatorEnum.NOT_BETWEEN) { | |
| 448 | - // 处理多值情况 | |
| 449 | - List<String> paramValues = StrUtil.split(paramValue, CharPool.COMMA); | |
| 450 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, | |
| 451 | - convertList(paramValues, NumberUtil::parseDouble)); | |
| 452 | - } else { | |
| 453 | - // 处理单值情况 | |
| 454 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, | |
| 455 | - NumberUtil.parseDouble(paramValue)); | |
| 456 | - } | |
| 457 | - } | |
| 458 | - | |
| 459 | - // 3. 计算 Spring 表达式 | |
| 460 | - return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); | |
| 461 | - } catch (Exception e) { | |
| 462 | - log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}", | |
| 463 | - sourceValue, operator, paramValue, e); | |
| 464 | - return false; | |
| 465 | - } | |
| 466 | - } | |
| 467 | - | |
| 468 | 354 | // TODO @芋艿:【可优化】可以考虑增加下单测,边界太多了。 |
| 469 | 355 | |
| 470 | 356 | /** | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleTriggerMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java
| ... | ... | @@ -15,14 +15,14 @@ import java.util.Map; |
| 15 | 15 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 16 | 16 | |
| 17 | 17 | /** |
| 18 | - * IoT 场景规则触发器匹配器抽象基类 | |
| 18 | + * IoT 场景规则匹配器抽象基类 | |
| 19 | 19 | * <p> |
| 20 | - * 提供通用的条件评估逻辑和工具方法 | |
| 20 | + * 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型 | |
| 21 | 21 | * |
| 22 | 22 | * @author HUIHUI |
| 23 | 23 | */ |
| 24 | 24 | @Slf4j |
| 25 | -public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRuleTriggerMatcher { | |
| 25 | +public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher { | |
| 26 | 26 | |
| 27 | 27 | /** |
| 28 | 28 | * 评估条件是否匹配 |
| ... | ... | @@ -68,6 +68,8 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule |
| 68 | 68 | } |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | + // ========== 触发器相关工具方法 ========== | |
| 72 | + | |
| 71 | 73 | /** |
| 72 | 74 | * 检查基础触发器参数是否有效 |
| 73 | 75 | * |
| ... | ... | @@ -79,45 +81,90 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule |
| 79 | 81 | } |
| 80 | 82 | |
| 81 | 83 | /** |
| 82 | - * 检查操作符和值是否有效 | |
| 84 | + * 检查触发器操作符和值是否有效 | |
| 83 | 85 | * |
| 84 | 86 | * @param trigger 触发器配置 |
| 85 | 87 | * @return 是否有效 |
| 86 | 88 | */ |
| 87 | - protected boolean isOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { | |
| 89 | + protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { | |
| 88 | 90 | return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); |
| 89 | 91 | } |
| 90 | 92 | |
| 91 | 93 | /** |
| 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 | - * 记录匹配成功日志 | |
| 94 | + * 记录触发器匹配成功日志 | |
| 104 | 95 | * |
| 105 | 96 | * @param message 设备消息 |
| 106 | 97 | * @param trigger 触发器配置 |
| 107 | 98 | */ |
| 108 | - protected void logMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 99 | + protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 109 | 100 | log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); |
| 110 | 101 | } |
| 111 | 102 | |
| 112 | 103 | /** |
| 113 | - * 记录匹配失败日志 | |
| 104 | + * 记录触发器匹配失败日志 | |
| 114 | 105 | * |
| 115 | 106 | * @param message 设备消息 |
| 116 | 107 | * @param trigger 触发器配置 |
| 117 | 108 | * @param reason 失败原因 |
| 118 | 109 | */ |
| 119 | - protected void logMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { | |
| 110 | + protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { | |
| 120 | 111 | log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); |
| 121 | 112 | } |
| 122 | 113 | |
| 114 | + // ========== 条件相关工具方法 ========== | |
| 115 | + | |
| 116 | + /** | |
| 117 | + * 检查基础条件参数是否有效 | |
| 118 | + * | |
| 119 | + * @param condition 触发条件 | |
| 120 | + * @return 是否有效 | |
| 121 | + */ | |
| 122 | + protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { | |
| 123 | + return condition != null && condition.getType() != null; | |
| 124 | + } | |
| 125 | + | |
| 126 | + /** | |
| 127 | + * 检查条件操作符和参数是否有效 | |
| 128 | + * | |
| 129 | + * @param condition 触发条件 | |
| 130 | + * @return 是否有效 | |
| 131 | + */ | |
| 132 | + protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { | |
| 133 | + return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam()); | |
| 134 | + } | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * 记录条件匹配成功日志 | |
| 138 | + * | |
| 139 | + * @param message 设备消息 | |
| 140 | + * @param condition 触发条件 | |
| 141 | + */ | |
| 142 | + protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 143 | + log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType()); | |
| 144 | + } | |
| 145 | + | |
| 146 | + /** | |
| 147 | + * 记录条件匹配失败日志 | |
| 148 | + * | |
| 149 | + * @param message 设备消息 | |
| 150 | + * @param condition 触发条件 | |
| 151 | + * @param reason 失败原因 | |
| 152 | + */ | |
| 153 | + protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { | |
| 154 | + log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason); | |
| 155 | + } | |
| 156 | + | |
| 157 | + // ========== 通用工具方法 ========== | |
| 158 | + | |
| 159 | + /** | |
| 160 | + * 检查标识符是否匹配 | |
| 161 | + * | |
| 162 | + * @param expectedIdentifier 期望的标识符 | |
| 163 | + * @param actualIdentifier 实际的标识符 | |
| 164 | + * @return 是否匹配 | |
| 165 | + */ | |
| 166 | + protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { | |
| 167 | + return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); | |
| 168 | + } | |
| 169 | + | |
| 123 | 170 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.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.IotSceneRuleConditionLevelEnum; | |
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | |
| 8 | +import lombok.extern.slf4j.Slf4j; | |
| 9 | +import org.springframework.stereotype.Component; | |
| 10 | + | |
| 11 | +import java.time.LocalDateTime; | |
| 12 | +import java.time.LocalTime; | |
| 13 | +import java.time.format.DateTimeFormatter; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 当前时间条件匹配器 | |
| 17 | + * <p> | |
| 18 | + * 处理时间相关的子条件匹配逻辑 | |
| 19 | + * | |
| 20 | + * @author HUIHUI | |
| 21 | + */ | |
| 22 | +@Component | |
| 23 | +@Slf4j | |
| 24 | +public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 时间格式化器 - HH:mm:ss | |
| 28 | + */ | |
| 29 | + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 时间格式化器 - HH:mm | |
| 33 | + */ | |
| 34 | + private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + public MatcherType getMatcherType() { | |
| 38 | + return MatcherType.CONDITION; | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Override | |
| 42 | + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { | |
| 43 | + return IotSceneRuleConditionTypeEnum.CURRENT_TIME; | |
| 44 | + } | |
| 45 | + | |
| 46 | + @Override | |
| 47 | + public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { | |
| 48 | + return IotSceneRuleConditionLevelEnum.SECONDARY; | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 53 | + // 1. 基础参数校验 | |
| 54 | + if (!isBasicConditionValid(condition)) { | |
| 55 | + logConditionMatchFailure(message, condition, "条件基础参数无效"); | |
| 56 | + return false; | |
| 57 | + } | |
| 58 | + | |
| 59 | + // 2. 检查操作符和参数是否有效 | |
| 60 | + if (!isConditionOperatorAndParamValid(condition)) { | |
| 61 | + logConditionMatchFailure(message, condition, "操作符或参数无效"); | |
| 62 | + return false; | |
| 63 | + } | |
| 64 | + | |
| 65 | + // 3. 获取当前时间 | |
| 66 | + LocalDateTime now = LocalDateTime.now(); | |
| 67 | + | |
| 68 | + // 4. 根据操作符类型进行不同的时间匹配 | |
| 69 | + String operator = condition.getOperator(); | |
| 70 | + String param = condition.getParam(); | |
| 71 | + | |
| 72 | + boolean matched = false; | |
| 73 | + | |
| 74 | + try { | |
| 75 | + if (operator.startsWith("date_time_")) { | |
| 76 | + // 日期时间匹配(时间戳) | |
| 77 | + matched = matchDateTime(now, operator, param); | |
| 78 | + } else if (operator.startsWith("time_")) { | |
| 79 | + // 当日时间匹配(HH:mm:ss) | |
| 80 | + matched = matchTime(now.toLocalTime(), operator, param); | |
| 81 | + } else { | |
| 82 | + // 其他操作符,使用通用条件评估器 | |
| 83 | + matched = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); | |
| 84 | + } | |
| 85 | + | |
| 86 | + if (matched) { | |
| 87 | + logConditionMatchSuccess(message, condition); | |
| 88 | + } else { | |
| 89 | + logConditionMatchFailure(message, condition, "时间条件不匹配"); | |
| 90 | + } | |
| 91 | + | |
| 92 | + } catch (Exception e) { | |
| 93 | + log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e); | |
| 94 | + logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage()); | |
| 95 | + matched = false; | |
| 96 | + } | |
| 97 | + | |
| 98 | + return matched; | |
| 99 | + } | |
| 100 | + | |
| 101 | + /** | |
| 102 | + * 匹配日期时间(时间戳) | |
| 103 | + */ | |
| 104 | + private boolean matchDateTime(LocalDateTime now, String operator, String param) { | |
| 105 | + long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); | |
| 106 | + return evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param); | |
| 107 | + } | |
| 108 | + | |
| 109 | + /** | |
| 110 | + * 匹配当日时间(HH:mm:ss) | |
| 111 | + */ | |
| 112 | + private boolean matchTime(LocalTime currentTime, String operator, String param) { | |
| 113 | + try { | |
| 114 | + String actualOperator = operator.substring("time_".length()); | |
| 115 | + | |
| 116 | + if ("between".equals(actualOperator)) { | |
| 117 | + // 时间区间匹配 | |
| 118 | + String[] timeRange = param.split(","); | |
| 119 | + if (timeRange.length != 2) { | |
| 120 | + return false; | |
| 121 | + } | |
| 122 | + | |
| 123 | + LocalTime startTime = parseTime(timeRange[0].trim()); | |
| 124 | + LocalTime endTime = parseTime(timeRange[1].trim()); | |
| 125 | + | |
| 126 | + return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime); | |
| 127 | + } else { | |
| 128 | + // 单个时间比较 | |
| 129 | + LocalTime targetTime = parseTime(param); | |
| 130 | + | |
| 131 | + switch (actualOperator) { | |
| 132 | + case ">": | |
| 133 | + return currentTime.isAfter(targetTime); | |
| 134 | + case "<": | |
| 135 | + return currentTime.isBefore(targetTime); | |
| 136 | + case ">=": | |
| 137 | + return !currentTime.isBefore(targetTime); | |
| 138 | + case "<=": | |
| 139 | + return !currentTime.isAfter(targetTime); | |
| 140 | + case "=": | |
| 141 | + return currentTime.equals(targetTime); | |
| 142 | + default: | |
| 143 | + return false; | |
| 144 | + } | |
| 145 | + } | |
| 146 | + } catch (Exception e) { | |
| 147 | + log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e); | |
| 148 | + return false; | |
| 149 | + } | |
| 150 | + } | |
| 151 | + | |
| 152 | + /** | |
| 153 | + * 解析时间字符串 | |
| 154 | + */ | |
| 155 | + private LocalTime parseTime(String timeStr) { | |
| 156 | + if (StrUtil.isBlank(timeStr)) { | |
| 157 | + throw new IllegalArgumentException("时间字符串不能为空"); | |
| 158 | + } | |
| 159 | + | |
| 160 | + // 尝试不同的时间格式 | |
| 161 | + try { | |
| 162 | + if (timeStr.length() == 5) { // HH:mm | |
| 163 | + return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); | |
| 164 | + } else { // HH:mm:ss | |
| 165 | + return LocalTime.parse(timeStr, TIME_FORMATTER); | |
| 166 | + } | |
| 167 | + } catch (Exception e) { | |
| 168 | + throw new IllegalArgumentException("时间格式无效: " + timeStr, e); | |
| 169 | + } | |
| 170 | + } | |
| 171 | + | |
| 172 | + @Override | |
| 173 | + public int getPriority() { | |
| 174 | + return 40; // 较低优先级 | |
| 175 | + } | |
| 176 | + | |
| 177 | +} | ... | ... |
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; |
| 16 | 16 | * @author HUIHUI |
| 17 | 17 | */ |
| 18 | 18 | @Component |
| 19 | -public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 19 | +public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { | |
| 20 | 20 | |
| 21 | 21 | /** |
| 22 | 22 | * 设备事件上报消息方法 |
| ... | ... | @@ -24,6 +24,11 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa |
| 24 | 24 | private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); |
| 25 | 25 | |
| 26 | 26 | @Override |
| 27 | + public MatcherType getMatcherType() { | |
| 28 | + return MatcherType.TRIGGER; | |
| 29 | + } | |
| 30 | + | |
| 31 | + @Override | |
| 27 | 32 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 28 | 33 | return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; |
| 29 | 34 | } |
| ... | ... | @@ -32,20 +37,20 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa |
| 32 | 37 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 33 | 38 | // 1. 基础参数校验 |
| 34 | 39 | if (!isBasicTriggerValid(trigger)) { |
| 35 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 40 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 36 | 41 | return false; |
| 37 | 42 | } |
| 38 | 43 | |
| 39 | 44 | // 2. 检查消息方法是否匹配 |
| 40 | 45 | if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { |
| 41 | - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); | |
| 46 | + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); | |
| 42 | 47 | return false; |
| 43 | 48 | } |
| 44 | 49 | |
| 45 | 50 | // 3. 检查标识符是否匹配 |
| 46 | 51 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 47 | 52 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 48 | - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 53 | + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 49 | 54 | return false; |
| 50 | 55 | } |
| 51 | 56 | |
| ... | ... | @@ -54,18 +59,18 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa |
| 54 | 59 | if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { |
| 55 | 60 | Object eventData = message.getData(); |
| 56 | 61 | if (eventData == null) { |
| 57 | - logMatchFailure(message, trigger, "消息中事件数据为空"); | |
| 62 | + logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); | |
| 58 | 63 | return false; |
| 59 | 64 | } |
| 60 | 65 | |
| 61 | 66 | boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); |
| 62 | 67 | if (!matched) { |
| 63 | - logMatchFailure(message, trigger, "事件数据条件不匹配"); | |
| 68 | + logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); | |
| 64 | 69 | return false; |
| 65 | 70 | } |
| 66 | 71 | } |
| 67 | 72 | |
| 68 | - logMatchSuccess(message, trigger); | |
| 73 | + logTriggerMatchSuccess(message, trigger); | |
| 69 | 74 | return true; |
| 70 | 75 | } |
| 71 | 76 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.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.IotSceneRuleConditionLevelEnum; | |
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 设备属性条件匹配器 | |
| 12 | + * <p> | |
| 13 | + * 处理设备属性相关的子条件匹配逻辑 | |
| 14 | + * | |
| 15 | + * @author HUIHUI | |
| 16 | + */ | |
| 17 | +@Component | |
| 18 | +public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher { | |
| 19 | + | |
| 20 | + @Override | |
| 21 | + public MatcherType getMatcherType() { | |
| 22 | + return MatcherType.CONDITION; | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { | |
| 27 | + return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { | |
| 32 | + return IotSceneRuleConditionLevelEnum.SECONDARY; | |
| 33 | + } | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 37 | + // 1. 基础参数校验 | |
| 38 | + if (!isBasicConditionValid(condition)) { | |
| 39 | + logConditionMatchFailure(message, condition, "条件基础参数无效"); | |
| 40 | + return false; | |
| 41 | + } | |
| 42 | + | |
| 43 | + // 2. 检查标识符是否匹配 | |
| 44 | + String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | |
| 45 | + if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { | |
| 46 | + logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 47 | + return false; | |
| 48 | + } | |
| 49 | + | |
| 50 | + // 3. 检查操作符和参数是否有效 | |
| 51 | + if (!isConditionOperatorAndParamValid(condition)) { | |
| 52 | + logConditionMatchFailure(message, condition, "操作符或参数无效"); | |
| 53 | + return false; | |
| 54 | + } | |
| 55 | + | |
| 56 | + // 4. 获取属性值 | |
| 57 | + Object propertyValue = message.getData(); | |
| 58 | + if (propertyValue == null) { | |
| 59 | + logConditionMatchFailure(message, condition, "消息中属性值为空"); | |
| 60 | + return false; | |
| 61 | + } | |
| 62 | + | |
| 63 | + // 5. 使用条件评估器进行匹配 | |
| 64 | + boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); | |
| 65 | + | |
| 66 | + if (matched) { | |
| 67 | + logConditionMatchSuccess(message, condition); | |
| 68 | + } else { | |
| 69 | + logConditionMatchFailure(message, condition, "设备属性条件不匹配"); | |
| 70 | + } | |
| 71 | + | |
| 72 | + return matched; | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + public int getPriority() { | |
| 77 | + return 25; // 中等优先级 | |
| 78 | + } | |
| 79 | + | |
| 80 | +} | ... | ... |
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; |
| 15 | 15 | * @author HUIHUI |
| 16 | 16 | */ |
| 17 | 17 | @Component |
| 18 | -public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 18 | +public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher { | |
| 19 | 19 | |
| 20 | 20 | /** |
| 21 | 21 | * 设备属性上报消息方法 |
| ... | ... | @@ -23,6 +23,11 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge |
| 23 | 23 | private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); |
| 24 | 24 | |
| 25 | 25 | @Override |
| 26 | + public MatcherType getMatcherType() { | |
| 27 | + return MatcherType.TRIGGER; | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Override | |
| 26 | 31 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 27 | 32 | return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; |
| 28 | 33 | } |
| ... | ... | @@ -31,33 +36,33 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge |
| 31 | 36 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 32 | 37 | // 1. 基础参数校验 |
| 33 | 38 | if (!isBasicTriggerValid(trigger)) { |
| 34 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 39 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 35 | 40 | return false; |
| 36 | 41 | } |
| 37 | 42 | |
| 38 | 43 | // 2. 检查消息方法是否匹配 |
| 39 | 44 | if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { |
| 40 | - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); | |
| 45 | + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); | |
| 41 | 46 | return false; |
| 42 | 47 | } |
| 43 | 48 | |
| 44 | 49 | // 3. 检查标识符是否匹配 |
| 45 | 50 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 46 | 51 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 47 | - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 52 | + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 48 | 53 | return false; |
| 49 | 54 | } |
| 50 | 55 | |
| 51 | 56 | // 4. 检查操作符和值是否有效 |
| 52 | - if (!isOperatorAndValueValid(trigger)) { | |
| 53 | - logMatchFailure(message, trigger, "操作符或值无效"); | |
| 57 | + if (!isTriggerOperatorAndValueValid(trigger)) { | |
| 58 | + logTriggerMatchFailure(message, trigger, "操作符或值无效"); | |
| 54 | 59 | return false; |
| 55 | 60 | } |
| 56 | 61 | |
| 57 | 62 | // 5. 获取属性值 |
| 58 | 63 | Object propertyValue = message.getData(); |
| 59 | 64 | if (propertyValue == null) { |
| 60 | - logMatchFailure(message, trigger, "消息中属性值为空"); | |
| 65 | + logTriggerMatchFailure(message, trigger, "消息中属性值为空"); | |
| 61 | 66 | return false; |
| 62 | 67 | } |
| 63 | 68 | |
| ... | ... | @@ -65,9 +70,9 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge |
| 65 | 70 | boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); |
| 66 | 71 | |
| 67 | 72 | if (matched) { |
| 68 | - logMatchSuccess(message, trigger); | |
| 73 | + logTriggerMatchSuccess(message, trigger); | |
| 69 | 74 | } else { |
| 70 | - logMatchFailure(message, trigger, "属性值条件不匹配"); | |
| 75 | + logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); | |
| 71 | 76 | } |
| 72 | 77 | |
| 73 | 78 | return matched; | ... | ... |
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; |
| 15 | 15 | * @author HUIHUI |
| 16 | 16 | */ |
| 17 | 17 | @Component |
| 18 | -public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 18 | +public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher { | |
| 19 | 19 | |
| 20 | 20 | /** |
| 21 | 21 | * 设备服务调用消息方法 |
| ... | ... | @@ -23,6 +23,11 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg |
| 23 | 23 | private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); |
| 24 | 24 | |
| 25 | 25 | @Override |
| 26 | + public MatcherType getMatcherType() { | |
| 27 | + return MatcherType.TRIGGER; | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Override | |
| 26 | 31 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 27 | 32 | return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; |
| 28 | 33 | } |
| ... | ... | @@ -31,27 +36,27 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg |
| 31 | 36 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 32 | 37 | // 1. 基础参数校验 |
| 33 | 38 | if (!isBasicTriggerValid(trigger)) { |
| 34 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 39 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 35 | 40 | return false; |
| 36 | 41 | } |
| 37 | 42 | |
| 38 | 43 | // 2. 检查消息方法是否匹配 |
| 39 | 44 | if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { |
| 40 | - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); | |
| 45 | + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); | |
| 41 | 46 | return false; |
| 42 | 47 | } |
| 43 | 48 | |
| 44 | 49 | // 3. 检查标识符是否匹配 |
| 45 | 50 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 46 | 51 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 47 | - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 52 | + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | |
| 48 | 53 | return false; |
| 49 | 54 | } |
| 50 | 55 | |
| 51 | 56 | // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 |
| 52 | 57 | // 不需要检查操作符和值,因为服务调用本身就是触发条件 |
| 53 | 58 | |
| 54 | - logMatchSuccess(message, trigger); | |
| 59 | + logTriggerMatchSuccess(message, trigger); | |
| 55 | 60 | return true; |
| 56 | 61 | } |
| 57 | 62 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.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.IotSceneRuleConditionLevelEnum; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | |
| 7 | +import org.springframework.stereotype.Component; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 设备状态条件匹配器 | |
| 11 | + * <p> | |
| 12 | + * 处理设备状态相关的子条件匹配逻辑 | |
| 13 | + * | |
| 14 | + * @author HUIHUI | |
| 15 | + */ | |
| 16 | +@Component | |
| 17 | +public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { | |
| 18 | + | |
| 19 | + @Override | |
| 20 | + public MatcherType getMatcherType() { | |
| 21 | + return MatcherType.CONDITION; | |
| 22 | + } | |
| 23 | + | |
| 24 | + @Override | |
| 25 | + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { | |
| 26 | + return IotSceneRuleConditionTypeEnum.DEVICE_STATE; | |
| 27 | + } | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + public IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { | |
| 31 | + return IotSceneRuleConditionLevelEnum.SECONDARY; | |
| 32 | + } | |
| 33 | + | |
| 34 | + @Override | |
| 35 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 36 | + // 1. 基础参数校验 | |
| 37 | + if (!isBasicConditionValid(condition)) { | |
| 38 | + logConditionMatchFailure(message, condition, "条件基础参数无效"); | |
| 39 | + return false; | |
| 40 | + } | |
| 41 | + | |
| 42 | + // 2. 检查操作符和参数是否有效 | |
| 43 | + if (!isConditionOperatorAndParamValid(condition)) { | |
| 44 | + logConditionMatchFailure(message, condition, "操作符或参数无效"); | |
| 45 | + return false; | |
| 46 | + } | |
| 47 | + | |
| 48 | + // 3. 获取设备状态值 | |
| 49 | + // 设备状态通常在消息的 data 字段中 | |
| 50 | + Object stateValue = message.getData(); | |
| 51 | + if (stateValue == null) { | |
| 52 | + logConditionMatchFailure(message, condition, "消息中设备状态值为空"); | |
| 53 | + return false; | |
| 54 | + } | |
| 55 | + | |
| 56 | + // 4. 使用条件评估器进行匹配 | |
| 57 | + boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); | |
| 58 | + | |
| 59 | + if (matched) { | |
| 60 | + logConditionMatchSuccess(message, condition); | |
| 61 | + } else { | |
| 62 | + logConditionMatchFailure(message, condition, "设备状态条件不匹配"); | |
| 63 | + } | |
| 64 | + | |
| 65 | + return matched; | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public int getPriority() { | |
| 70 | + return 30; // 中等优先级 | |
| 71 | + } | |
| 72 | + | |
| 73 | +} | ... | ... |
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; |
| 14 | 14 | * @author HUIHUI |
| 15 | 15 | */ |
| 16 | 16 | @Component |
| 17 | -public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 17 | +public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher { | |
| 18 | 18 | |
| 19 | 19 | /** |
| 20 | 20 | * 设备状态更新消息方法 |
| ... | ... | @@ -22,6 +22,11 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger |
| 22 | 22 | private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); |
| 23 | 23 | |
| 24 | 24 | @Override |
| 25 | + public MatcherType getMatcherType() { | |
| 26 | + return MatcherType.TRIGGER; | |
| 27 | + } | |
| 28 | + | |
| 29 | + @Override | |
| 25 | 30 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 26 | 31 | return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; |
| 27 | 32 | } |
| ... | ... | @@ -30,26 +35,26 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger |
| 30 | 35 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 31 | 36 | // 1. 基础参数校验 |
| 32 | 37 | if (!isBasicTriggerValid(trigger)) { |
| 33 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 38 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 34 | 39 | return false; |
| 35 | 40 | } |
| 36 | 41 | |
| 37 | 42 | // 2. 检查消息方法是否匹配 |
| 38 | 43 | if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { |
| 39 | - logMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); | |
| 44 | + logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); | |
| 40 | 45 | return false; |
| 41 | 46 | } |
| 42 | 47 | |
| 43 | 48 | // 3. 检查操作符和值是否有效 |
| 44 | - if (!isOperatorAndValueValid(trigger)) { | |
| 45 | - logMatchFailure(message, trigger, "操作符或值无效"); | |
| 49 | + if (!isTriggerOperatorAndValueValid(trigger)) { | |
| 50 | + logTriggerMatchFailure(message, trigger, "操作符或值无效"); | |
| 46 | 51 | return false; |
| 47 | 52 | } |
| 48 | 53 | |
| 49 | 54 | // 4. 获取设备状态值 |
| 50 | 55 | Object stateValue = message.getData(); |
| 51 | 56 | if (stateValue == null) { |
| 52 | - logMatchFailure(message, trigger, "消息中设备状态值为空"); | |
| 57 | + logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); | |
| 53 | 58 | return false; |
| 54 | 59 | } |
| 55 | 60 | |
| ... | ... | @@ -57,9 +62,9 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger |
| 57 | 62 | boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); |
| 58 | 63 | |
| 59 | 64 | if (matched) { |
| 60 | - logMatchSuccess(message, trigger); | |
| 65 | + logTriggerMatchSuccess(message, trigger); | |
| 61 | 66 | } else { |
| 62 | - logMatchFailure(message, trigger, "状态值条件不匹配"); | |
| 67 | + logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); | |
| 63 | 68 | } |
| 64 | 69 | |
| 65 | 70 | return matched; | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcher.java renamed to 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; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 5 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionLevelEnum; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | |
| 5 | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 6 | 8 | |
| 7 | 9 | /** |
| 8 | - * IoT 场景规则触发器匹配策略接口 | |
| 10 | + * IoT 场景规则匹配器统一接口 | |
| 9 | 11 | * <p> |
| 10 | - * 用于实现不同类型触发器的匹配逻辑,遵循策略模式设计 | |
| 12 | + * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计 | |
| 13 | + * <p> | |
| 14 | + * 匹配器类型说明: | |
| 15 | + * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等) | |
| 16 | + * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等) | |
| 11 | 17 | * |
| 12 | 18 | * @author HUIHUI |
| 13 | 19 | */ |
| 14 | -public interface IotSceneRuleTriggerMatcher { | |
| 20 | +public interface IotSceneRuleMatcher { | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 匹配器类型枚举 | |
| 24 | + */ | |
| 25 | + enum MatcherType { | |
| 26 | + /** | |
| 27 | + * 触发器匹配器 - 用于匹配主触发条件 | |
| 28 | + */ | |
| 29 | + TRIGGER, | |
| 30 | + /** | |
| 31 | + * 条件匹配器 - 用于匹配子条件 | |
| 32 | + */ | |
| 33 | + CONDITION | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 获取匹配器类型 | |
| 38 | + * | |
| 39 | + * @return 匹配器类型 | |
| 40 | + */ | |
| 41 | + MatcherType getMatcherType(); | |
| 15 | 42 | |
| 16 | 43 | /** |
| 17 | - * 获取支持的触发器类型 | |
| 44 | + * 获取支持的触发器类型(仅触发器匹配器需要实现) | |
| 18 | 45 | * |
| 19 | - * @return 触发器类型枚举 | |
| 46 | + * @return 触发器类型枚举,条件匹配器返回 null | |
| 20 | 47 | */ |
| 21 | - IotSceneRuleTriggerTypeEnum getSupportedTriggerType(); | |
| 48 | + default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | |
| 49 | + return null; | |
| 50 | + } | |
| 22 | 51 | |
| 23 | 52 | /** |
| 24 | - * 检查触发器是否匹配消息 | |
| 53 | + * 获取支持的条件类型(仅条件匹配器需要实现) | |
| 54 | + * | |
| 55 | + * @return 条件类型枚举,触发器匹配器返回 null | |
| 56 | + */ | |
| 57 | + default IotSceneRuleConditionTypeEnum getSupportedConditionType() { | |
| 58 | + return null; | |
| 59 | + } | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * 获取支持的条件层级(仅条件匹配器需要实现) | |
| 63 | + * | |
| 64 | + * @return 条件层级枚举,触发器匹配器返回 null | |
| 65 | + */ | |
| 66 | + default IotSceneRuleConditionLevelEnum getSupportedConditionLevel() { | |
| 67 | + return null; | |
| 68 | + } | |
| 69 | + | |
| 70 | + /** | |
| 71 | + * 检查触发器是否匹配消息(仅触发器匹配器需要实现) | |
| 25 | 72 | * |
| 26 | 73 | * @param message 设备消息 |
| 27 | 74 | * @param trigger 触发器配置 |
| 28 | 75 | * @return 是否匹配 |
| 29 | 76 | */ |
| 30 | - boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger); | |
| 77 | + default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 78 | + throw new UnsupportedOperationException("触发器匹配方法仅支持触发器匹配器"); | |
| 79 | + } | |
| 80 | + | |
| 81 | + /** | |
| 82 | + * 检查条件是否匹配消息(仅条件匹配器需要实现) | |
| 83 | + * | |
| 84 | + * @param message 设备消息 | |
| 85 | + * @param condition 触发条件 | |
| 86 | + * @return 是否匹配 | |
| 87 | + */ | |
| 88 | + default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 89 | + throw new UnsupportedOperationException("条件匹配方法仅支持条件匹配器"); | |
| 90 | + } | |
| 31 | 91 | |
| 32 | 92 | /** |
| 33 | 93 | * 获取匹配优先级(数值越小优先级越高) |
| 34 | 94 | * <p> |
| 35 | - * 用于在多个匹配器支持同一触发器类型时确定优先级 | |
| 95 | + * 用于在多个匹配器支持同一类型时确定优先级 | |
| 36 | 96 | * |
| 37 | 97 | * @return 优先级数值 |
| 38 | 98 | */ | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.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.IotSceneRuleConditionLevelEnum; | |
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | |
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | |
| 9 | +import lombok.extern.slf4j.Slf4j; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | + | |
| 12 | +import java.util.*; | |
| 13 | +import java.util.function.Function; | |
| 14 | +import java.util.stream.Collectors; | |
| 15 | + | |
| 16 | +import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * IoT 场景规则匹配器统一管理器 | |
| 20 | + * <p> | |
| 21 | + * 负责管理所有匹配器(触发器匹配器和条件匹配器),并提供统一的匹配入口 | |
| 22 | + * | |
| 23 | + * @author HUIHUI | |
| 24 | + */ | |
| 25 | +@Component | |
| 26 | +@Slf4j | |
| 27 | +public class IotSceneRuleMatcherManager { | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 触发器匹配器映射表 | |
| 31 | + * Key: 触发器类型枚举 | |
| 32 | + * Value: 对应的匹配器实例 | |
| 33 | + */ | |
| 34 | + private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleMatcher> triggerMatcherMap; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 条件匹配器映射表 | |
| 38 | + * Key: 条件类型枚举 | |
| 39 | + * Value: 对应的匹配器实例 | |
| 40 | + */ | |
| 41 | + private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleMatcher> conditionMatcherMap; | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * 所有匹配器列表(按优先级排序) | |
| 45 | + */ | |
| 46 | + private final List<IotSceneRuleMatcher> allMatchers; | |
| 47 | + | |
| 48 | + public IotSceneRuleMatcherManager(List<IotSceneRuleMatcher> matchers) { | |
| 49 | + if (CollUtil.isEmpty(matchers)) { | |
| 50 | + log.warn("[IotSceneRuleMatcherManager][没有找到任何匹配器]"); | |
| 51 | + this.triggerMatcherMap = new HashMap<>(); | |
| 52 | + this.conditionMatcherMap = new HashMap<>(); | |
| 53 | + this.allMatchers = new ArrayList<>(); | |
| 54 | + return; | |
| 55 | + } | |
| 56 | + | |
| 57 | + // 按优先级排序并过滤启用的匹配器 | |
| 58 | + this.allMatchers = matchers.stream() | |
| 59 | + .filter(IotSceneRuleMatcher::isEnabled) | |
| 60 | + .sorted(Comparator.comparing(IotSceneRuleMatcher::getPriority)) | |
| 61 | + .collect(Collectors.toList()); | |
| 62 | + | |
| 63 | + // 分离触发器匹配器和条件匹配器 | |
| 64 | + List<IotSceneRuleMatcher> triggerMatchers = this.allMatchers.stream() | |
| 65 | + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER) | |
| 66 | + .toList(); | |
| 67 | + | |
| 68 | + List<IotSceneRuleMatcher> conditionMatchers = this.allMatchers.stream() | |
| 69 | + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) | |
| 70 | + .toList(); | |
| 71 | + | |
| 72 | + // 构建触发器匹配器映射表 | |
| 73 | + this.triggerMatcherMap = triggerMatchers.stream() | |
| 74 | + .collect(Collectors.toMap( | |
| 75 | + IotSceneRuleMatcher::getSupportedTriggerType, | |
| 76 | + Function.identity(), | |
| 77 | + (existing, replacement) -> { | |
| 78 | + log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", | |
| 79 | + existing.getSupportedTriggerType(), | |
| 80 | + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); | |
| 81 | + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; | |
| 82 | + }, | |
| 83 | + LinkedHashMap::new | |
| 84 | + )); | |
| 85 | + | |
| 86 | + // 构建条件匹配器映射表 | |
| 87 | + this.conditionMatcherMap = conditionMatchers.stream() | |
| 88 | + .collect(Collectors.toMap( | |
| 89 | + IotSceneRuleMatcher::getSupportedConditionType, | |
| 90 | + Function.identity(), | |
| 91 | + (existing, replacement) -> { | |
| 92 | + log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", | |
| 93 | + existing.getSupportedConditionType(), | |
| 94 | + existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); | |
| 95 | + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; | |
| 96 | + }, | |
| 97 | + LinkedHashMap::new | |
| 98 | + )); | |
| 99 | + | |
| 100 | + log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]", | |
| 101 | + this.allMatchers.size(), this.triggerMatcherMap.size(), this.conditionMatcherMap.size()); | |
| 102 | + | |
| 103 | + // 记录触发器匹配器详情 | |
| 104 | + this.triggerMatcherMap.forEach((type, matcher) -> | |
| 105 | + log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}", | |
| 106 | + type, matcher.getMatcherName(), matcher.getPriority())); | |
| 107 | + | |
| 108 | + // 记录条件匹配器详情 | |
| 109 | + this.conditionMatcherMap.forEach((type, matcher) -> | |
| 110 | + log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}, 层级: {}", | |
| 111 | + type, matcher.getMatcherName(), matcher.getPriority(), matcher.getSupportedConditionLevel())); | |
| 112 | + } | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * 检查触发器是否匹配消息(主条件匹配) | |
| 116 | + * | |
| 117 | + * @param message 设备消息 | |
| 118 | + * @param trigger 触发器配置 | |
| 119 | + * @return 是否匹配 | |
| 120 | + */ | |
| 121 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | |
| 122 | + if (message == null || trigger == null || trigger.getType() == null) { | |
| 123 | + log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger); | |
| 124 | + return false; | |
| 125 | + } | |
| 126 | + | |
| 127 | + // 根据触发器类型查找对应的匹配器 | |
| 128 | + IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType()); | |
| 129 | + if (triggerType == null) { | |
| 130 | + log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); | |
| 131 | + return false; | |
| 132 | + } | |
| 133 | + | |
| 134 | + IotSceneRuleMatcher matcher = triggerMatcherMap.get(triggerType); | |
| 135 | + if (matcher == null) { | |
| 136 | + log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); | |
| 137 | + return false; | |
| 138 | + } | |
| 139 | + | |
| 140 | + try { | |
| 141 | + return matcher.isMatched(message, trigger); | |
| 142 | + } catch (Exception e) { | |
| 143 | + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}", | |
| 144 | + message, trigger, matcher.getMatcherName(), e); | |
| 145 | + return false; | |
| 146 | + } | |
| 147 | + } | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * 检查子条件是否匹配消息 | |
| 151 | + * | |
| 152 | + * @param message 设备消息 | |
| 153 | + * @param condition 触发条件 | |
| 154 | + * @return 是否匹配 | |
| 155 | + */ | |
| 156 | + public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | |
| 157 | + if (message == null || condition == null || condition.getType() == null) { | |
| 158 | + log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition); | |
| 159 | + return false; | |
| 160 | + } | |
| 161 | + | |
| 162 | + // 根据条件类型查找对应的匹配器 | |
| 163 | + IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType()); | |
| 164 | + if (conditionType == null) { | |
| 165 | + log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType()); | |
| 166 | + return false; | |
| 167 | + } | |
| 168 | + | |
| 169 | + IotSceneRuleMatcher matcher = conditionMatcherMap.get(conditionType); | |
| 170 | + if (matcher == null) { | |
| 171 | + log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType); | |
| 172 | + return false; | |
| 173 | + } | |
| 174 | + | |
| 175 | + try { | |
| 176 | + return matcher.isMatched(message, condition); | |
| 177 | + } catch (Exception e) { | |
| 178 | + log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}", | |
| 179 | + message, condition, matcher.getMatcherName(), e); | |
| 180 | + return false; | |
| 181 | + } | |
| 182 | + } | |
| 183 | + | |
| 184 | + /** | |
| 185 | + * 根据类型值查找条件类型枚举 | |
| 186 | + * | |
| 187 | + * @param typeValue 类型值 | |
| 188 | + * @return 条件类型枚举 | |
| 189 | + */ | |
| 190 | + private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) { | |
| 191 | + return Arrays.stream(IotSceneRuleConditionTypeEnum.values()) | |
| 192 | + .filter(type -> type.getType().equals(typeValue)) | |
| 193 | + .findFirst() | |
| 194 | + .orElse(null); | |
| 195 | + } | |
| 196 | + | |
| 197 | + /** | |
| 198 | + * 获取所有支持的触发器类型 | |
| 199 | + * | |
| 200 | + * @return 支持的触发器类型列表 | |
| 201 | + */ | |
| 202 | + public Set<IotSceneRuleTriggerTypeEnum> getSupportedTriggerTypes() { | |
| 203 | + return new HashSet<>(triggerMatcherMap.keySet()); | |
| 204 | + } | |
| 205 | + | |
| 206 | + /** | |
| 207 | + * 获取所有支持的条件类型 | |
| 208 | + * | |
| 209 | + * @return 支持的条件类型列表 | |
| 210 | + */ | |
| 211 | + public Set<IotSceneRuleConditionTypeEnum> getSupportedConditionTypes() { | |
| 212 | + return new HashSet<>(conditionMatcherMap.keySet()); | |
| 213 | + } | |
| 214 | + | |
| 215 | + /** | |
| 216 | + * 获取指定触发器类型的匹配器 | |
| 217 | + * | |
| 218 | + * @param triggerType 触发器类型 | |
| 219 | + * @return 匹配器实例,如果不存在则返回 null | |
| 220 | + */ | |
| 221 | + public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) { | |
| 222 | + return triggerMatcherMap.get(triggerType); | |
| 223 | + } | |
| 224 | + | |
| 225 | + /** | |
| 226 | + * 获取指定条件类型的匹配器 | |
| 227 | + * | |
| 228 | + * @param conditionType 条件类型 | |
| 229 | + * @return 匹配器实例,如果不存在则返回 null | |
| 230 | + */ | |
| 231 | + public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) { | |
| 232 | + return conditionMatcherMap.get(conditionType); | |
| 233 | + } | |
| 234 | + | |
| 235 | + /** | |
| 236 | + * 根据条件层级获取匹配器列表 | |
| 237 | + * | |
| 238 | + * @param level 条件层级 | |
| 239 | + * @return 匹配器列表 | |
| 240 | + */ | |
| 241 | + public List<IotSceneRuleMatcher> getMatchersByLevel(IotSceneRuleConditionLevelEnum level) { | |
| 242 | + return allMatchers.stream() | |
| 243 | + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) | |
| 244 | + .filter(matcher -> matcher.getSupportedConditionLevel() == level) | |
| 245 | + .collect(Collectors.toList()); | |
| 246 | + } | |
| 247 | + | |
| 248 | + /** | |
| 249 | + * 获取所有匹配器的统计信息 | |
| 250 | + * | |
| 251 | + * @return 统计信息映射表 | |
| 252 | + */ | |
| 253 | + public Map<String, Object> getMatcherStatistics() { | |
| 254 | + Map<String, Object> statistics = new HashMap<>(); | |
| 255 | + statistics.put("totalMatchers", allMatchers.size()); | |
| 256 | + statistics.put("triggerMatchers", triggerMatcherMap.size()); | |
| 257 | + statistics.put("conditionMatchers", conditionMatcherMap.size()); | |
| 258 | + statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); | |
| 259 | + statistics.put("supportedConditionTypes", getSupportedConditionTypes()); | |
| 260 | + | |
| 261 | + // 按层级统计条件匹配器 | |
| 262 | + Map<IotSceneRuleConditionLevelEnum, Long> levelStats = allMatchers.stream() | |
| 263 | + .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) | |
| 264 | + .collect(Collectors.groupingBy( | |
| 265 | + IotSceneRuleMatcher::getSupportedConditionLevel, | |
| 266 | + Collectors.counting() | |
| 267 | + )); | |
| 268 | + statistics.put("conditionLevelStatistics", levelStats); | |
| 269 | + | |
| 270 | + // 触发器匹配器详情 | |
| 271 | + Map<String, Object> triggerMatcherDetails = new HashMap<>(); | |
| 272 | + triggerMatcherMap.forEach((type, matcher) -> { | |
| 273 | + Map<String, Object> detail = new HashMap<>(); | |
| 274 | + detail.put("matcherName", matcher.getMatcherName()); | |
| 275 | + detail.put("priority", matcher.getPriority()); | |
| 276 | + detail.put("enabled", matcher.isEnabled()); | |
| 277 | + triggerMatcherDetails.put(type.name(), detail); | |
| 278 | + }); | |
| 279 | + statistics.put("triggerMatcherDetails", triggerMatcherDetails); | |
| 280 | + | |
| 281 | + // 条件匹配器详情 | |
| 282 | + Map<String, Object> conditionMatcherDetails = new HashMap<>(); | |
| 283 | + conditionMatcherMap.forEach((type, matcher) -> { | |
| 284 | + Map<String, Object> detail = new HashMap<>(); | |
| 285 | + detail.put("matcherName", matcher.getMatcherName()); | |
| 286 | + detail.put("priority", matcher.getPriority()); | |
| 287 | + detail.put("level", matcher.getSupportedConditionLevel()); | |
| 288 | + detail.put("enabled", matcher.isEnabled()); | |
| 289 | + conditionMatcherDetails.put(type.name(), detail); | |
| 290 | + }); | |
| 291 | + statistics.put("conditionMatcherDetails", conditionMatcherDetails); | |
| 292 | + | |
| 293 | + return statistics; | |
| 294 | + } | |
| 295 | + | |
| 296 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherManager.java deleted
| 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 | - | |
| 151 | -} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java
| ... | ... | @@ -15,7 +15,12 @@ import org.springframework.stereotype.Component; |
| 15 | 15 | * @author HUIHUI |
| 16 | 16 | */ |
| 17 | 17 | @Component |
| 18 | -public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | |
| 18 | +public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { | |
| 19 | + | |
| 20 | + @Override | |
| 21 | + public MatcherType getMatcherType() { | |
| 22 | + return MatcherType.TRIGGER; | |
| 23 | + } | |
| 19 | 24 | |
| 20 | 25 | @Override |
| 21 | 26 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| ... | ... | @@ -26,13 +31,13 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { |
| 26 | 31 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 27 | 32 | // 1. 基础参数校验 |
| 28 | 33 | if (!isBasicTriggerValid(trigger)) { |
| 29 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 34 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | |
| 30 | 35 | return false; |
| 31 | 36 | } |
| 32 | 37 | |
| 33 | 38 | // 2. 检查 CRON 表达式是否存在 |
| 34 | 39 | if (StrUtil.isBlank(trigger.getCronExpression())) { |
| 35 | - logMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); | |
| 40 | + logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); | |
| 36 | 41 | return false; |
| 37 | 42 | } |
| 38 | 43 | |
| ... | ... | @@ -41,11 +46,11 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { |
| 41 | 46 | |
| 42 | 47 | // 4. 可以添加 CRON 表达式格式验证 |
| 43 | 48 | if (!isValidCronExpression(trigger.getCronExpression())) { |
| 44 | - logMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); | |
| 49 | + logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); | |
| 45 | 50 | return false; |
| 46 | 51 | } |
| 47 | 52 | |
| 48 | - logMatchSuccess(message, trigger); | |
| 53 | + logTriggerMatchSuccess(message, trigger); | |
| 49 | 54 | return true; |
| 50 | 55 | } |
| 51 | 56 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java
| ... | ... | @@ -20,13 +20,12 @@ import static org.junit.jupiter.api.Assertions.*; |
| 20 | 20 | */ |
| 21 | 21 | public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { |
| 22 | 22 | |
| 23 | - private IotSceneRuleTriggerMatcherManager matcherManager; | |
| 24 | - private List<IotSceneRuleTriggerMatcher> matchers; | |
| 23 | + private IotSceneRuleMatcherManager matcherManager; | |
| 25 | 24 | |
| 26 | 25 | @BeforeEach |
| 27 | 26 | void setUp() { |
| 28 | 27 | // 创建所有匹配器实例 |
| 29 | - matchers = Arrays.asList( | |
| 28 | + List<IotSceneRuleMatcher> matchers = Arrays.asList( | |
| 30 | 29 | new DeviceStateUpdateTriggerMatcher(), |
| 31 | 30 | new DevicePropertyPostTriggerMatcher(), |
| 32 | 31 | new DeviceEventPostTriggerMatcher(), |
| ... | ... | @@ -35,7 +34,7 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { |
| 35 | 34 | ); |
| 36 | 35 | |
| 37 | 36 | // 初始化匹配器管理器 |
| 38 | - matcherManager = new IotSceneRuleTriggerMatcherManager(matchers); | |
| 37 | + matcherManager = new IotSceneRuleMatcherManager(matchers); | |
| 39 | 38 | } |
| 40 | 39 | |
| 41 | 40 | @Test |
| ... | ... | @@ -197,4 +196,5 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { |
| 197 | 196 | assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); |
| 198 | 197 | assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); |
| 199 | 198 | } |
| 199 | + | |
| 200 | 200 | } | ... | ... |