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 +19,17 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | ||
| 19 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; | 19 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; |
| 20 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; | 20 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRuleSaveReqVO; |
| 21 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 21 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 22 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | ||
| 23 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 22 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 24 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | 23 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 25 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 24 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 26 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; | 25 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; |
| 27 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | 26 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; |
| 28 | -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | ||
| 29 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 27 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 30 | import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; | 28 | import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; |
| 31 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | 29 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| 32 | import cn.iocoder.yudao.module.iot.service.product.IotProductService; | 30 | import cn.iocoder.yudao.module.iot.service.product.IotProductService; |
| 33 | import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction; | 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 | import jakarta.annotation.Resource; | 33 | import jakarta.annotation.Resource; |
| 36 | import lombok.extern.slf4j.Slf4j; | 34 | import lombok.extern.slf4j.Slf4j; |
| 37 | import org.springframework.stereotype.Service; | 35 | import org.springframework.stereotype.Service; |
| @@ -67,7 +65,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -67,7 +65,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 67 | @Resource | 65 | @Resource |
| 68 | private IotDeviceService deviceService; | 66 | private IotDeviceService deviceService; |
| 69 | @Resource | 67 | @Resource |
| 70 | - private IotSceneRuleTriggerMatcherManager triggerMatcherManager; | 68 | + private IotSceneRuleMatcherManager matcherManager; |
| 71 | @Resource | 69 | @Resource |
| 72 | private List<IotSceneRuleAction> sceneRuleActions; | 70 | private List<IotSceneRuleAction> sceneRuleActions; |
| 73 | 71 | ||
| @@ -285,7 +283,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -285,7 +283,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 285 | private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { | 283 | private boolean matchSingleTrigger(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, IotSceneRuleDO sceneRule) { |
| 286 | try { | 284 | try { |
| 287 | // 2. 检查触发器的条件分组 | 285 | // 2. 检查触发器的条件分组 |
| 288 | - return triggerMatcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); | 286 | + return matcherManager.isMatched(message, trigger) && isTriggerConditionGroupsMatched(message, trigger, sceneRule); |
| 289 | } catch (Exception e) { | 287 | } catch (Exception e) { |
| 290 | log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}", | 288 | log.error("[matchSingleTrigger][触发器匹配异常] sceneRuleId: {}, triggerType: {}, message: {}", |
| 291 | sceneRule.getId(), trigger.getType(), message, e); | 289 | sceneRule.getId(), trigger.getType(), message, e); |
| @@ -333,7 +331,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -333,7 +331,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 333 | } | 331 | } |
| 334 | 332 | ||
| 335 | /** | 333 | /** |
| 336 | - * 基于消息,判断触发器的条件是否匹配 | 334 | + * 基于消息,判断触发器的子条件是否匹配 |
| 337 | * | 335 | * |
| 338 | * @param message 设备消息 | 336 | * @param message 设备消息 |
| 339 | * @param condition 触发条件 | 337 | * @param condition 触发条件 |
| @@ -344,21 +342,8 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -344,21 +342,8 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 344 | private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, | 342 | private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, |
| 345 | IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { | 343 | IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { |
| 346 | try { | 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 | } catch (Exception e) { | 347 | } catch (Exception e) { |
| 363 | log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]", | 348 | log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]", |
| 364 | sceneRule.getId(), trigger, e); | 349 | sceneRule.getId(), trigger, e); |
| @@ -366,105 +351,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -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 | // TODO @芋艿:【可优化】可以考虑增加下单测,边界太多了。 | 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,14 +15,14 @@ import java.util.Map; | ||
| 15 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 15 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 16 | 16 | ||
| 17 | /** | 17 | /** |
| 18 | - * IoT 场景规则触发器匹配器抽象基类 | 18 | + * IoT 场景规则匹配器抽象基类 |
| 19 | * <p> | 19 | * <p> |
| 20 | - * 提供通用的条件评估逻辑和工具方法 | 20 | + * 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型 |
| 21 | * | 21 | * |
| 22 | * @author HUIHUI | 22 | * @author HUIHUI |
| 23 | */ | 23 | */ |
| 24 | @Slf4j | 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,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,45 +81,90 @@ public abstract class AbstractIotSceneRuleTriggerMatcher implements IotSceneRule | ||
| 79 | } | 81 | } |
| 80 | 82 | ||
| 81 | /** | 83 | /** |
| 82 | - * 检查操作符和值是否有效 | 84 | + * 检查触发器操作符和值是否有效 |
| 83 | * | 85 | * |
| 84 | * @param trigger 触发器配置 | 86 | * @param trigger 触发器配置 |
| 85 | * @return 是否有效 | 87 | * @return 是否有效 |
| 86 | */ | 88 | */ |
| 87 | - protected boolean isOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { | 89 | + protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { |
| 88 | return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); | 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 | * @param message 设备消息 | 96 | * @param message 设备消息 |
| 106 | * @param trigger 触发器配置 | 97 | * @param trigger 触发器配置 |
| 107 | */ | 98 | */ |
| 108 | - protected void logMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 99 | + protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 109 | log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); | 100 | log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); |
| 110 | } | 101 | } |
| 111 | 102 | ||
| 112 | /** | 103 | /** |
| 113 | - * 记录匹配失败日志 | 104 | + * 记录触发器匹配失败日志 |
| 114 | * | 105 | * |
| 115 | * @param message 设备消息 | 106 | * @param message 设备消息 |
| 116 | * @param trigger 触发器配置 | 107 | * @param trigger 触发器配置 |
| 117 | * @param reason 失败原因 | 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 | log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); | 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,7 +16,7 @@ import org.springframework.stereotype.Component; | ||
| 16 | * @author HUIHUI | 16 | * @author HUIHUI |
| 17 | */ | 17 | */ |
| 18 | @Component | 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,6 +24,11 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa | ||
| 24 | private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); | 24 | private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); |
| 25 | 25 | ||
| 26 | @Override | 26 | @Override |
| 27 | + public MatcherType getMatcherType() { | ||
| 28 | + return MatcherType.TRIGGER; | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + @Override | ||
| 27 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 32 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 28 | return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; | 33 | return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST; |
| 29 | } | 34 | } |
| @@ -32,20 +37,20 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa | @@ -32,20 +37,20 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa | ||
| 32 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 37 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 33 | // 1. 基础参数校验 | 38 | // 1. 基础参数校验 |
| 34 | if (!isBasicTriggerValid(trigger)) { | 39 | if (!isBasicTriggerValid(trigger)) { |
| 35 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | 40 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); |
| 36 | return false; | 41 | return false; |
| 37 | } | 42 | } |
| 38 | 43 | ||
| 39 | // 2. 检查消息方法是否匹配 | 44 | // 2. 检查消息方法是否匹配 |
| 40 | if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { | 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 | return false; | 47 | return false; |
| 43 | } | 48 | } |
| 44 | 49 | ||
| 45 | // 3. 检查标识符是否匹配 | 50 | // 3. 检查标识符是否匹配 |
| 46 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | 51 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 47 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | 52 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 48 | - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | 53 | + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); |
| 49 | return false; | 54 | return false; |
| 50 | } | 55 | } |
| 51 | 56 | ||
| @@ -54,18 +59,18 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa | @@ -54,18 +59,18 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleTriggerMa | ||
| 54 | if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { | 59 | if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { |
| 55 | Object eventData = message.getData(); | 60 | Object eventData = message.getData(); |
| 56 | if (eventData == null) { | 61 | if (eventData == null) { |
| 57 | - logMatchFailure(message, trigger, "消息中事件数据为空"); | 62 | + logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); |
| 58 | return false; | 63 | return false; |
| 59 | } | 64 | } |
| 60 | 65 | ||
| 61 | boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); | 66 | boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); |
| 62 | if (!matched) { | 67 | if (!matched) { |
| 63 | - logMatchFailure(message, trigger, "事件数据条件不匹配"); | 68 | + logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); |
| 64 | return false; | 69 | return false; |
| 65 | } | 70 | } |
| 66 | } | 71 | } |
| 67 | 72 | ||
| 68 | - logMatchSuccess(message, trigger); | 73 | + logTriggerMatchSuccess(message, trigger); |
| 69 | return true; | 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,7 +15,7 @@ import org.springframework.stereotype.Component; | ||
| 15 | * @author HUIHUI | 15 | * @author HUIHUI |
| 16 | */ | 16 | */ |
| 17 | @Component | 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,6 +23,11 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge | ||
| 23 | private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); | 23 | private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); |
| 24 | 24 | ||
| 25 | @Override | 25 | @Override |
| 26 | + public MatcherType getMatcherType() { | ||
| 27 | + return MatcherType.TRIGGER; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + @Override | ||
| 26 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 31 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 27 | return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; | 32 | return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST; |
| 28 | } | 33 | } |
| @@ -31,33 +36,33 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge | @@ -31,33 +36,33 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge | ||
| 31 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 36 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 32 | // 1. 基础参数校验 | 37 | // 1. 基础参数校验 |
| 33 | if (!isBasicTriggerValid(trigger)) { | 38 | if (!isBasicTriggerValid(trigger)) { |
| 34 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | 39 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); |
| 35 | return false; | 40 | return false; |
| 36 | } | 41 | } |
| 37 | 42 | ||
| 38 | // 2. 检查消息方法是否匹配 | 43 | // 2. 检查消息方法是否匹配 |
| 39 | if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { | 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 | return false; | 46 | return false; |
| 42 | } | 47 | } |
| 43 | 48 | ||
| 44 | // 3. 检查标识符是否匹配 | 49 | // 3. 检查标识符是否匹配 |
| 45 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | 50 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 46 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | 51 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 47 | - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | 52 | + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); |
| 48 | return false; | 53 | return false; |
| 49 | } | 54 | } |
| 50 | 55 | ||
| 51 | // 4. 检查操作符和值是否有效 | 56 | // 4. 检查操作符和值是否有效 |
| 52 | - if (!isOperatorAndValueValid(trigger)) { | ||
| 53 | - logMatchFailure(message, trigger, "操作符或值无效"); | 57 | + if (!isTriggerOperatorAndValueValid(trigger)) { |
| 58 | + logTriggerMatchFailure(message, trigger, "操作符或值无效"); | ||
| 54 | return false; | 59 | return false; |
| 55 | } | 60 | } |
| 56 | 61 | ||
| 57 | // 5. 获取属性值 | 62 | // 5. 获取属性值 |
| 58 | Object propertyValue = message.getData(); | 63 | Object propertyValue = message.getData(); |
| 59 | if (propertyValue == null) { | 64 | if (propertyValue == null) { |
| 60 | - logMatchFailure(message, trigger, "消息中属性值为空"); | 65 | + logTriggerMatchFailure(message, trigger, "消息中属性值为空"); |
| 61 | return false; | 66 | return false; |
| 62 | } | 67 | } |
| 63 | 68 | ||
| @@ -65,9 +70,9 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge | @@ -65,9 +70,9 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleTrigge | ||
| 65 | boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); | 70 | boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); |
| 66 | 71 | ||
| 67 | if (matched) { | 72 | if (matched) { |
| 68 | - logMatchSuccess(message, trigger); | 73 | + logTriggerMatchSuccess(message, trigger); |
| 69 | } else { | 74 | } else { |
| 70 | - logMatchFailure(message, trigger, "属性值条件不匹配"); | 75 | + logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); |
| 71 | } | 76 | } |
| 72 | 77 | ||
| 73 | return matched; | 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,7 +15,7 @@ import org.springframework.stereotype.Component; | ||
| 15 | * @author HUIHUI | 15 | * @author HUIHUI |
| 16 | */ | 16 | */ |
| 17 | @Component | 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,6 +23,11 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg | ||
| 23 | private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); | 23 | private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); |
| 24 | 24 | ||
| 25 | @Override | 25 | @Override |
| 26 | + public MatcherType getMatcherType() { | ||
| 27 | + return MatcherType.TRIGGER; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + @Override | ||
| 26 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 31 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 27 | return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; | 32 | return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE; |
| 28 | } | 33 | } |
| @@ -31,27 +36,27 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg | @@ -31,27 +36,27 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleTrigg | ||
| 31 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 36 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 32 | // 1. 基础参数校验 | 37 | // 1. 基础参数校验 |
| 33 | if (!isBasicTriggerValid(trigger)) { | 38 | if (!isBasicTriggerValid(trigger)) { |
| 34 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | 39 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); |
| 35 | return false; | 40 | return false; |
| 36 | } | 41 | } |
| 37 | 42 | ||
| 38 | // 2. 检查消息方法是否匹配 | 43 | // 2. 检查消息方法是否匹配 |
| 39 | if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { | 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 | return false; | 46 | return false; |
| 42 | } | 47 | } |
| 43 | 48 | ||
| 44 | // 3. 检查标识符是否匹配 | 49 | // 3. 检查标识符是否匹配 |
| 45 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | 50 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 46 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | 51 | if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 47 | - logMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | 52 | + logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); |
| 48 | return false; | 53 | return false; |
| 49 | } | 54 | } |
| 50 | 55 | ||
| 51 | // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 | 56 | // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 |
| 52 | // 不需要检查操作符和值,因为服务调用本身就是触发条件 | 57 | // 不需要检查操作符和值,因为服务调用本身就是触发条件 |
| 53 | 58 | ||
| 54 | - logMatchSuccess(message, trigger); | 59 | + logTriggerMatchSuccess(message, trigger); |
| 55 | return true; | 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,7 +14,7 @@ import org.springframework.stereotype.Component; | ||
| 14 | * @author HUIHUI | 14 | * @author HUIHUI |
| 15 | */ | 15 | */ |
| 16 | @Component | 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,6 +22,11 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger | ||
| 22 | private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); | 22 | private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); |
| 23 | 23 | ||
| 24 | @Override | 24 | @Override |
| 25 | + public MatcherType getMatcherType() { | ||
| 26 | + return MatcherType.TRIGGER; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + @Override | ||
| 25 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 30 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| 26 | return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; | 31 | return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE; |
| 27 | } | 32 | } |
| @@ -30,26 +35,26 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger | @@ -30,26 +35,26 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger | ||
| 30 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 35 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 31 | // 1. 基础参数校验 | 36 | // 1. 基础参数校验 |
| 32 | if (!isBasicTriggerValid(trigger)) { | 37 | if (!isBasicTriggerValid(trigger)) { |
| 33 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | 38 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); |
| 34 | return false; | 39 | return false; |
| 35 | } | 40 | } |
| 36 | 41 | ||
| 37 | // 2. 检查消息方法是否匹配 | 42 | // 2. 检查消息方法是否匹配 |
| 38 | if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { | 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 | return false; | 45 | return false; |
| 41 | } | 46 | } |
| 42 | 47 | ||
| 43 | // 3. 检查操作符和值是否有效 | 48 | // 3. 检查操作符和值是否有效 |
| 44 | - if (!isOperatorAndValueValid(trigger)) { | ||
| 45 | - logMatchFailure(message, trigger, "操作符或值无效"); | 49 | + if (!isTriggerOperatorAndValueValid(trigger)) { |
| 50 | + logTriggerMatchFailure(message, trigger, "操作符或值无效"); | ||
| 46 | return false; | 51 | return false; |
| 47 | } | 52 | } |
| 48 | 53 | ||
| 49 | // 4. 获取设备状态值 | 54 | // 4. 获取设备状态值 |
| 50 | Object stateValue = message.getData(); | 55 | Object stateValue = message.getData(); |
| 51 | if (stateValue == null) { | 56 | if (stateValue == null) { |
| 52 | - logMatchFailure(message, trigger, "消息中设备状态值为空"); | 57 | + logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); |
| 53 | return false; | 58 | return false; |
| 54 | } | 59 | } |
| 55 | 60 | ||
| @@ -57,9 +62,9 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger | @@ -57,9 +62,9 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleTrigger | ||
| 57 | boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); | 62 | boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); |
| 58 | 63 | ||
| 59 | if (matched) { | 64 | if (matched) { |
| 60 | - logMatchSuccess(message, trigger); | 65 | + logTriggerMatchSuccess(message, trigger); |
| 61 | } else { | 66 | } else { |
| 62 | - logMatchFailure(message, trigger, "状态值条件不匹配"); | 67 | + logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); |
| 63 | } | 68 | } |
| 64 | 69 | ||
| 65 | return matched; | 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,37 +2,97 @@ package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 4 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 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 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 6 | 8 | ||
| 7 | /** | 9 | /** |
| 8 | - * IoT 场景规则触发器匹配策略接口 | 10 | + * IoT 场景规则匹配器统一接口 |
| 9 | * <p> | 11 | * <p> |
| 10 | - * 用于实现不同类型触发器的匹配逻辑,遵循策略模式设计 | 12 | + * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计 |
| 13 | + * <p> | ||
| 14 | + * 匹配器类型说明: | ||
| 15 | + * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等) | ||
| 16 | + * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等) | ||
| 11 | * | 17 | * |
| 12 | * @author HUIHUI | 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 | * @param message 设备消息 | 73 | * @param message 设备消息 |
| 27 | * @param trigger 触发器配置 | 74 | * @param trigger 触发器配置 |
| 28 | * @return 是否匹配 | 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 | * <p> | 94 | * <p> |
| 35 | - * 用于在多个匹配器支持同一触发器类型时确定优先级 | 95 | + * 用于在多个匹配器支持同一类型时确定优先级 |
| 36 | * | 96 | * |
| 37 | * @return 优先级数值 | 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,7 +15,12 @@ import org.springframework.stereotype.Component; | ||
| 15 | * @author HUIHUI | 15 | * @author HUIHUI |
| 16 | */ | 16 | */ |
| 17 | @Component | 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 | @Override | 25 | @Override |
| 21 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 26 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| @@ -26,13 +31,13 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | @@ -26,13 +31,13 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | ||
| 26 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 31 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 27 | // 1. 基础参数校验 | 32 | // 1. 基础参数校验 |
| 28 | if (!isBasicTriggerValid(trigger)) { | 33 | if (!isBasicTriggerValid(trigger)) { |
| 29 | - logMatchFailure(message, trigger, "触发器基础参数无效"); | 34 | + logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); |
| 30 | return false; | 35 | return false; |
| 31 | } | 36 | } |
| 32 | 37 | ||
| 33 | // 2. 检查 CRON 表达式是否存在 | 38 | // 2. 检查 CRON 表达式是否存在 |
| 34 | if (StrUtil.isBlank(trigger.getCronExpression())) { | 39 | if (StrUtil.isBlank(trigger.getCronExpression())) { |
| 35 | - logMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); | 40 | + logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); |
| 36 | return false; | 41 | return false; |
| 37 | } | 42 | } |
| 38 | 43 | ||
| @@ -41,11 +46,11 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | @@ -41,11 +46,11 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleTriggerMatcher { | ||
| 41 | 46 | ||
| 42 | // 4. 可以添加 CRON 表达式格式验证 | 47 | // 4. 可以添加 CRON 表达式格式验证 |
| 43 | if (!isValidCronExpression(trigger.getCronExpression())) { | 48 | if (!isValidCronExpression(trigger.getCronExpression())) { |
| 44 | - logMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); | 49 | + logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); |
| 45 | return false; | 50 | return false; |
| 46 | } | 51 | } |
| 47 | 52 | ||
| 48 | - logMatchSuccess(message, trigger); | 53 | + logTriggerMatchSuccess(message, trigger); |
| 49 | return true; | 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,13 +20,12 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 20 | */ | 20 | */ |
| 21 | public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { | 21 | public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { |
| 22 | 22 | ||
| 23 | - private IotSceneRuleTriggerMatcherManager matcherManager; | ||
| 24 | - private List<IotSceneRuleTriggerMatcher> matchers; | 23 | + private IotSceneRuleMatcherManager matcherManager; |
| 25 | 24 | ||
| 26 | @BeforeEach | 25 | @BeforeEach |
| 27 | void setUp() { | 26 | void setUp() { |
| 28 | // 创建所有匹配器实例 | 27 | // 创建所有匹配器实例 |
| 29 | - matchers = Arrays.asList( | 28 | + List<IotSceneRuleMatcher> matchers = Arrays.asList( |
| 30 | new DeviceStateUpdateTriggerMatcher(), | 29 | new DeviceStateUpdateTriggerMatcher(), |
| 31 | new DevicePropertyPostTriggerMatcher(), | 30 | new DevicePropertyPostTriggerMatcher(), |
| 32 | new DeviceEventPostTriggerMatcher(), | 31 | new DeviceEventPostTriggerMatcher(), |
| @@ -35,7 +34,7 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { | @@ -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 | @Test | 40 | @Test |
| @@ -197,4 +196,5 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { | @@ -197,4 +196,5 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { | ||
| 197 | assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); | 196 | assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); |
| 198 | assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); | 197 | assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); |
| 199 | } | 198 | } |
| 199 | + | ||
| 200 | } | 200 | } |