Commit 9e38f56f885aa93c876a04b8a1bb6db92e2bc2c8

Authored by haohao
2 parents b0949c08 887bf175

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