Commit 9e38f56f885aa93c876a04b8a1bb6db92e2bc2c8
Merge remote-tracking branch 'origin/feature/iot' into feature/iot
Showing
28 changed files
with
3178 additions
and
903 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java
| 1 | package cn.iocoder.yudao.module.iot.enums.rule; | 1 | package cn.iocoder.yudao.module.iot.enums.rule; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.ArrayUtil; | ||
| 3 | import cn.iocoder.yudao.framework.common.core.ArrayValuable; | 4 | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| 4 | import lombok.Getter; | 5 | import lombok.Getter; |
| 5 | import lombok.RequiredArgsConstructor; | 6 | import lombok.RequiredArgsConstructor; |
| @@ -60,18 +61,8 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> { | @@ -60,18 +61,8 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> { | ||
| 60 | return ARRAYS; | 61 | return ARRAYS; |
| 61 | } | 62 | } |
| 62 | 63 | ||
| 63 | - // TODO @puhui999:可以参考下别的枚举哈,方法名,和实现都可以更简洁;of(String type) { firstMatch | ||
| 64 | - /** | ||
| 65 | - * 根据类型值查找触发器类型枚举 | ||
| 66 | - * | ||
| 67 | - * @param typeValue 类型值 | ||
| 68 | - * @return 触发器类型枚举 | ||
| 69 | - */ | ||
| 70 | - public static IotSceneRuleTriggerTypeEnum findTriggerTypeEnum(Integer typeValue) { | ||
| 71 | - return Arrays.stream(IotSceneRuleTriggerTypeEnum.values()) | ||
| 72 | - .filter(type -> type.getType().equals(typeValue)) | ||
| 73 | - .findFirst() | ||
| 74 | - .orElse(null); | 64 | + public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) { |
| 65 | + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); | ||
| 75 | } | 66 | } |
| 76 | 67 | ||
| 77 | } | 68 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleServiceImpl.java
| @@ -2,18 +2,11 @@ package cn.iocoder.yudao.module.iot.service.rule.scene; | @@ -2,18 +2,11 @@ package cn.iocoder.yudao.module.iot.service.rule.scene; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.collection.ListUtil; | 4 | import cn.hutool.core.collection.ListUtil; |
| 5 | -import cn.hutool.core.map.MapUtil; | ||
| 6 | -import cn.hutool.core.text.CharPool; | ||
| 7 | -import cn.hutool.core.util.NumberUtil; | ||
| 8 | import cn.hutool.core.util.ObjUtil; | 5 | import cn.hutool.core.util.ObjUtil; |
| 9 | -import cn.hutool.core.util.StrUtil; | ||
| 10 | import cn.hutool.extra.spring.SpringUtil; | 6 | import cn.hutool.extra.spring.SpringUtil; |
| 11 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 7 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 12 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 8 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 13 | -import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||
| 14 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 9 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 15 | -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||
| 16 | -import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; | ||
| 17 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | 10 | import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; |
| 18 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | 11 | import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; |
| 19 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; | 12 | import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO; |
| @@ -23,7 +16,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | @@ -23,7 +16,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | ||
| 23 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | 16 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 24 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 17 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 25 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; | 18 | import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper; |
| 26 | -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | ||
| 27 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 19 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 28 | import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; | 20 | import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager; |
| 29 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; | 21 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| @@ -36,12 +28,9 @@ import org.springframework.stereotype.Service; | @@ -36,12 +28,9 @@ import org.springframework.stereotype.Service; | ||
| 36 | import org.springframework.validation.annotation.Validated; | 28 | import org.springframework.validation.annotation.Validated; |
| 37 | 29 | ||
| 38 | import java.util.Collection; | 30 | import java.util.Collection; |
| 39 | -import java.util.HashMap; | ||
| 40 | import java.util.List; | 31 | import java.util.List; |
| 41 | -import java.util.Map; | ||
| 42 | 32 | ||
| 43 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 33 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 44 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||
| 45 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; | 34 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; |
| 46 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS; | 35 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS; |
| 47 | 36 | ||
| @@ -353,65 +342,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | @@ -353,65 +342,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService { | ||
| 353 | } | 342 | } |
| 354 | } | 343 | } |
| 355 | 344 | ||
| 356 | - // TODO @puhui999:下面还需要么? | ||
| 357 | - /** | ||
| 358 | - * 判断触发器的条件参数是否匹配 | ||
| 359 | - * | ||
| 360 | - * @param message 设备消息 | ||
| 361 | - * @param condition 触发条件 | ||
| 362 | - * @param sceneRule 规则场景(用于日志,无其它作用) | ||
| 363 | - * @param trigger 触发器(用于日志,无其它作用) | ||
| 364 | - * @return 是否匹配 | ||
| 365 | - */ | ||
| 366 | - @SuppressWarnings({"unchecked", "DataFlowIssue"}) | ||
| 367 | - private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, | ||
| 368 | - IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) { | ||
| 369 | - // 1.1 校验操作符是否合法 | ||
| 370 | - IotSceneRuleConditionOperatorEnum operator = | ||
| 371 | - IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator()); | ||
| 372 | - if (operator == null) { | ||
| 373 | - log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]", | ||
| 374 | - sceneRule.getId(), trigger, condition.getOperator()); | ||
| 375 | - return false; | ||
| 376 | - } | ||
| 377 | - // 1.2 校验消息是否包含对应的值 | ||
| 378 | - String messageValue = MapUtil.getStr((Map<String, Object>) message.getData(), condition.getIdentifier()); | ||
| 379 | - if (messageValue == null) { | ||
| 380 | - return false; | ||
| 381 | - } | ||
| 382 | - | ||
| 383 | - // 2.1 构建 Spring 表达式的变量 | ||
| 384 | - Map<String, Object> springExpressionVariables = new HashMap<>(); | ||
| 385 | - try { | ||
| 386 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue); | ||
| 387 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam()); | ||
| 388 | - List<String> parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA); | ||
| 389 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); | ||
| 390 | - // 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题! | ||
| 391 | - if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN, | ||
| 392 | - IotSceneRuleConditionOperatorEnum.NOT_BETWEEN, | ||
| 393 | - IotSceneRuleConditionOperatorEnum.GREATER_THAN, | ||
| 394 | - IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS, | ||
| 395 | - IotSceneRuleConditionOperatorEnum.LESS_THAN, | ||
| 396 | - IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS) | ||
| 397 | - && NumberUtil.isNumber(messageValue) | ||
| 398 | - && NumberUtils.isAllNumber(parameterValues)) { | ||
| 399 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, | ||
| 400 | - NumberUtil.parseDouble(messageValue)); | ||
| 401 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, | ||
| 402 | - NumberUtil.parseDouble(condition.getParam())); | ||
| 403 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, | ||
| 404 | - convertList(parameterValues, NumberUtil::parseDouble)); | ||
| 405 | - } | ||
| 406 | - // 2.2 计算 Spring 表达式 | ||
| 407 | - return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables); | ||
| 408 | - } catch (Exception e) { | ||
| 409 | - log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]", | ||
| 410 | - message, sceneRule.getId(), trigger, operator, springExpressionVariables, e); | ||
| 411 | - return false; | ||
| 412 | - } | ||
| 413 | - } | ||
| 414 | - | ||
| 415 | /** | 345 | /** |
| 416 | * 执行规则场景的动作 | 346 | * 执行规则场景的动作 |
| 417 | * | 347 | * |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/CurrentTimeConditionMatcher.java deleted
| 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.IotSceneRuleConditionTypeEnum; | ||
| 7 | -import lombok.extern.slf4j.Slf4j; | ||
| 8 | -import org.springframework.stereotype.Component; | ||
| 9 | - | ||
| 10 | -import java.time.LocalDateTime; | ||
| 11 | -import java.time.LocalTime; | ||
| 12 | -import java.time.format.DateTimeFormatter; | ||
| 13 | - | ||
| 14 | -/** | ||
| 15 | - * 当前时间条件匹配器 | ||
| 16 | - * <p> | ||
| 17 | - * 处理时间相关的子条件匹配逻辑 | ||
| 18 | - * | ||
| 19 | - * @author HUIHUI | ||
| 20 | - */ | ||
| 21 | -@Component | ||
| 22 | -@Slf4j | ||
| 23 | -public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher { | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 时间格式化器 - HH:mm:ss | ||
| 27 | - */ | ||
| 28 | - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); | ||
| 29 | - | ||
| 30 | - /** | ||
| 31 | - * 时间格式化器 - HH:mm | ||
| 32 | - */ | ||
| 33 | - private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); | ||
| 34 | - | ||
| 35 | - @Override | ||
| 36 | - public MatcherType getMatcherType() { | ||
| 37 | - return MatcherType.CONDITION; | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - @Override | ||
| 41 | - public IotSceneRuleConditionTypeEnum getSupportedConditionType() { | ||
| 42 | - return IotSceneRuleConditionTypeEnum.CURRENT_TIME; | ||
| 43 | - } | ||
| 44 | - | ||
| 45 | - @Override | ||
| 46 | - public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | ||
| 47 | - // 1. 基础参数校验 | ||
| 48 | - if (!isBasicConditionValid(condition)) { | ||
| 49 | - logConditionMatchFailure(message, condition, "条件基础参数无效"); | ||
| 50 | - return false; | ||
| 51 | - } | ||
| 52 | - | ||
| 53 | - // 2. 检查操作符和参数是否有效 | ||
| 54 | - if (!isConditionOperatorAndParamValid(condition)) { | ||
| 55 | - logConditionMatchFailure(message, condition, "操作符或参数无效"); | ||
| 56 | - return false; | ||
| 57 | - } | ||
| 58 | - | ||
| 59 | - // 3. 根据操作符类型进行不同的时间匹配 | ||
| 60 | - LocalDateTime now = LocalDateTime.now(); | ||
| 61 | - String operator = condition.getOperator(); | ||
| 62 | - String param = condition.getParam(); | ||
| 63 | - boolean matched; | ||
| 64 | - try { | ||
| 65 | - if (operator.startsWith("date_time_")) { | ||
| 66 | - // 日期时间匹配(时间戳) | ||
| 67 | - matched = matchDateTime(now, operator, param); | ||
| 68 | - } else if (operator.startsWith("time_")) { | ||
| 69 | - // 当日时间匹配(HH:mm:ss) | ||
| 70 | - matched = matchTime(now.toLocalTime(), operator, param); | ||
| 71 | - } else { | ||
| 72 | - // 其他操作符,使用通用条件评估器 | ||
| 73 | - matched = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param); | ||
| 74 | - } | ||
| 75 | - | ||
| 76 | - if (matched) { | ||
| 77 | - logConditionMatchSuccess(message, condition); | ||
| 78 | - } else { | ||
| 79 | - logConditionMatchFailure(message, condition, "时间条件不匹配"); | ||
| 80 | - } | ||
| 81 | - } catch (Exception e) { | ||
| 82 | - log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e); | ||
| 83 | - logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage()); | ||
| 84 | - matched = false; | ||
| 85 | - } | ||
| 86 | - return matched; | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | - /** | ||
| 90 | - * 匹配日期时间(时间戳) | ||
| 91 | - */ | ||
| 92 | - private boolean matchDateTime(LocalDateTime now, String operator, String param) { | ||
| 93 | - long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); | ||
| 94 | - return evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param); | ||
| 95 | - } | ||
| 96 | - | ||
| 97 | - /** | ||
| 98 | - * 匹配当日时间(HH:mm:ss) | ||
| 99 | - */ | ||
| 100 | - private boolean matchTime(LocalTime currentTime, String operator, String param) { | ||
| 101 | - try { | ||
| 102 | - String actualOperator = operator.substring("time_".length()); | ||
| 103 | - | ||
| 104 | - // TODO @puhui999:if return 简化; | ||
| 105 | - if ("between".equals(actualOperator)) { | ||
| 106 | - // 时间区间匹配 | ||
| 107 | - String[] timeRange = param.split(","); | ||
| 108 | - if (timeRange.length != 2) { | ||
| 109 | - return false; | ||
| 110 | - } | ||
| 111 | - LocalTime startTime = parseTime(timeRange[0].trim()); | ||
| 112 | - LocalTime endTime = parseTime(timeRange[1].trim()); | ||
| 113 | - return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime); | ||
| 114 | - } else { | ||
| 115 | - // 单个时间比较 | ||
| 116 | - LocalTime targetTime = parseTime(param); | ||
| 117 | - // TODO @puhui999:枚举类; | ||
| 118 | - switch (actualOperator) { | ||
| 119 | - case ">": | ||
| 120 | - return currentTime.isAfter(targetTime); | ||
| 121 | - case "<": | ||
| 122 | - return currentTime.isBefore(targetTime); | ||
| 123 | - case ">=": | ||
| 124 | - return !currentTime.isBefore(targetTime); | ||
| 125 | - case "<=": | ||
| 126 | - return !currentTime.isAfter(targetTime); | ||
| 127 | - case "=": | ||
| 128 | - return currentTime.equals(targetTime); | ||
| 129 | - default: | ||
| 130 | - return false; | ||
| 131 | - } | ||
| 132 | - } | ||
| 133 | - } catch (Exception e) { | ||
| 134 | - // TODO @puhui999:1)日志格式 [][];2)方法名不对哈; | ||
| 135 | - log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e); | ||
| 136 | - return false; | ||
| 137 | - } | ||
| 138 | - } | ||
| 139 | - | ||
| 140 | - /** | ||
| 141 | - * 解析时间字符串 | ||
| 142 | - */ | ||
| 143 | - private LocalTime parseTime(String timeStr) { | ||
| 144 | - // TODO @puhui999:可以用 hutool Assert 类简化 | ||
| 145 | - if (StrUtil.isBlank(timeStr)) { | ||
| 146 | - throw new IllegalArgumentException("时间字符串不能为空"); | ||
| 147 | - } | ||
| 148 | - // 尝试不同的时间格式 | ||
| 149 | - try { | ||
| 150 | - if (timeStr.length() == 5) { // HH:mm | ||
| 151 | - return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); | ||
| 152 | - } else { // HH:mm:ss | ||
| 153 | - return LocalTime.parse(timeStr, TIME_FORMATTER); | ||
| 154 | - } | ||
| 155 | - } catch (Exception e) { | ||
| 156 | - throw new IllegalArgumentException("时间格式无效: " + timeStr, e); | ||
| 157 | - } | ||
| 158 | - } | ||
| 159 | - | ||
| 160 | - @Override | ||
| 161 | - public int getPriority() { | ||
| 162 | - return 40; // 较低优先级 | ||
| 163 | - } | ||
| 164 | - | ||
| 165 | -} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; |
| 2 | 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.IotSceneRuleConditionTypeEnum; | ||
| 6 | -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 3 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher; |
| 4 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher; | ||
| 7 | 5 | ||
| 8 | /** | 6 | /** |
| 9 | - * IoT 场景规则匹配器统一接口 | 7 | + * IoT 场景规则匹配器基础接口 |
| 10 | * <p> | 8 | * <p> |
| 11 | - * 支持触发器匹配和条件匹配两种类型,遵循策略模式设计 | 9 | + * 定义所有匹配器的通用行为,包括优先级、名称和启用状态 |
| 12 | * <p> | 10 | * <p> |
| 13 | - * 匹配器类型说明: | ||
| 14 | - * - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等) | ||
| 15 | - * - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等) | 11 | + * - {@link IotSceneRuleTriggerMatcher} 触发器匹配器 |
| 12 | + * - {@link IotSceneRuleConditionMatcher} 条件匹配器 | ||
| 16 | * | 13 | * |
| 17 | * @author HUIHUI | 14 | * @author HUIHUI |
| 18 | */ | 15 | */ |
| 19 | public interface IotSceneRuleMatcher { | 16 | public interface IotSceneRuleMatcher { |
| 20 | 17 | ||
| 21 | - // TODO @puhui999:MatcherTypeEnum; | ||
| 22 | - // TODO @puhui999:可以考虑根据类型,新建 trigger、condition 包,然后把对应的实现类放进去哈; | ||
| 23 | - /** | ||
| 24 | - * 匹配器类型枚举 | ||
| 25 | - */ | ||
| 26 | - enum MatcherType { | ||
| 27 | - | ||
| 28 | - /** | ||
| 29 | - * 触发器匹配器 - 用于匹配主触发条件 | ||
| 30 | - */ | ||
| 31 | - TRIGGER, | ||
| 32 | - | ||
| 33 | - /** | ||
| 34 | - * 条件匹配器 - 用于匹配子条件 | ||
| 35 | - */ | ||
| 36 | - CONDITION | ||
| 37 | - | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | - /** | ||
| 41 | - * 获取匹配器类型 | ||
| 42 | - * | ||
| 43 | - * @return 匹配器类型 | ||
| 44 | - */ | ||
| 45 | - MatcherType getMatcherType(); | ||
| 46 | - | ||
| 47 | - // TODO @puhui999:【重要】有个思路,IotSceneRuleMatcher 拆分成 2 种 mather 接口;然后 AbstractIotSceneRuleMatcher 是个 Helper 工具类; | ||
| 48 | - | ||
| 49 | - // TODO @puhui999:是不是和 AbstractSceneRuleMatcher 一样,分下块; | ||
| 50 | - | ||
| 51 | - /** | ||
| 52 | - * 获取支持的触发器类型(仅触发器匹配器需要实现) | ||
| 53 | - * | ||
| 54 | - * @return 触发器类型枚举,条件匹配器返回 null | ||
| 55 | - */ | ||
| 56 | - default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | ||
| 57 | - return null; | ||
| 58 | - } | ||
| 59 | - | ||
| 60 | - /** | ||
| 61 | - * 获取支持的条件类型(仅条件匹配器需要实现) | ||
| 62 | - * | ||
| 63 | - * @return 条件类型枚举,触发器匹配器返回 null | ||
| 64 | - */ | ||
| 65 | - default IotSceneRuleConditionTypeEnum getSupportedConditionType() { | ||
| 66 | - return null; | ||
| 67 | - } | ||
| 68 | - | ||
| 69 | - /** | ||
| 70 | - * 检查触发器是否匹配消息(仅触发器匹配器需要实现) | ||
| 71 | - * | ||
| 72 | - * @param message 设备消息 | ||
| 73 | - * @param trigger 触发器配置 | ||
| 74 | - * @return 是否匹配 | ||
| 75 | - */ | ||
| 76 | - default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | ||
| 77 | - throw new UnsupportedOperationException("触发器匹配方法仅支持触发器匹配器"); | ||
| 78 | - } | ||
| 79 | - | ||
| 80 | - /** | ||
| 81 | - * 检查条件是否匹配消息(仅条件匹配器需要实现) | ||
| 82 | - * | ||
| 83 | - * @param message 设备消息 | ||
| 84 | - * @param condition 触发条件 | ||
| 85 | - * @return 是否匹配 | ||
| 86 | - */ | ||
| 87 | - default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | ||
| 88 | - throw new UnsupportedOperationException("条件匹配方法仅支持条件匹配器"); | ||
| 89 | - } | ||
| 90 | - | ||
| 91 | /** | 18 | /** |
| 92 | * 获取匹配优先级(数值越小优先级越高) | 19 | * 获取匹配优先级(数值越小优先级越高) |
| 93 | * <p> | 20 | * <p> |
| @@ -99,16 +26,6 @@ public interface IotSceneRuleMatcher { | @@ -99,16 +26,6 @@ public interface IotSceneRuleMatcher { | ||
| 99 | return 100; | 26 | return 100; |
| 100 | } | 27 | } |
| 101 | 28 | ||
| 102 | - // TODO @puhui999:如果目前没自定义,体感可以删除哈; | ||
| 103 | - /** | ||
| 104 | - * 获取匹配器名称,用于日志和调试 | ||
| 105 | - * | ||
| 106 | - * @return 匹配器名称 | ||
| 107 | - */ | ||
| 108 | - default String getMatcherName() { | ||
| 109 | - return this.getClass().getSimpleName(); | ||
| 110 | - } | ||
| 111 | - | ||
| 112 | /** | 29 | /** |
| 113 | * 是否启用该匹配器 | 30 | * 是否启用该匹配器 |
| 114 | * <p> | 31 | * <p> |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/AbstractIotSceneRuleMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java
| 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.text.CharPool; | ||
| 3 | import cn.hutool.core.util.NumberUtil; | 4 | import cn.hutool.core.util.NumberUtil; |
| 4 | import cn.hutool.core.util.StrUtil; | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||
| 7 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||
| 5 | import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; | 8 | import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 9 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 10 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| @@ -15,16 +18,22 @@ import java.util.Map; | @@ -15,16 +18,22 @@ import java.util.Map; | ||
| 15 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 18 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 16 | 19 | ||
| 17 | /** | 20 | /** |
| 18 | - * IoT 场景规则匹配器抽象基类 | 21 | + * IoT 场景规则匹配器工具类 |
| 19 | * <p> | 22 | * <p> |
| 20 | - * 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型 | 23 | + * 提供通用的条件评估逻辑和工具方法,供触发器和条件匹配器使用 |
| 24 | + * <p> | ||
| 25 | + * 该类包含了匹配器实现中常用的工具方法,如条件评估、参数校验、日志记录等 | ||
| 21 | * | 26 | * |
| 22 | * @author HUIHUI | 27 | * @author HUIHUI |
| 23 | */ | 28 | */ |
| 24 | @Slf4j | 29 | @Slf4j |
| 25 | -public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher { | 30 | +public final class IotSceneRuleMatcherHelper { |
| 26 | 31 | ||
| 27 | - // TODO @puhui999:这个是不是也是【通用】条件哈? | 32 | + /** |
| 33 | + * 私有构造函数,防止实例化 | ||
| 34 | + */ | ||
| 35 | + private IotSceneRuleMatcherHelper() { | ||
| 36 | + } | ||
| 28 | 37 | ||
| 29 | /** | 38 | /** |
| 30 | * 评估条件是否匹配 | 39 | * 评估条件是否匹配 |
| @@ -34,44 +43,99 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | @@ -34,44 +43,99 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | ||
| 34 | * @param paramValue 参数值(来自条件配置) | 43 | * @param paramValue 参数值(来自条件配置) |
| 35 | * @return 是否匹配 | 44 | * @return 是否匹配 |
| 36 | */ | 45 | */ |
| 37 | - @SuppressWarnings("DataFlowIssue") | ||
| 38 | - protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { | 46 | + public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) { |
| 39 | try { | 47 | try { |
| 40 | // 1. 校验操作符是否合法 | 48 | // 1. 校验操作符是否合法 |
| 41 | IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); | 49 | IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); |
| 42 | if (operatorEnum == null) { | 50 | if (operatorEnum == null) { |
| 43 | - log.warn("[evaluateCondition][存在错误的操作符({})]", operator); | 51 | + log.warn("[evaluateCondition][operator({}) 操作符无效]", operator); |
| 44 | return false; | 52 | return false; |
| 45 | } | 53 | } |
| 46 | 54 | ||
| 47 | // 2. 构建 Spring 表达式变量 | 55 | // 2. 构建 Spring 表达式变量 |
| 48 | - Map<String, Object> springExpressionVariables = new HashMap<>(); | ||
| 49 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); | ||
| 50 | - | ||
| 51 | - // 处理参数值 | ||
| 52 | - if (StrUtil.isNotBlank(paramValue)) { | ||
| 53 | - // 处理多值情况(如 IN、BETWEEN 操作符) | ||
| 54 | - // TODO @puhui999:使用这个,会不会有问题?例如说:string 恰好有 , 分隔? | ||
| 55 | - if (paramValue.contains(",")) { | ||
| 56 | - List<String> paramValues = StrUtil.split(paramValue, ','); | ||
| 57 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, | ||
| 58 | - convertList(paramValues, NumberUtil::parseDouble)); | ||
| 59 | - } else { | ||
| 60 | - // 处理单值情况 | ||
| 61 | - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, | ||
| 62 | - NumberUtil.parseDouble(paramValue)); | ||
| 63 | - } | ||
| 64 | - } | 56 | + return evaluateConditionWithOperatorEnum(sourceValue, operatorEnum, paramValue); |
| 57 | + } catch (Exception e) { | ||
| 58 | + log.error("[evaluateCondition][sourceValue({}) operator({}) paramValue({}) 条件评估异常]", | ||
| 59 | + sourceValue, operator, paramValue, e); | ||
| 60 | + return false; | ||
| 61 | + } | ||
| 62 | + } | ||
| 65 | 63 | ||
| 66 | - // 3. 计算 Spring 表达式 | 64 | + /** |
| 65 | + * 使用操作符枚举评估条件是否匹配 | ||
| 66 | + * | ||
| 67 | + * @param sourceValue 源值(来自消息) | ||
| 68 | + * @param operatorEnum 操作符枚举 | ||
| 69 | + * @param paramValue 参数值(来自条件配置) | ||
| 70 | + * @return 是否匹配 | ||
| 71 | + */ | ||
| 72 | + @SuppressWarnings("DataFlowIssue") | ||
| 73 | + public static boolean evaluateConditionWithOperatorEnum(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) { | ||
| 74 | + try { | ||
| 75 | + // 1. 构建 Spring 表达式变量 | ||
| 76 | + Map<String, Object> springExpressionVariables = buildSpringExpressionVariables(sourceValue, operatorEnum, paramValue); | ||
| 77 | + | ||
| 78 | + // 2. 计算 Spring 表达式 | ||
| 67 | return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); | 79 | return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables); |
| 68 | } catch (Exception e) { | 80 | } catch (Exception e) { |
| 69 | - log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}", | ||
| 70 | - sourceValue, operator, paramValue, e); | 81 | + log.error("[evaluateConditionWithOperatorEnum][sourceValue({}) operatorEnum({}) paramValue({}) 条件评估异常]", |
| 82 | + sourceValue, operatorEnum, paramValue, e); | ||
| 71 | return false; | 83 | return false; |
| 72 | } | 84 | } |
| 73 | } | 85 | } |
| 74 | 86 | ||
| 87 | + /** | ||
| 88 | + * 构建 Spring 表达式变量 | ||
| 89 | + */ | ||
| 90 | + private static Map<String, Object> buildSpringExpressionVariables(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) { | ||
| 91 | + Map<String, Object> springExpressionVariables = new HashMap<>(); | ||
| 92 | + | ||
| 93 | + // 设置源值 | ||
| 94 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue); | ||
| 95 | + | ||
| 96 | + // 处理参数值 | ||
| 97 | + if (StrUtil.isNotBlank(paramValue)) { | ||
| 98 | + List<String> parameterValues = StrUtil.splitTrim(paramValue, CharPool.COMMA); | ||
| 99 | + | ||
| 100 | + // 设置原始参数值 | ||
| 101 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, paramValue); | ||
| 102 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues); | ||
| 103 | + | ||
| 104 | + // 特殊处理:解决数字比较问题 | ||
| 105 | + // Spring 表达式基于 compareTo 方法,对数字的比较存在问题,需要转换为数字类型 | ||
| 106 | + if (isNumericComparisonOperator(operatorEnum) && isNumericComparison(sourceValue, parameterValues)) { | ||
| 107 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, | ||
| 108 | + NumberUtil.parseDouble(String.valueOf(sourceValue))); | ||
| 109 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, | ||
| 110 | + NumberUtil.parseDouble(paramValue)); | ||
| 111 | + springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, | ||
| 112 | + convertList(parameterValues, NumberUtil::parseDouble)); | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + return springExpressionVariables; | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + /** | ||
| 120 | + * 判断是否为数字比较操作符 | ||
| 121 | + */ | ||
| 122 | + private static boolean isNumericComparisonOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { | ||
| 123 | + return ObjectUtils.equalsAny(operatorEnum, | ||
| 124 | + IotSceneRuleConditionOperatorEnum.BETWEEN, | ||
| 125 | + IotSceneRuleConditionOperatorEnum.NOT_BETWEEN, | ||
| 126 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN, | ||
| 127 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS, | ||
| 128 | + IotSceneRuleConditionOperatorEnum.LESS_THAN, | ||
| 129 | + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + /** | ||
| 133 | + * 判断是否为数字比较场景 | ||
| 134 | + */ | ||
| 135 | + private static boolean isNumericComparison(Object sourceValue, List<String> parameterValues) { | ||
| 136 | + return NumberUtil.isNumber(String.valueOf(sourceValue)) && NumberUtils.isAllNumber(parameterValues); | ||
| 137 | + } | ||
| 138 | + | ||
| 75 | // ========== 【触发器】相关工具方法 ========== | 139 | // ========== 【触发器】相关工具方法 ========== |
| 76 | 140 | ||
| 77 | /** | 141 | /** |
| @@ -80,7 +144,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | @@ -80,7 +144,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | ||
| 80 | * @param trigger 触发器配置 | 144 | * @param trigger 触发器配置 |
| 81 | * @return 是否有效 | 145 | * @return 是否有效 |
| 82 | */ | 146 | */ |
| 83 | - protected boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { | 147 | + public static boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) { |
| 84 | return trigger != null && trigger.getType() != null; | 148 | return trigger != null && trigger.getType() != null; |
| 85 | } | 149 | } |
| 86 | 150 | ||
| @@ -90,29 +154,29 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | @@ -90,29 +154,29 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | ||
| 90 | * @param trigger 触发器配置 | 154 | * @param trigger 触发器配置 |
| 91 | * @return 是否有效 | 155 | * @return 是否有效 |
| 92 | */ | 156 | */ |
| 93 | - protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { | 157 | + public static boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) { |
| 94 | return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); | 158 | return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue()); |
| 95 | } | 159 | } |
| 96 | 160 | ||
| 97 | /** | 161 | /** |
| 98 | * 记录触发器匹配成功日志 | 162 | * 记录触发器匹配成功日志 |
| 99 | * | 163 | * |
| 100 | - * @param message 设备消息 | ||
| 101 | - * @param trigger 触发器配置 | 164 | + * @param message 设备消息 |
| 165 | + * @param trigger 触发器配置 | ||
| 102 | */ | 166 | */ |
| 103 | - protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | ||
| 104 | - log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType()); | 167 | + public static void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 168 | + log.debug("[isMatched][message({}) trigger({}) 匹配触发器成功]", message.getRequestId(), trigger.getType()); | ||
| 105 | } | 169 | } |
| 106 | 170 | ||
| 107 | /** | 171 | /** |
| 108 | * 记录触发器匹配失败日志 | 172 | * 记录触发器匹配失败日志 |
| 109 | * | 173 | * |
| 110 | - * @param message 设备消息 | ||
| 111 | - * @param trigger 触发器配置 | ||
| 112 | - * @param reason 失败原因 | 174 | + * @param message 设备消息 |
| 175 | + * @param trigger 触发器配置 | ||
| 176 | + * @param reason 失败原因 | ||
| 113 | */ | 177 | */ |
| 114 | - protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { | ||
| 115 | - log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason); | 178 | + public static void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) { |
| 179 | + log.debug("[isMatched][message({}) trigger({}) reason({}) 匹配触发器失败]", message.getRequestId(), trigger.getType(), reason); | ||
| 116 | } | 180 | } |
| 117 | 181 | ||
| 118 | // ========== 【条件】相关工具方法 ========== | 182 | // ========== 【条件】相关工具方法 ========== |
| @@ -123,7 +187,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | @@ -123,7 +187,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | ||
| 123 | * @param condition 触发条件 | 187 | * @param condition 触发条件 |
| 124 | * @return 是否有效 | 188 | * @return 是否有效 |
| 125 | */ | 189 | */ |
| 126 | - protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { | 190 | + public static boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) { |
| 127 | return condition != null && condition.getType() != null; | 191 | return condition != null && condition.getType() != null; |
| 128 | } | 192 | } |
| 129 | 193 | ||
| @@ -133,29 +197,29 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | @@ -133,29 +197,29 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | ||
| 133 | * @param condition 触发条件 | 197 | * @param condition 触发条件 |
| 134 | * @return 是否有效 | 198 | * @return 是否有效 |
| 135 | */ | 199 | */ |
| 136 | - protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { | 200 | + public static boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) { |
| 137 | return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam()); | 201 | return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam()); |
| 138 | } | 202 | } |
| 139 | 203 | ||
| 140 | /** | 204 | /** |
| 141 | * 记录条件匹配成功日志 | 205 | * 记录条件匹配成功日志 |
| 142 | * | 206 | * |
| 143 | - * @param message 设备消息 | ||
| 144 | - * @param condition 触发条件 | 207 | + * @param message 设备消息 |
| 208 | + * @param condition 触发条件 | ||
| 145 | */ | 209 | */ |
| 146 | - protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | ||
| 147 | - log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType()); | 210 | + public static void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { |
| 211 | + log.debug("[isMatched][message({}) condition({}) 匹配条件成功]", message.getRequestId(), condition.getType()); | ||
| 148 | } | 212 | } |
| 149 | 213 | ||
| 150 | /** | 214 | /** |
| 151 | * 记录条件匹配失败日志 | 215 | * 记录条件匹配失败日志 |
| 152 | * | 216 | * |
| 153 | - * @param message 设备消息 | ||
| 154 | - * @param condition 触发条件 | ||
| 155 | - * @param reason 失败原因 | 217 | + * @param message 设备消息 |
| 218 | + * @param condition 触发条件 | ||
| 219 | + * @param reason 失败原因 | ||
| 156 | */ | 220 | */ |
| 157 | - protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { | ||
| 158 | - log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason); | 221 | + public static void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) { |
| 222 | + log.debug("[isMatched][message({}) condition({}) reason({}) 匹配条件失败]", message.getRequestId(), condition.getType(), reason); | ||
| 159 | } | 223 | } |
| 160 | 224 | ||
| 161 | // ========== 【通用】工具方法 ========== | 225 | // ========== 【通用】工具方法 ========== |
| @@ -167,7 +231,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | @@ -167,7 +231,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher | ||
| 167 | * @param actualIdentifier 实际的标识符 | 231 | * @param actualIdentifier 实际的标识符 |
| 168 | * @return 是否匹配 | 232 | * @return 是否匹配 |
| 169 | */ | 233 | */ |
| 170 | - protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { | 234 | + public static boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) { |
| 171 | return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); | 235 | return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier); |
| 172 | } | 236 | } |
| 173 | 237 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java
| @@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | @@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; |
| 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher; | ||
| 9 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher; | ||
| 8 | import lombok.extern.slf4j.Slf4j; | 10 | import lombok.extern.slf4j.Slf4j; |
| 9 | import org.springframework.stereotype.Component; | 11 | import org.springframework.stereotype.Component; |
| 10 | 12 | ||
| @@ -12,7 +14,7 @@ import java.util.*; | @@ -12,7 +14,7 @@ import java.util.*; | ||
| 12 | import java.util.function.Function; | 14 | import java.util.function.Function; |
| 13 | import java.util.stream.Collectors; | 15 | import java.util.stream.Collectors; |
| 14 | 16 | ||
| 15 | -import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum; | 17 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 16 | 18 | ||
| 17 | /** | 19 | /** |
| 18 | * IoT 场景规则匹配器统一管理器 | 20 | * IoT 场景规则匹配器统一管理器 |
| @@ -27,21 +29,18 @@ public class IotSceneRuleMatcherManager { | @@ -27,21 +29,18 @@ public class IotSceneRuleMatcherManager { | ||
| 27 | 29 | ||
| 28 | /** | 30 | /** |
| 29 | * 触发器匹配器映射表 | 31 | * 触发器匹配器映射表 |
| 30 | - * Key: 触发器类型枚举 | ||
| 31 | - * Value: 对应的匹配器实例 | ||
| 32 | */ | 32 | */ |
| 33 | - private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleMatcher> triggerMatchers; | 33 | + private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleTriggerMatcher> triggerMatchers; |
| 34 | 34 | ||
| 35 | /** | 35 | /** |
| 36 | * 条件匹配器映射表 | 36 | * 条件匹配器映射表 |
| 37 | - * Key: 条件类型枚举 | ||
| 38 | - * Value: 对应的匹配器实例 | ||
| 39 | */ | 37 | */ |
| 40 | - private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleMatcher> conditionMatchers; | 38 | + private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleConditionMatcher> conditionMatchers; |
| 41 | 39 | ||
| 42 | /** | 40 | /** |
| 43 | * 所有匹配器列表(按优先级排序) | 41 | * 所有匹配器列表(按优先级排序) |
| 44 | */ | 42 | */ |
| 43 | + // TODO @puhui999:貌似 local variable 也可以 | ||
| 45 | private final List<IotSceneRuleMatcher> allMatchers; | 44 | private final List<IotSceneRuleMatcher> allMatchers; |
| 46 | 45 | ||
| 47 | public IotSceneRuleMatcherManager(List<IotSceneRuleMatcher> matchers) { | 46 | public IotSceneRuleMatcherManager(List<IotSceneRuleMatcher> matchers) { |
| @@ -60,50 +59,44 @@ public class IotSceneRuleMatcherManager { | @@ -60,50 +59,44 @@ public class IotSceneRuleMatcherManager { | ||
| 60 | .collect(Collectors.toList()); | 59 | .collect(Collectors.toList()); |
| 61 | 60 | ||
| 62 | // 分离触发器匹配器和条件匹配器 | 61 | // 分离触发器匹配器和条件匹配器 |
| 63 | - List<IotSceneRuleMatcher> triggerMatchers = this.allMatchers.stream() | ||
| 64 | - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER) | 62 | + List<IotSceneRuleTriggerMatcher> triggerMatchers = this.allMatchers.stream() |
| 63 | + .filter(matcher -> matcher instanceof IotSceneRuleTriggerMatcher) | ||
| 64 | + .map(matcher -> (IotSceneRuleTriggerMatcher) matcher) | ||
| 65 | .toList(); | 65 | .toList(); |
| 66 | - List<IotSceneRuleMatcher> conditionMatchers = this.allMatchers.stream() | ||
| 67 | - .filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION) | 66 | + List<IotSceneRuleConditionMatcher> conditionMatchers = this.allMatchers.stream() |
| 67 | + .filter(matcher -> matcher instanceof IotSceneRuleConditionMatcher) | ||
| 68 | + .map(matcher -> (IotSceneRuleConditionMatcher) matcher) | ||
| 68 | .toList(); | 69 | .toList(); |
| 69 | 70 | ||
| 70 | // 构建触发器匹配器映射表 | 71 | // 构建触发器匹配器映射表 |
| 71 | - // TODO @puhui999:convertMap() | ||
| 72 | - this.triggerMatchers = triggerMatchers.stream() | ||
| 73 | - .collect(Collectors.toMap( | ||
| 74 | - IotSceneRuleMatcher::getSupportedTriggerType, | ||
| 75 | - Function.identity(), | ||
| 76 | - (existing, replacement) -> { | ||
| 77 | - log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", | ||
| 78 | - existing.getSupportedTriggerType(), | ||
| 79 | - existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); | ||
| 80 | - return existing.getPriority() <= replacement.getPriority() ? existing : replacement; | ||
| 81 | - }, | ||
| 82 | - LinkedHashMap::new | ||
| 83 | - )); | 72 | + this.triggerMatchers = convertMap(triggerMatchers, IotSceneRuleTriggerMatcher::getSupportedTriggerType, |
| 73 | + Function.identity(), | ||
| 74 | + (existing, replacement) -> { | ||
| 75 | + log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]", | ||
| 76 | + existing.getSupportedTriggerType(), | ||
| 77 | + existing.getPriority() <= replacement.getPriority() ? | ||
| 78 | + existing.getSupportedTriggerType() : replacement.getSupportedTriggerType()); | ||
| 79 | + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; | ||
| 80 | + }, LinkedHashMap::new); | ||
| 84 | // 构建条件匹配器映射表 | 81 | // 构建条件匹配器映射表 |
| 85 | - this.conditionMatchers = conditionMatchers.stream() | ||
| 86 | - .collect(Collectors.toMap( | ||
| 87 | - IotSceneRuleMatcher::getSupportedConditionType, | ||
| 88 | - Function.identity(), | ||
| 89 | - (existing, replacement) -> { | ||
| 90 | - log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", | ||
| 91 | - existing.getSupportedConditionType(), | ||
| 92 | - existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName()); | ||
| 93 | - return existing.getPriority() <= replacement.getPriority() ? existing : replacement; | ||
| 94 | - }, | ||
| 95 | - LinkedHashMap::new | ||
| 96 | - )); | 82 | + this.conditionMatchers = convertMap(conditionMatchers, IotSceneRuleConditionMatcher::getSupportedConditionType, |
| 83 | + Function.identity(), | ||
| 84 | + (existing, replacement) -> { | ||
| 85 | + log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]", | ||
| 86 | + existing.getSupportedConditionType(), | ||
| 87 | + existing.getPriority() <= replacement.getPriority() ? | ||
| 88 | + existing.getSupportedConditionType() : replacement.getSupportedConditionType()); | ||
| 89 | + return existing.getPriority() <= replacement.getPriority() ? existing : replacement; | ||
| 90 | + }, | ||
| 91 | + LinkedHashMap::new); | ||
| 97 | 92 | ||
| 98 | // 日志输出初始化信息 | 93 | // 日志输出初始化信息 |
| 99 | - log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]", | 94 | + log.info("[IotSceneRuleMatcherManager][初始化完成,共加载({})个匹配器,其中触发器匹配器({})个,条件匹配器({})个]", |
| 100 | this.allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size()); | 95 | this.allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size()); |
| 101 | this.triggerMatchers.forEach((type, matcher) -> | 96 | this.triggerMatchers.forEach((type, matcher) -> |
| 102 | - log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}", | ||
| 103 | - type, matcher.getMatcherName(), matcher.getPriority())); | 97 | + log.info("[IotSceneRuleMatcherManager][触发器匹配器类型: ({}), 优先级: ({})] ", type, matcher.getPriority())); |
| 104 | this.conditionMatchers.forEach((type, matcher) -> | 98 | this.conditionMatchers.forEach((type, matcher) -> |
| 105 | - log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}", | ||
| 106 | - type, matcher.getMatcherName(), matcher.getPriority())); | 99 | + log.info("[IotSceneRuleMatcherManager][条件匹配器类型: ({}), 优先级: ({})]", type, matcher.getPriority())); |
| 107 | } | 100 | } |
| 108 | 101 | ||
| 109 | /** | 102 | /** |
| @@ -114,27 +107,25 @@ public class IotSceneRuleMatcherManager { | @@ -114,27 +107,25 @@ public class IotSceneRuleMatcherManager { | ||
| 114 | * @return 是否匹配 | 107 | * @return 是否匹配 |
| 115 | */ | 108 | */ |
| 116 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 109 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 117 | - // TODO @puhui999:日志优化下;claude 打出来的日志风格,和项目有点不一样哈; | ||
| 118 | if (message == null || trigger == null || trigger.getType() == null) { | 110 | if (message == null || trigger == null || trigger.getType() == null) { |
| 119 | - log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger); | 111 | + log.debug("[isMatched][message({}) trigger({}) 参数无效]", message, trigger); |
| 120 | return false; | 112 | return false; |
| 121 | } | 113 | } |
| 122 | - IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType()); | 114 | + IotSceneRuleTriggerTypeEnum triggerType = IotSceneRuleTriggerTypeEnum.typeOf(trigger.getType()); |
| 123 | if (triggerType == null) { | 115 | if (triggerType == null) { |
| 124 | - log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType()); | 116 | + log.warn("[isMatched][triggerType({}) 未知的触发器类型]", trigger.getType()); |
| 125 | return false; | 117 | return false; |
| 126 | } | 118 | } |
| 127 | - IotSceneRuleMatcher matcher = triggerMatchers.get(triggerType); | 119 | + IotSceneRuleTriggerMatcher matcher = triggerMatchers.get(triggerType); |
| 128 | if (matcher == null) { | 120 | if (matcher == null) { |
| 129 | - log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType); | 121 | + log.warn("[isMatched][triggerType({}) 没有对应的匹配器]", triggerType); |
| 130 | return false; | 122 | return false; |
| 131 | } | 123 | } |
| 132 | 124 | ||
| 133 | try { | 125 | try { |
| 134 | return matcher.isMatched(message, trigger); | 126 | return matcher.isMatched(message, trigger); |
| 135 | } catch (Exception e) { | 127 | } catch (Exception e) { |
| 136 | - log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}", | ||
| 137 | - message, trigger, matcher.getMatcherName(), e); | 128 | + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}", message, trigger, e); |
| 138 | return false; | 129 | return false; |
| 139 | } | 130 | } |
| 140 | } | 131 | } |
| @@ -148,28 +139,27 @@ public class IotSceneRuleMatcherManager { | @@ -148,28 +139,27 @@ public class IotSceneRuleMatcherManager { | ||
| 148 | */ | 139 | */ |
| 149 | public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | 140 | public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { |
| 150 | if (message == null || condition == null || condition.getType() == null) { | 141 | if (message == null || condition == null || condition.getType() == null) { |
| 151 | - log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition); | 142 | + log.debug("[isConditionMatched][message({}) condition({}) 参数无效]", message, condition); |
| 152 | return false; | 143 | return false; |
| 153 | } | 144 | } |
| 154 | 145 | ||
| 155 | // 根据条件类型查找对应的匹配器 | 146 | // 根据条件类型查找对应的匹配器 |
| 156 | IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType()); | 147 | IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType()); |
| 157 | if (conditionType == null) { | 148 | if (conditionType == null) { |
| 158 | - log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType()); | 149 | + log.warn("[isConditionMatched][conditionType({}) 未知的条件类型]", condition.getType()); |
| 159 | return false; | 150 | return false; |
| 160 | } | 151 | } |
| 161 | - | ||
| 162 | - IotSceneRuleMatcher matcher = conditionMatchers.get(conditionType); | 152 | + IotSceneRuleConditionMatcher matcher = conditionMatchers.get(conditionType); |
| 163 | if (matcher == null) { | 153 | if (matcher == null) { |
| 164 | - log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType); | 154 | + log.warn("[isConditionMatched][conditionType({}) 没有对应的匹配器]", conditionType); |
| 165 | return false; | 155 | return false; |
| 166 | } | 156 | } |
| 167 | 157 | ||
| 158 | + // 执行匹配逻辑 | ||
| 168 | try { | 159 | try { |
| 169 | return matcher.isMatched(message, condition); | 160 | return matcher.isMatched(message, condition); |
| 170 | } catch (Exception e) { | 161 | } catch (Exception e) { |
| 171 | - log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}", | ||
| 172 | - message, condition, matcher.getMatcherName(), e); | 162 | + log.error("[isConditionMatched][message({}) condition({}) 条件匹配异常]", message, condition, e); |
| 173 | return false; | 163 | return false; |
| 174 | } | 164 | } |
| 175 | } | 165 | } |
| @@ -181,12 +171,15 @@ public class IotSceneRuleMatcherManager { | @@ -181,12 +171,15 @@ public class IotSceneRuleMatcherManager { | ||
| 181 | * @return 条件类型枚举 | 171 | * @return 条件类型枚举 |
| 182 | */ | 172 | */ |
| 183 | private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) { | 173 | private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) { |
| 174 | + // TODO @puhui999:是不是搞到枚举类里? | ||
| 184 | return Arrays.stream(IotSceneRuleConditionTypeEnum.values()) | 175 | return Arrays.stream(IotSceneRuleConditionTypeEnum.values()) |
| 185 | .filter(type -> type.getType().equals(typeValue)) | 176 | .filter(type -> type.getType().equals(typeValue)) |
| 186 | .findFirst() | 177 | .findFirst() |
| 187 | .orElse(null); | 178 | .orElse(null); |
| 188 | } | 179 | } |
| 189 | 180 | ||
| 181 | + // TODO @puhui999:下面两个方法,是不是也可以删除哈? | ||
| 182 | + | ||
| 190 | /** | 183 | /** |
| 191 | * 获取所有支持的触发器类型 | 184 | * 获取所有支持的触发器类型 |
| 192 | * | 185 | * |
| @@ -205,65 +198,4 @@ public class IotSceneRuleMatcherManager { | @@ -205,65 +198,4 @@ public class IotSceneRuleMatcherManager { | ||
| 205 | return new HashSet<>(conditionMatchers.keySet()); | 198 | return new HashSet<>(conditionMatchers.keySet()); |
| 206 | } | 199 | } |
| 207 | 200 | ||
| 208 | - // TODO @puhui999:用不到的方法,可以去掉先哈; | ||
| 209 | - | ||
| 210 | - /** | ||
| 211 | - * 获取指定触发器类型的匹配器 | ||
| 212 | - * | ||
| 213 | - * @param triggerType 触发器类型 | ||
| 214 | - * @return 匹配器实例,如果不存在则返回 null | ||
| 215 | - */ | ||
| 216 | - public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) { | ||
| 217 | - return triggerMatchers.get(triggerType); | ||
| 218 | - } | ||
| 219 | - | ||
| 220 | - /** | ||
| 221 | - * 获取指定条件类型的匹配器 | ||
| 222 | - * | ||
| 223 | - * @param conditionType 条件类型 | ||
| 224 | - * @return 匹配器实例,如果不存在则返回 null | ||
| 225 | - */ | ||
| 226 | - public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) { | ||
| 227 | - return conditionMatchers.get(conditionType); | ||
| 228 | - } | ||
| 229 | - | ||
| 230 | - // TODO @puhui999:是不是不用这个哈;直接 @Getter,单测直接处理; | ||
| 231 | - /** | ||
| 232 | - * 获取所有匹配器的统计信息 | ||
| 233 | - * | ||
| 234 | - * @return 统计信息映射表 | ||
| 235 | - */ | ||
| 236 | - public Map<String, Object> getMatcherStatistics() { | ||
| 237 | - Map<String, Object> statistics = new HashMap<>(); | ||
| 238 | - statistics.put("totalMatchers", allMatchers.size()); | ||
| 239 | - statistics.put("triggerMatchers", triggerMatchers.size()); | ||
| 240 | - statistics.put("conditionMatchers", conditionMatchers.size()); | ||
| 241 | - statistics.put("supportedTriggerTypes", getSupportedTriggerTypes()); | ||
| 242 | - statistics.put("supportedConditionTypes", getSupportedConditionTypes()); | ||
| 243 | - | ||
| 244 | - // 触发器匹配器详情 | ||
| 245 | - Map<String, Object> triggerMatcherDetails = new HashMap<>(); | ||
| 246 | - triggerMatchers.forEach((type, matcher) -> { | ||
| 247 | - Map<String, Object> detail = new HashMap<>(); | ||
| 248 | - detail.put("matcherName", matcher.getMatcherName()); | ||
| 249 | - detail.put("priority", matcher.getPriority()); | ||
| 250 | - detail.put("enabled", matcher.isEnabled()); | ||
| 251 | - triggerMatcherDetails.put(type.name(), detail); | ||
| 252 | - }); | ||
| 253 | - statistics.put("triggerMatcherDetails", triggerMatcherDetails); | ||
| 254 | - | ||
| 255 | - // 条件匹配器详情 | ||
| 256 | - Map<String, Object> conditionMatcherDetails = new HashMap<>(); | ||
| 257 | - conditionMatchers.forEach((type, matcher) -> { | ||
| 258 | - Map<String, Object> detail = new HashMap<>(); | ||
| 259 | - detail.put("matcherName", matcher.getMatcherName()); | ||
| 260 | - detail.put("priority", matcher.getPriority()); | ||
| 261 | - detail.put("enabled", matcher.isEnabled()); | ||
| 262 | - conditionMatcherDetails.put(type.name(), detail); | ||
| 263 | - }); | ||
| 264 | - statistics.put("conditionMatcherDetails", conditionMatcherDetails); | ||
| 265 | - | ||
| 266 | - return statistics; | ||
| 267 | - } | ||
| 268 | - | ||
| 269 | } | 201 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.lang.Assert; | ||
| 4 | +import cn.hutool.core.text.CharPool; | ||
| 5 | +import cn.hutool.core.util.StrUtil; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | ||
| 9 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | ||
| 10 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 11 | +import lombok.extern.slf4j.Slf4j; | ||
| 12 | +import org.springframework.stereotype.Component; | ||
| 13 | + | ||
| 14 | +import java.time.LocalDateTime; | ||
| 15 | +import java.time.LocalTime; | ||
| 16 | +import java.time.format.DateTimeFormatter; | ||
| 17 | +import java.util.List; | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * 当前时间条件匹配器 | ||
| 21 | + * <p> | ||
| 22 | + * 处理时间相关的子条件匹配逻辑 | ||
| 23 | + * | ||
| 24 | + * @author HUIHUI | ||
| 25 | + */ | ||
| 26 | +@Component | ||
| 27 | +@Slf4j | ||
| 28 | +public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher { | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 时间格式化器 - HH:mm:ss | ||
| 32 | + */ | ||
| 33 | + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 时间格式化器 - HH:mm | ||
| 37 | + */ | ||
| 38 | + private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm"); | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public IotSceneRuleConditionTypeEnum getSupportedConditionType() { | ||
| 42 | + return IotSceneRuleConditionTypeEnum.CURRENT_TIME; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Override | ||
| 46 | + public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | ||
| 47 | + // 1.1 基础参数校验 | ||
| 48 | + if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { | ||
| 49 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); | ||
| 50 | + return false; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + // 1.2 检查操作符和参数是否有效 | ||
| 54 | + if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { | ||
| 55 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); | ||
| 56 | + return false; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + // 1.3 验证操作符是否为支持的时间操作符 | ||
| 60 | + String operator = condition.getOperator(); | ||
| 61 | + IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator); | ||
| 62 | + if (operatorEnum == null) { | ||
| 63 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "无效的操作符: " + operator); | ||
| 64 | + return false; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + if (!isTimeOperator(operatorEnum)) { | ||
| 68 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "不支持的时间操作符: " + operator); | ||
| 69 | + return false; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + // 2.1 执行时间匹配 | ||
| 73 | + boolean matched = executeTimeMatching(operatorEnum, condition.getParam()); | ||
| 74 | + | ||
| 75 | + // 2.2 记录匹配结果 | ||
| 76 | + if (matched) { | ||
| 77 | + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); | ||
| 78 | + } else { | ||
| 79 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "时间条件不匹配"); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + return matched; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * 执行时间匹配逻辑 | ||
| 87 | + * 直接实现时间条件匹配,不使用 Spring EL 表达式 | ||
| 88 | + */ | ||
| 89 | + private boolean executeTimeMatching(IotSceneRuleConditionOperatorEnum operatorEnum, String param) { | ||
| 90 | + try { | ||
| 91 | + LocalDateTime now = LocalDateTime.now(); | ||
| 92 | + | ||
| 93 | + if (isDateTimeOperator(operatorEnum)) { | ||
| 94 | + // 日期时间匹配(时间戳) | ||
| 95 | + long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8")); | ||
| 96 | + return matchDateTime(currentTimestamp, operatorEnum, param); | ||
| 97 | + } else { | ||
| 98 | + // 当日时间匹配(HH:mm:ss) | ||
| 99 | + return matchTime(now.toLocalTime(), operatorEnum, param); | ||
| 100 | + } | ||
| 101 | + } catch (Exception e) { | ||
| 102 | + log.error("[executeTimeMatching][operatorEnum({}) param({}) 时间匹配异常]", operatorEnum, param, e); | ||
| 103 | + return false; | ||
| 104 | + } | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + /** | ||
| 108 | + * 判断是否为日期时间操作符 | ||
| 109 | + */ | ||
| 110 | + private boolean isDateTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { | ||
| 111 | + return operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN || | ||
| 112 | + operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN || | ||
| 113 | + operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + /** | ||
| 117 | + * 判断是否为时间操作符 | ||
| 118 | + */ | ||
| 119 | + private boolean isTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) { | ||
| 120 | + return operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN || | ||
| 121 | + operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN || | ||
| 122 | + operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_BETWEEN || | ||
| 123 | + isDateTimeOperator(operatorEnum); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + // TODO @puhui999:switch 兼容下 jdk8 | ||
| 127 | + /** | ||
| 128 | + * 匹配日期时间(时间戳) | ||
| 129 | + * 直接实现时间戳比较逻辑 | ||
| 130 | + */ | ||
| 131 | + private boolean matchDateTime(long currentTimestamp, IotSceneRuleConditionOperatorEnum operatorEnum, String param) { | ||
| 132 | + try { | ||
| 133 | + long targetTimestamp = Long.parseLong(param); | ||
| 134 | + return switch (operatorEnum) { | ||
| 135 | + case DATE_TIME_GREATER_THAN -> currentTimestamp > targetTimestamp; | ||
| 136 | + case DATE_TIME_LESS_THAN -> currentTimestamp < targetTimestamp; | ||
| 137 | + case DATE_TIME_BETWEEN -> matchDateTimeBetween(currentTimestamp, param); | ||
| 138 | + default -> { | ||
| 139 | + log.warn("[matchDateTime][operatorEnum({}) 不支持的日期时间操作符]", operatorEnum); | ||
| 140 | + yield false; | ||
| 141 | + } | ||
| 142 | + }; | ||
| 143 | + } catch (Exception e) { | ||
| 144 | + log.error("[matchDateTime][operatorEnum({}) param({}) 日期时间匹配异常]", operatorEnum, param, e); | ||
| 145 | + return false; | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + /** | ||
| 150 | + * 匹配日期时间区间 | ||
| 151 | + */ | ||
| 152 | + private boolean matchDateTimeBetween(long currentTimestamp, String param) { | ||
| 153 | + List<String> timestampRange = StrUtil.splitTrim(param, CharPool.COMMA); | ||
| 154 | + if (timestampRange.size() != 2) { | ||
| 155 | + log.warn("[matchDateTimeBetween][param({}) 时间戳区间参数格式错误]", param); | ||
| 156 | + return false; | ||
| 157 | + } | ||
| 158 | + long startTimestamp = Long.parseLong(timestampRange.get(0).trim()); | ||
| 159 | + long endTimestamp = Long.parseLong(timestampRange.get(1).trim()); | ||
| 160 | + return currentTimestamp >= startTimestamp && currentTimestamp <= endTimestamp; | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + /** | ||
| 164 | + * 匹配当日时间(HH:mm:ss) | ||
| 165 | + * 直接实现时间比较逻辑 | ||
| 166 | + */ | ||
| 167 | + private boolean matchTime(LocalTime currentTime, IotSceneRuleConditionOperatorEnum operatorEnum, String param) { | ||
| 168 | + try { | ||
| 169 | + LocalTime targetTime = parseTime(param); | ||
| 170 | + return switch (operatorEnum) { | ||
| 171 | + case TIME_GREATER_THAN -> currentTime.isAfter(targetTime); | ||
| 172 | + case TIME_LESS_THAN -> currentTime.isBefore(targetTime); | ||
| 173 | + case TIME_BETWEEN -> matchTimeBetween(currentTime, param); | ||
| 174 | + default -> { | ||
| 175 | + log.warn("[matchTime][operatorEnum({}) 不支持的时间操作符]", operatorEnum); | ||
| 176 | + yield false; | ||
| 177 | + } | ||
| 178 | + }; | ||
| 179 | + } catch (Exception e) { | ||
| 180 | + log.error("[matchTime][][operatorEnum({}) param({}) 时间解析异常]", operatorEnum, param, e); | ||
| 181 | + return false; | ||
| 182 | + } | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + /** | ||
| 186 | + * 匹配时间区间 | ||
| 187 | + */ | ||
| 188 | + private boolean matchTimeBetween(LocalTime currentTime, String param) { | ||
| 189 | + List<String> timeRange = StrUtil.splitTrim(param, CharPool.COMMA); | ||
| 190 | + if (timeRange.size() != 2) { | ||
| 191 | + log.warn("[matchTimeBetween][param({}) 时间区间参数格式错误]", param); | ||
| 192 | + return false; | ||
| 193 | + } | ||
| 194 | + LocalTime startTime = parseTime(timeRange.get(0).trim()); | ||
| 195 | + LocalTime endTime = parseTime(timeRange.get(1).trim()); | ||
| 196 | + return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime); | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + /** | ||
| 200 | + * 解析时间字符串 | ||
| 201 | + * 支持 HH:mm 和 HH:mm:ss 两种格式 | ||
| 202 | + */ | ||
| 203 | + private LocalTime parseTime(String timeStr) { | ||
| 204 | + Assert.isFalse(StrUtil.isBlank(timeStr), "时间字符串不能为空"); | ||
| 205 | + | ||
| 206 | + try { | ||
| 207 | + // 尝试不同的时间格式 | ||
| 208 | + if (timeStr.length() == 5) { // HH:mm | ||
| 209 | + return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT); | ||
| 210 | + } else if (timeStr.length() == 8) { // HH:mm:ss | ||
| 211 | + return LocalTime.parse(timeStr, TIME_FORMATTER); | ||
| 212 | + } else { | ||
| 213 | + throw new IllegalArgumentException("时间格式长度不正确,期望 HH:mm 或 HH:mm:ss 格式"); | ||
| 214 | + } | ||
| 215 | + } catch (Exception e) { | ||
| 216 | + log.error("[parseTime][timeStr({}) 时间格式解析失败]", timeStr, e); | ||
| 217 | + throw new IllegalArgumentException("时间格式无效: " + timeStr, e); | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + @Override | ||
| 222 | + public int getPriority() { | ||
| 223 | + return 40; // 较低优先级 | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | +} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyConditionMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; |
| 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.core.util.IotDeviceMessageUtils; | 4 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; |
| 7 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 7 | import org.springframework.stereotype.Component; | 8 | import org.springframework.stereotype.Component; |
| 8 | 9 | ||
| 9 | /** | 10 | /** |
| @@ -14,53 +15,48 @@ import org.springframework.stereotype.Component; | @@ -14,53 +15,48 @@ import org.springframework.stereotype.Component; | ||
| 14 | * @author HUIHUI | 15 | * @author HUIHUI |
| 15 | */ | 16 | */ |
| 16 | @Component | 17 | @Component |
| 17 | -public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher { | ||
| 18 | - | ||
| 19 | - @Override | ||
| 20 | - public MatcherType getMatcherType() { | ||
| 21 | - return MatcherType.CONDITION; | ||
| 22 | - } | 18 | +public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher { |
| 23 | 19 | ||
| 24 | @Override | 20 | @Override |
| 25 | public IotSceneRuleConditionTypeEnum getSupportedConditionType() { | 21 | public IotSceneRuleConditionTypeEnum getSupportedConditionType() { |
| 26 | return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; | 22 | return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY; |
| 27 | } | 23 | } |
| 28 | 24 | ||
| 29 | - // TODO @puhui999:参数校验的,要不要 1.1 1.2 1.3 1.4 ?这样最终看到 2. 3. 就是核心逻辑列; | 25 | + // TODO @puhui999:matches 会不会更好?参考的 org.hamcrest.Matcher jdk 接口 |
| 30 | @Override | 26 | @Override |
| 31 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | 27 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { |
| 32 | - // 1. 基础参数校验 | ||
| 33 | - if (!isBasicConditionValid(condition)) { | ||
| 34 | - logConditionMatchFailure(message, condition, "条件基础参数无效"); | 28 | + // 1.1 基础参数校验 |
| 29 | + if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { | ||
| 30 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); | ||
| 35 | return false; | 31 | return false; |
| 36 | } | 32 | } |
| 37 | 33 | ||
| 38 | - // 2. 检查标识符是否匹配 | 34 | + // 1.2 检查标识符是否匹配 |
| 39 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | 35 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 40 | - if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { | ||
| 41 | - logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); | 36 | + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) { |
| 37 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier); | ||
| 42 | return false; | 38 | return false; |
| 43 | } | 39 | } |
| 44 | 40 | ||
| 45 | - // 3. 检查操作符和参数是否有效 | ||
| 46 | - if (!isConditionOperatorAndParamValid(condition)) { | ||
| 47 | - logConditionMatchFailure(message, condition, "操作符或参数无效"); | 41 | + // 1.3 检查操作符和参数是否有效 |
| 42 | + if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { | ||
| 43 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); | ||
| 48 | return false; | 44 | return false; |
| 49 | } | 45 | } |
| 50 | 46 | ||
| 51 | - // 4. 获取属性值 | ||
| 52 | - Object propertyValue = message.getData(); | 47 | + // 2.1. 获取属性值 |
| 48 | + Object propertyValue = message.getParams(); | ||
| 53 | if (propertyValue == null) { | 49 | if (propertyValue == null) { |
| 54 | - logConditionMatchFailure(message, condition, "消息中属性值为空"); | 50 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空"); |
| 55 | return false; | 51 | return false; |
| 56 | } | 52 | } |
| 57 | 53 | ||
| 58 | - // 5. 使用条件评估器进行匹配 | ||
| 59 | - boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); | 54 | + // 2.2 使用条件评估器进行匹配 |
| 55 | + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, condition.getOperator(), condition.getParam()); | ||
| 60 | if (matched) { | 56 | if (matched) { |
| 61 | - logConditionMatchSuccess(message, condition); | 57 | + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); |
| 62 | } else { | 58 | } else { |
| 63 | - logConditionMatchFailure(message, condition, "设备属性条件不匹配"); | 59 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备属性条件不匹配"); |
| 64 | } | 60 | } |
| 65 | return matched; | 61 | return matched; |
| 66 | } | 62 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateConditionMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; |
| 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.IotSceneRuleConditionTypeEnum; | 5 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; |
| 6 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 6 | import org.springframework.stereotype.Component; | 7 | import org.springframework.stereotype.Component; |
| 7 | 8 | ||
| 8 | /** | 9 | /** |
| @@ -13,12 +14,7 @@ import org.springframework.stereotype.Component; | @@ -13,12 +14,7 @@ import org.springframework.stereotype.Component; | ||
| 13 | * @author HUIHUI | 14 | * @author HUIHUI |
| 14 | */ | 15 | */ |
| 15 | @Component | 16 | @Component |
| 16 | -public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { | ||
| 17 | - | ||
| 18 | - @Override | ||
| 19 | - public MatcherType getMatcherType() { | ||
| 20 | - return MatcherType.CONDITION; | ||
| 21 | - } | 17 | +public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher { |
| 22 | 18 | ||
| 23 | @Override | 19 | @Override |
| 24 | public IotSceneRuleConditionTypeEnum getSupportedConditionType() { | 20 | public IotSceneRuleConditionTypeEnum getSupportedConditionType() { |
| @@ -27,32 +23,31 @@ public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { | @@ -27,32 +23,31 @@ public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher { | ||
| 27 | 23 | ||
| 28 | @Override | 24 | @Override |
| 29 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { | 25 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) { |
| 30 | - // 1. 基础参数校验 | ||
| 31 | - if (!isBasicConditionValid(condition)) { | ||
| 32 | - logConditionMatchFailure(message, condition, "条件基础参数无效"); | 26 | + // 1.1 基础参数校验 |
| 27 | + if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) { | ||
| 28 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效"); | ||
| 33 | return false; | 29 | return false; |
| 34 | } | 30 | } |
| 35 | 31 | ||
| 36 | - // 2. 检查操作符和参数是否有效 | ||
| 37 | - if (!isConditionOperatorAndParamValid(condition)) { | ||
| 38 | - logConditionMatchFailure(message, condition, "操作符或参数无效"); | 32 | + // 1.2 检查操作符和参数是否有效 |
| 33 | + if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) { | ||
| 34 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效"); | ||
| 39 | return false; | 35 | return false; |
| 40 | } | 36 | } |
| 41 | 37 | ||
| 42 | - // 3. 获取设备状态值 | ||
| 43 | - // 设备状态通常在消息的 data 字段中 | ||
| 44 | - Object stateValue = message.getData(); | 38 | + // 2.1 获取设备状态值 |
| 39 | + Object stateValue = message.getParams(); | ||
| 45 | if (stateValue == null) { | 40 | if (stateValue == null) { |
| 46 | - logConditionMatchFailure(message, condition, "消息中设备状态值为空"); | 41 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空"); |
| 47 | return false; | 42 | return false; |
| 48 | } | 43 | } |
| 49 | 44 | ||
| 50 | - // 4. 使用条件评估器进行匹配 | ||
| 51 | - boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); | 45 | + // 2.2 使用条件评估器进行匹配 |
| 46 | + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, condition.getOperator(), condition.getParam()); | ||
| 52 | if (matched) { | 47 | if (matched) { |
| 53 | - logConditionMatchSuccess(message, condition); | 48 | + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition); |
| 54 | } else { | 49 | } else { |
| 55 | - logConditionMatchFailure(message, condition, "设备状态条件不匹配"); | 50 | + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备状态条件不匹配"); |
| 56 | } | 51 | } |
| 57 | return matched; | 52 | return matched; |
| 58 | } | 53 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotSceneRuleConditionMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | ||
| 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.IotSceneRuleConditionTypeEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * IoT 场景规则条件匹配器接口 | ||
| 10 | + * <p> | ||
| 11 | + * 专门处理子条件的匹配逻辑,如设备状态、属性值、时间条件等 | ||
| 12 | + * <p> | ||
| 13 | + * 条件匹配器负责判断设备消息是否满足场景规则的附加条件, | ||
| 14 | + * 在触发器匹配成功后进行进一步的条件筛选 | ||
| 15 | + * | ||
| 16 | + * @author HUIHUI | ||
| 17 | + */ | ||
| 18 | +public interface IotSceneRuleConditionMatcher extends IotSceneRuleMatcher { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 获取支持的条件类型 | ||
| 22 | + * | ||
| 23 | + * @return 条件类型枚举 | ||
| 24 | + */ | ||
| 25 | + IotSceneRuleConditionTypeEnum getSupportedConditionType(); | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 检查条件是否匹配消息 | ||
| 29 | + * <p> | ||
| 30 | + * 判断设备消息是否满足指定的触发条件 | ||
| 31 | + * | ||
| 32 | + * @param message 设备消息 | ||
| 33 | + * @param condition 触发条件 | ||
| 34 | + * @return 是否匹配 | ||
| 35 | + */ | ||
| 36 | + boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition); | ||
| 37 | + | ||
| 38 | +} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceEventPostTriggerMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.util.StrUtil; | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 4 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 6 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 6 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 8 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 9 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 9 | import org.springframework.stereotype.Component; | 10 | import org.springframework.stereotype.Component; |
| 10 | 11 | ||
| 11 | /** | 12 | /** |
| @@ -16,17 +17,7 @@ import org.springframework.stereotype.Component; | @@ -16,17 +17,7 @@ import org.springframework.stereotype.Component; | ||
| 16 | * @author HUIHUI | 17 | * @author HUIHUI |
| 17 | */ | 18 | */ |
| 18 | @Component | 19 | @Component |
| 19 | -public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { | ||
| 20 | - | ||
| 21 | - /** | ||
| 22 | - * 设备事件上报消息方法 | ||
| 23 | - */ | ||
| 24 | - private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); | ||
| 25 | - | ||
| 26 | - @Override | ||
| 27 | - public MatcherType getMatcherType() { | ||
| 28 | - return MatcherType.TRIGGER; | ||
| 29 | - } | 20 | +public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher { |
| 30 | 21 | ||
| 31 | @Override | 22 | @Override |
| 32 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 23 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| @@ -35,42 +26,44 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { | @@ -35,42 +26,44 @@ public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher { | ||
| 35 | 26 | ||
| 36 | @Override | 27 | @Override |
| 37 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 28 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 38 | - // 1. 基础参数校验 | ||
| 39 | - if (!isBasicTriggerValid(trigger)) { | ||
| 40 | - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | 29 | + // 1.1 基础参数校验 |
| 30 | + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { | ||
| 31 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | ||
| 41 | return false; | 32 | return false; |
| 42 | } | 33 | } |
| 43 | 34 | ||
| 44 | - // 2. 检查消息方法是否匹配 | ||
| 45 | - if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) { | ||
| 46 | - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod()); | 35 | + // 1.2 检查消息方法是否匹配 |
| 36 | + if (!IotDeviceMessageMethodEnum.EVENT_POST.getMethod().equals(message.getMethod())) { | ||
| 37 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + | ||
| 38 | + IotDeviceMessageMethodEnum.EVENT_POST.getMethod() + ", 实际: " + message.getMethod()); | ||
| 47 | return false; | 39 | return false; |
| 48 | } | 40 | } |
| 49 | 41 | ||
| 50 | - // 3. 检查标识符是否匹配 | 42 | + // 1.3 检查标识符是否匹配 |
| 51 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | 43 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 52 | - if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | ||
| 53 | - logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | 44 | + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 45 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + | ||
| 46 | + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | ||
| 54 | return false; | 47 | return false; |
| 55 | } | 48 | } |
| 56 | 49 | ||
| 57 | - // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 | 50 | + // 2. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配 |
| 58 | // 但如果配置了操作符和值,则需要进行条件匹配 | 51 | // 但如果配置了操作符和值,则需要进行条件匹配 |
| 59 | if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { | 52 | if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) { |
| 60 | Object eventData = message.getData(); | 53 | Object eventData = message.getData(); |
| 61 | if (eventData == null) { | 54 | if (eventData == null) { |
| 62 | - logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); | 55 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中事件数据为空"); |
| 63 | return false; | 56 | return false; |
| 64 | } | 57 | } |
| 65 | 58 | ||
| 66 | - boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); | 59 | + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(eventData, trigger.getOperator(), trigger.getValue()); |
| 67 | if (!matched) { | 60 | if (!matched) { |
| 68 | - logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); | 61 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "事件数据条件不匹配"); |
| 69 | return false; | 62 | return false; |
| 70 | } | 63 | } |
| 71 | } | 64 | } |
| 72 | 65 | ||
| 73 | - logTriggerMatchSuccess(message, trigger); | 66 | + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); |
| 74 | return true; | 67 | return true; |
| 75 | } | 68 | } |
| 76 | 69 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DevicePropertyPostTriggerMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 5 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 8 | import org.springframework.stereotype.Component; | 9 | import org.springframework.stereotype.Component; |
| 9 | 10 | ||
| 10 | /** | 11 | /** |
| @@ -15,18 +16,7 @@ import org.springframework.stereotype.Component; | @@ -15,18 +16,7 @@ import org.springframework.stereotype.Component; | ||
| 15 | * @author HUIHUI | 16 | * @author HUIHUI |
| 16 | */ | 17 | */ |
| 17 | @Component | 18 | @Component |
| 18 | -public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher { | ||
| 19 | - | ||
| 20 | - /** | ||
| 21 | - * 设备属性上报消息方法 | ||
| 22 | - */ | ||
| 23 | - // TODO @puhui999:是不是不用枚举哈?直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() | ||
| 24 | - private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); | ||
| 25 | - | ||
| 26 | - @Override | ||
| 27 | - public MatcherType getMatcherType() { | ||
| 28 | - return MatcherType.TRIGGER; | ||
| 29 | - } | 19 | +public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher { |
| 30 | 20 | ||
| 31 | @Override | 21 | @Override |
| 32 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 22 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| @@ -35,44 +25,46 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatche | @@ -35,44 +25,46 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatche | ||
| 35 | 25 | ||
| 36 | @Override | 26 | @Override |
| 37 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 27 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 38 | - // 1. 基础参数校验 | ||
| 39 | - if (!isBasicTriggerValid(trigger)) { | ||
| 40 | - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | 28 | + // 1.1 基础参数校验 |
| 29 | + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { | ||
| 30 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | ||
| 41 | return false; | 31 | return false; |
| 42 | } | 32 | } |
| 43 | 33 | ||
| 44 | - // 2. 检查消息方法是否匹配 | ||
| 45 | - if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) { | ||
| 46 | - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod()); | 34 | + // 1.2 检查消息方法是否匹配 |
| 35 | + if (!IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod().equals(message.getMethod())) { | ||
| 36 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + | ||
| 37 | + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() + ", 实际: " + message.getMethod()); | ||
| 47 | return false; | 38 | return false; |
| 48 | } | 39 | } |
| 49 | 40 | ||
| 50 | - // 3. 检查标识符是否匹配 | 41 | + // 1.3 检查标识符是否匹配 |
| 51 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | 42 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 52 | - if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | ||
| 53 | - logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | 43 | + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 44 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + | ||
| 45 | + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | ||
| 54 | return false; | 46 | return false; |
| 55 | } | 47 | } |
| 56 | 48 | ||
| 57 | - // 4. 检查操作符和值是否有效 | ||
| 58 | - if (!isTriggerOperatorAndValueValid(trigger)) { | ||
| 59 | - logTriggerMatchFailure(message, trigger, "操作符或值无效"); | 49 | + // 1.4 检查操作符和值是否有效 |
| 50 | + if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { | ||
| 51 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效"); | ||
| 60 | return false; | 52 | return false; |
| 61 | } | 53 | } |
| 62 | 54 | ||
| 63 | - // 5. 获取属性值 | ||
| 64 | - Object propertyValue = message.getData(); | 55 | + // 2.1 获取属性值 |
| 56 | + Object propertyValue = message.getParams(); | ||
| 65 | if (propertyValue == null) { | 57 | if (propertyValue == null) { |
| 66 | - logTriggerMatchFailure(message, trigger, "消息中属性值为空"); | 58 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空"); |
| 67 | return false; | 59 | return false; |
| 68 | } | 60 | } |
| 69 | 61 | ||
| 70 | - // 6. 使用条件评估器进行匹配 | ||
| 71 | - boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); | 62 | + // 2.2 使用条件评估器进行匹配 |
| 63 | + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue()); | ||
| 72 | if (matched) { | 64 | if (matched) { |
| 73 | - logTriggerMatchSuccess(message, trigger); | 65 | + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); |
| 74 | } else { | 66 | } else { |
| 75 | - logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); | 67 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "属性值条件不匹配"); |
| 76 | } | 68 | } |
| 77 | return matched; | 69 | return matched; |
| 78 | } | 70 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceServiceInvokeTriggerMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 5 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 8 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 8 | import org.springframework.stereotype.Component; | 9 | import org.springframework.stereotype.Component; |
| 9 | 10 | ||
| 10 | /** | 11 | /** |
| @@ -15,17 +16,7 @@ import org.springframework.stereotype.Component; | @@ -15,17 +16,7 @@ import org.springframework.stereotype.Component; | ||
| 15 | * @author HUIHUI | 16 | * @author HUIHUI |
| 16 | */ | 17 | */ |
| 17 | @Component | 18 | @Component |
| 18 | -public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher { | ||
| 19 | - | ||
| 20 | - /** | ||
| 21 | - * 设备服务调用消息方法 | ||
| 22 | - */ | ||
| 23 | - private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(); | ||
| 24 | - | ||
| 25 | - @Override | ||
| 26 | - public MatcherType getMatcherType() { | ||
| 27 | - return MatcherType.TRIGGER; | ||
| 28 | - } | 19 | +public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher { |
| 29 | 20 | ||
| 30 | @Override | 21 | @Override |
| 31 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 22 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| @@ -34,29 +25,29 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatch | @@ -34,29 +25,29 @@ public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatch | ||
| 34 | 25 | ||
| 35 | @Override | 26 | @Override |
| 36 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 27 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 37 | - // 1. 基础参数校验 | ||
| 38 | - if (!isBasicTriggerValid(trigger)) { | ||
| 39 | - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | 28 | + // 1.1 基础参数校验 |
| 29 | + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { | ||
| 30 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | ||
| 40 | return false; | 31 | return false; |
| 41 | } | 32 | } |
| 42 | 33 | ||
| 43 | - // 2. 检查消息方法是否匹配 | ||
| 44 | - if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) { | ||
| 45 | - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod()); | 34 | + // 1.2 检查消息方法是否匹配 |
| 35 | + if (!IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod().equals(message.getMethod())) { | ||
| 36 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod() + ", 实际: " + message.getMethod()); | ||
| 46 | return false; | 37 | return false; |
| 47 | } | 38 | } |
| 48 | 39 | ||
| 49 | - // 3. 检查标识符是否匹配 | 40 | + // 1.3 检查标识符是否匹配 |
| 50 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); | 41 | String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message); |
| 51 | - if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { | ||
| 52 | - logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | 42 | + if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) { |
| 43 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier); | ||
| 53 | return false; | 44 | return false; |
| 54 | } | 45 | } |
| 55 | 46 | ||
| 56 | - // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可 | 47 | + // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可 |
| 57 | // 不需要检查操作符和值,因为服务调用本身就是触发条件 | 48 | // 不需要检查操作符和值,因为服务调用本身就是触发条件 |
| 58 | - | ||
| 59 | - logTriggerMatchSuccess(message, trigger); | 49 | + // TODO @puhui999: 服务调用时校验输入参数是否匹配条件 |
| 50 | + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); | ||
| 60 | return true; | 51 | return true; |
| 61 | } | 52 | } |
| 62 | 53 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/DeviceStateUpdateTriggerMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 3 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 7 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 7 | import org.springframework.stereotype.Component; | 8 | import org.springframework.stereotype.Component; |
| 8 | 9 | ||
| 9 | /** | 10 | /** |
| @@ -14,18 +15,7 @@ import org.springframework.stereotype.Component; | @@ -14,18 +15,7 @@ import org.springframework.stereotype.Component; | ||
| 14 | * @author HUIHUI | 15 | * @author HUIHUI |
| 15 | */ | 16 | */ |
| 16 | @Component | 17 | @Component |
| 17 | -public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher { | ||
| 18 | - | ||
| 19 | - // TODO @puhui999:是不是不用枚举哈; | ||
| 20 | - /** | ||
| 21 | - * 设备状态更新消息方法 | ||
| 22 | - */ | ||
| 23 | - private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); | ||
| 24 | - | ||
| 25 | - @Override | ||
| 26 | - public MatcherType getMatcherType() { | ||
| 27 | - return MatcherType.TRIGGER; | ||
| 28 | - } | 18 | +public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher { |
| 29 | 19 | ||
| 30 | @Override | 20 | @Override |
| 31 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 21 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| @@ -34,37 +24,39 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher | @@ -34,37 +24,39 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher | ||
| 34 | 24 | ||
| 35 | @Override | 25 | @Override |
| 36 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 26 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 37 | - // 1. 基础参数校验 | ||
| 38 | - if (!isBasicTriggerValid(trigger)) { | ||
| 39 | - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | 27 | + // 1.1 基础参数校验 |
| 28 | + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { | ||
| 29 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | ||
| 40 | return false; | 30 | return false; |
| 41 | } | 31 | } |
| 42 | 32 | ||
| 43 | - // 2. 检查消息方法是否匹配 | ||
| 44 | - if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) { | ||
| 45 | - logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod()); | 33 | + // 1.2 检查消息方法是否匹配 |
| 34 | + if (!IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod().equals(message.getMethod())) { | ||
| 35 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + | ||
| 36 | + IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod() + ", 实际: " + message.getMethod()); | ||
| 46 | return false; | 37 | return false; |
| 47 | } | 38 | } |
| 48 | 39 | ||
| 49 | - // 3. 检查操作符和值是否有效 | ||
| 50 | - if (!isTriggerOperatorAndValueValid(trigger)) { | ||
| 51 | - logTriggerMatchFailure(message, trigger, "操作符或值无效"); | 40 | + // 1.3 检查操作符和值是否有效 |
| 41 | + if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) { | ||
| 42 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效"); | ||
| 52 | return false; | 43 | return false; |
| 53 | } | 44 | } |
| 54 | 45 | ||
| 55 | - // 4. 获取设备状态值 | ||
| 56 | - Object stateValue = message.getData(); | 46 | + // 2.1 获取设备状态值 |
| 47 | + Object stateValue = message.getParams(); | ||
| 57 | if (stateValue == null) { | 48 | if (stateValue == null) { |
| 58 | - logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); | 49 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空"); |
| 59 | return false; | 50 | return false; |
| 60 | } | 51 | } |
| 61 | 52 | ||
| 62 | - // 5. 使用条件评估器进行匹配 | ||
| 63 | - boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); | 53 | + // 2.2 使用条件评估器进行匹配 |
| 54 | + // TODO @puhui999: 状态匹配重新实现 | ||
| 55 | + boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue()); | ||
| 64 | if (matched) { | 56 | if (matched) { |
| 65 | - logTriggerMatchSuccess(message, trigger); | 57 | + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); |
| 66 | } else { | 58 | } else { |
| 67 | - logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); | 59 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "状态值条件不匹配"); |
| 68 | } | 60 | } |
| 69 | return matched; | 61 | return matched; |
| 70 | } | 62 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotSceneRuleTriggerMatcher.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 4 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 5 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * IoT 场景规则触发器匹配器接口 | ||
| 10 | + * <p> | ||
| 11 | + * 专门处理主触发条件的匹配逻辑,如设备消息类型、定时器等 | ||
| 12 | + * <p> | ||
| 13 | + * 触发器匹配器负责判断设备消息是否满足场景规则的主触发条件, | ||
| 14 | + * 是场景规则执行的第一道门槛 | ||
| 15 | + * | ||
| 16 | + * @author HUIHUI | ||
| 17 | + */ | ||
| 18 | +public interface IotSceneRuleTriggerMatcher extends IotSceneRuleMatcher { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 获取支持的触发器类型 | ||
| 22 | + * | ||
| 23 | + * @return 触发器类型枚举 | ||
| 24 | + */ | ||
| 25 | + IotSceneRuleTriggerTypeEnum getSupportedTriggerType(); | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 检查触发器是否匹配消息 | ||
| 29 | + * <p> | ||
| 30 | + * 判断设备消息是否满足指定的触发器条件 | ||
| 31 | + * | ||
| 32 | + * @param message 设备消息 | ||
| 33 | + * @param trigger 触发器配置 | ||
| 34 | + * @return 是否匹配 | ||
| 35 | + */ | ||
| 36 | + boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger); | ||
| 37 | + | ||
| 38 | +} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/TimerTriggerMatcher.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.util.StrUtil; | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; |
| 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; |
| 7 | +import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper; | ||
| 8 | +import org.quartz.CronExpression; | ||
| 7 | import org.springframework.stereotype.Component; | 9 | import org.springframework.stereotype.Component; |
| 8 | 10 | ||
| 9 | /** | 11 | /** |
| @@ -15,12 +17,7 @@ import org.springframework.stereotype.Component; | @@ -15,12 +17,7 @@ import org.springframework.stereotype.Component; | ||
| 15 | * @author HUIHUI | 17 | * @author HUIHUI |
| 16 | */ | 18 | */ |
| 17 | @Component | 19 | @Component |
| 18 | -public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { | ||
| 19 | - | ||
| 20 | - @Override | ||
| 21 | - public MatcherType getMatcherType() { | ||
| 22 | - return MatcherType.TRIGGER; | ||
| 23 | - } | 20 | +public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher { |
| 24 | 21 | ||
| 25 | @Override | 22 | @Override |
| 26 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { | 23 | public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() { |
| @@ -29,58 +26,32 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { | @@ -29,58 +26,32 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher { | ||
| 29 | 26 | ||
| 30 | @Override | 27 | @Override |
| 31 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { | 28 | public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) { |
| 32 | - // 1. 基础参数校验 | ||
| 33 | - if (!isBasicTriggerValid(trigger)) { | ||
| 34 | - logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | 29 | + // 1.1 基础参数校验 |
| 30 | + if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) { | ||
| 31 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效"); | ||
| 35 | return false; | 32 | return false; |
| 36 | } | 33 | } |
| 37 | 34 | ||
| 38 | - // 2. 检查 CRON 表达式是否存在 | 35 | + // 1.2 检查 CRON 表达式是否存在 |
| 39 | if (StrUtil.isBlank(trigger.getCronExpression())) { | 36 | if (StrUtil.isBlank(trigger.getCronExpression())) { |
| 40 | - logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); | 37 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式"); |
| 41 | return false; | 38 | return false; |
| 42 | } | 39 | } |
| 43 | 40 | ||
| 44 | - // 3. 定时触发器通常不依赖具体的设备消息 | 41 | + // 1.3 定时触发器通常不依赖具体的设备消息 |
| 45 | // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 | 42 | // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性 |
| 46 | - | ||
| 47 | - // 4. 可以添加 CRON 表达式格式验证 | ||
| 48 | - if (!isValidCronExpression(trigger.getCronExpression())) { | ||
| 49 | - logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); | 43 | + if (!CronExpression.isValidExpression(trigger.getCronExpression())) { |
| 44 | + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression()); | ||
| 50 | return false; | 45 | return false; |
| 51 | } | 46 | } |
| 52 | 47 | ||
| 53 | - logTriggerMatchSuccess(message, trigger); | 48 | + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger); |
| 54 | return true; | 49 | return true; |
| 55 | } | 50 | } |
| 56 | 51 | ||
| 57 | - /** | ||
| 58 | - * 验证 CRON 表达式格式是否有效 | ||
| 59 | - * | ||
| 60 | - * @param cronExpression CRON 表达式 | ||
| 61 | - * @return 是否有效 | ||
| 62 | - */ | ||
| 63 | - private boolean isValidCronExpression(String cronExpression) { | ||
| 64 | - // TODO @puhui999:CronExpression.isValidExpression(cronExpression); | ||
| 65 | - try { | ||
| 66 | - // 简单的 CRON 表达式格式验证 | ||
| 67 | - // 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年]) | ||
| 68 | - String[] fields = cronExpression.trim().split("\\s+"); | ||
| 69 | - return fields.length >= 6 && fields.length <= 7; | ||
| 70 | - } catch (Exception e) { | ||
| 71 | - return false; | ||
| 72 | - } | ||
| 73 | - } | ||
| 74 | - | ||
| 75 | @Override | 52 | @Override |
| 76 | public int getPriority() { | 53 | public int getPriority() { |
| 77 | return 50; // 最低优先级,因为定时触发器不依赖消息 | 54 | return 50; // 最低优先级,因为定时触发器不依赖消息 |
| 78 | } | 55 | } |
| 79 | 56 | ||
| 80 | - @Override | ||
| 81 | - public boolean isEnabled() { | ||
| 82 | - // 定时触发器可以根据配置动态启用/禁用 | ||
| 83 | - return true; | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | } | 57 | } |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.service.rule.scene.matcher; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 5 | -import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 6 | -import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 7 | -import org.junit.jupiter.api.BeforeEach; | ||
| 8 | -import org.junit.jupiter.api.Test; | ||
| 9 | - | ||
| 10 | -import java.util.Arrays; | ||
| 11 | -import java.util.HashMap; | ||
| 12 | -import java.util.List; | ||
| 13 | - | ||
| 14 | -import static org.junit.jupiter.api.Assertions.*; | ||
| 15 | - | ||
| 16 | -/** | ||
| 17 | - * IoT 场景规则触发器匹配器测试类 | ||
| 18 | - * | ||
| 19 | - * @author HUIHUI | ||
| 20 | - */ | ||
| 21 | -public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest { | ||
| 22 | - | ||
| 23 | - // TODO @puhui999:public 都加下哈; | ||
| 24 | - | ||
| 25 | - private IotSceneRuleMatcherManager matcherManager; | ||
| 26 | - | ||
| 27 | - @BeforeEach | ||
| 28 | - void setUp() { | ||
| 29 | - // 创建所有匹配器实例 | ||
| 30 | - List<IotSceneRuleMatcher> matchers = Arrays.asList( | ||
| 31 | - new DeviceStateUpdateTriggerMatcher(), | ||
| 32 | - new DevicePropertyPostTriggerMatcher(), | ||
| 33 | - new DeviceEventPostTriggerMatcher(), | ||
| 34 | - new DeviceServiceInvokeTriggerMatcher(), | ||
| 35 | - new TimerTriggerMatcher() | ||
| 36 | - ); | ||
| 37 | - | ||
| 38 | - // 初始化匹配器管理器 | ||
| 39 | - matcherManager = new IotSceneRuleMatcherManager(matchers); | ||
| 40 | - } | ||
| 41 | - | ||
| 42 | - @Test | ||
| 43 | - void testDeviceStateUpdateTriggerMatcher() { | ||
| 44 | - // 1. 准备测试数据 | ||
| 45 | - IotDeviceMessage message = IotDeviceMessage.builder() | ||
| 46 | - .requestId("test-001") | ||
| 47 | - .method("thing.state.update") // TODO @puhui999:这里的枚举; | ||
| 48 | - .data(1) // 在线状态 TODO @puhui999:这里的枚举; | ||
| 49 | - .build(); | ||
| 50 | - | ||
| 51 | - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 52 | - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); | ||
| 53 | - trigger.setOperator("="); // TODO @puhui999:这里的枚举;下面也是类似; | ||
| 54 | - trigger.setValue("1"); | ||
| 55 | - | ||
| 56 | - // 2. 执行测试 | ||
| 57 | - boolean matched = matcherManager.isMatched(message, trigger); | ||
| 58 | - | ||
| 59 | - // 3. 验证结果 | ||
| 60 | - assertTrue(matched, "设备状态更新触发器应该匹配"); | ||
| 61 | - } | ||
| 62 | - | ||
| 63 | - @Test | ||
| 64 | - void testDevicePropertyPostTriggerMatcher() { | ||
| 65 | - // 1. 准备测试数据 | ||
| 66 | - HashMap<String, Object> params = new HashMap<>(); | ||
| 67 | - IotDeviceMessage message = IotDeviceMessage.builder() | ||
| 68 | - .requestId("test-002") | ||
| 69 | - .method("thing.property.post") | ||
| 70 | - .data(25.5) // 温度值 | ||
| 71 | - .params(params) | ||
| 72 | - .build(); | ||
| 73 | - // 模拟标识符 | ||
| 74 | - params.put("identifier", "temperature"); | ||
| 75 | - | ||
| 76 | - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 77 | - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); | ||
| 78 | - trigger.setIdentifier("temperature"); | ||
| 79 | - trigger.setOperator(">"); | ||
| 80 | - trigger.setValue("20"); | ||
| 81 | - | ||
| 82 | - // 2. 执行测试 | ||
| 83 | - boolean matched = matcherManager.isMatched(message, trigger); | ||
| 84 | - | ||
| 85 | - // 3. 验证结果 | ||
| 86 | - assertTrue(matched, "设备属性上报触发器应该匹配"); | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | - @Test | ||
| 90 | - void testDeviceEventPostTriggerMatcher() { | ||
| 91 | - // 1. 准备测试数据 | ||
| 92 | - HashMap<String, Object> params = new HashMap<>(); | ||
| 93 | - IotDeviceMessage message = IotDeviceMessage.builder() | ||
| 94 | - .requestId("test-003") | ||
| 95 | - .method("thing.event.post") | ||
| 96 | - .data("alarm_data") | ||
| 97 | - .params(params) | ||
| 98 | - .build(); | ||
| 99 | - // 模拟标识符 | ||
| 100 | - params.put("identifier", "high_temperature_alarm"); | ||
| 101 | - | ||
| 102 | - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 103 | - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); | ||
| 104 | - trigger.setIdentifier("high_temperature_alarm"); | ||
| 105 | - | ||
| 106 | - // 2. 执行测试 | ||
| 107 | - boolean matched = matcherManager.isMatched(message, trigger); | ||
| 108 | - | ||
| 109 | - // 3. 验证结果 | ||
| 110 | - assertTrue(matched, "设备事件上报触发器应该匹配"); | ||
| 111 | - } | ||
| 112 | - | ||
| 113 | - @Test | ||
| 114 | - void testDeviceServiceInvokeTriggerMatcher() { | ||
| 115 | - // 1. 准备测试数据 | ||
| 116 | - HashMap<String, Object> params = new HashMap<>(); | ||
| 117 | - IotDeviceMessage message = IotDeviceMessage.builder() | ||
| 118 | - .requestId("test-004") | ||
| 119 | - .method("thing.service.invoke") | ||
| 120 | - .msg("alarm_data") | ||
| 121 | - .params(params) | ||
| 122 | - .build(); | ||
| 123 | - // 模拟标识符 | ||
| 124 | - params.put("identifier", "restart_device"); | ||
| 125 | - | ||
| 126 | - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 127 | - trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); | ||
| 128 | - trigger.setIdentifier("restart_device"); | ||
| 129 | - | ||
| 130 | - // 2. 执行测试 | ||
| 131 | - boolean matched = matcherManager.isMatched(message, trigger); | ||
| 132 | - | ||
| 133 | - // 3. 验证结果 | ||
| 134 | - assertTrue(matched, "设备服务调用触发器应该匹配"); | ||
| 135 | - } | ||
| 136 | - | ||
| 137 | - @Test | ||
| 138 | - void testTimerTriggerMatcher() { | ||
| 139 | - // 1. 准备测试数据 | ||
| 140 | - IotDeviceMessage message = IotDeviceMessage.builder() | ||
| 141 | - .requestId("test-005") | ||
| 142 | - .method("timer.trigger") // 定时触发器不依赖具体消息方法 | ||
| 143 | - .build(); | ||
| 144 | - | ||
| 145 | - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 146 | - trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); | ||
| 147 | - trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点 | ||
| 148 | - | ||
| 149 | - // 2. 执行测试 | ||
| 150 | - boolean matched = matcherManager.isMatched(message, trigger); | ||
| 151 | - | ||
| 152 | - // 3. 验证结果 | ||
| 153 | - assertTrue(matched, "定时触发器应该匹配"); | ||
| 154 | - } | ||
| 155 | - | ||
| 156 | - @Test | ||
| 157 | - void testInvalidTriggerType() { | ||
| 158 | - // 1. 准备测试数据 | ||
| 159 | - IotDeviceMessage message = IotDeviceMessage.builder() | ||
| 160 | - .requestId("test-006") | ||
| 161 | - .method("unknown.method") | ||
| 162 | - .build(); | ||
| 163 | - | ||
| 164 | - IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 165 | - trigger.setType(999); // 无效的触发器类型 | ||
| 166 | - | ||
| 167 | - // 2. 执行测试 | ||
| 168 | - boolean matched = matcherManager.isMatched(message, trigger); | ||
| 169 | - | ||
| 170 | - // 3. 验证结果 | ||
| 171 | - assertFalse(matched, "无效的触发器类型应该不匹配"); | ||
| 172 | - } | ||
| 173 | - | ||
| 174 | - @Test | ||
| 175 | - void testMatcherManagerStatistics() { | ||
| 176 | - // 1. 执行测试 | ||
| 177 | - var statistics = matcherManager.getMatcherStatistics(); | ||
| 178 | - | ||
| 179 | - // 2. 验证结果 | ||
| 180 | - assertNotNull(statistics); | ||
| 181 | - assertEquals(5, statistics.get("totalMatchers")); | ||
| 182 | - assertEquals(5, statistics.get("enabledMatchers")); | ||
| 183 | - assertNotNull(statistics.get("supportedTriggerTypes")); | ||
| 184 | - assertNotNull(statistics.get("matcherDetails")); | ||
| 185 | - } | ||
| 186 | - | ||
| 187 | - @Test | ||
| 188 | - void testGetSupportedTriggerTypes() { | ||
| 189 | - // 1. 执行测试 | ||
| 190 | - var supportedTypes = matcherManager.getSupportedTriggerTypes(); | ||
| 191 | - | ||
| 192 | - // 2. 验证结果 | ||
| 193 | - assertNotNull(supportedTypes); | ||
| 194 | - assertEquals(5, supportedTypes.size()); | ||
| 195 | - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE)); | ||
| 196 | - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST)); | ||
| 197 | - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST)); | ||
| 198 | - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE)); | ||
| 199 | - assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER)); | ||
| 200 | - } | ||
| 201 | - | ||
| 202 | -} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | ||
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | ||
| 8 | +import org.junit.jupiter.api.BeforeEach; | ||
| 9 | +import org.junit.jupiter.api.Test; | ||
| 10 | + | ||
| 11 | +import java.time.LocalDateTime; | ||
| 12 | +import java.time.ZoneOffset; | ||
| 13 | + | ||
| 14 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * {@link CurrentTimeConditionMatcher} 的单元测试类 | ||
| 18 | + * | ||
| 19 | + * @author HUIHUI | ||
| 20 | + */ | ||
| 21 | +public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 22 | + | ||
| 23 | + private CurrentTimeConditionMatcher matcher; | ||
| 24 | + | ||
| 25 | + @BeforeEach | ||
| 26 | + public void setUp() { | ||
| 27 | + matcher = new CurrentTimeConditionMatcher(); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + @Test | ||
| 31 | + public void testGetSupportedConditionType() { | ||
| 32 | + // when & then | ||
| 33 | + assertEquals(IotSceneRuleConditionTypeEnum.CURRENT_TIME, matcher.getSupportedConditionType()); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + @Test | ||
| 37 | + public void testGetPriority() { | ||
| 38 | + // when & then | ||
| 39 | + assertEquals(40, matcher.getPriority()); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + @Test | ||
| 43 | + public void testIsEnabled() { | ||
| 44 | + // when & then | ||
| 45 | + assertTrue(matcher.isEnabled()); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + // ========== 时间戳条件测试 ========== | ||
| 49 | + | ||
| 50 | + @Test | ||
| 51 | + public void testIsMatched_DateTimeGreaterThan_Success() { | ||
| 52 | + // given | ||
| 53 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 54 | + long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); | ||
| 55 | + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( | ||
| 56 | + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), | ||
| 57 | + String.valueOf(pastTimestamp) | ||
| 58 | + ); | ||
| 59 | + | ||
| 60 | + // when | ||
| 61 | + boolean result = matcher.isMatched(message, condition); | ||
| 62 | + | ||
| 63 | + // then | ||
| 64 | + assertTrue(result); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + @Test | ||
| 68 | + public void testIsMatched_DateTimeGreaterThan_Failure() { | ||
| 69 | + // given | ||
| 70 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 71 | + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); | ||
| 72 | + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( | ||
| 73 | + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), | ||
| 74 | + String.valueOf(futureTimestamp) | ||
| 75 | + ); | ||
| 76 | + | ||
| 77 | + // when | ||
| 78 | + boolean result = matcher.isMatched(message, condition); | ||
| 79 | + | ||
| 80 | + // then | ||
| 81 | + assertFalse(result); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + @Test | ||
| 85 | + public void testIsMatched_DateTimeLessThan_Success() { | ||
| 86 | + // given | ||
| 87 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 88 | + long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); | ||
| 89 | + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( | ||
| 90 | + IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN.getOperator(), | ||
| 91 | + String.valueOf(futureTimestamp) | ||
| 92 | + ); | ||
| 93 | + | ||
| 94 | + // when | ||
| 95 | + boolean result = matcher.isMatched(message, condition); | ||
| 96 | + | ||
| 97 | + // then | ||
| 98 | + assertTrue(result); | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + @Test | ||
| 102 | + public void testIsMatched_DateTimeBetween_Success() { | ||
| 103 | + // given | ||
| 104 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 105 | + long startTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8")); | ||
| 106 | + long endTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); | ||
| 107 | + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( | ||
| 108 | + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), | ||
| 109 | + startTimestamp + "," + endTimestamp | ||
| 110 | + ); | ||
| 111 | + | ||
| 112 | + // when | ||
| 113 | + boolean result = matcher.isMatched(message, condition); | ||
| 114 | + | ||
| 115 | + // then | ||
| 116 | + assertTrue(result); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + @Test | ||
| 120 | + public void testIsMatched_DateTimeBetween_Failure() { | ||
| 121 | + // given | ||
| 122 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 123 | + long startTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8")); | ||
| 124 | + long endTimestamp = LocalDateTime.now().plusHours(2).toEpochSecond(ZoneOffset.of("+8")); | ||
| 125 | + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( | ||
| 126 | + IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(), | ||
| 127 | + startTimestamp + "," + endTimestamp | ||
| 128 | + ); | ||
| 129 | + | ||
| 130 | + // when | ||
| 131 | + boolean result = matcher.isMatched(message, condition); | ||
| 132 | + | ||
| 133 | + // then | ||
| 134 | + assertFalse(result); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + // ========== 当日时间条件测试 ========== | ||
| 138 | + | ||
| 139 | + @Test | ||
| 140 | + public void testIsMatched_TimeGreaterThan_EarlyMorning() { | ||
| 141 | + // given | ||
| 142 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 143 | + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( | ||
| 144 | + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), | ||
| 145 | + "06:00:00" // 早上6点 | ||
| 146 | + ); | ||
| 147 | + | ||
| 148 | + // when | ||
| 149 | + boolean result = matcher.isMatched(message, condition); | ||
| 150 | + | ||
| 151 | + // then | ||
| 152 | + // 结果取决于当前时间,如果当前时间大于6点则为true | ||
| 153 | + assertNotNull(result); | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + @Test | ||
| 157 | + public void testIsMatched_TimeLessThan_LateNight() { | ||
| 158 | + // given | ||
| 159 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 160 | + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( | ||
| 161 | + IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN.getOperator(), | ||
| 162 | + "23:59:59" // 晚上11点59分59秒 | ||
| 163 | + ); | ||
| 164 | + | ||
| 165 | + // when | ||
| 166 | + boolean result = matcher.isMatched(message, condition); | ||
| 167 | + | ||
| 168 | + // then | ||
| 169 | + // 大部分情况下应该为true,除非在午夜前1秒运行测试 | ||
| 170 | + assertNotNull(result); | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + @Test | ||
| 174 | + public void testIsMatched_TimeBetween_AllDay() { | ||
| 175 | + // given | ||
| 176 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 177 | + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( | ||
| 178 | + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), | ||
| 179 | + "00:00:00,23:59:59" // 全天 | ||
| 180 | + ); | ||
| 181 | + | ||
| 182 | + // when | ||
| 183 | + boolean result = matcher.isMatched(message, condition); | ||
| 184 | + | ||
| 185 | + // then | ||
| 186 | + assertTrue(result); // 全天范围应该总是匹配 | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + @Test | ||
| 190 | + public void testIsMatched_TimeBetween_WorkingHours() { | ||
| 191 | + // given | ||
| 192 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 193 | + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( | ||
| 194 | + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), | ||
| 195 | + "09:00:00,17:00:00" // 工作时间 | ||
| 196 | + ); | ||
| 197 | + | ||
| 198 | + // when | ||
| 199 | + boolean result = matcher.isMatched(message, condition); | ||
| 200 | + | ||
| 201 | + // then | ||
| 202 | + // 结果取决于当前时间是否在工作时间内 | ||
| 203 | + assertNotNull(result); | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + // ========== 异常情况测试 ========== | ||
| 207 | + | ||
| 208 | + @Test | ||
| 209 | + public void testIsMatched_NullCondition() { | ||
| 210 | + // given | ||
| 211 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 212 | + | ||
| 213 | + // when | ||
| 214 | + boolean result = matcher.isMatched(message, null); | ||
| 215 | + | ||
| 216 | + // then | ||
| 217 | + assertFalse(result); | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + @Test | ||
| 221 | + public void testIsMatched_NullConditionType() { | ||
| 222 | + // given | ||
| 223 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 224 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 225 | + condition.setType(null); | ||
| 226 | + | ||
| 227 | + // when | ||
| 228 | + boolean result = matcher.isMatched(message, condition); | ||
| 229 | + | ||
| 230 | + // then | ||
| 231 | + assertFalse(result); | ||
| 232 | + } | ||
| 233 | + | ||
| 234 | + @Test | ||
| 235 | + public void testIsMatched_InvalidOperator() { | ||
| 236 | + // given | ||
| 237 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 238 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 239 | + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); | ||
| 240 | + condition.setOperator("invalid_operator"); | ||
| 241 | + condition.setParam("12:00:00"); | ||
| 242 | + | ||
| 243 | + // when | ||
| 244 | + boolean result = matcher.isMatched(message, condition); | ||
| 245 | + | ||
| 246 | + // then | ||
| 247 | + assertFalse(result); | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + @Test | ||
| 251 | + public void testIsMatched_InvalidTimeFormat() { | ||
| 252 | + // given | ||
| 253 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 254 | + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( | ||
| 255 | + IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(), | ||
| 256 | + "invalid-time-format" | ||
| 257 | + ); | ||
| 258 | + | ||
| 259 | + // when | ||
| 260 | + boolean result = matcher.isMatched(message, condition); | ||
| 261 | + | ||
| 262 | + // then | ||
| 263 | + assertFalse(result); | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + @Test | ||
| 267 | + public void testIsMatched_InvalidTimestampFormat() { | ||
| 268 | + // given | ||
| 269 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 270 | + IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition( | ||
| 271 | + IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(), | ||
| 272 | + "invalid-timestamp" | ||
| 273 | + ); | ||
| 274 | + | ||
| 275 | + // when | ||
| 276 | + boolean result = matcher.isMatched(message, condition); | ||
| 277 | + | ||
| 278 | + // then | ||
| 279 | + assertFalse(result); | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + @Test | ||
| 283 | + public void testIsMatched_InvalidBetweenFormat() { | ||
| 284 | + // given | ||
| 285 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 286 | + IotSceneRuleDO.TriggerCondition condition = createTimeCondition( | ||
| 287 | + IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(), | ||
| 288 | + "09:00:00" // 缺少结束时间 | ||
| 289 | + ); | ||
| 290 | + | ||
| 291 | + // when | ||
| 292 | + boolean result = matcher.isMatched(message, condition); | ||
| 293 | + | ||
| 294 | + // then | ||
| 295 | + assertFalse(result); | ||
| 296 | + } | ||
| 297 | + | ||
| 298 | + // ========== 辅助方法 ========== | ||
| 299 | + | ||
| 300 | + /** | ||
| 301 | + * 创建日期时间条件 | ||
| 302 | + */ | ||
| 303 | + private IotSceneRuleDO.TriggerCondition createDateTimeCondition(String operator, String param) { | ||
| 304 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 305 | + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); | ||
| 306 | + condition.setOperator(operator); | ||
| 307 | + condition.setParam(param); | ||
| 308 | + return condition; | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + /** | ||
| 312 | + * 创建当日时间条件 | ||
| 313 | + */ | ||
| 314 | + private IotSceneRuleDO.TriggerCondition createTimeCondition(String operator, String param) { | ||
| 315 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 316 | + condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType()); | ||
| 317 | + condition.setOperator(operator); | ||
| 318 | + condition.setParam(param); | ||
| 319 | + return condition; | ||
| 320 | + } | ||
| 321 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.map.MapUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 6 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | ||
| 9 | +import org.junit.jupiter.api.BeforeEach; | ||
| 10 | +import org.junit.jupiter.api.Test; | ||
| 11 | + | ||
| 12 | +import java.util.HashMap; | ||
| 13 | +import java.util.Map; | ||
| 14 | + | ||
| 15 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * {@link DevicePropertyConditionMatcher} 的单元测试类 | ||
| 19 | + * | ||
| 20 | + * @author HUIHUI | ||
| 21 | + */ | ||
| 22 | +public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 23 | + | ||
| 24 | + private DevicePropertyConditionMatcher matcher; | ||
| 25 | + | ||
| 26 | + @BeforeEach | ||
| 27 | + public void setUp() { | ||
| 28 | + matcher = new DevicePropertyConditionMatcher(); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + @Test | ||
| 32 | + public void testGetSupportedConditionType() { | ||
| 33 | + // when & then | ||
| 34 | + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY, matcher.getSupportedConditionType()); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + @Test | ||
| 38 | + public void testGetPriority() { | ||
| 39 | + // when & then | ||
| 40 | + assertEquals(20, matcher.getPriority()); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + @Test | ||
| 44 | + public void testIsEnabled() { | ||
| 45 | + // when & then | ||
| 46 | + assertTrue(matcher.isEnabled()); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + @Test | ||
| 50 | + public void testIsMatched_Success_TemperatureEquals() { | ||
| 51 | + // given | ||
| 52 | + Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 53 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 54 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 55 | + "temperature", | ||
| 56 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 57 | + "25.5" | ||
| 58 | + ); | ||
| 59 | + | ||
| 60 | + // when | ||
| 61 | + boolean result = matcher.isMatched(message, condition); | ||
| 62 | + | ||
| 63 | + // then | ||
| 64 | + assertTrue(result); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + @Test | ||
| 68 | + public void testIsMatched_Success_HumidityGreaterThan() { | ||
| 69 | + // given | ||
| 70 | + Map<String, Object> properties = MapUtil.of("humidity", 75); | ||
| 71 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 72 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 73 | + "humidity", | ||
| 74 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 75 | + "70" | ||
| 76 | + ); | ||
| 77 | + | ||
| 78 | + // when | ||
| 79 | + boolean result = matcher.isMatched(message, condition); | ||
| 80 | + | ||
| 81 | + // then | ||
| 82 | + assertTrue(result); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @Test | ||
| 86 | + public void testIsMatched_Success_PressureLessThan() { | ||
| 87 | + // given | ||
| 88 | + Map<String, Object> properties = MapUtil.of("pressure", 1010.5); | ||
| 89 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 90 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 91 | + "pressure", | ||
| 92 | + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), | ||
| 93 | + "1020" | ||
| 94 | + ); | ||
| 95 | + | ||
| 96 | + // when | ||
| 97 | + boolean result = matcher.isMatched(message, condition); | ||
| 98 | + | ||
| 99 | + // then | ||
| 100 | + assertTrue(result); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + @Test | ||
| 104 | + public void testIsMatched_Success_StatusNotEquals() { | ||
| 105 | + // given | ||
| 106 | + Map<String, Object> properties = MapUtil.of("status", "active"); | ||
| 107 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 108 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 109 | + "status", | ||
| 110 | + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), | ||
| 111 | + "inactive" | ||
| 112 | + ); | ||
| 113 | + | ||
| 114 | + // when | ||
| 115 | + boolean result = matcher.isMatched(message, condition); | ||
| 116 | + | ||
| 117 | + // then | ||
| 118 | + assertTrue(result); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + @Test | ||
| 122 | + public void testIsMatched_Failure_PropertyMismatch() { | ||
| 123 | + // given | ||
| 124 | + Map<String, Object> properties = MapUtil.of("temperature", 15.0); | ||
| 125 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 126 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 127 | + "temperature", | ||
| 128 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 129 | + "20" | ||
| 130 | + ); | ||
| 131 | + | ||
| 132 | + // when | ||
| 133 | + boolean result = matcher.isMatched(message, condition); | ||
| 134 | + | ||
| 135 | + // then | ||
| 136 | + assertFalse(result); | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + @Test | ||
| 140 | + public void testIsMatched_Failure_PropertyNotFound() { | ||
| 141 | + // given | ||
| 142 | + Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 143 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 144 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 145 | + "humidity", // 不存在的属性 | ||
| 146 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 147 | + "50" | ||
| 148 | + ); | ||
| 149 | + | ||
| 150 | + // when | ||
| 151 | + boolean result = matcher.isMatched(message, condition); | ||
| 152 | + | ||
| 153 | + // then | ||
| 154 | + assertFalse(result); | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + @Test | ||
| 158 | + public void testIsMatched_Failure_NullCondition() { | ||
| 159 | + // given | ||
| 160 | + Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 161 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 162 | + | ||
| 163 | + // when | ||
| 164 | + boolean result = matcher.isMatched(message, null); | ||
| 165 | + | ||
| 166 | + // then | ||
| 167 | + assertFalse(result); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + @Test | ||
| 171 | + public void testIsMatched_Failure_NullConditionType() { | ||
| 172 | + // given | ||
| 173 | + Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 174 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 175 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 176 | + condition.setType(null); | ||
| 177 | + | ||
| 178 | + // when | ||
| 179 | + boolean result = matcher.isMatched(message, condition); | ||
| 180 | + | ||
| 181 | + // then | ||
| 182 | + assertFalse(result); | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + @Test | ||
| 186 | + public void testIsMatched_Failure_MissingIdentifier() { | ||
| 187 | + // given | ||
| 188 | + Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 189 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 190 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 191 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); | ||
| 192 | + condition.setIdentifier(null); // 缺少标识符 | ||
| 193 | + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); | ||
| 194 | + condition.setParam("20"); | ||
| 195 | + | ||
| 196 | + // when | ||
| 197 | + boolean result = matcher.isMatched(message, condition); | ||
| 198 | + | ||
| 199 | + // then | ||
| 200 | + assertFalse(result); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + @Test | ||
| 204 | + public void testIsMatched_Failure_MissingOperator() { | ||
| 205 | + // given | ||
| 206 | + Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 207 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 208 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 209 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); | ||
| 210 | + condition.setIdentifier("temperature"); | ||
| 211 | + condition.setOperator(null); // 缺少操作符 | ||
| 212 | + condition.setParam("20"); | ||
| 213 | + | ||
| 214 | + // when | ||
| 215 | + boolean result = matcher.isMatched(message, condition); | ||
| 216 | + | ||
| 217 | + // then | ||
| 218 | + assertFalse(result); | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + @Test | ||
| 222 | + public void testIsMatched_Failure_MissingParam() { | ||
| 223 | + // given | ||
| 224 | + Map<String, Object> properties = MapUtil.of("temperature", 25.5); | ||
| 225 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 226 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 227 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); | ||
| 228 | + condition.setIdentifier("temperature"); | ||
| 229 | + condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); | ||
| 230 | + condition.setParam(null); // 缺少参数 | ||
| 231 | + | ||
| 232 | + // when | ||
| 233 | + boolean result = matcher.isMatched(message, condition); | ||
| 234 | + | ||
| 235 | + // then | ||
| 236 | + assertFalse(result); | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + @Test | ||
| 240 | + public void testIsMatched_Failure_NullMessage() { | ||
| 241 | + // given | ||
| 242 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 243 | + "temperature", | ||
| 244 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 245 | + "20" | ||
| 246 | + ); | ||
| 247 | + | ||
| 248 | + // when | ||
| 249 | + boolean result = matcher.isMatched(null, condition); | ||
| 250 | + | ||
| 251 | + // then | ||
| 252 | + assertFalse(result); | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + @Test | ||
| 256 | + public void testIsMatched_Failure_NullDeviceProperties() { | ||
| 257 | + // given | ||
| 258 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 259 | + message.setParams(null); | ||
| 260 | + | ||
| 261 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 262 | + "temperature", | ||
| 263 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 264 | + "20" | ||
| 265 | + ); | ||
| 266 | + | ||
| 267 | + // when | ||
| 268 | + boolean result = matcher.isMatched(message, condition); | ||
| 269 | + | ||
| 270 | + // then | ||
| 271 | + assertFalse(result); | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + @Test | ||
| 275 | + public void testIsMatched_Success_GreaterThanOrEquals() { | ||
| 276 | + // given | ||
| 277 | + Map<String, Object> properties = MapUtil.of("voltage", 12.0); | ||
| 278 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 279 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 280 | + "voltage", | ||
| 281 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), | ||
| 282 | + "12.0" | ||
| 283 | + ); | ||
| 284 | + | ||
| 285 | + // when | ||
| 286 | + boolean result = matcher.isMatched(message, condition); | ||
| 287 | + | ||
| 288 | + // then | ||
| 289 | + assertTrue(result); | ||
| 290 | + } | ||
| 291 | + | ||
| 292 | + @Test | ||
| 293 | + public void testIsMatched_Success_LessThanOrEquals() { | ||
| 294 | + // given | ||
| 295 | + Map<String, Object> properties = MapUtil.of("current", 2.5); | ||
| 296 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 297 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 298 | + "current", | ||
| 299 | + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), | ||
| 300 | + "3.0" | ||
| 301 | + ); | ||
| 302 | + | ||
| 303 | + // when | ||
| 304 | + boolean result = matcher.isMatched(message, condition); | ||
| 305 | + | ||
| 306 | + // then | ||
| 307 | + assertTrue(result); | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + @Test | ||
| 311 | + public void testIsMatched_Success_StringProperty() { | ||
| 312 | + // given | ||
| 313 | + Map<String, Object> properties = MapUtil.of("mode", "auto"); | ||
| 314 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 315 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 316 | + "mode", | ||
| 317 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 318 | + "auto" | ||
| 319 | + ); | ||
| 320 | + | ||
| 321 | + // when | ||
| 322 | + boolean result = matcher.isMatched(message, condition); | ||
| 323 | + | ||
| 324 | + // then | ||
| 325 | + assertTrue(result); | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + @Test | ||
| 329 | + public void testIsMatched_Success_BooleanProperty() { | ||
| 330 | + // given | ||
| 331 | + Map<String, Object> properties = MapUtil.of("enabled", true); | ||
| 332 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 333 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 334 | + "enabled", | ||
| 335 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 336 | + "true" | ||
| 337 | + ); | ||
| 338 | + | ||
| 339 | + // when | ||
| 340 | + boolean result = matcher.isMatched(message, condition); | ||
| 341 | + | ||
| 342 | + // then | ||
| 343 | + assertTrue(result); | ||
| 344 | + } | ||
| 345 | + | ||
| 346 | + @Test | ||
| 347 | + public void testIsMatched_Success_MultipleProperties() { | ||
| 348 | + // given | ||
| 349 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 350 | + .put("temperature", 25.5) | ||
| 351 | + .put("humidity", 60) | ||
| 352 | + .put("status", "active") | ||
| 353 | + .put("enabled", true) | ||
| 354 | + .build(); | ||
| 355 | + IotDeviceMessage message = createDeviceMessage(properties); | ||
| 356 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 357 | + "humidity", | ||
| 358 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 359 | + "60" | ||
| 360 | + ); | ||
| 361 | + | ||
| 362 | + // when | ||
| 363 | + boolean result = matcher.isMatched(message, condition); | ||
| 364 | + | ||
| 365 | + // then | ||
| 366 | + assertTrue(result); | ||
| 367 | + } | ||
| 368 | + | ||
| 369 | + // ========== 辅助方法 ========== | ||
| 370 | + | ||
| 371 | + /** | ||
| 372 | + * 创建设备消息 | ||
| 373 | + */ | ||
| 374 | + private IotDeviceMessage createDeviceMessage(Map<String, Object> properties) { | ||
| 375 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 376 | + message.setParams(properties); | ||
| 377 | + return message; | ||
| 378 | + } | ||
| 379 | + | ||
| 380 | + /** | ||
| 381 | + * 创建有效的条件 | ||
| 382 | + */ | ||
| 383 | + private IotSceneRuleDO.TriggerCondition createValidCondition(String identifier, String operator, String param) { | ||
| 384 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 385 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType()); | ||
| 386 | + condition.setIdentifier(identifier); | ||
| 387 | + condition.setOperator(operator); | ||
| 388 | + condition.setParam(param); | ||
| 389 | + return condition; | ||
| 390 | + } | ||
| 391 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 6 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 7 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum; | ||
| 9 | +import org.junit.jupiter.api.BeforeEach; | ||
| 10 | +import org.junit.jupiter.api.Test; | ||
| 11 | + | ||
| 12 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * {@link DeviceStateConditionMatcher} 的单元测试类 | ||
| 16 | + * | ||
| 17 | + * @author HUIHUI | ||
| 18 | + */ | ||
| 19 | +public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest { | ||
| 20 | + | ||
| 21 | + private DeviceStateConditionMatcher matcher; | ||
| 22 | + | ||
| 23 | + @BeforeEach | ||
| 24 | + public void setUp() { | ||
| 25 | + matcher = new DeviceStateConditionMatcher(); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + @Test | ||
| 29 | + public void testGetSupportedConditionType() { | ||
| 30 | + // when & then | ||
| 31 | + assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_STATE, matcher.getSupportedConditionType()); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + @Test | ||
| 35 | + public void testGetPriority() { | ||
| 36 | + // when & then | ||
| 37 | + assertEquals(30, matcher.getPriority()); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Test | ||
| 41 | + public void testIsEnabled() { | ||
| 42 | + // when & then | ||
| 43 | + assertTrue(matcher.isEnabled()); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + @Test | ||
| 47 | + public void testIsMatched_Success_OnlineState() { | ||
| 48 | + // given | ||
| 49 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 50 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 51 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 52 | + IotDeviceStateEnum.ONLINE.getState().toString() | ||
| 53 | + ); | ||
| 54 | + | ||
| 55 | + // when | ||
| 56 | + boolean result = matcher.isMatched(message, condition); | ||
| 57 | + | ||
| 58 | + // then | ||
| 59 | + assertTrue(result); | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + @Test | ||
| 63 | + public void testIsMatched_Success_OfflineState() { | ||
| 64 | + // given | ||
| 65 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); | ||
| 66 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 67 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 68 | + IotDeviceStateEnum.OFFLINE.getState().toString() | ||
| 69 | + ); | ||
| 70 | + | ||
| 71 | + // when | ||
| 72 | + boolean result = matcher.isMatched(message, condition); | ||
| 73 | + | ||
| 74 | + // then | ||
| 75 | + assertTrue(result); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + @Test | ||
| 79 | + public void testIsMatched_Success_InactiveState() { | ||
| 80 | + // given | ||
| 81 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); | ||
| 82 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 83 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 84 | + IotDeviceStateEnum.INACTIVE.getState().toString() | ||
| 85 | + ); | ||
| 86 | + | ||
| 87 | + // when | ||
| 88 | + boolean result = matcher.isMatched(message, condition); | ||
| 89 | + | ||
| 90 | + // then | ||
| 91 | + assertTrue(result); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + @Test | ||
| 95 | + public void testIsMatched_Failure_StateMismatch() { | ||
| 96 | + // given | ||
| 97 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 98 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 99 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 100 | + IotDeviceStateEnum.OFFLINE.getState().toString() | ||
| 101 | + ); | ||
| 102 | + | ||
| 103 | + // when | ||
| 104 | + boolean result = matcher.isMatched(message, condition); | ||
| 105 | + | ||
| 106 | + // then | ||
| 107 | + assertFalse(result); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + @Test | ||
| 111 | + public void testIsMatched_Success_NotEqualsOperator() { | ||
| 112 | + // given | ||
| 113 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 114 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 115 | + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), | ||
| 116 | + IotDeviceStateEnum.OFFLINE.getState().toString() | ||
| 117 | + ); | ||
| 118 | + | ||
| 119 | + // when | ||
| 120 | + boolean result = matcher.isMatched(message, condition); | ||
| 121 | + | ||
| 122 | + // then | ||
| 123 | + assertTrue(result); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + @Test | ||
| 127 | + public void testIsMatched_Success_GreaterThanOperator() { | ||
| 128 | + // given | ||
| 129 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); // 2 | ||
| 130 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 131 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 132 | + IotDeviceStateEnum.ONLINE.getState().toString() // 1 | ||
| 133 | + ); | ||
| 134 | + | ||
| 135 | + // when | ||
| 136 | + boolean result = matcher.isMatched(message, condition); | ||
| 137 | + | ||
| 138 | + // then | ||
| 139 | + assertTrue(result); | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + @Test | ||
| 143 | + public void testIsMatched_Success_LessThanOperator() { | ||
| 144 | + // given | ||
| 145 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); // 0 | ||
| 146 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 147 | + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), | ||
| 148 | + IotDeviceStateEnum.ONLINE.getState().toString() // 1 | ||
| 149 | + ); | ||
| 150 | + | ||
| 151 | + // when | ||
| 152 | + boolean result = matcher.isMatched(message, condition); | ||
| 153 | + | ||
| 154 | + // then | ||
| 155 | + assertTrue(result); | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + @Test | ||
| 159 | + public void testIsMatched_Failure_NullCondition() { | ||
| 160 | + // given | ||
| 161 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 162 | + | ||
| 163 | + // when | ||
| 164 | + boolean result = matcher.isMatched(message, null); | ||
| 165 | + | ||
| 166 | + // then | ||
| 167 | + assertFalse(result); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + @Test | ||
| 171 | + public void testIsMatched_Failure_NullConditionType() { | ||
| 172 | + // given | ||
| 173 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 174 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 175 | + condition.setType(null); | ||
| 176 | + | ||
| 177 | + // when | ||
| 178 | + boolean result = matcher.isMatched(message, condition); | ||
| 179 | + | ||
| 180 | + // then | ||
| 181 | + assertFalse(result); | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | + @Test | ||
| 185 | + public void testIsMatched_Failure_MissingOperator() { | ||
| 186 | + // given | ||
| 187 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 188 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 189 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); | ||
| 190 | + condition.setOperator(null); | ||
| 191 | + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); | ||
| 192 | + | ||
| 193 | + // when | ||
| 194 | + boolean result = matcher.isMatched(message, condition); | ||
| 195 | + | ||
| 196 | + // then | ||
| 197 | + assertFalse(result); | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + @Test | ||
| 201 | + public void testIsMatched_Failure_MissingParam() { | ||
| 202 | + // given | ||
| 203 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 204 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 205 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); | ||
| 206 | + condition.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); | ||
| 207 | + condition.setParam(null); | ||
| 208 | + | ||
| 209 | + // when | ||
| 210 | + boolean result = matcher.isMatched(message, condition); | ||
| 211 | + | ||
| 212 | + // then | ||
| 213 | + assertFalse(result); | ||
| 214 | + } | ||
| 215 | + | ||
| 216 | + @Test | ||
| 217 | + public void testIsMatched_Failure_NullMessage() { | ||
| 218 | + // given | ||
| 219 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 220 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 221 | + IotDeviceStateEnum.ONLINE.getState().toString() | ||
| 222 | + ); | ||
| 223 | + | ||
| 224 | + // when | ||
| 225 | + boolean result = matcher.isMatched(null, condition); | ||
| 226 | + | ||
| 227 | + // then | ||
| 228 | + assertFalse(result); | ||
| 229 | + } | ||
| 230 | + | ||
| 231 | + @Test | ||
| 232 | + public void testIsMatched_Failure_NullDeviceState() { | ||
| 233 | + // given | ||
| 234 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 235 | + message.setParams(null); | ||
| 236 | + | ||
| 237 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 238 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 239 | + IotDeviceStateEnum.ONLINE.getState().toString() | ||
| 240 | + ); | ||
| 241 | + | ||
| 242 | + // when | ||
| 243 | + boolean result = matcher.isMatched(message, condition); | ||
| 244 | + | ||
| 245 | + // then | ||
| 246 | + assertFalse(result); | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + @Test | ||
| 250 | + public void testIsMatched_Success_GreaterThanOrEqualsOperator() { | ||
| 251 | + // given | ||
| 252 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1 | ||
| 253 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 254 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(), | ||
| 255 | + IotDeviceStateEnum.ONLINE.getState().toString() // 1 | ||
| 256 | + ); | ||
| 257 | + | ||
| 258 | + // when | ||
| 259 | + boolean result = matcher.isMatched(message, condition); | ||
| 260 | + | ||
| 261 | + // then | ||
| 262 | + assertTrue(result); | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + @Test | ||
| 266 | + public void testIsMatched_Success_LessThanOrEqualsOperator() { | ||
| 267 | + // given | ||
| 268 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1 | ||
| 269 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 270 | + IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(), | ||
| 271 | + IotDeviceStateEnum.OFFLINE.getState().toString() // 2 | ||
| 272 | + ); | ||
| 273 | + | ||
| 274 | + // when | ||
| 275 | + boolean result = matcher.isMatched(message, condition); | ||
| 276 | + | ||
| 277 | + // then | ||
| 278 | + assertTrue(result); | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + @Test | ||
| 282 | + public void testIsMatched_Failure_InvalidOperator() { | ||
| 283 | + // given | ||
| 284 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 285 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 286 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); | ||
| 287 | + condition.setOperator("invalid_operator"); | ||
| 288 | + condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString()); | ||
| 289 | + | ||
| 290 | + // when | ||
| 291 | + boolean result = matcher.isMatched(message, condition); | ||
| 292 | + | ||
| 293 | + // then | ||
| 294 | + assertFalse(result); | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + @Test | ||
| 298 | + public void testIsMatched_Failure_InvalidParamFormat() { | ||
| 299 | + // given | ||
| 300 | + IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 301 | + IotSceneRuleDO.TriggerCondition condition = createValidCondition( | ||
| 302 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 303 | + "invalid_state_value" | ||
| 304 | + ); | ||
| 305 | + | ||
| 306 | + // when | ||
| 307 | + boolean result = matcher.isMatched(message, condition); | ||
| 308 | + | ||
| 309 | + // then | ||
| 310 | + assertFalse(result); | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + // ========== 辅助方法 ========== | ||
| 314 | + | ||
| 315 | + /** | ||
| 316 | + * 创建设备消息 | ||
| 317 | + */ | ||
| 318 | + private IotDeviceMessage createDeviceMessage(Integer deviceState) { | ||
| 319 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 320 | + message.setParams(deviceState); | ||
| 321 | + return message; | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + /** | ||
| 325 | + * 创建有效的条件 | ||
| 326 | + */ | ||
| 327 | + private IotSceneRuleDO.TriggerCondition createValidCondition(String operator, String param) { | ||
| 328 | + IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition(); | ||
| 329 | + condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType()); | ||
| 330 | + condition.setOperator(operator); | ||
| 331 | + condition.setParam(param); | ||
| 332 | + return condition; | ||
| 333 | + } | ||
| 334 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.map.MapUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 9 | +import org.junit.jupiter.api.BeforeEach; | ||
| 10 | +import org.junit.jupiter.api.Test; | ||
| 11 | + | ||
| 12 | +import java.util.HashMap; | ||
| 13 | +import java.util.Map; | ||
| 14 | + | ||
| 15 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * {@link DeviceEventPostTriggerMatcher} 的单元测试类 | ||
| 19 | + * | ||
| 20 | + * @author HUIHUI | ||
| 21 | + */ | ||
| 22 | +public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest { | ||
| 23 | + | ||
| 24 | + private DeviceEventPostTriggerMatcher matcher; | ||
| 25 | + | ||
| 26 | + @BeforeEach | ||
| 27 | + public void setUp() { | ||
| 28 | + matcher = new DeviceEventPostTriggerMatcher(); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + @Test | ||
| 32 | + public void testGetSupportedTriggerType() { | ||
| 33 | + // when & then | ||
| 34 | + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST, matcher.getSupportedTriggerType()); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + @Test | ||
| 38 | + public void testGetPriority() { | ||
| 39 | + // when & then | ||
| 40 | + assertEquals(30, matcher.getPriority()); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + @Test | ||
| 44 | + public void testIsEnabled() { | ||
| 45 | + // when & then | ||
| 46 | + assertTrue(matcher.isEnabled()); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + @Test | ||
| 50 | + public void testIsMatched_Success_AlarmEvent() { | ||
| 51 | + // given | ||
| 52 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 53 | + .put("identifier", "alarm") | ||
| 54 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 55 | + .put("level", "high") | ||
| 56 | + .put("message", "Temperature too high") | ||
| 57 | + .build()) | ||
| 58 | + .build(); | ||
| 59 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 60 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); | ||
| 61 | + | ||
| 62 | + // when | ||
| 63 | + boolean result = matcher.isMatched(message, trigger); | ||
| 64 | + | ||
| 65 | + // then | ||
| 66 | + assertTrue(result); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + @Test | ||
| 70 | + public void testIsMatched_Success_ErrorEvent() { | ||
| 71 | + // given | ||
| 72 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 73 | + .put("identifier", "error") | ||
| 74 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 75 | + .put("code", 500) | ||
| 76 | + .put("description", "System error") | ||
| 77 | + .build()) | ||
| 78 | + .build(); | ||
| 79 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 80 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); | ||
| 81 | + | ||
| 82 | + // when | ||
| 83 | + boolean result = matcher.isMatched(message, trigger); | ||
| 84 | + | ||
| 85 | + // then | ||
| 86 | + assertTrue(result); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + @Test | ||
| 90 | + public void testIsMatched_Success_InfoEvent() { | ||
| 91 | + // given | ||
| 92 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 93 | + .put("identifier", "info") | ||
| 94 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 95 | + .put("status", "normal") | ||
| 96 | + .put("timestamp", System.currentTimeMillis()) | ||
| 97 | + .build()) | ||
| 98 | + .build(); | ||
| 99 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 100 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("info"); | ||
| 101 | + | ||
| 102 | + // when | ||
| 103 | + boolean result = matcher.isMatched(message, trigger); | ||
| 104 | + | ||
| 105 | + // then | ||
| 106 | + assertTrue(result); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + @Test | ||
| 110 | + public void testIsMatched_Failure_EventIdentifierMismatch() { | ||
| 111 | + // given | ||
| 112 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 113 | + .put("identifier", "alarm") | ||
| 114 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 115 | + .put("level", "high") | ||
| 116 | + .build()) | ||
| 117 | + .build(); | ||
| 118 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 119 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); // 不匹配的事件标识符 | ||
| 120 | + | ||
| 121 | + // when | ||
| 122 | + boolean result = matcher.isMatched(message, trigger); | ||
| 123 | + | ||
| 124 | + // then | ||
| 125 | + assertFalse(result); | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + @Test | ||
| 129 | + public void testIsMatched_Failure_WrongMessageMethod() { | ||
| 130 | + // given | ||
| 131 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 132 | + .put("identifier", "alarm") | ||
| 133 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 134 | + .put("level", "high") | ||
| 135 | + .build()) | ||
| 136 | + .build(); | ||
| 137 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 138 | + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 | ||
| 139 | + message.setParams(eventParams); | ||
| 140 | + | ||
| 141 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); | ||
| 142 | + | ||
| 143 | + // when | ||
| 144 | + boolean result = matcher.isMatched(message, trigger); | ||
| 145 | + | ||
| 146 | + // then | ||
| 147 | + assertFalse(result); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + @Test | ||
| 151 | + public void testIsMatched_Failure_MissingIdentifier() { | ||
| 152 | + // given | ||
| 153 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 154 | + .put("identifier", "alarm") | ||
| 155 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 156 | + .put("level", "high") | ||
| 157 | + .build()) | ||
| 158 | + .build(); | ||
| 159 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 160 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 161 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); | ||
| 162 | + trigger.setIdentifier(null); // 缺少标识符 | ||
| 163 | + | ||
| 164 | + // when | ||
| 165 | + boolean result = matcher.isMatched(message, trigger); | ||
| 166 | + | ||
| 167 | + // then | ||
| 168 | + assertFalse(result); | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + @Test | ||
| 172 | + public void testIsMatched_Failure_NullMessageParams() { | ||
| 173 | + // given | ||
| 174 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 175 | + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); | ||
| 176 | + message.setParams(null); | ||
| 177 | + | ||
| 178 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); | ||
| 179 | + | ||
| 180 | + // when | ||
| 181 | + boolean result = matcher.isMatched(message, trigger); | ||
| 182 | + | ||
| 183 | + // then | ||
| 184 | + assertFalse(result); | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + @Test | ||
| 188 | + public void testIsMatched_Failure_InvalidMessageParams() { | ||
| 189 | + // given | ||
| 190 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 191 | + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); | ||
| 192 | + message.setParams("invalid-params"); // 不是 Map 类型 | ||
| 193 | + | ||
| 194 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); | ||
| 195 | + | ||
| 196 | + // when | ||
| 197 | + boolean result = matcher.isMatched(message, trigger); | ||
| 198 | + | ||
| 199 | + // then | ||
| 200 | + assertFalse(result); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + @Test | ||
| 204 | + public void testIsMatched_Failure_MissingEventIdentifierInParams() { | ||
| 205 | + // given | ||
| 206 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 207 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 208 | + .put("level", "high") | ||
| 209 | + .build()) // 缺少 identifier 字段 | ||
| 210 | + .build(); | ||
| 211 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 212 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); | ||
| 213 | + | ||
| 214 | + // when | ||
| 215 | + boolean result = matcher.isMatched(message, trigger); | ||
| 216 | + | ||
| 217 | + // then | ||
| 218 | + assertFalse(result); | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + @Test | ||
| 222 | + public void testIsMatched_Failure_NullTrigger() { | ||
| 223 | + // given | ||
| 224 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 225 | + .put("identifier", "alarm") | ||
| 226 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 227 | + .put("level", "high") | ||
| 228 | + .build()) | ||
| 229 | + .build(); | ||
| 230 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 231 | + | ||
| 232 | + // when | ||
| 233 | + boolean result = matcher.isMatched(message, null); | ||
| 234 | + | ||
| 235 | + // then | ||
| 236 | + assertFalse(result); | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + @Test | ||
| 240 | + public void testIsMatched_Failure_NullTriggerType() { | ||
| 241 | + // given | ||
| 242 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 243 | + .put("identifier", "alarm") | ||
| 244 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 245 | + .put("level", "high") | ||
| 246 | + .build()) | ||
| 247 | + .build(); | ||
| 248 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 249 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 250 | + trigger.setType(null); | ||
| 251 | + trigger.setIdentifier("alarm"); | ||
| 252 | + | ||
| 253 | + // when | ||
| 254 | + boolean result = matcher.isMatched(message, trigger); | ||
| 255 | + | ||
| 256 | + // then | ||
| 257 | + assertFalse(result); | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + @Test | ||
| 261 | + public void testIsMatched_Success_ComplexEventValue() { | ||
| 262 | + // given | ||
| 263 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 264 | + .put("identifier", "maintenance") | ||
| 265 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 266 | + .put("type", "scheduled") | ||
| 267 | + .put("duration", 120) | ||
| 268 | + .put("components", new String[]{"motor", "sensor"}) | ||
| 269 | + .put("priority", "medium") | ||
| 270 | + .build()) | ||
| 271 | + .build(); | ||
| 272 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 273 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("maintenance"); | ||
| 274 | + | ||
| 275 | + // when | ||
| 276 | + boolean result = matcher.isMatched(message, trigger); | ||
| 277 | + | ||
| 278 | + // then | ||
| 279 | + assertTrue(result); | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + @Test | ||
| 283 | + public void testIsMatched_Success_EmptyEventValue() { | ||
| 284 | + // given | ||
| 285 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 286 | + .put("identifier", "heartbeat") | ||
| 287 | + .put("value", MapUtil.of()) // 空的事件值 | ||
| 288 | + .build(); | ||
| 289 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 290 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("heartbeat"); | ||
| 291 | + | ||
| 292 | + // when | ||
| 293 | + boolean result = matcher.isMatched(message, trigger); | ||
| 294 | + | ||
| 295 | + // then | ||
| 296 | + assertTrue(result); | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + @Test | ||
| 300 | + public void testIsMatched_Success_CaseInsensitiveIdentifier() { | ||
| 301 | + // given | ||
| 302 | + Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 303 | + .put("identifier", "ALARM") // 大写 | ||
| 304 | + .put("value", MapUtil.builder(new HashMap<String, Object>()) | ||
| 305 | + .put("level", "high") | ||
| 306 | + .build()) | ||
| 307 | + .build(); | ||
| 308 | + IotDeviceMessage message = createEventPostMessage(eventParams); | ||
| 309 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); // 小写 | ||
| 310 | + | ||
| 311 | + // when | ||
| 312 | + boolean result = matcher.isMatched(message, trigger); | ||
| 313 | + | ||
| 314 | + // then | ||
| 315 | + // 根据实际实现,这里可能需要调整期望结果 | ||
| 316 | + // 如果实现是大小写敏感的,则应该为 false | ||
| 317 | + assertFalse(result); | ||
| 318 | + } | ||
| 319 | + | ||
| 320 | + // ========== 辅助方法 ========== | ||
| 321 | + | ||
| 322 | + /** | ||
| 323 | + * 创建事件上报消息 | ||
| 324 | + */ | ||
| 325 | + private IotDeviceMessage createEventPostMessage(Map<String, Object> eventParams) { | ||
| 326 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 327 | + message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod()); | ||
| 328 | + message.setParams(eventParams); | ||
| 329 | + return message; | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + /** | ||
| 333 | + * 创建有效的触发器 | ||
| 334 | + */ | ||
| 335 | + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { | ||
| 336 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 337 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType()); | ||
| 338 | + trigger.setIdentifier(identifier); | ||
| 339 | + return trigger; | ||
| 340 | + } | ||
| 341 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.map.MapUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | ||
| 9 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 10 | +import org.junit.jupiter.api.BeforeEach; | ||
| 11 | +import org.junit.jupiter.api.Test; | ||
| 12 | + | ||
| 13 | +import java.util.HashMap; | ||
| 14 | +import java.util.Map; | ||
| 15 | + | ||
| 16 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * {@link DevicePropertyPostTriggerMatcher} 的单元测试类 | ||
| 20 | + * | ||
| 21 | + * @author HUIHUI | ||
| 22 | + */ | ||
| 23 | +public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest { | ||
| 24 | + | ||
| 25 | + private DevicePropertyPostTriggerMatcher matcher; | ||
| 26 | + | ||
| 27 | + @BeforeEach | ||
| 28 | + public void setUp() { | ||
| 29 | + matcher = new DevicePropertyPostTriggerMatcher(); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + @Test | ||
| 33 | + public void testGetSupportedTriggerType() { | ||
| 34 | + // when & then | ||
| 35 | + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST, matcher.getSupportedTriggerType()); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + @Test | ||
| 39 | + public void testGetPriority() { | ||
| 40 | + // when & then | ||
| 41 | + assertEquals(20, matcher.getPriority()); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @Test | ||
| 45 | + public void testIsEnabled() { | ||
| 46 | + // when & then | ||
| 47 | + assertTrue(matcher.isEnabled()); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Test | ||
| 51 | + public void testIsMatched_Success_TemperatureProperty() { | ||
| 52 | + // given | ||
| 53 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 54 | + .put("temperature", 25.5) | ||
| 55 | + .build(); | ||
| 56 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 57 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 58 | + "temperature", | ||
| 59 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 60 | + "20" | ||
| 61 | + ); | ||
| 62 | + | ||
| 63 | + // when | ||
| 64 | + boolean result = matcher.isMatched(message, trigger); | ||
| 65 | + | ||
| 66 | + // then | ||
| 67 | + assertTrue(result); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @Test | ||
| 71 | + public void testIsMatched_Success_HumidityProperty() { | ||
| 72 | + // given | ||
| 73 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 74 | + .put("humidity", 60) | ||
| 75 | + .build(); | ||
| 76 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 77 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 78 | + "humidity", | ||
| 79 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 80 | + "60" | ||
| 81 | + ); | ||
| 82 | + | ||
| 83 | + // when | ||
| 84 | + boolean result = matcher.isMatched(message, trigger); | ||
| 85 | + | ||
| 86 | + // then | ||
| 87 | + assertTrue(result); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + @Test | ||
| 91 | + public void testIsMatched_Failure_PropertyMismatch() { | ||
| 92 | + // given | ||
| 93 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 94 | + .put("temperature", 15.0) | ||
| 95 | + .build(); | ||
| 96 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 97 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 98 | + "temperature", | ||
| 99 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 100 | + "20" | ||
| 101 | + ); | ||
| 102 | + | ||
| 103 | + // when | ||
| 104 | + boolean result = matcher.isMatched(message, trigger); | ||
| 105 | + | ||
| 106 | + // then | ||
| 107 | + assertFalse(result); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + @Test | ||
| 111 | + public void testIsMatched_Failure_PropertyNotFound() { | ||
| 112 | + // given | ||
| 113 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 114 | + .put("temperature", 25.5) | ||
| 115 | + .build(); | ||
| 116 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 117 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 118 | + "humidity", // 不存在的属性 | ||
| 119 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 120 | + "50" | ||
| 121 | + ); | ||
| 122 | + | ||
| 123 | + // when | ||
| 124 | + boolean result = matcher.isMatched(message, trigger); | ||
| 125 | + | ||
| 126 | + // then | ||
| 127 | + assertFalse(result); | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + @Test | ||
| 131 | + public void testIsMatched_Failure_WrongMessageMethod() { | ||
| 132 | + // given | ||
| 133 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 134 | + .put("temperature", 25.5) | ||
| 135 | + .build(); | ||
| 136 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 137 | + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); | ||
| 138 | + message.setParams(properties); | ||
| 139 | + | ||
| 140 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 141 | + "temperature", | ||
| 142 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 143 | + "20" | ||
| 144 | + ); | ||
| 145 | + | ||
| 146 | + // when | ||
| 147 | + boolean result = matcher.isMatched(message, trigger); | ||
| 148 | + | ||
| 149 | + // then | ||
| 150 | + assertFalse(result); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + @Test | ||
| 154 | + public void testIsMatched_Failure_MissingIdentifier() { | ||
| 155 | + // given | ||
| 156 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 157 | + .put("temperature", 25.5) | ||
| 158 | + .build(); | ||
| 159 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 160 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 161 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); | ||
| 162 | + trigger.setIdentifier(null); // 缺少标识符 | ||
| 163 | + trigger.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator()); | ||
| 164 | + trigger.setValue("20"); | ||
| 165 | + | ||
| 166 | + // when | ||
| 167 | + boolean result = matcher.isMatched(message, trigger); | ||
| 168 | + | ||
| 169 | + // then | ||
| 170 | + assertFalse(result); | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + @Test | ||
| 174 | + public void testIsMatched_Failure_NullMessageParams() { | ||
| 175 | + // given | ||
| 176 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 177 | + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); | ||
| 178 | + message.setParams(null); | ||
| 179 | + | ||
| 180 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 181 | + "temperature", | ||
| 182 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 183 | + "20" | ||
| 184 | + ); | ||
| 185 | + | ||
| 186 | + // when | ||
| 187 | + boolean result = matcher.isMatched(message, trigger); | ||
| 188 | + | ||
| 189 | + // then | ||
| 190 | + assertFalse(result); | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + @Test | ||
| 194 | + public void testIsMatched_Failure_InvalidMessageParams() { | ||
| 195 | + // given | ||
| 196 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 197 | + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); | ||
| 198 | + message.setParams("invalid-params"); // 不是 Map 类型 | ||
| 199 | + | ||
| 200 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 201 | + "temperature", | ||
| 202 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 203 | + "20" | ||
| 204 | + ); | ||
| 205 | + | ||
| 206 | + // when | ||
| 207 | + boolean result = matcher.isMatched(message, trigger); | ||
| 208 | + | ||
| 209 | + // then | ||
| 210 | + assertFalse(result); | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + @Test | ||
| 214 | + public void testIsMatched_Success_LessThanOperator() { | ||
| 215 | + // given | ||
| 216 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 217 | + .put("temperature", 15.0) | ||
| 218 | + .build(); | ||
| 219 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 220 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 221 | + "temperature", | ||
| 222 | + IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(), | ||
| 223 | + "20" | ||
| 224 | + ); | ||
| 225 | + | ||
| 226 | + // when | ||
| 227 | + boolean result = matcher.isMatched(message, trigger); | ||
| 228 | + | ||
| 229 | + // then | ||
| 230 | + assertTrue(result); | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + @Test | ||
| 234 | + public void testIsMatched_Success_NotEqualsOperator() { | ||
| 235 | + // given | ||
| 236 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 237 | + .put("status", "active") | ||
| 238 | + .build(); | ||
| 239 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 240 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 241 | + "status", | ||
| 242 | + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), | ||
| 243 | + "inactive" | ||
| 244 | + ); | ||
| 245 | + | ||
| 246 | + // when | ||
| 247 | + boolean result = matcher.isMatched(message, trigger); | ||
| 248 | + | ||
| 249 | + // then | ||
| 250 | + assertTrue(result); | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | + @Test | ||
| 254 | + public void testIsMatched_Success_MultipleProperties() { | ||
| 255 | + // given | ||
| 256 | + Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>()) | ||
| 257 | + .put("temperature", 25.5) | ||
| 258 | + .put("humidity", 60) | ||
| 259 | + .put("status", "active") | ||
| 260 | + .build(); | ||
| 261 | + IotDeviceMessage message = createPropertyPostMessage(properties); | ||
| 262 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 263 | + "humidity", | ||
| 264 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 265 | + "60" | ||
| 266 | + ); | ||
| 267 | + | ||
| 268 | + // when | ||
| 269 | + boolean result = matcher.isMatched(message, trigger); | ||
| 270 | + | ||
| 271 | + // then | ||
| 272 | + assertTrue(result); | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + // ========== 辅助方法 ========== | ||
| 276 | + | ||
| 277 | + /** | ||
| 278 | + * 创建属性上报消息 | ||
| 279 | + */ | ||
| 280 | + private IotDeviceMessage createPropertyPostMessage(Map<String, Object> properties) { | ||
| 281 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 282 | + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); | ||
| 283 | + message.setParams(properties); | ||
| 284 | + return message; | ||
| 285 | + } | ||
| 286 | + | ||
| 287 | + /** | ||
| 288 | + * 创建有效的触发器 | ||
| 289 | + */ | ||
| 290 | + private IotSceneRuleDO.Trigger createValidTrigger(String identifier, String operator, String value) { | ||
| 291 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 292 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType()); | ||
| 293 | + trigger.setIdentifier(identifier); | ||
| 294 | + trigger.setOperator(operator); | ||
| 295 | + trigger.setValue(value); | ||
| 296 | + return trigger; | ||
| 297 | + } | ||
| 298 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.map.MapUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 9 | +import org.junit.jupiter.api.BeforeEach; | ||
| 10 | +import org.junit.jupiter.api.Test; | ||
| 11 | + | ||
| 12 | +import java.util.HashMap; | ||
| 13 | +import java.util.Map; | ||
| 14 | + | ||
| 15 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * {@link DeviceServiceInvokeTriggerMatcher} 的单元测试类 | ||
| 19 | + * | ||
| 20 | + * @author HUIHUI | ||
| 21 | + */ | ||
| 22 | +public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest { | ||
| 23 | + | ||
| 24 | + private DeviceServiceInvokeTriggerMatcher matcher; | ||
| 25 | + | ||
| 26 | + @BeforeEach | ||
| 27 | + public void setUp() { | ||
| 28 | + matcher = new DeviceServiceInvokeTriggerMatcher(); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + @Test | ||
| 32 | + public void testGetSupportedTriggerType() { | ||
| 33 | + // when & then | ||
| 34 | + // TODO @puhui999:单测按照现有项目的注释风格哈;类似 // 调用;// 断言 | ||
| 35 | + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, matcher.getSupportedTriggerType()); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + @Test | ||
| 39 | + public void testGetPriority() { | ||
| 40 | + // when & then | ||
| 41 | + assertEquals(40, matcher.getPriority()); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @Test | ||
| 45 | + public void testIsEnabled() { | ||
| 46 | + // when & then | ||
| 47 | + assertTrue(matcher.isEnabled()); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Test | ||
| 51 | + public void testIsMatched_Success_RestartService() { | ||
| 52 | + // given | ||
| 53 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 54 | + .put("identifier", "restart") | ||
| 55 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 56 | + .put("mode", "soft") | ||
| 57 | + .build()) | ||
| 58 | + .build(); | ||
| 59 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 60 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); | ||
| 61 | + | ||
| 62 | + // when | ||
| 63 | + boolean result = matcher.isMatched(message, trigger); | ||
| 64 | + | ||
| 65 | + // then | ||
| 66 | + assertTrue(result); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + @Test | ||
| 70 | + public void testIsMatched_Success_ConfigService() { | ||
| 71 | + // given | ||
| 72 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 73 | + .put("identifier", "config") | ||
| 74 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 75 | + .put("interval", 30) | ||
| 76 | + .put("enabled", true) | ||
| 77 | + .put("threshold", 75.5) | ||
| 78 | + .build()) | ||
| 79 | + .build(); | ||
| 80 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 81 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); | ||
| 82 | + | ||
| 83 | + // when | ||
| 84 | + boolean result = matcher.isMatched(message, trigger); | ||
| 85 | + | ||
| 86 | + // then | ||
| 87 | + assertTrue(result); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + @Test | ||
| 91 | + public void testIsMatched_Success_UpdateService() { | ||
| 92 | + // given | ||
| 93 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 94 | + .put("identifier", "update") | ||
| 95 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 96 | + .put("version", "1.2.3") | ||
| 97 | + .put("url", "http://example.com/firmware.bin") | ||
| 98 | + .build()) | ||
| 99 | + .build(); | ||
| 100 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 101 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("update"); | ||
| 102 | + | ||
| 103 | + // when | ||
| 104 | + boolean result = matcher.isMatched(message, trigger); | ||
| 105 | + | ||
| 106 | + // then | ||
| 107 | + assertTrue(result); | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + @Test | ||
| 111 | + public void testIsMatched_Failure_ServiceIdentifierMismatch() { | ||
| 112 | + // given | ||
| 113 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 114 | + .put("identifier", "restart") | ||
| 115 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 116 | + .put("mode", "soft") | ||
| 117 | + .build()) | ||
| 118 | + .build(); | ||
| 119 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 120 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); // 不匹配的服务标识符 | ||
| 121 | + | ||
| 122 | + // when | ||
| 123 | + boolean result = matcher.isMatched(message, trigger); | ||
| 124 | + | ||
| 125 | + // then | ||
| 126 | + assertFalse(result); | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + @Test | ||
| 130 | + public void testIsMatched_Failure_WrongMessageMethod() { | ||
| 131 | + // given | ||
| 132 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 133 | + .put("identifier", "restart") | ||
| 134 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 135 | + .put("mode", "soft") | ||
| 136 | + .build()) | ||
| 137 | + .build(); | ||
| 138 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 139 | + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法 | ||
| 140 | + message.setParams(serviceParams); | ||
| 141 | + | ||
| 142 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); | ||
| 143 | + | ||
| 144 | + // when | ||
| 145 | + boolean result = matcher.isMatched(message, trigger); | ||
| 146 | + | ||
| 147 | + // then | ||
| 148 | + assertFalse(result); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + @Test | ||
| 152 | + public void testIsMatched_Failure_MissingIdentifier() { | ||
| 153 | + // given | ||
| 154 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 155 | + .put("identifier", "restart") | ||
| 156 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 157 | + .put("mode", "soft") | ||
| 158 | + .build()) | ||
| 159 | + .build(); | ||
| 160 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 161 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 162 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); | ||
| 163 | + trigger.setIdentifier(null); // 缺少标识符 | ||
| 164 | + | ||
| 165 | + // when | ||
| 166 | + boolean result = matcher.isMatched(message, trigger); | ||
| 167 | + | ||
| 168 | + // then | ||
| 169 | + assertFalse(result); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + @Test | ||
| 173 | + public void testIsMatched_Failure_NullMessageParams() { | ||
| 174 | + // given | ||
| 175 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 176 | + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); | ||
| 177 | + message.setParams(null); | ||
| 178 | + | ||
| 179 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); | ||
| 180 | + | ||
| 181 | + // when | ||
| 182 | + boolean result = matcher.isMatched(message, trigger); | ||
| 183 | + | ||
| 184 | + // then | ||
| 185 | + assertFalse(result); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + @Test | ||
| 189 | + public void testIsMatched_Failure_InvalidMessageParams() { | ||
| 190 | + // given | ||
| 191 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 192 | + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); | ||
| 193 | + message.setParams("invalid-params"); // 不是 Map 类型 | ||
| 194 | + | ||
| 195 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); | ||
| 196 | + | ||
| 197 | + // when | ||
| 198 | + boolean result = matcher.isMatched(message, trigger); | ||
| 199 | + | ||
| 200 | + // then | ||
| 201 | + assertFalse(result); | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + @Test | ||
| 205 | + public void testIsMatched_Failure_MissingServiceIdentifierInParams() { | ||
| 206 | + // given | ||
| 207 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 208 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 209 | + .put("mode", "soft") | ||
| 210 | + .build()) // 缺少 identifier 字段 | ||
| 211 | + .build(); | ||
| 212 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 213 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); | ||
| 214 | + | ||
| 215 | + // when | ||
| 216 | + boolean result = matcher.isMatched(message, trigger); | ||
| 217 | + | ||
| 218 | + // then | ||
| 219 | + assertFalse(result); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + @Test | ||
| 223 | + public void testIsMatched_Failure_NullTrigger() { | ||
| 224 | + // given | ||
| 225 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 226 | + .put("identifier", "restart") | ||
| 227 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 228 | + .put("mode", "soft") | ||
| 229 | + .build()) | ||
| 230 | + .build(); | ||
| 231 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 232 | + | ||
| 233 | + // when | ||
| 234 | + boolean result = matcher.isMatched(message, null); | ||
| 235 | + | ||
| 236 | + // then | ||
| 237 | + assertFalse(result); | ||
| 238 | + } | ||
| 239 | + | ||
| 240 | + @Test | ||
| 241 | + public void testIsMatched_Failure_NullTriggerType() { | ||
| 242 | + // given | ||
| 243 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 244 | + .put("identifier", "restart") | ||
| 245 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 246 | + .put("mode", "soft") | ||
| 247 | + .build()) | ||
| 248 | + .build(); | ||
| 249 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 250 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 251 | + trigger.setType(null); | ||
| 252 | + trigger.setIdentifier("restart"); | ||
| 253 | + | ||
| 254 | + // when | ||
| 255 | + boolean result = matcher.isMatched(message, trigger); | ||
| 256 | + | ||
| 257 | + // then | ||
| 258 | + assertFalse(result); | ||
| 259 | + } | ||
| 260 | + | ||
| 261 | + @Test | ||
| 262 | + public void testIsMatched_Success_EmptyInputData() { | ||
| 263 | + // given | ||
| 264 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 265 | + .put("identifier", "ping") | ||
| 266 | + .put("inputData", MapUtil.of()) // 空的输入数据 | ||
| 267 | + .build(); | ||
| 268 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 269 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("ping"); | ||
| 270 | + | ||
| 271 | + // when | ||
| 272 | + boolean result = matcher.isMatched(message, trigger); | ||
| 273 | + | ||
| 274 | + // then | ||
| 275 | + assertTrue(result); | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + @Test | ||
| 279 | + public void testIsMatched_Success_NoInputData() { | ||
| 280 | + // given | ||
| 281 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 282 | + .put("identifier", "status") | ||
| 283 | + // 没有 inputData 字段 | ||
| 284 | + .build(); | ||
| 285 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 286 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("status"); | ||
| 287 | + | ||
| 288 | + // when | ||
| 289 | + boolean result = matcher.isMatched(message, trigger); | ||
| 290 | + | ||
| 291 | + // then | ||
| 292 | + assertTrue(result); | ||
| 293 | + } | ||
| 294 | + | ||
| 295 | + @Test | ||
| 296 | + public void testIsMatched_Success_ComplexInputData() { | ||
| 297 | + // given | ||
| 298 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 299 | + .put("identifier", "calibrate") | ||
| 300 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 301 | + .put("sensors", new String[]{"temperature", "humidity", "pressure"}) | ||
| 302 | + .put("precision", 0.01) | ||
| 303 | + .put("duration", 300) | ||
| 304 | + .put("autoSave", true) | ||
| 305 | + .put("config", MapUtil.builder(new HashMap<String, Object>()) | ||
| 306 | + .put("mode", "auto") | ||
| 307 | + .put("level", "high") | ||
| 308 | + .build()) | ||
| 309 | + .build()) | ||
| 310 | + .build(); | ||
| 311 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 312 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("calibrate"); | ||
| 313 | + | ||
| 314 | + // when | ||
| 315 | + boolean result = matcher.isMatched(message, trigger); | ||
| 316 | + | ||
| 317 | + // then | ||
| 318 | + assertTrue(result); | ||
| 319 | + } | ||
| 320 | + | ||
| 321 | + @Test | ||
| 322 | + public void testIsMatched_Success_CaseInsensitiveIdentifier() { | ||
| 323 | + // given | ||
| 324 | + Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>()) | ||
| 325 | + .put("identifier", "RESTART") // 大写 | ||
| 326 | + .put("inputData", MapUtil.builder(new HashMap<String, Object>()) | ||
| 327 | + .put("mode", "soft") | ||
| 328 | + .build()) | ||
| 329 | + .build(); | ||
| 330 | + IotDeviceMessage message = createServiceInvokeMessage(serviceParams); | ||
| 331 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); // 小写 | ||
| 332 | + | ||
| 333 | + // when | ||
| 334 | + boolean result = matcher.isMatched(message, trigger); | ||
| 335 | + | ||
| 336 | + // then | ||
| 337 | + // 根据实际实现,这里可能需要调整期望结果 | ||
| 338 | + // 如果实现是大小写敏感的,则应该为 false | ||
| 339 | + assertFalse(result); | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + // ========== 辅助方法 ========== | ||
| 343 | + | ||
| 344 | + /** | ||
| 345 | + * 创建服务调用消息 | ||
| 346 | + */ | ||
| 347 | + private IotDeviceMessage createServiceInvokeMessage(Map<String, Object> serviceParams) { | ||
| 348 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 349 | + message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod()); | ||
| 350 | + message.setParams(serviceParams); | ||
| 351 | + return message; | ||
| 352 | + } | ||
| 353 | + | ||
| 354 | + /** | ||
| 355 | + * 创建有效的触发器 | ||
| 356 | + */ | ||
| 357 | + private IotSceneRuleDO.Trigger createValidTrigger(String identifier) { | ||
| 358 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 359 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType()); | ||
| 360 | + trigger.setIdentifier(identifier); | ||
| 361 | + return trigger; | ||
| 362 | + } | ||
| 363 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | ||
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; | ||
| 6 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 8 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; | ||
| 9 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 10 | +import org.junit.jupiter.api.BeforeEach; | ||
| 11 | +import org.junit.jupiter.api.Test; | ||
| 12 | + | ||
| 13 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * {@link DeviceStateUpdateTriggerMatcher} 的单元测试类 | ||
| 17 | + * | ||
| 18 | + * @author HUIHUI | ||
| 19 | + */ | ||
| 20 | +public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest { | ||
| 21 | + | ||
| 22 | + private DeviceStateUpdateTriggerMatcher matcher; | ||
| 23 | + | ||
| 24 | + @BeforeEach | ||
| 25 | + public void setUp() { | ||
| 26 | + matcher = new DeviceStateUpdateTriggerMatcher(); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + @Test | ||
| 30 | + public void testGetSupportedTriggerType() { | ||
| 31 | + // when & then | ||
| 32 | + assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE, matcher.getSupportedTriggerType()); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + @Test | ||
| 36 | + public void testGetPriority() { | ||
| 37 | + // when & then | ||
| 38 | + assertEquals(10, matcher.getPriority()); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + @Test | ||
| 42 | + public void testIsEnabled() { | ||
| 43 | + // when & then | ||
| 44 | + assertTrue(matcher.isEnabled()); | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + @Test | ||
| 48 | + public void testIsMatched_Success_OnlineState() { | ||
| 49 | + // given | ||
| 50 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 51 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 52 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 53 | + IotDeviceStateEnum.ONLINE.getState().toString() | ||
| 54 | + ); | ||
| 55 | + | ||
| 56 | + // when | ||
| 57 | + boolean result = matcher.isMatched(message, trigger); | ||
| 58 | + | ||
| 59 | + // then | ||
| 60 | + assertTrue(result); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @Test | ||
| 64 | + public void testIsMatched_Success_OfflineState() { | ||
| 65 | + // given | ||
| 66 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.OFFLINE.getState()); | ||
| 67 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 68 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 69 | + IotDeviceStateEnum.OFFLINE.getState().toString() | ||
| 70 | + ); | ||
| 71 | + | ||
| 72 | + // when | ||
| 73 | + boolean result = matcher.isMatched(message, trigger); | ||
| 74 | + | ||
| 75 | + // then | ||
| 76 | + assertTrue(result); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + @Test | ||
| 80 | + public void testIsMatched_Failure_StateMismatch() { | ||
| 81 | + // given | ||
| 82 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 83 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 84 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 85 | + IotDeviceStateEnum.OFFLINE.getState().toString() | ||
| 86 | + ); | ||
| 87 | + | ||
| 88 | + // when | ||
| 89 | + boolean result = matcher.isMatched(message, trigger); | ||
| 90 | + | ||
| 91 | + // then | ||
| 92 | + assertFalse(result); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + @Test | ||
| 96 | + public void testIsMatched_Failure_NullTrigger() { | ||
| 97 | + // given | ||
| 98 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 99 | + | ||
| 100 | + // when | ||
| 101 | + boolean result = matcher.isMatched(message, null); | ||
| 102 | + | ||
| 103 | + // then | ||
| 104 | + assertFalse(result); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + @Test | ||
| 108 | + public void testIsMatched_Failure_NullTriggerType() { | ||
| 109 | + // given | ||
| 110 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 111 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 112 | + trigger.setType(null); | ||
| 113 | + | ||
| 114 | + // when | ||
| 115 | + boolean result = matcher.isMatched(message, trigger); | ||
| 116 | + | ||
| 117 | + // then | ||
| 118 | + assertFalse(result); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + @Test | ||
| 122 | + public void testIsMatched_Failure_WrongMessageMethod() { | ||
| 123 | + // given | ||
| 124 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 125 | + message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); | ||
| 126 | + message.setParams(IotDeviceStateEnum.ONLINE.getState()); | ||
| 127 | + | ||
| 128 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 129 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 130 | + IotDeviceStateEnum.ONLINE.getState().toString() | ||
| 131 | + ); | ||
| 132 | + | ||
| 133 | + // when | ||
| 134 | + boolean result = matcher.isMatched(message, trigger); | ||
| 135 | + | ||
| 136 | + // then | ||
| 137 | + assertFalse(result); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + @Test | ||
| 141 | + public void testIsMatched_Failure_MissingOperator() { | ||
| 142 | + // given | ||
| 143 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 144 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 145 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); | ||
| 146 | + trigger.setOperator(null); | ||
| 147 | + trigger.setValue(IotDeviceStateEnum.ONLINE.getState().toString()); | ||
| 148 | + | ||
| 149 | + // when | ||
| 150 | + boolean result = matcher.isMatched(message, trigger); | ||
| 151 | + | ||
| 152 | + // then | ||
| 153 | + assertFalse(result); | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + @Test | ||
| 157 | + public void testIsMatched_Failure_MissingValue() { | ||
| 158 | + // given | ||
| 159 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 160 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 161 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); | ||
| 162 | + trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator()); | ||
| 163 | + trigger.setValue(null); | ||
| 164 | + | ||
| 165 | + // when | ||
| 166 | + boolean result = matcher.isMatched(message, trigger); | ||
| 167 | + | ||
| 168 | + // then | ||
| 169 | + assertFalse(result); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + @Test | ||
| 173 | + public void testIsMatched_Failure_NullMessageParams() { | ||
| 174 | + // given | ||
| 175 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 176 | + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); | ||
| 177 | + message.setParams(null); | ||
| 178 | + | ||
| 179 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 180 | + IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(), | ||
| 181 | + IotDeviceStateEnum.ONLINE.getState().toString() | ||
| 182 | + ); | ||
| 183 | + | ||
| 184 | + // when | ||
| 185 | + boolean result = matcher.isMatched(message, trigger); | ||
| 186 | + | ||
| 187 | + // then | ||
| 188 | + assertFalse(result); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + @Test | ||
| 192 | + public void testIsMatched_Success_GreaterThanOperator() { | ||
| 193 | + // given | ||
| 194 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 195 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 196 | + IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(), | ||
| 197 | + IotDeviceStateEnum.INACTIVE.getState().toString() | ||
| 198 | + ); | ||
| 199 | + | ||
| 200 | + // when | ||
| 201 | + boolean result = matcher.isMatched(message, trigger); | ||
| 202 | + | ||
| 203 | + // then | ||
| 204 | + assertTrue(result); | ||
| 205 | + } | ||
| 206 | + | ||
| 207 | + @Test | ||
| 208 | + public void testIsMatched_Success_NotEqualsOperator() { | ||
| 209 | + // given | ||
| 210 | + IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState()); | ||
| 211 | + IotSceneRuleDO.Trigger trigger = createValidTrigger( | ||
| 212 | + IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(), | ||
| 213 | + IotDeviceStateEnum.OFFLINE.getState().toString() | ||
| 214 | + ); | ||
| 215 | + | ||
| 216 | + // when | ||
| 217 | + boolean result = matcher.isMatched(message, trigger); | ||
| 218 | + | ||
| 219 | + // then | ||
| 220 | + assertTrue(result); | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + // ========== 辅助方法 ========== | ||
| 224 | + | ||
| 225 | + /** | ||
| 226 | + * 创建设备状态更新消息 | ||
| 227 | + */ | ||
| 228 | + private IotDeviceMessage createStateUpdateMessage(Integer state) { | ||
| 229 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 230 | + message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod()); | ||
| 231 | + message.setParams(state); | ||
| 232 | + return message; | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | + /** | ||
| 236 | + * 创建有效的触发器 | ||
| 237 | + */ | ||
| 238 | + private IotSceneRuleDO.Trigger createValidTrigger(String operator, String value) { | ||
| 239 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 240 | + trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType()); | ||
| 241 | + trigger.setOperator(operator); | ||
| 242 | + trigger.setValue(value); | ||
| 243 | + return trigger; | ||
| 244 | + } | ||
| 245 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcherTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | ||
| 5 | +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; | ||
| 6 | +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum; | ||
| 7 | +import org.junit.jupiter.api.BeforeEach; | ||
| 8 | +import org.junit.jupiter.api.Test; | ||
| 9 | + | ||
| 10 | +import static org.junit.jupiter.api.Assertions.*; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * {@link TimerTriggerMatcher} 的单元测试类 | ||
| 14 | + * | ||
| 15 | + * @author HUIHUI | ||
| 16 | + */ | ||
| 17 | +public class TimerTriggerMatcherTest extends BaseMockitoUnitTest { | ||
| 18 | + | ||
| 19 | + private TimerTriggerMatcher matcher; | ||
| 20 | + | ||
| 21 | + @BeforeEach | ||
| 22 | + public void setUp() { | ||
| 23 | + matcher = new TimerTriggerMatcher(); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + @Test | ||
| 27 | + public void testGetSupportedTriggerType() { | ||
| 28 | + // when & then | ||
| 29 | + assertEquals(IotSceneRuleTriggerTypeEnum.TIMER, matcher.getSupportedTriggerType()); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + @Test | ||
| 33 | + public void testGetPriority() { | ||
| 34 | + // when & then | ||
| 35 | + assertEquals(50, matcher.getPriority()); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + @Test | ||
| 39 | + public void testIsEnabled() { | ||
| 40 | + // when & then | ||
| 41 | + assertTrue(matcher.isEnabled()); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @Test | ||
| 45 | + public void testIsMatched_Success_ValidCronExpression() { | ||
| 46 | + // given | ||
| 47 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 48 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * ?"); // 每天中午12点 | ||
| 49 | + | ||
| 50 | + // when | ||
| 51 | + boolean result = matcher.isMatched(message, trigger); | ||
| 52 | + | ||
| 53 | + // then | ||
| 54 | + assertTrue(result); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + @Test | ||
| 58 | + public void testIsMatched_Success_EveryMinuteCron() { | ||
| 59 | + // given | ||
| 60 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 61 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 * * * * ?"); // 每分钟 | ||
| 62 | + | ||
| 63 | + // when | ||
| 64 | + boolean result = matcher.isMatched(message, trigger); | ||
| 65 | + | ||
| 66 | + // then | ||
| 67 | + assertTrue(result); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @Test | ||
| 71 | + public void testIsMatched_Success_WeekdaysCron() { | ||
| 72 | + // given | ||
| 73 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 74 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9 ? * MON-FRI"); // 工作日上午9点 | ||
| 75 | + | ||
| 76 | + // when | ||
| 77 | + boolean result = matcher.isMatched(message, trigger); | ||
| 78 | + | ||
| 79 | + // then | ||
| 80 | + assertTrue(result); | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + @Test | ||
| 84 | + public void testIsMatched_Failure_InvalidCronExpression() { | ||
| 85 | + // given | ||
| 86 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 87 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("invalid-cron-expression"); | ||
| 88 | + | ||
| 89 | + // when | ||
| 90 | + boolean result = matcher.isMatched(message, trigger); | ||
| 91 | + | ||
| 92 | + // then | ||
| 93 | + assertFalse(result); | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + @Test | ||
| 97 | + public void testIsMatched_Failure_EmptyCronExpression() { | ||
| 98 | + // given | ||
| 99 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 100 | + IotSceneRuleDO.Trigger trigger = createValidTrigger(""); | ||
| 101 | + | ||
| 102 | + // when | ||
| 103 | + boolean result = matcher.isMatched(message, trigger); | ||
| 104 | + | ||
| 105 | + // then | ||
| 106 | + assertFalse(result); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + @Test | ||
| 110 | + public void testIsMatched_Failure_NullCronExpression() { | ||
| 111 | + // given | ||
| 112 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 113 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 114 | + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); | ||
| 115 | + trigger.setCronExpression(null); | ||
| 116 | + | ||
| 117 | + // when | ||
| 118 | + boolean result = matcher.isMatched(message, trigger); | ||
| 119 | + | ||
| 120 | + // then | ||
| 121 | + assertFalse(result); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + @Test | ||
| 125 | + public void testIsMatched_Failure_NullTrigger() { | ||
| 126 | + // given | ||
| 127 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 128 | + | ||
| 129 | + // when | ||
| 130 | + boolean result = matcher.isMatched(message, null); | ||
| 131 | + | ||
| 132 | + // then | ||
| 133 | + assertFalse(result); | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + @Test | ||
| 137 | + public void testIsMatched_Failure_NullTriggerType() { | ||
| 138 | + // given | ||
| 139 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 140 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 141 | + trigger.setType(null); | ||
| 142 | + trigger.setCronExpression("0 0 12 * * ?"); | ||
| 143 | + | ||
| 144 | + // when | ||
| 145 | + boolean result = matcher.isMatched(message, trigger); | ||
| 146 | + | ||
| 147 | + // then | ||
| 148 | + assertFalse(result); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + @Test | ||
| 152 | + public void testIsMatched_Success_ComplexCronExpression() { | ||
| 153 | + // given | ||
| 154 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 155 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 15 10 ? * 6#3"); // 每月第三个星期五上午10:15 | ||
| 156 | + | ||
| 157 | + // when | ||
| 158 | + boolean result = matcher.isMatched(message, trigger); | ||
| 159 | + | ||
| 160 | + // then | ||
| 161 | + assertTrue(result); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + @Test | ||
| 165 | + public void testIsMatched_Failure_IncorrectCronFormat() { | ||
| 166 | + // given | ||
| 167 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 168 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * *"); // 缺少字段 | ||
| 169 | + | ||
| 170 | + // when | ||
| 171 | + boolean result = matcher.isMatched(message, trigger); | ||
| 172 | + | ||
| 173 | + // then | ||
| 174 | + assertFalse(result); | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + @Test | ||
| 178 | + public void testIsMatched_Success_SpecificDateCron() { | ||
| 179 | + // given | ||
| 180 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 181 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 0 1 1 ? 2025"); // 2025年1月1日午夜 | ||
| 182 | + | ||
| 183 | + // when | ||
| 184 | + boolean result = matcher.isMatched(message, trigger); | ||
| 185 | + | ||
| 186 | + // then | ||
| 187 | + assertTrue(result); | ||
| 188 | + } | ||
| 189 | + | ||
| 190 | + @Test | ||
| 191 | + public void testIsMatched_Success_EverySecondCron() { | ||
| 192 | + // given | ||
| 193 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 194 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("* * * * * ?"); // 每秒 | ||
| 195 | + | ||
| 196 | + // when | ||
| 197 | + boolean result = matcher.isMatched(message, trigger); | ||
| 198 | + | ||
| 199 | + // then | ||
| 200 | + assertTrue(result); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + @Test | ||
| 204 | + public void testIsMatched_Failure_InvalidCharactersCron() { | ||
| 205 | + // given | ||
| 206 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 207 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * @ #"); // 包含无效字符 | ||
| 208 | + | ||
| 209 | + // when | ||
| 210 | + boolean result = matcher.isMatched(message, trigger); | ||
| 211 | + | ||
| 212 | + // then | ||
| 213 | + assertFalse(result); | ||
| 214 | + } | ||
| 215 | + | ||
| 216 | + @Test | ||
| 217 | + public void testIsMatched_Success_RangeCron() { | ||
| 218 | + // given | ||
| 219 | + IotDeviceMessage message = new IotDeviceMessage(); | ||
| 220 | + IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点 | ||
| 221 | + | ||
| 222 | + // when | ||
| 223 | + boolean result = matcher.isMatched(message, trigger); | ||
| 224 | + | ||
| 225 | + // then | ||
| 226 | + assertTrue(result); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + // ========== 辅助方法 ========== | ||
| 230 | + | ||
| 231 | + /** | ||
| 232 | + * 创建有效的定时触发器 | ||
| 233 | + */ | ||
| 234 | + private IotSceneRuleDO.Trigger createValidTrigger(String cronExpression) { | ||
| 235 | + IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger(); | ||
| 236 | + trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType()); | ||
| 237 | + trigger.setCronExpression(cronExpression); | ||
| 238 | + return trigger; | ||
| 239 | + } | ||
| 240 | +} |
yudao-module-iot/yudao-module-iot-biz/src/test/resources/application-unit-test.yaml
| @@ -20,6 +20,15 @@ mybatis-plus: | @@ -20,6 +20,15 @@ mybatis-plus: | ||
| 20 | lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 | 20 | lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 |
| 21 | type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject | 21 | type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject |
| 22 | 22 | ||
| 23 | +# 日志配置 | ||
| 24 | +logging: | ||
| 25 | + level: | ||
| 26 | + cn.iocoder.yudao.module.iot.service.rule.scene.matcher: DEBUG | ||
| 27 | + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager: INFO | ||
| 28 | + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition: DEBUG | ||
| 29 | + cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger: DEBUG | ||
| 30 | + root: WARN | ||
| 31 | + | ||
| 23 | --- #################### 定时任务相关配置 #################### | 32 | --- #################### 定时任务相关配置 #################### |
| 24 | 33 | ||
| 25 | --- #################### 配置中心相关配置 #################### | 34 | --- #################### 配置中心相关配置 #################### |
yudao-module-iot/yudao-module-iot-biz/src/test/resources/logback.xml
| 1 | <configuration> | 1 | <configuration> |
| 2 | <!-- 引用 Spring Boot 的 logback 基础配置 --> | 2 | <!-- 引用 Spring Boot 的 logback 基础配置 --> |
| 3 | <include resource="org/springframework/boot/logging/logback/defaults.xml"/> | 3 | <include resource="org/springframework/boot/logging/logback/defaults.xml"/> |
| 4 | + | ||
| 5 | + <!-- 控制台输出 --> | ||
| 6 | + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | ||
| 7 | + <encoder> | ||
| 8 | + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
| 9 | + <charset>UTF-8</charset> | ||
| 10 | + </encoder> | ||
| 11 | + </appender> | ||
| 12 | + | ||
| 13 | + <!-- 设置特定包的日志级别 --> | ||
| 14 | + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher" level="DEBUG" additivity="false"> | ||
| 15 | + <appender-ref ref="CONSOLE"/> | ||
| 16 | + </logger> | ||
| 17 | + | ||
| 18 | + <!-- 设置 IotSceneRuleMatcherManager 的日志级别 --> | ||
| 19 | + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager" level="INFO" | ||
| 20 | + additivity="false"> | ||
| 21 | + <appender-ref ref="CONSOLE"/> | ||
| 22 | + </logger> | ||
| 23 | + | ||
| 24 | + <!-- 设置所有匹配器的日志级别 --> | ||
| 25 | + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition" level="DEBUG" additivity="false"> | ||
| 26 | + <appender-ref ref="CONSOLE"/> | ||
| 27 | + </logger> | ||
| 28 | + | ||
| 29 | + <logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger" level="DEBUG" additivity="false"> | ||
| 30 | + <appender-ref ref="CONSOLE"/> | ||
| 31 | + </logger> | ||
| 32 | + | ||
| 33 | + <!-- 根日志级别 --> | ||
| 34 | + <root level="WARN"> | ||
| 35 | + <appender-ref ref="CONSOLE"/> | ||
| 36 | + </root> | ||
| 4 | </configuration> | 37 | </configuration> |
yudao-server/src/main/resources/application-local.yaml
| @@ -185,6 +185,7 @@ logging: | @@ -185,6 +185,7 @@ logging: | ||
| 185 | cn.iocoder.yudao.module.erp.dal.mysql: debug | 185 | cn.iocoder.yudao.module.erp.dal.mysql: debug |
| 186 | cn.iocoder.yudao.module.iot.dal.mysql: debug | 186 | cn.iocoder.yudao.module.iot.dal.mysql: debug |
| 187 | cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG | 187 | cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG |
| 188 | + cn.iocoder.yudao.module.iot.service.rule: debug | ||
| 188 | cn.iocoder.yudao.module.ai.dal.mysql: debug | 189 | cn.iocoder.yudao.module.ai.dal.mysql: debug |
| 189 | org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 | 190 | org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 |
| 190 | 191 |