mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-03-22 05:27:15 +08:00
perf:【IoT 物联网】场景联动优化 review 提到的逻辑
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.iot.enums.rule;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -60,7 +61,9 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
// TODO @puhui999:可以参考下别的枚举哈,方法名,和实现都可以更简洁;of(String type) { firstMatch
|
||||
public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) {
|
||||
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
|
||||
}
|
||||
/**
|
||||
* 根据类型值查找触发器类型枚举
|
||||
*
|
||||
|
||||
@@ -26,16 +26,6 @@ public interface IotSceneRuleMatcher {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// TODO @puhui999:如果目前没自定义,体感可以删除哈;
|
||||
/**
|
||||
* 获取匹配器名称,用于日志和调试
|
||||
*
|
||||
* @return 匹配器名称
|
||||
*/
|
||||
default String getMatcherName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用该匹配器
|
||||
* <p>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
@@ -40,44 +43,99 @@ public final class IotSceneRuleMatcherHelper {
|
||||
* @param paramValue 参数值(来自条件配置)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
|
||||
try {
|
||||
// 1. 校验操作符是否合法
|
||||
IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator);
|
||||
if (operatorEnum == null) {
|
||||
log.warn("[evaluateCondition][存在错误的操作符({})]", operator);
|
||||
log.warn("[evaluateCondition][operator({}) 操作符无效]", operator);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 构建 Spring 表达式变量
|
||||
Map<String, Object> springExpressionVariables = new HashMap<>();
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue);
|
||||
|
||||
// 处理参数值
|
||||
if (StrUtil.isNotBlank(paramValue)) {
|
||||
// 处理多值情况(如 IN、BETWEEN 操作符)
|
||||
// TODO @puhui999:使用这个,会不会有问题?例如说:string 恰好有 , 分隔?
|
||||
if (paramValue.contains(",")) {
|
||||
List<String> paramValues = StrUtil.split(paramValue, ',');
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
|
||||
convertList(paramValues, NumberUtil::parseDouble));
|
||||
} else {
|
||||
// 处理单值情况
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
|
||||
NumberUtil.parseDouble(paramValue));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 计算 Spring 表达式
|
||||
return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables);
|
||||
return evaluateConditionWithOperatorEnum(sourceValue, operatorEnum, paramValue);
|
||||
} catch (Exception e) {
|
||||
log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}",
|
||||
log.error("[evaluateCondition][sourceValue({}) operator({}) paramValue({}) 条件评估异常]",
|
||||
sourceValue, operator, paramValue, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用操作符枚举评估条件是否匹配
|
||||
*
|
||||
* @param sourceValue 源值(来自消息)
|
||||
* @param operatorEnum 操作符枚举
|
||||
* @param paramValue 参数值(来自条件配置)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
public static boolean evaluateConditionWithOperatorEnum(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) {
|
||||
try {
|
||||
// 1. 构建 Spring 表达式变量
|
||||
Map<String, Object> springExpressionVariables = buildSpringExpressionVariables(sourceValue, operatorEnum, paramValue);
|
||||
|
||||
// 2. 计算 Spring 表达式
|
||||
return (Boolean) SpringExpressionUtils.parseExpression(operatorEnum.getSpringExpression(), springExpressionVariables);
|
||||
} catch (Exception e) {
|
||||
log.error("[evaluateConditionWithOperatorEnum][sourceValue({}) operatorEnum({}) paramValue({}) 条件评估异常]",
|
||||
sourceValue, operatorEnum, paramValue, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Spring 表达式变量
|
||||
*/
|
||||
private static Map<String, Object> buildSpringExpressionVariables(Object sourceValue, IotSceneRuleConditionOperatorEnum operatorEnum, String paramValue) {
|
||||
Map<String, Object> springExpressionVariables = new HashMap<>();
|
||||
|
||||
// 设置源值
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue);
|
||||
|
||||
// 处理参数值
|
||||
if (StrUtil.isNotBlank(paramValue)) {
|
||||
List<String> parameterValues = StrUtil.splitTrim(paramValue, CharPool.COMMA);
|
||||
|
||||
// 设置原始参数值
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, paramValue);
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues);
|
||||
|
||||
// 特殊处理:解决数字比较问题
|
||||
// Spring 表达式基于 compareTo 方法,对数字的比较存在问题,需要转换为数字类型
|
||||
if (isNumericComparisonOperator(operatorEnum) && isNumericComparison(sourceValue, parameterValues)) {
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE,
|
||||
NumberUtil.parseDouble(String.valueOf(sourceValue)));
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
|
||||
NumberUtil.parseDouble(paramValue));
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
|
||||
convertList(parameterValues, NumberUtil::parseDouble));
|
||||
}
|
||||
}
|
||||
|
||||
return springExpressionVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数字比较操作符
|
||||
*/
|
||||
private static boolean isNumericComparisonOperator(IotSceneRuleConditionOperatorEnum operatorEnum) {
|
||||
return ObjectUtils.equalsAny(operatorEnum,
|
||||
IotSceneRuleConditionOperatorEnum.BETWEEN,
|
||||
IotSceneRuleConditionOperatorEnum.NOT_BETWEEN,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数字比较场景
|
||||
*/
|
||||
private static boolean isNumericComparison(Object sourceValue, List<String> parameterValues) {
|
||||
return NumberUtil.isNumber(String.valueOf(sourceValue)) && NumberUtils.isAllNumber(parameterValues);
|
||||
}
|
||||
|
||||
// ========== 【触发器】相关工具方法 ==========
|
||||
|
||||
/**
|
||||
@@ -103,24 +161,22 @@ public final class IotSceneRuleMatcherHelper {
|
||||
/**
|
||||
* 记录触发器匹配成功日志
|
||||
*
|
||||
* @param matcherName 匹配器名称
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
*/
|
||||
public static void logTriggerMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
log.debug("[{}][消息({}) 匹配触发器({}) 成功]", matcherName, message.getRequestId(), trigger.getType());
|
||||
public static void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
log.debug("[isMatched][message({}) trigger({}) 匹配触发器成功]", message.getRequestId(), trigger.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录触发器匹配失败日志
|
||||
*
|
||||
* @param matcherName 匹配器名称
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
public static void logTriggerMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
|
||||
log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", matcherName, message.getRequestId(), trigger.getType(), reason);
|
||||
public static void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
|
||||
log.debug("[isMatched][message({}) trigger({}) reason({}) 匹配触发器失败]", message.getRequestId(), trigger.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 【条件】相关工具方法 ==========
|
||||
@@ -148,24 +204,22 @@ public final class IotSceneRuleMatcherHelper {
|
||||
/**
|
||||
* 记录条件匹配成功日志
|
||||
*
|
||||
* @param matcherName 匹配器名称
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
*/
|
||||
public static void logConditionMatchSuccess(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
log.debug("[{}][消息({}) 匹配条件({}) 成功]", matcherName, message.getRequestId(), condition.getType());
|
||||
public static void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
log.debug("[isMatched][message({}) condition({}) 匹配条件成功]", message.getRequestId(), condition.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录条件匹配失败日志
|
||||
*
|
||||
* @param matcherName 匹配器名称
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
public static void logConditionMatchFailure(String matcherName, IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
|
||||
log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", matcherName, message.getRequestId(), condition.getType(), reason);
|
||||
public static void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
|
||||
log.debug("[isMatched][message({}) condition({}) reason({}) 匹配条件失败]", message.getRequestId(), condition.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 【通用】工具方法 ==========
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum.findTriggerTypeEnum;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
/**
|
||||
* IoT 场景规则匹配器统一管理器
|
||||
@@ -72,42 +72,34 @@ public class IotSceneRuleMatcherManager {
|
||||
.toList();
|
||||
|
||||
// 构建触发器匹配器映射表
|
||||
// TODO @puhui999:convertMap()
|
||||
this.triggerMatchers = triggerMatchers.stream()
|
||||
.collect(Collectors.toMap(
|
||||
IotSceneRuleTriggerMatcher::getSupportedTriggerType,
|
||||
Function.identity(),
|
||||
(existing, replacement) -> {
|
||||
log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]",
|
||||
existing.getSupportedTriggerType(),
|
||||
existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
|
||||
return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
|
||||
},
|
||||
LinkedHashMap::new
|
||||
));
|
||||
this.triggerMatchers = convertMap(triggerMatchers, IotSceneRuleTriggerMatcher::getSupportedTriggerType,
|
||||
Function.identity(),
|
||||
(existing, replacement) -> {
|
||||
log.warn("[IotSceneRuleMatcherManager][触发器类型({})存在多个匹配器,使用优先级更高的: {}]",
|
||||
existing.getSupportedTriggerType(),
|
||||
existing.getPriority() <= replacement.getPriority() ?
|
||||
existing.getSupportedTriggerType() : replacement.getSupportedTriggerType());
|
||||
return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
|
||||
}, LinkedHashMap::new);
|
||||
// 构建条件匹配器映射表
|
||||
this.conditionMatchers = conditionMatchers.stream()
|
||||
.collect(Collectors.toMap(
|
||||
IotSceneRuleConditionMatcher::getSupportedConditionType,
|
||||
Function.identity(),
|
||||
(existing, replacement) -> {
|
||||
log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]",
|
||||
existing.getSupportedConditionType(),
|
||||
existing.getPriority() <= replacement.getPriority() ? existing.getMatcherName() : replacement.getMatcherName());
|
||||
return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
|
||||
},
|
||||
LinkedHashMap::new
|
||||
));
|
||||
this.conditionMatchers = convertMap(conditionMatchers, IotSceneRuleConditionMatcher::getSupportedConditionType,
|
||||
Function.identity(),
|
||||
(existing, replacement) -> {
|
||||
log.warn("[IotSceneRuleMatcherManager][条件类型({})存在多个匹配器,使用优先级更高的: {}]",
|
||||
existing.getSupportedConditionType(),
|
||||
existing.getPriority() <= replacement.getPriority() ?
|
||||
existing.getSupportedConditionType() : replacement.getSupportedConditionType());
|
||||
return existing.getPriority() <= replacement.getPriority() ? existing : replacement;
|
||||
},
|
||||
LinkedHashMap::new);
|
||||
|
||||
// 日志输出初始化信息
|
||||
log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]",
|
||||
log.info("[IotSceneRuleMatcherManager][初始化完成,共加载({})个匹配器,其中触发器匹配器({})个,条件匹配器({})个]",
|
||||
this.allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size());
|
||||
this.triggerMatchers.forEach((type, matcher) ->
|
||||
log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
|
||||
type, matcher.getMatcherName(), matcher.getPriority()));
|
||||
log.info("[IotSceneRuleMatcherManager][触发器匹配器类型: ({}), 优先级: ({})] ", type, matcher.getPriority()));
|
||||
this.conditionMatchers.forEach((type, matcher) ->
|
||||
log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
|
||||
type, matcher.getMatcherName(), matcher.getPriority()));
|
||||
log.info("[IotSceneRuleMatcherManager][条件匹配器类型: ({}), 优先级: ({})]", type, matcher.getPriority()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,27 +110,25 @@ public class IotSceneRuleMatcherManager {
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// TODO @puhui999:日志优化下;claude 打出来的日志风格,和项目有点不一样哈;
|
||||
if (message == null || trigger == null || trigger.getType() == null) {
|
||||
log.debug("[isMatched][参数无效] message: {}, trigger: {}", message, trigger);
|
||||
log.debug("[isMatched][message({}) trigger({}) 参数无效]", message, trigger);
|
||||
return false;
|
||||
}
|
||||
IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType());
|
||||
IotSceneRuleTriggerTypeEnum triggerType = IotSceneRuleTriggerTypeEnum.typeOf(trigger.getType());
|
||||
if (triggerType == null) {
|
||||
log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType());
|
||||
log.warn("[isMatched][triggerType({}) 未知的触发器类型]", trigger.getType());
|
||||
return false;
|
||||
}
|
||||
IotSceneRuleTriggerMatcher matcher = triggerMatchers.get(triggerType);
|
||||
if (matcher == null) {
|
||||
log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType);
|
||||
log.warn("[isMatched][triggerType({}) 没有对应的匹配器]", triggerType);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return matcher.isMatched(message, trigger);
|
||||
} catch (Exception e) {
|
||||
log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}, matcher: {}",
|
||||
message, trigger, matcher.getMatcherName(), e);
|
||||
log.error("[isMatched][触发器匹配异常] message: {}, trigger: {}", message, trigger, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -152,28 +142,27 @@ public class IotSceneRuleMatcherManager {
|
||||
*/
|
||||
public boolean isConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
if (message == null || condition == null || condition.getType() == null) {
|
||||
log.debug("[isConditionMatched][参数无效] message: {}, condition: {}", message, condition);
|
||||
log.debug("[isConditionMatched][message({}) condition({}) 参数无效]", message, condition);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据条件类型查找对应的匹配器
|
||||
IotSceneRuleConditionTypeEnum conditionType = findConditionTypeEnum(condition.getType());
|
||||
if (conditionType == null) {
|
||||
log.warn("[isConditionMatched][未知的条件类型: {}]", condition.getType());
|
||||
log.warn("[isConditionMatched][conditionType({}) 未知的条件类型]", condition.getType());
|
||||
return false;
|
||||
}
|
||||
|
||||
IotSceneRuleConditionMatcher matcher = conditionMatchers.get(conditionType);
|
||||
if (matcher == null) {
|
||||
log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType);
|
||||
log.warn("[isConditionMatched][conditionType({}) 没有对应的匹配器]", conditionType);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return matcher.isMatched(message, condition);
|
||||
} catch (Exception e) {
|
||||
log.error("[isConditionMatched][条件匹配异常] message: {}, condition: {}, matcher: {}",
|
||||
message, condition, matcher.getMatcherName(), e);
|
||||
log.error("[isConditionMatched][message({}) condition({}) 条件匹配异常]", message, condition, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -11,6 +14,7 @@ import org.springframework.stereotype.Component;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 当前时间条件匹配器
|
||||
@@ -40,115 +44,175 @@ public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查操作符和参数是否有效
|
||||
// 1.2 检查操作符和参数是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 根据操作符类型进行不同的时间匹配
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 1.3 验证操作符是否为支持的时间操作符
|
||||
String operator = condition.getOperator();
|
||||
String param = condition.getParam();
|
||||
boolean matched;
|
||||
try {
|
||||
if (operator.startsWith("date_time_")) {
|
||||
// 日期时间匹配(时间戳)
|
||||
matched = matchDateTime(now, operator, param);
|
||||
} else if (operator.startsWith("time_")) {
|
||||
// 当日时间匹配(HH:mm:ss)
|
||||
matched = matchTime(now.toLocalTime(), operator, param);
|
||||
} else {
|
||||
// 其他操作符,使用通用条件评估器
|
||||
matched = IotSceneRuleMatcherHelper.evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件不匹配");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e);
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "时间条件匹配异常: " + e.getMessage());
|
||||
matched = false;
|
||||
IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator);
|
||||
if (operatorEnum == null) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "无效的操作符: " + operator);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isTimeOperator(operatorEnum)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "不支持的时间操作符: " + operator);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 执行时间匹配
|
||||
boolean matched = executeTimeMatching(operatorEnum, condition.getParam());
|
||||
|
||||
// 2.2 记录匹配结果
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "时间条件不匹配");
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配日期时间(时间戳)
|
||||
* 执行时间匹配逻辑
|
||||
* 直接实现时间条件匹配,不使用 Spring EL 表达式
|
||||
*/
|
||||
private boolean matchDateTime(LocalDateTime now, String operator, String param) {
|
||||
long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
|
||||
return IotSceneRuleMatcherHelper.evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配当日时间(HH:mm:ss)
|
||||
*/
|
||||
private boolean matchTime(LocalTime currentTime, String operator, String param) {
|
||||
private boolean executeTimeMatching(IotSceneRuleConditionOperatorEnum operatorEnum, String param) {
|
||||
try {
|
||||
String actualOperator = operator.substring("time_".length());
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// TODO @puhui999:if return 简化;
|
||||
if ("between".equals(actualOperator)) {
|
||||
// 时间区间匹配
|
||||
String[] timeRange = param.split(",");
|
||||
if (timeRange.length != 2) {
|
||||
return false;
|
||||
}
|
||||
LocalTime startTime = parseTime(timeRange[0].trim());
|
||||
LocalTime endTime = parseTime(timeRange[1].trim());
|
||||
return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
|
||||
if (isDateTimeOperator(operatorEnum)) {
|
||||
// 日期时间匹配(时间戳)
|
||||
long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
|
||||
return matchDateTime(currentTimestamp, operatorEnum, param);
|
||||
} else {
|
||||
// 单个时间比较
|
||||
LocalTime targetTime = parseTime(param);
|
||||
// TODO @puhui999:枚举类;
|
||||
switch (actualOperator) {
|
||||
case ">":
|
||||
return currentTime.isAfter(targetTime);
|
||||
case "<":
|
||||
return currentTime.isBefore(targetTime);
|
||||
case ">=":
|
||||
return !currentTime.isBefore(targetTime);
|
||||
case "<=":
|
||||
return !currentTime.isAfter(targetTime);
|
||||
case "=":
|
||||
return currentTime.equals(targetTime);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// 当日时间匹配(HH:mm:ss)
|
||||
return matchTime(now.toLocalTime(), operatorEnum, param);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO @puhui999:1)日志格式 [][];2)方法名不对哈;
|
||||
log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e);
|
||||
log.error("[executeTimeMatching][operatorEnum({}) param({}) 时间匹配异常]", operatorEnum, param, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为日期时间操作符
|
||||
*/
|
||||
private boolean isDateTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) {
|
||||
return operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN ||
|
||||
operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN ||
|
||||
operatorEnum == IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为时间操作符
|
||||
*/
|
||||
private boolean isTimeOperator(IotSceneRuleConditionOperatorEnum operatorEnum) {
|
||||
return operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN ||
|
||||
operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN ||
|
||||
operatorEnum == IotSceneRuleConditionOperatorEnum.TIME_BETWEEN ||
|
||||
isDateTimeOperator(operatorEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配日期时间(时间戳)
|
||||
* 直接实现时间戳比较逻辑
|
||||
*/
|
||||
private boolean matchDateTime(long currentTimestamp, IotSceneRuleConditionOperatorEnum operatorEnum, String param) {
|
||||
try {
|
||||
long targetTimestamp = Long.parseLong(param);
|
||||
return switch (operatorEnum) {
|
||||
case DATE_TIME_GREATER_THAN -> currentTimestamp > targetTimestamp;
|
||||
case DATE_TIME_LESS_THAN -> currentTimestamp < targetTimestamp;
|
||||
case DATE_TIME_BETWEEN -> matchDateTimeBetween(currentTimestamp, param);
|
||||
default -> {
|
||||
log.warn("[matchDateTime][operatorEnum({}) 不支持的日期时间操作符]", operatorEnum);
|
||||
yield false;
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
log.error("[matchDateTime][operatorEnum({}) param({}) 日期时间匹配异常]", operatorEnum, param, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配日期时间区间
|
||||
*/
|
||||
private boolean matchDateTimeBetween(long currentTimestamp, String param) {
|
||||
List<String> timestampRange = StrUtil.splitTrim(param, CharPool.COMMA);
|
||||
if (timestampRange.size() != 2) {
|
||||
log.warn("[matchDateTimeBetween][param({}) 时间戳区间参数格式错误]", param);
|
||||
return false;
|
||||
}
|
||||
long startTimestamp = Long.parseLong(timestampRange.get(0).trim());
|
||||
long endTimestamp = Long.parseLong(timestampRange.get(1).trim());
|
||||
return currentTimestamp >= startTimestamp && currentTimestamp <= endTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配当日时间(HH:mm:ss)
|
||||
* 直接实现时间比较逻辑
|
||||
*/
|
||||
private boolean matchTime(LocalTime currentTime, IotSceneRuleConditionOperatorEnum operatorEnum, String param) {
|
||||
try {
|
||||
LocalTime targetTime = parseTime(param);
|
||||
return switch (operatorEnum) {
|
||||
case TIME_GREATER_THAN -> currentTime.isAfter(targetTime);
|
||||
case TIME_LESS_THAN -> currentTime.isBefore(targetTime);
|
||||
case TIME_BETWEEN -> matchTimeBetween(currentTime, param);
|
||||
default -> {
|
||||
log.warn("[matchTime][operatorEnum({}) 不支持的时间操作符]", operatorEnum);
|
||||
yield false;
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
log.error("[matchTime][][operatorEnum({}) param({}) 时间解析异常]", operatorEnum, param, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配时间区间
|
||||
*/
|
||||
private boolean matchTimeBetween(LocalTime currentTime, String param) {
|
||||
List<String> timeRange = StrUtil.splitTrim(param, CharPool.COMMA);
|
||||
if (timeRange.size() != 2) {
|
||||
log.warn("[matchTimeBetween][param({}) 时间区间参数格式错误]", param);
|
||||
return false;
|
||||
}
|
||||
LocalTime startTime = parseTime(timeRange.get(0).trim());
|
||||
LocalTime endTime = parseTime(timeRange.get(1).trim());
|
||||
return !currentTime.isBefore(startTime) && !currentTime.isAfter(endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析时间字符串
|
||||
* 支持 HH:mm 和 HH:mm:ss 两种格式
|
||||
*/
|
||||
private LocalTime parseTime(String timeStr) {
|
||||
// TODO @puhui999:可以用 hutool Assert 类简化
|
||||
if (StrUtil.isBlank(timeStr)) {
|
||||
throw new IllegalArgumentException("时间字符串不能为空");
|
||||
}
|
||||
// 尝试不同的时间格式
|
||||
Assert.isFalse(StrUtil.isBlank(timeStr), "时间字符串不能为空");
|
||||
|
||||
try {
|
||||
// 尝试不同的时间格式
|
||||
if (timeStr.length() == 5) { // HH:mm
|
||||
return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT);
|
||||
} else { // HH:mm:ss
|
||||
} else if (timeStr.length() == 8) { // HH:mm:ss
|
||||
return LocalTime.parse(timeStr, TIME_FORMATTER);
|
||||
} else {
|
||||
throw new IllegalArgumentException("时间格式长度不正确,期望 HH:mm 或 HH:mm:ss 格式");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[parseTime][timeStr({}) 时间格式解析失败]", timeStr, e);
|
||||
throw new IllegalArgumentException("时间格式无效: " + timeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,41 +22,40 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc
|
||||
return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY;
|
||||
}
|
||||
|
||||
// TODO @puhui999:参数校验的,要不要 1.1 1.2 1.3 1.4 ?这样最终看到 2. 3. 就是核心逻辑列;
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查标识符是否匹配
|
||||
// 1.2 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查操作符和参数是否有效
|
||||
// 1.3 检查操作符和参数是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 获取属性值
|
||||
Object propertyValue = message.getData();
|
||||
// 2.1. 获取属性值
|
||||
Object propertyValue = message.getParams();
|
||||
if (propertyValue == null) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中属性值为空");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 使用条件评估器进行匹配
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, condition.getOperator(), condition.getParam());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition);
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "设备属性条件不匹配");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备属性条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@@ -23,32 +23,31 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "条件基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查操作符和参数是否有效
|
||||
// 1.2 检查操作符和参数是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "操作符或参数无效");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 获取设备状态值
|
||||
// 设备状态通常在消息的 data 字段中
|
||||
Object stateValue = message.getData();
|
||||
// 2.1 获取设备状态值
|
||||
Object stateValue = message.getParams();
|
||||
if (stateValue == null) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "消息中设备状态值为空");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 使用条件评估器进行匹配
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, condition.getOperator(), condition.getParam());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(getMatcherName(), message, condition);
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(getMatcherName(), message, condition, "设备状态条件不匹配");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备状态条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
/**
|
||||
* 设备事件上报消息方法
|
||||
*/
|
||||
private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod();
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST;
|
||||
@@ -31,42 +26,44 @@ public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod());
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.EVENT_POST.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " +
|
||||
IotDeviceMessageMethodEnum.EVENT_POST.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
// 1.3 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " +
|
||||
trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
|
||||
// 2. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
|
||||
// 但如果配置了操作符和值,则需要进行条件匹配
|
||||
if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) {
|
||||
Object eventData = message.getData();
|
||||
if (eventData == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中事件数据为空");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中事件数据为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(eventData, trigger.getOperator(), trigger.getValue());
|
||||
if (!matched) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "事件数据条件不匹配");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "事件数据条件不匹配");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,6 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
/**
|
||||
* 设备属性上报消息方法
|
||||
*/
|
||||
// TODO @puhui999:是不是不用枚举哈?直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()
|
||||
private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod();
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST;
|
||||
@@ -31,44 +25,46 @@ public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatc
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod());
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " +
|
||||
IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
// 1.3 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " +
|
||||
trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 检查操作符和值是否有效
|
||||
// 1.4 检查操作符和值是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 获取属性值
|
||||
Object propertyValue = message.getData();
|
||||
// 2.1 获取属性值
|
||||
Object propertyValue = message.getParams();
|
||||
if (propertyValue == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中属性值为空");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. 使用条件评估器进行匹配
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "属性值条件不匹配");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "属性值条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,6 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
/**
|
||||
* 设备服务调用消息方法
|
||||
*/
|
||||
private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod();
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
|
||||
@@ -30,29 +25,29 @@ public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMat
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod());
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
// 1.3 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 对于服务调用触发器,通常只需要匹配服务标识符即可
|
||||
// 2. 对于服务调用触发器,通常只需要匹配服务标识符即可
|
||||
// 不需要检查操作符和值,因为服务调用本身就是触发条件
|
||||
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
|
||||
// TODO @puhui999: 服务调用时校验输入参数是否匹配条件
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,6 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
// TODO @puhui999:是不是不用枚举哈;
|
||||
/**
|
||||
* 设备状态更新消息方法
|
||||
*/
|
||||
private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod();
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE;
|
||||
@@ -30,37 +24,39 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod());
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " +
|
||||
IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查操作符和值是否有效
|
||||
// 1.3 检查操作符和值是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "操作符或值无效");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 获取设备状态值
|
||||
Object stateValue = message.getData();
|
||||
// 2.1 获取设备状态值
|
||||
Object stateValue = message.getParams();
|
||||
if (stateValue == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "消息中设备状态值为空");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 使用条件评估器进行匹配
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
// TODO @puhui999: 状态匹配重新实现
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "状态值条件不匹配");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "状态值条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
|
||||
import org.quartz.CronExpression;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -25,58 +26,32 @@ public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "触发器基础参数无效");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查 CRON 表达式是否存在
|
||||
// 1.2 检查 CRON 表达式是否存在
|
||||
if (StrUtil.isBlank(trigger.getCronExpression())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "定时触发器缺少 CRON 表达式");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 定时触发器通常不依赖具体的设备消息
|
||||
// 1.3 定时触发器通常不依赖具体的设备消息
|
||||
// 它是通过定时任务调度器触发的,这里主要是验证配置的有效性
|
||||
|
||||
// 4. 可以添加 CRON 表达式格式验证
|
||||
if (!isValidCronExpression(trigger.getCronExpression())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(getMatcherName(), message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
|
||||
if (!CronExpression.isValidExpression(trigger.getCronExpression())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
|
||||
return false;
|
||||
}
|
||||
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(getMatcherName(), message, trigger);
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 CRON 表达式格式是否有效
|
||||
*
|
||||
* @param cronExpression CRON 表达式
|
||||
* @return 是否有效
|
||||
*/
|
||||
private boolean isValidCronExpression(String cronExpression) {
|
||||
// TODO @puhui999:CronExpression.isValidExpression(cronExpression);
|
||||
try {
|
||||
// 简单的 CRON 表达式格式验证
|
||||
// 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年])
|
||||
String[] fields = cronExpression.trim().split("\\s+");
|
||||
return fields.length >= 6 && fields.length <= 7;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 50; // 最低优先级,因为定时触发器不依赖消息
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
// 定时触发器可以根据配置动态启用/禁用
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -172,19 +172,6 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
assertFalse(matched, "无效的触发器类型应该不匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMatcherManagerStatistics() {
|
||||
// 1. 执行测试
|
||||
var statistics = matcherManager.getMatcherStatistics();
|
||||
|
||||
// 2. 验证结果
|
||||
assertNotNull(statistics);
|
||||
assertEquals(5, statistics.get("totalMatchers"));
|
||||
assertEquals(5, statistics.get("enabledMatchers"));
|
||||
assertNotNull(statistics.get("supportedTriggerTypes"));
|
||||
assertNotNull(statistics.get("matcherDetails"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSupportedTriggerTypes() {
|
||||
// 1. 执行测试
|
||||
|
||||
Reference in New Issue
Block a user