Commit fb35807ebf952001e661cb0283b6fcaeb0222991

Authored by puhui999
1 parent 34f1a2ed

perf:【IoT 物联网】场景联动优化 review 提到的逻辑

Showing 13 changed files with 341 additions and 297 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotSceneRuleTriggerTypeEnum.java
1 1 package cn.iocoder.yudao.module.iot.enums.rule;
2 2  
  3 +import cn.hutool.core.util.ArrayUtil;
3 4 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4 5 import lombok.Getter;
5 6 import lombok.RequiredArgsConstructor;
... ... @@ -60,7 +61,9 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
60 61 return ARRAYS;
61 62 }
62 63  
63   - // TODO @puhui999:可以参考下别的枚举哈,方法名,和实现都可以更简洁;of(String type) { firstMatch
  64 + public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) {
  65 + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
  66 + }
64 67 /**
65 68 * 根据类型值查找触发器类型枚举
66 69 *
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcher.java
... ... @@ -26,16 +26,6 @@ public interface IotSceneRuleMatcher {
26 26 return 100;
27 27 }
28 28  
29   - // TODO @puhui999:如果目前没自定义,体感可以删除哈;
30   - /**
31   - * 获取匹配器名称,用于日志和调试
32   - *
33   - * @return 匹配器名称
34   - */
35   - default String getMatcherName() {
36   - return this.getClass().getSimpleName();
37   - }
38   -
39 29 /**
40 30 * 是否启用该匹配器
41 31 * <p>
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherHelper.java
1 1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
2 2  
  3 +import cn.hutool.core.text.CharPool;
3 4 import cn.hutool.core.util.NumberUtil;
4 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 8 import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
6 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 10 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
... ... @@ -40,44 +43,99 @@ public final class IotSceneRuleMatcherHelper {
40 43 * @param paramValue 参数值(来自条件配置)
41 44 * @return 是否匹配
42 45 */
43   - @SuppressWarnings("DataFlowIssue")
44 46 public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
45 47 try {
46 48 // 1. 校验操作符是否合法
47 49 IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator);
48 50 if (operatorEnum == null) {
49   - log.warn("[evaluateCondition][存在错误的操作符({})]", operator);
  51 + log.warn("[evaluateCondition][operator({}) 操作符无效]", operator);
50 52 return false;
51 53 }
52 54  
53 55 // 2. 构建 Spring 表达式变量
54   - Map<String, Object> springExpressionVariables = new HashMap<>();
55   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue);
56   -
57   - // 处理参数值
58   - if (StrUtil.isNotBlank(paramValue)) {
59   - // 处理多值情况(如 IN、BETWEEN 操作符)
60   - // TODO @puhui999:使用这个,会不会有问题?例如说:string 恰好有 , 分隔?
61   - if (paramValue.contains(",")) {
62   - List<String> paramValues = StrUtil.split(paramValue, ',');
63   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
64   - convertList(paramValues, NumberUtil::parseDouble));
65   - } else {
66   - // 处理单值情况
67   - springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
68   - NumberUtil.parseDouble(paramValue));
69   - }
70   - }
  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 + }
71 63  
72   - // 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 表达式
73 79 return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables);
74 80 } catch (Exception e) {
75   - log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}",
76   - sourceValue, operator, paramValue, e);
  81 + log.error("[evaluateConditionWithOperatorEnum][sourceValue({}) operatorEnum({}) paramValue({}) 条件评估异常]",
  82 + sourceValue, operatorEnum, paramValue, e);
77 83 return false;
78 84 }
79 85 }
80 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 +
81 139 // ========== 【触发器】相关工具方法 ==========
82 140  
83 141 /**
... ... @@ -103,24 +161,22 @@ public final class IotSceneRuleMatcherHelper {
103 161 /**
104 162 * 记录触发器匹配成功日志
105 163 *
106   - * @param matcherName 匹配器名称
107 164 * @param message 设备消息
108 165 * @param trigger 触发器配置
109 166 */
110   - public static void logTriggerMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
111   - log.debug("[{}][消息({}) 匹配触发器({}) 成功]", matcherName, message.getRequestId(), trigger.getType());
  167 + public static void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
  168 + log.debug("[isMatched][message({}) trigger({}) 匹配触发器成功]", message.getRequestId(), trigger.getType());
112 169 }
113 170  
114 171 /**
115 172 * 记录触发器匹配失败日志
116 173 *
117   - * @param matcherName 匹配器名称
118 174 * @param message 设备消息
119 175 * @param trigger 触发器配置
120 176 * @param reason 失败原因
121 177 */
122   - public static void logTriggerMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
123   - log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", matcherName, 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);
124 180 }
125 181  
126 182 // ========== 【条件】相关工具方法 ==========
... ... @@ -148,24 +204,22 @@ public final class IotSceneRuleMatcherHelper {
148 204 /**
149 205 * 记录条件匹配成功日志
150 206 *
151   - * @param matcherName 匹配器名称
152 207 * @param message 设备消息
153 208 * @param condition 触发条件
154 209 */
155   - public static void logConditionMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
156   - log.debug("[{}][消息({}) 匹配条件({}) 成功]", matcherName, message.getRequestId(), condition.getType());
  210 + public static void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
  211 + log.debug("[isMatched][message({}) condition({}) 匹配条件成功]", message.getRequestId(), condition.getType());
157 212 }
158 213  
159 214 /**
160 215 * 记录条件匹配失败日志
161 216 *
162   - * @param matcherName 匹配器名称
163 217 * @param message 设备消息
164 218 * @param condition 触发条件
165 219 * @param reason 失败原因
166 220 */
167   - public static void logConditionMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
168   - log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", matcherName, 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);
169 223 }
170 224  
171 225 // ========== 【通用】工具方法 ==========
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleMatcherManager.java
... ... @@ -14,7 +14,7 @@ import java.util.*;
14 14 import java.util.function.Function;
15 15 import java.util.stream.Collectors;
16 16  
17   -import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum;
  17 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
18 18  
19 19 /**
20 20 * IoT 场景规则匹配器统一管理器
... ... @@ -72,42 +72,34 @@ public class IotSceneRuleMatcherManager {
72 72 .toList();
73 73  
74 74 // 构建触发器匹配器映射表
75   - // TODO @puhui999:convertMap()
76   - this.triggerMatchers = triggerMatchers.stream()
77   - .collect(Collectors.toMap(
78   - IotSceneRuleTriggerMatcher::getSupportedTriggerType,
79   - Function.identity(),
80   - (existing, replacement) -> {
81   - log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]",
82   - existing.getSupportedTriggerType(),
83   - existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
84   - return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
85   - },
86   - LinkedHashMap::new
87   - ));
  75 + this.triggerMatchers = convertMap(triggerMatchers, IotSceneRuleTriggerMatcher::getSupportedTriggerType,
  76 + Function.identity(),
  77 + (existing, replacement) -> {
  78 + log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]",
  79 + existing.getSupportedTriggerType(),
  80 + existing.getPriority() <= replacement.getPriority() ?
  81 + existing.getSupportedTriggerType() : replacement.getSupportedTriggerType());
  82 + return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
  83 + }, LinkedHashMap::new);
88 84 // 构建条件匹配器映射表
89   - this.conditionMatchers = conditionMatchers.stream()
90   - .collect(Collectors.toMap(
91   - IotSceneRuleConditionMatcher::getSupportedConditionType,
92   - Function.identity(),
93   - (existing, replacement) -> {
94   - log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]",
95   - existing.getSupportedConditionType(),
96   - existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
97   - return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
98   - },
99   - LinkedHashMap::new
100   - ));
  85 + this.conditionMatchers = convertMap(conditionMatchers, IotSceneRuleConditionMatcher::getSupportedConditionType,
  86 + Function.identity(),
  87 + (existing, replacement) -> {
  88 + log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]",
  89 + existing.getSupportedConditionType(),
  90 + existing.getPriority() <= replacement.getPriority() ?
  91 + existing.getSupportedConditionType() : replacement.getSupportedConditionType());
  92 + return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
  93 + },
  94 + LinkedHashMap::new);
101 95  
102 96 // 日志输出初始化信息
103   - log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]",
  97 + log.info("[IotSceneRuleMatcherManager][初始化完成,共加载({})个匹配器,其中触发器匹配器({})个,条件匹配器({})个]",
104 98 this.allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size());
105 99 this.triggerMatchers.forEach((type, matcher) ->
106   - log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
107   - type, matcher.getMatcherName(), matcher.getPriority()));
  100 + log.info("[IotSceneRuleMatcherManager][触发器匹配器类型: ({}), 优先级: ({})] ", type, matcher.getPriority()));
108 101 this.conditionMatchers.forEach((type, matcher) ->
109   - log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
110   - type, matcher.getMatcherName(), matcher.getPriority()));
  102 + log.info("[IotSceneRuleMatcherManager][条件匹配器类型: ({}), 优先级: ({})]", type, matcher.getPriority()));
111 103 }
112 104  
113 105 /**
... ... @@ -118,27 +110,25 @@ public class IotSceneRuleMatcherManager {
118 110 * @return 是否匹配
119 111 */
120 112 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
121   - // TODO @puhui999:日志优化下;claude 打出来的日志风格,和项目有点不一样哈;
122 113 if (message == null || trigger == null || trigger.getType() == null) {
123   - log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger);
  114 + log.debug("[isMatched][message({}) trigger({}) 参数无效]", message, trigger);
124 115 return false;
125 116 }
126   - IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType());
  117 + IotSceneRuleTriggerTypeEnum triggerType = IotSceneRuleTriggerTypeEnum.typeOf(trigger.getType());
127 118 if (triggerType == null) {
128   - log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType());
  119 + log.warn("[isMatched][triggerType({}) 未知的触发器类型]", trigger.getType());
129 120 return false;
130 121 }
131 122 IotSceneRuleTriggerMatcher matcher = triggerMatchers.get(triggerType);
132 123 if (matcher == null) {
133   - log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType);
  124 + log.warn("[isMatched][triggerType({}) 没有对应的匹配器]", triggerType);
134 125 return false;
135 126 }
136 127  
137 128 try {
138 129 return matcher.isMatched(message, trigger);
139 130 } catch (Exception e) {
140   - log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}",
141   - message, trigger, matcher.getMatcherName(), e);
  131 + log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}", message, trigger, e);
142 132 return false;
143 133 }
144 134 }
... ... @@ -152,28 +142,27 @@ public class IotSceneRuleMatcherManager {
152 142 */
153 143 public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
154 144 if (message == null || condition == null || condition.getType() == null) {
155   - log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition);
  145 + log.debug("[isConditionMatched][message({}) condition({}) 参数无效]", message, condition);
156 146 return false;
157 147 }
158 148  
159 149 // 根据条件类型查找对应的匹配器
160 150 IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType());
161 151 if (conditionType == null) {
162   - log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType());
  152 + log.warn("[isConditionMatched][conditionType({}) 未知的条件类型]", condition.getType());
163 153 return false;
164 154 }
165 155  
166 156 IotSceneRuleConditionMatcher matcher = conditionMatchers.get(conditionType);
167 157 if (matcher == null) {
168   - log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType);
  158 + log.warn("[isConditionMatched][conditionType({}) 没有对应的匹配器]", conditionType);
169 159 return false;
170 160 }
171 161  
172 162 try {
173 163 return matcher.isMatched(message, condition);
174 164 } catch (Exception e) {
175   - log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}",
176   - message, condition, matcher.getMatcherName(), e);
  165 + log.error("[isConditionMatched][message({}) condition({}) 条件匹配异常]", message, condition, e);
177 166 return false;
178 167 }
179 168 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/CurrentTimeConditionMatcher.java
1 1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
2 2  
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.text.CharPool;
3 5 import cn.hutool.core.util.StrUtil;
4 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 7 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
  8 +import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
6 9 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
7 10 import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
8 11 import lombok.extern.slf4j.Slf4j;
... ... @@ -11,6 +14,7 @@ import org.springframework.stereotype.Component;
11 14 import java.time.LocalDateTime;
12 15 import java.time.LocalTime;
13 16 import java.time.format.DateTimeFormatter;
  17 +import java.util.List;
14 18  
15 19 /**
16 20 * 当前时间条件匹配器
... ... @@ -40,115 +44,175 @@ public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher
40 44  
41 45 @Override
42 46 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
43   - // 1. 基础参数校验
  47 + // 1.1 基础参数校验
44 48 if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
45   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效");
  49 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
46 50 return false;
47 51 }
48 52  
49   - // 2. 检查操作符和参数是否有效
  53 + // 1.2 检查操作符和参数是否有效
50 54 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
51   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效");
  55 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
52 56 return false;
53 57 }
54 58  
55   - // 3. 根据操作符类型进行不同的时间匹配
56   - LocalDateTime now = LocalDateTime.now();
  59 + // 1.3 验证操作符是否为支持的时间操作符
57 60 String operator = condition.getOperator();
58   - String param = condition.getParam();
59   - boolean matched;
  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) {
60 90 try {
61   - if (operator.startsWith("date_time_")) {
62   - // 日期时间匹配(时间戳)
63   - matched = matchDateTime(now, operator, param);
64   - } else if (operator.startsWith("time_")) {
65   - // 当日时间匹配(HH:mm:ss)
66   - matched = matchTime(now.toLocalTime(), operator, param);
67   - } else {
68   - // 其他操作符,使用通用条件评估器
69   - matched = IotSceneRuleMatcherHelper.evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param);
70   - }
  91 + LocalDateTime now = LocalDateTime.now();
71 92  
72   - if (matched) {
73   - IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition);
  93 + if (isDateTimeOperator(operatorEnum)) {
  94 + // 日期时间匹配(时间戳)
  95 + long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
  96 + return matchDateTime(currentTimestamp, operatorEnum, param);
74 97 } else {
75   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件不匹配");
  98 + // 当日时间匹配(HH:mm:ss)
  99 + return matchTime(now.toLocalTime(), operatorEnum, param);
76 100 }
77 101 } catch (Exception e) {
78   - log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e);
79   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件匹配异常: " + e.getMessage());
80   - matched = false;
  102 + log.error("[executeTimeMatching][operatorEnum({}) param({}) 时间匹配异常]", operatorEnum, param, e);
  103 + return false;
81 104 }
82   - return matched;
  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);
83 124 }
84 125  
85 126 /**
86 127 * 匹配日期时间(时间戳)
  128 + * 直接实现时间戳比较逻辑
87 129 */
88   - private boolean matchDateTime(LocalDateTime now, String operator, String param) {
89   - long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
90   - return IotSceneRuleMatcherHelper.evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param);
  130 + private boolean matchDateTime(long currentTimestamp, IotSceneRuleConditionOperatorEnum operatorEnum, String param) {
  131 + try {
  132 + long targetTimestamp = Long.parseLong(param);
  133 + return switch (operatorEnum) {
  134 + case DATE_TIME_GREATER_THAN -> currentTimestamp > targetTimestamp;
  135 + case DATE_TIME_LESS_THAN -> currentTimestamp < targetTimestamp;
  136 + case DATE_TIME_BETWEEN -> matchDateTimeBetween(currentTimestamp, param);
  137 + default -> {
  138 + log.warn("[matchDateTime][operatorEnum({}) 不支持的日期时间操作符]", operatorEnum);
  139 + yield false;
  140 + }
  141 + };
  142 + } catch (Exception e) {
  143 + log.error("[matchDateTime][operatorEnum({}) param({}) 日期时间匹配异常]", operatorEnum, param, e);
  144 + return false;
  145 + }
  146 + }
  147 +
  148 + /**
  149 + * 匹配日期时间区间
  150 + */
  151 + private boolean matchDateTimeBetween(long currentTimestamp, String param) {
  152 + List<String> timestampRange = StrUtil.splitTrim(param, CharPool.COMMA);
  153 + if (timestampRange.size() != 2) {
  154 + log.warn("[matchDateTimeBetween][param({}) 时间戳区间参数格式错误]", param);
  155 + return false;
  156 + }
  157 + long startTimestamp = Long.parseLong(timestampRange.get(0).trim());
  158 + long endTimestamp = Long.parseLong(timestampRange.get(1).trim());
  159 + return currentTimestamp >= startTimestamp && currentTimestamp <= endTimestamp;
91 160 }
92 161  
93 162 /**
94 163 * 匹配当日时间(HH:mm:ss)
  164 + * 直接实现时间比较逻辑
95 165 */
96   - private boolean matchTime(LocalTime currentTime, String operator, String param) {
  166 + private boolean matchTime(LocalTime currentTime, IotSceneRuleConditionOperatorEnum operatorEnum, String param) {
97 167 try {
98   - String actualOperator = operator.substring("time_".length());
99   -
100   - // TODO @puhui999:if return 简化;
101   - if ("between".equals(actualOperator)) {
102   - // 时间区间匹配
103   - String[] timeRange = param.split(",");
104   - if (timeRange.length != 2) {
105   - return false;
106   - }
107   - LocalTime startTime = parseTime(timeRange[0].trim());
108   - LocalTime endTime = parseTime(timeRange[1].trim());
109   - return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
110   - } else {
111   - // 单个时间比较
112   - LocalTime targetTime = parseTime(param);
113   - // TODO @puhui999:枚举类;
114   - switch (actualOperator) {
115   - case ">":
116   - return currentTime.isAfter(targetTime);
117   - case "<":
118   - return currentTime.isBefore(targetTime);
119   - case ">=":
120   - return !currentTime.isBefore(targetTime);
121   - case "<=":
122   - return !currentTime.isAfter(targetTime);
123   - case "=":
124   - return currentTime.equals(targetTime);
125   - default:
126   - return false;
  168 + LocalTime targetTime = parseTime(param);
  169 + return switch (operatorEnum) {
  170 + case TIME_GREATER_THAN -> currentTime.isAfter(targetTime);
  171 + case TIME_LESS_THAN -> currentTime.isBefore(targetTime);
  172 + case TIME_BETWEEN -> matchTimeBetween(currentTime, param);
  173 + default -> {
  174 + log.warn("[matchTime][operatorEnum({}) 不支持的时间操作符]", operatorEnum);
  175 + yield false;
127 176 }
128   - }
  177 + };
129 178 } catch (Exception e) {
130   - // TODO @puhui999:1)日志格式 [][];2)方法名不对哈;
131   - log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e);
  179 + log.error("[matchTime][][operatorEnum({}) param({}) 时间解析异常]", operatorEnum, param, e);
132 180 return false;
133 181 }
134 182 }
135 183  
136 184 /**
  185 + * 匹配时间区间
  186 + */
  187 + private boolean matchTimeBetween(LocalTime currentTime, String param) {
  188 + List<String> timeRange = StrUtil.splitTrim(param, CharPool.COMMA);
  189 + if (timeRange.size() != 2) {
  190 + log.warn("[matchTimeBetween][param({}) 时间区间参数格式错误]", param);
  191 + return false;
  192 + }
  193 + LocalTime startTime = parseTime(timeRange.get(0).trim());
  194 + LocalTime endTime = parseTime(timeRange.get(1).trim());
  195 + return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
  196 + }
  197 +
  198 + /**
137 199 * 解析时间字符串
  200 + * 支持 HH:mm 和 HH:mm:ss 两种格式
138 201 */
139 202 private LocalTime parseTime(String timeStr) {
140   - // TODO @puhui999:可以用 hutool Assert 类简化
141   - if (StrUtil.isBlank(timeStr)) {
142   - throw new IllegalArgumentException("时间字符串不能为空");
143   - }
144   - // 尝试不同的时间格式
  203 + Assert.isFalse(StrUtil.isBlank(timeStr), "时间字符串不能为空");
  204 +
145 205 try {
  206 + // 尝试不同的时间格式
146 207 if (timeStr.length() == 5) { // HH:mm
147 208 return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT);
148   - } else { // HH:mm:ss
  209 + } else if (timeStr.length() == 8) { // HH:mm:ss
149 210 return LocalTime.parse(timeStr, TIME_FORMATTER);
  211 + } else {
  212 + throw new IllegalArgumentException("时间格式长度不正确,期望 HH:mm 或 HH:mm:ss 格式");
150 213 }
151 214 } catch (Exception e) {
  215 + log.error("[parseTime][timeStr({}) 时间格式解析失败]", timeStr, e);
152 216 throw new IllegalArgumentException("时间格式无效: " + timeStr, e);
153 217 }
154 218 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DevicePropertyConditionMatcher.java
... ... @@ -22,41 +22,40 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc
22 22 return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY;
23 23 }
24 24  
25   - // TODO @puhui999:参数校验的,要不要 1.1 1.2 1.3 1.4 ?这样最终看到 2. 3. 就是核心逻辑列;
26 25 @Override
27 26 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
28   - // 1. 基础参数校验
  27 + // 1.1 基础参数校验
29 28 if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
30   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效");
  29 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
31 30 return false;
32 31 }
33 32  
34   - // 2. 检查标识符是否匹配
  33 + // 1.2 检查标识符是否匹配
35 34 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
36 35 if (!IotSceneRuleMatcherHelper.isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) {
37   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
  36 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
38 37 return false;
39 38 }
40 39  
41   - // 3. 检查操作符和参数是否有效
  40 + // 1.3 检查操作符和参数是否有效
42 41 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
43   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效");
  42 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
44 43 return false;
45 44 }
46 45  
47   - // 4. 获取属性值
48   - Object propertyValue = message.getData();
  46 + // 2.1. 获取属性值
  47 + Object propertyValue = message.getParams();
49 48 if (propertyValue == null) {
50   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中属性值为空");
  49 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空");
51 50 return false;
52 51 }
53 52  
54   - // 5. 使用条件评估器进行匹配
  53 + // 2.2 使用条件评估器进行匹配
55 54 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, condition.getOperator(), condition.getParam());
56 55 if (matched) {
57   - IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition);
  56 + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition);
58 57 } else {
59   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "设备属性条件不匹配");
  58 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备属性条件不匹配");
60 59 }
61 60 return matched;
62 61 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/DeviceStateConditionMatcher.java
... ... @@ -23,32 +23,31 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher
23 23  
24 24 @Override
25 25 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
26   - // 1. 基础参数校验
  26 + // 1.1 基础参数校验
27 27 if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
28   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效");
  28 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
29 29 return false;
30 30 }
31 31  
32   - // 2. 检查操作符和参数是否有效
  32 + // 1.2 检查操作符和参数是否有效
33 33 if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
34   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效");
  34 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
35 35 return false;
36 36 }
37 37  
38   - // 3. 获取设备状态值
39   - // 设备状态通常在消息的 data 字段中
40   - Object stateValue = message.getData();
  38 + // 2.1 获取设备状态值
  39 + Object stateValue = message.getParams();
41 40 if (stateValue == null) {
42   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中设备状态值为空");
  41 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空");
43 42 return false;
44 43 }
45 44  
46   - // 4. 使用条件评估器进行匹配
  45 + // 2.2 使用条件评估器进行匹配
47 46 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, condition.getOperator(), condition.getParam());
48 47 if (matched) {
49   - IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition);
  48 + IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition);
50 49 } else {
51   - IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "设备状态条件不匹配");
  50 + IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备状态条件不匹配");
52 51 }
53 52 return matched;
54 53 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceEventPostTriggerMatcher.java
... ... @@ -19,11 +19,6 @@ import org.springframework.stereotype.Component;
19 19 @Component
20 20 public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
21 21  
22   - /**
23   - * 设备事件上报消息方法
24   - */
25   - private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod();
26   -
27 22 @Override
28 23 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
29 24 return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST;
... ... @@ -31,42 +26,44 @@ public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher
31 26  
32 27 @Override
33 28 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
34   - // 1. 基础参数校验
  29 + // 1.1 基础参数校验
35 30 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
36   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
  31 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
37 32 return false;
38 33 }
39 34  
40   - // 2. 检查消息方法是否匹配
41   - if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) {
42   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), 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());
43 39 return false;
44 40 }
45 41  
46   - // 3. 检查标识符是否匹配
  42 + // 1.3 检查标识符是否匹配
47 43 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
48 44 if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
49   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
  45 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " +
  46 + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
50 47 return false;
51 48 }
52 49  
53   - // 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
  50 + // 2. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
54 51 // 但如果配置了操作符和值,则需要进行条件匹配
55 52 if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) {
56 53 Object eventData = message.getData();
57 54 if (eventData == null) {
58   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中事件数据为空");
  55 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中事件数据为空");
59 56 return false;
60 57 }
61 58  
62 59 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(eventData, trigger.getOperator(), trigger.getValue());
63 60 if (!matched) {
64   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "事件数据条件不匹配");
  61 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "事件数据条件不匹配");
65 62 return false;
66 63 }
67 64 }
68 65  
69   - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
  66 + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
70 67 return true;
71 68 }
72 69  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DevicePropertyPostTriggerMatcher.java
... ... @@ -18,12 +18,6 @@ import org.springframework.stereotype.Component;
18 18 @Component
19 19 public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
20 20  
21   - /**
22   - * 设备属性上报消息方法
23   - */
24   - // TODO @puhui999:是不是不用枚举哈?直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()
25   - private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod();
26   -
27 21 @Override
28 22 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
29 23 return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST;
... ... @@ -31,44 +25,46 @@ public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatc
31 25  
32 26 @Override
33 27 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
34   - // 1. 基础参数校验
  28 + // 1.1 基础参数校验
35 29 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
36   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
  30 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
37 31 return false;
38 32 }
39 33  
40   - // 2. 检查消息方法是否匹配
41   - if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) {
42   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), 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());
43 38 return false;
44 39 }
45 40  
46   - // 3. 检查标识符是否匹配
  41 + // 1.3 检查标识符是否匹配
47 42 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
48 43 if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
49   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
  44 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " +
  45 + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
50 46 return false;
51 47 }
52 48  
53   - // 4. 检查操作符和值是否有效
  49 + // 1.4 检查操作符和值是否有效
54 50 if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) {
55   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效");
  51 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效");
56 52 return false;
57 53 }
58 54  
59   - // 5. 获取属性值
60   - Object propertyValue = message.getData();
  55 + // 2.1 获取属性值
  56 + Object propertyValue = message.getParams();
61 57 if (propertyValue == null) {
62   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中属性值为空");
  58 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空");
63 59 return false;
64 60 }
65 61  
66   - // 6. 使用条件评估器进行匹配
  62 + // 2.2 使用条件评估器进行匹配
67 63 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
68 64 if (matched) {
69   - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
  65 + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
70 66 } else {
71   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "属性值条件不匹配");
  67 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "属性值条件不匹配");
72 68 }
73 69 return matched;
74 70 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceServiceInvokeTriggerMatcher.java
... ... @@ -18,11 +18,6 @@ import org.springframework.stereotype.Component;
18 18 @Component
19 19 public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher {
20 20  
21   - /**
22   - * 设备服务调用消息方法
23   - */
24   - private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod();
25   -
26 21 @Override
27 22 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
28 23 return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
... ... @@ -30,29 +25,29 @@ public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMat
30 25  
31 26 @Override
32 27 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
33   - // 1. 基础参数校验
  28 + // 1.1 基础参数校验
34 29 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
35   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
  30 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
36 31 return false;
37 32 }
38 33  
39   - // 2. 检查消息方法是否匹配
40   - if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) {
41   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), 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());
42 37 return false;
43 38 }
44 39  
45   - // 3. 检查标识符是否匹配
  40 + // 1.3 检查标识符是否匹配
46 41 String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
47 42 if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
48   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
  43 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
49 44 return false;
50 45 }
51 46  
52   - // 4. 对于服务调用触发器,通常只需要匹配服务标识符即可
  47 + // 2. 对于服务调用触发器,通常只需要匹配服务标识符即可
53 48 // 不需要检查操作符和值,因为服务调用本身就是触发条件
54   -
55   - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
  49 + // TODO @puhui999: 服务调用时校验输入参数是否匹配条件
  50 + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
56 51 return true;
57 52 }
58 53  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/DeviceStateUpdateTriggerMatcher.java
... ... @@ -17,12 +17,6 @@ import org.springframework.stereotype.Component;
17 17 @Component
18 18 public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher {
19 19  
20   - // TODO @puhui999:是不是不用枚举哈;
21   - /**
22   - * 设备状态更新消息方法
23   - */
24   - private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod();
25   -
26 20 @Override
27 21 public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
28 22 return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE;
... ... @@ -30,37 +24,39 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch
30 24  
31 25 @Override
32 26 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
33   - // 1. 基础参数校验
  27 + // 1.1 基础参数校验
34 28 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
35   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
  29 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
36 30 return false;
37 31 }
38 32  
39   - // 2. 检查消息方法是否匹配
40   - if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) {
41   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), 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());
42 37 return false;
43 38 }
44 39  
45   - // 3. 检查操作符和值是否有效
  40 + // 1.3 检查操作符和值是否有效
46 41 if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) {
47   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效");
  42 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效");
48 43 return false;
49 44 }
50 45  
51   - // 4. 获取设备状态值
52   - Object stateValue = message.getData();
  46 + // 2.1 获取设备状态值
  47 + Object stateValue = message.getParams();
53 48 if (stateValue == null) {
54   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中设备状态值为空");
  49 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
55 50 return false;
56 51 }
57 52  
58   - // 5. 使用条件评估器进行匹配
  53 + // 2.2 使用条件评估器进行匹配
  54 + // TODO @puhui999: 状态匹配重新实现
59 55 boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
60 56 if (matched) {
61   - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
  57 + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
62 58 } else {
63   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "状态值条件不匹配");
  59 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "状态值条件不匹配");
64 60 }
65 61 return matched;
66 62 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/TimerTriggerMatcher.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 5 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
6 6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
7 7 import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
  8 +import org.quartz.CronExpression;
8 9 import org.springframework.stereotype.Component;
9 10  
10 11 /**
... ... @@ -25,58 +26,32 @@ public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher {
25 26  
26 27 @Override
27 28 public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
28   - // 1. 基础参数校验
  29 + // 1.1 基础参数校验
29 30 if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
30   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
  31 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
31 32 return false;
32 33 }
33 34  
34   - // 2. 检查 CRON 表达式是否存在
  35 + // 1.2 检查 CRON 表达式是否存在
35 36 if (StrUtil.isBlank(trigger.getCronExpression())) {
36   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "定时触发器缺少 CRON 表达式");
  37 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式");
37 38 return false;
38 39 }
39 40  
40   - // 3. 定时触发器通常不依赖具体的设备消息
  41 + // 1.3 定时触发器通常不依赖具体的设备消息
41 42 // 它是通过定时任务调度器触发的,这里主要是验证配置的有效性
42   -
43   - // 4. 可以添加 CRON 表达式格式验证
44   - if (!isValidCronExpression(trigger.getCronExpression())) {
45   - IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
  43 + if (!CronExpression.isValidExpression(trigger.getCronExpression())) {
  44 + IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
46 45 return false;
47 46 }
48 47  
49   - IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
  48 + IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
50 49 return true;
51 50 }
52 51  
53   - /**
54   - * 验证 CRON 表达式格式是否有效
55   - *
56   - * @param cronExpression CRON 表达式
57   - * @return 是否有效
58   - */
59   - private boolean isValidCronExpression(String cronExpression) {
60   - // TODO @puhui999:CronExpression.isValidExpression(cronExpression);
61   - try {
62   - // 简单的 CRON 表达式格式验证
63   - // 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年])
64   - String[] fields = cronExpression.trim().split("\\s+");
65   - return fields.length >= 6 && fields.length <= 7;
66   - } catch (Exception e) {
67   - return false;
68   - }
69   - }
70   -
71 52 @Override
72 53 public int getPriority() {
73 54 return 50; // 最低优先级,因为定时触发器不依赖消息
74 55 }
75 56  
76   - @Override
77   - public boolean isEnabled() {
78   - // 定时触发器可以根据配置动态启用/禁用
79   - return true;
80   - }
81   -
82 57 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/IotSceneRuleTriggerMatcherTest.java
... ... @@ -173,19 +173,6 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
173 173 }
174 174  
175 175 @Test
176   - void testMatcherManagerStatistics() {
177   - // 1. 执行测试
178   - var statistics = matcherManager.getMatcherStatistics();
179   -
180   - // 2. 验证结果
181   - assertNotNull(statistics);
182   - assertEquals(5, statistics.get("totalMatchers"));
183   - assertEquals(5, statistics.get("enabledMatchers"));
184   - assertNotNull(statistics.get("supportedTriggerTypes"));
185   - assertNotNull(statistics.get("matcherDetails"));
186   - }
187   -
188   - @Test
189 176 void testGetSupportedTriggerTypes() {
190 177 // 1. 执行测试
191 178 var supportedTypes = matcherManager.getSupportedTriggerTypes();
... ...