mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-03-22 05:27:15 +08:00
Merge remote-tracking branch 'origin/feature/iot' into feature/iot
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,18 +61,8 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
// TODO @puhui999:可以参考下别的枚举哈,方法名,和实现都可以更简洁;of(String type) { firstMatch
|
||||
/**
|
||||
* 根据类型值查找触发器类型枚举
|
||||
*
|
||||
* @param typeValue 类型值
|
||||
* @return 触发器类型枚举
|
||||
*/
|
||||
public static IotSceneRuleTriggerTypeEnum findTriggerTypeEnum(Integer typeValue) {
|
||||
return Arrays.stream(IotSceneRuleTriggerTypeEnum.values())
|
||||
.filter(type -> type.getType().equals(typeValue))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
public static IotSceneRuleTriggerTypeEnum typeOf(Integer type) {
|
||||
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,18 +2,11 @@ package cn.iocoder.yudao.module.iot.service.rule.scene;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.module.iot.controller.admin.rule.vo.scene.IotSceneRulePageReqVO;
|
||||
@@ -23,7 +16,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotSceneRuleMapper;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
@@ -36,12 +28,9 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.RULE_SCENE_NOT_EXISTS;
|
||||
|
||||
@@ -353,65 +342,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @puhui999:下面还需要么?
|
||||
/**
|
||||
* 判断触发器的条件参数是否匹配
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param sceneRule 规则场景(用于日志,无其它作用)
|
||||
* @param trigger 触发器(用于日志,无其它作用)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "DataFlowIssue"})
|
||||
private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
|
||||
IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 校验操作符是否合法
|
||||
IotSceneRuleConditionOperatorEnum operator =
|
||||
IotSceneRuleConditionOperatorEnum.operatorOf(condition.getOperator());
|
||||
if (operator == null) {
|
||||
log.error("[isTriggerConditionParameterMatched][规则场景编号({}) 的触发器({}) 存在错误的操作符({})]",
|
||||
sceneRule.getId(), trigger, condition.getOperator());
|
||||
return false;
|
||||
}
|
||||
// 1.2 校验消息是否包含对应的值
|
||||
String messageValue = MapUtil.getStr((Map<String, Object>) message.getData(), condition.getIdentifier());
|
||||
if (messageValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 构建 Spring 表达式的变量
|
||||
Map<String, Object> springExpressionVariables = new HashMap<>();
|
||||
try {
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, messageValue);
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE, condition.getParam());
|
||||
List<String> parameterValues = StrUtil.splitTrim(condition.getParam(), CharPool.COMMA);
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST, parameterValues);
|
||||
// 特殊:解决数字的比较。因为 Spring 是基于它的 compareTo 方法,对数字的比较存在问题!
|
||||
if (ObjectUtils.equalsAny(operator, IotSceneRuleConditionOperatorEnum.BETWEEN,
|
||||
IotSceneRuleConditionOperatorEnum.NOT_BETWEEN,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS)
|
||||
&& NumberUtil.isNumber(messageValue)
|
||||
&& NumberUtils.isAllNumber(parameterValues)) {
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE,
|
||||
NumberUtil.parseDouble(messageValue));
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE,
|
||||
NumberUtil.parseDouble(condition.getParam()));
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_VALUE_LIST,
|
||||
convertList(parameterValues, NumberUtil::parseDouble));
|
||||
}
|
||||
// 2.2 计算 Spring 表达式
|
||||
return (Boolean) SpringExpressionUtils.parseExpression(operator.getSpringExpression(), springExpressionVariables);
|
||||
} catch (Exception e) {
|
||||
log.error("[isTriggerConditionParameterMatched][消息({}) 规则场景编号({}) 的触发器({}) 的匹配表达式({}/{}) 计算异常]",
|
||||
message, sceneRule.getId(), trigger, operator, springExpressionVariables, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行规则场景的动作
|
||||
*
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* IoT 场景规则匹配器抽象基类
|
||||
* <p>
|
||||
* 提供通用的条件评估逻辑和工具方法,支持触发器和条件两种匹配类型
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher {
|
||||
|
||||
// TODO @puhui999:这个是不是也是【通用】条件哈?
|
||||
|
||||
/**
|
||||
* 评估条件是否匹配
|
||||
*
|
||||
* @param sourceValue 源值(来自消息)
|
||||
* @param operator 操作符
|
||||
* @param paramValue 参数值(来自条件配置)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
|
||||
try {
|
||||
// 1. 校验操作符是否合法
|
||||
IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator);
|
||||
if (operatorEnum == null) {
|
||||
log.warn("[evaluateCondition][存在错误的操作符({})]", 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);
|
||||
} catch (Exception e) {
|
||||
log.error("[evaluateCondition][条件评估异常] sourceValue: {}, operator: {}, paramValue: {}",
|
||||
sourceValue, operator, paramValue, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 【触发器】相关工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查基础触发器参数是否有效
|
||||
*
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) {
|
||||
return trigger != null && trigger.getType() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器操作符和值是否有效
|
||||
*
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) {
|
||||
return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录触发器匹配成功日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
*/
|
||||
protected void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
log.debug("[{}][消息({}) 匹配触发器({}) 成功]", getMatcherName(), message.getRequestId(), trigger.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录触发器匹配失败日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
protected void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
|
||||
log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 【条件】相关工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查基础条件参数是否有效
|
||||
*
|
||||
* @param condition 触发条件
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) {
|
||||
return condition != null && condition.getType() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查条件操作符和参数是否有效
|
||||
*
|
||||
* @param condition 触发条件
|
||||
* @return 是否有效
|
||||
*/
|
||||
protected boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) {
|
||||
return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录条件匹配成功日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
*/
|
||||
protected void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
log.debug("[{}][消息({}) 匹配条件({}) 成功]", getMatcherName(), message.getRequestId(), condition.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录条件匹配失败日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
protected void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
|
||||
log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 【通用】工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查标识符是否匹配
|
||||
*
|
||||
* @param expectedIdentifier 期望的标识符
|
||||
* @param actualIdentifier 实际的标识符
|
||||
* @return 是否匹配
|
||||
*/
|
||||
protected boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) {
|
||||
return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
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.IotSceneRuleConditionTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 当前时间条件匹配器
|
||||
* <p>
|
||||
* 处理时间相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 时间格式化器 - HH:mm:ss
|
||||
*/
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 时间格式化器 - HH:mm
|
||||
*/
|
||||
private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.CONDITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.CURRENT_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicConditionValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查操作符和参数是否有效
|
||||
if (!isConditionOperatorAndParamValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 根据操作符类型进行不同的时间匹配
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
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 = evaluateCondition(now.toEpochSecond(java.time.ZoneOffset.of("+8")), operator, param);
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
logConditionMatchFailure(message, condition, "时间条件不匹配");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e);
|
||||
logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage());
|
||||
matched = false;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配日期时间(时间戳)
|
||||
*/
|
||||
private boolean matchDateTime(LocalDateTime now, String operator, String param) {
|
||||
long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
|
||||
return evaluateCondition(currentTimestamp, operator.substring("date_time_".length()), param);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配当日时间(HH:mm:ss)
|
||||
*/
|
||||
private boolean matchTime(LocalTime currentTime, String operator, String param) {
|
||||
try {
|
||||
String actualOperator = operator.substring("time_".length());
|
||||
|
||||
// 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);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO @puhui999:1)日志格式 [][];2)方法名不对哈;
|
||||
log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析时间字符串
|
||||
*/
|
||||
private LocalTime parseTime(String timeStr) {
|
||||
// TODO @puhui999:可以用 hutool Assert 类简化
|
||||
if (StrUtil.isBlank(timeStr)) {
|
||||
throw new IllegalArgumentException("时间字符串不能为空");
|
||||
}
|
||||
// 尝试不同的时间格式
|
||||
try {
|
||||
if (timeStr.length() == 5) { // HH:mm
|
||||
return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT);
|
||||
} else { // HH:mm:ss
|
||||
return LocalTime.parse(timeStr, TIME_FORMATTER);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("时间格式无效: " + timeStr, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 40; // 较低优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备事件上报触发器匹配器
|
||||
* <p>
|
||||
* 处理设备事件上报的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceEventPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 设备事件上报消息方法
|
||||
*/
|
||||
private static final String DEVICE_EVENT_POST_METHOD = IotDeviceMessageMethodEnum.EVENT_POST.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_EVENT_POST_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_EVENT_POST_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
|
||||
// 但如果配置了操作符和值,则需要进行条件匹配
|
||||
if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) {
|
||||
Object eventData = message.getData();
|
||||
if (eventData == null) {
|
||||
logTriggerMatchFailure(message, trigger, "消息中事件数据为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean matched = evaluateCondition(eventData, trigger.getOperator(), trigger.getValue());
|
||||
if (!matched) {
|
||||
logTriggerMatchFailure(message, trigger, "事件数据条件不匹配");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 30; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备属性条件匹配器
|
||||
* <p>
|
||||
* 处理设备属性相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.CONDITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
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. 基础参数校验
|
||||
if (!isBasicConditionValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) {
|
||||
logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查操作符和参数是否有效
|
||||
if (!isConditionOperatorAndParamValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 获取属性值
|
||||
Object propertyValue = message.getData();
|
||||
if (propertyValue == null) {
|
||||
logConditionMatchFailure(message, condition, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam());
|
||||
if (matched) {
|
||||
logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
logConditionMatchFailure(message, condition, "设备属性条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 25; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备属性上报触发器匹配器
|
||||
* <p>
|
||||
* 处理设备属性数据上报的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 设备属性上报消息方法
|
||||
*/
|
||||
// TODO @puhui999:是不是不用枚举哈?直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()
|
||||
private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_PROPERTY_POST_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_PROPERTY_POST_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 检查操作符和值是否有效
|
||||
if (!isTriggerOperatorAndValueValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 获取属性值
|
||||
Object propertyValue = message.getData();
|
||||
if (propertyValue == null) {
|
||||
logTriggerMatchFailure(message, trigger, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
|
||||
if (matched) {
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
logTriggerMatchFailure(message, trigger, "属性值条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 20; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备服务调用触发器匹配器
|
||||
* <p>
|
||||
* 处理设备服务调用的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceServiceInvokeTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 设备服务调用消息方法
|
||||
*/
|
||||
private static final String DEVICE_SERVICE_INVOKE_METHOD = IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_SERVICE_INVOKE_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_SERVICE_INVOKE_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 对于服务调用触发器,通常只需要匹配服务标识符即可
|
||||
// 不需要检查操作符和值,因为服务调用本身就是触发条件
|
||||
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 40; // 较低优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
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.IotSceneRuleConditionTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备状态条件匹配器
|
||||
* <p>
|
||||
* 处理设备状态相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.CONDITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.DEVICE_STATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicConditionValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查操作符和参数是否有效
|
||||
if (!isConditionOperatorAndParamValid(condition)) {
|
||||
logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 获取设备状态值
|
||||
// 设备状态通常在消息的 data 字段中
|
||||
Object stateValue = message.getData();
|
||||
if (stateValue == null) {
|
||||
logConditionMatchFailure(message, condition, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam());
|
||||
if (matched) {
|
||||
logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
logConditionMatchFailure(message, condition, "设备状态条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 30; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
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 org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备状态更新触发器匹配器
|
||||
* <p>
|
||||
* 处理设备上下线状态变更的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
// TODO @puhui999:是不是不用枚举哈;
|
||||
/**
|
||||
* 设备状态更新消息方法
|
||||
*/
|
||||
private static final String DEVICE_STATE_UPDATE_METHOD = IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod();
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查消息方法是否匹配
|
||||
if (!DEVICE_STATE_UPDATE_METHOD.equals(message.getMethod())) {
|
||||
logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + DEVICE_STATE_UPDATE_METHOD + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 检查操作符和值是否有效
|
||||
if (!isTriggerOperatorAndValueValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 获取设备状态值
|
||||
Object stateValue = message.getData();
|
||||
if (stateValue == null) {
|
||||
logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. 使用条件评估器进行匹配
|
||||
boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
|
||||
if (matched) {
|
||||
logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
logTriggerMatchFailure(message, trigger, "状态值条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 10; // 高优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,93 +1,20 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
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.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher;
|
||||
|
||||
/**
|
||||
* IoT 场景规则匹配器统一接口
|
||||
* IoT 场景规则匹配器基础接口
|
||||
* <p>
|
||||
* 支持触发器匹配和条件匹配两种类型,遵循策略模式设计
|
||||
* 定义所有匹配器的通用行为,包括优先级、名称和启用状态
|
||||
* <p>
|
||||
* 匹配器类型说明:
|
||||
* - 触发器匹配器:用于匹配主触发条件(如设备消息类型、定时器等)
|
||||
* - 条件匹配器:用于匹配子条件(如设备状态、属性值、时间条件等)
|
||||
* - {@link IotSceneRuleTriggerMatcher} 触发器匹配器
|
||||
* - {@link IotSceneRuleConditionMatcher} 条件匹配器
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public interface IotSceneRuleMatcher {
|
||||
|
||||
// TODO @puhui999:MatcherTypeEnum;
|
||||
// TODO @puhui999:可以考虑根据类型,新建 trigger、condition 包,然后把对应的实现类放进去哈;
|
||||
/**
|
||||
* 匹配器类型枚举
|
||||
*/
|
||||
enum MatcherType {
|
||||
|
||||
/**
|
||||
* 触发器匹配器 - 用于匹配主触发条件
|
||||
*/
|
||||
TRIGGER,
|
||||
|
||||
/**
|
||||
* 条件匹配器 - 用于匹配子条件
|
||||
*/
|
||||
CONDITION
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配器类型
|
||||
*
|
||||
* @return 匹配器类型
|
||||
*/
|
||||
MatcherType getMatcherType();
|
||||
|
||||
// TODO @puhui999:【重要】有个思路,IotSceneRuleMatcher 拆分成 2 种 mather 接口;然后 AbstractIotSceneRuleMatcher 是个 Helper 工具类;
|
||||
|
||||
// TODO @puhui999:是不是和 AbstractSceneRuleMatcher 一样,分下块;
|
||||
|
||||
/**
|
||||
* 获取支持的触发器类型(仅触发器匹配器需要实现)
|
||||
*
|
||||
* @return 触发器类型枚举,条件匹配器返回 null
|
||||
*/
|
||||
default IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的条件类型(仅条件匹配器需要实现)
|
||||
*
|
||||
* @return 条件类型枚举,触发器匹配器返回 null
|
||||
*/
|
||||
default IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器是否匹配消息(仅触发器匹配器需要实现)
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否匹配
|
||||
*/
|
||||
default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
throw new UnsupportedOperationException("触发器匹配方法仅支持触发器匹配器");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查条件是否匹配消息(仅条件匹配器需要实现)
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @return 是否匹配
|
||||
*/
|
||||
default boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
throw new UnsupportedOperationException("条件匹配方法仅支持条件匹配器");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配优先级(数值越小优先级越高)
|
||||
* <p>
|
||||
@@ -99,16 +26,6 @@ public interface IotSceneRuleMatcher {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// TODO @puhui999:如果目前没自定义,体感可以删除哈;
|
||||
/**
|
||||
* 获取匹配器名称,用于日志和调试
|
||||
*
|
||||
* @return 匹配器名称
|
||||
*/
|
||||
default String getMatcherName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否启用该匹配器
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
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;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* IoT 场景规则匹配器工具类
|
||||
* <p>
|
||||
* 提供通用的条件评估逻辑和工具方法,供触发器和条件匹配器使用
|
||||
* <p>
|
||||
* 该类包含了匹配器实现中常用的工具方法,如条件评估、参数校验、日志记录等
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Slf4j
|
||||
public final class IotSceneRuleMatcherHelper {
|
||||
|
||||
/**
|
||||
* 私有构造函数,防止实例化
|
||||
*/
|
||||
private IotSceneRuleMatcherHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 评估条件是否匹配
|
||||
*
|
||||
* @param sourceValue 源值(来自消息)
|
||||
* @param operator 操作符
|
||||
* @param paramValue 参数值(来自条件配置)
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
|
||||
try {
|
||||
// 1. 校验操作符是否合法
|
||||
IotSceneRuleConditionOperatorEnum operatorEnum = IotSceneRuleConditionOperatorEnum.operatorOf(operator);
|
||||
if (operatorEnum == null) {
|
||||
log.warn("[evaluateCondition][operator({}) 操作符无效]", operator);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 构建 Spring 表达式变量
|
||||
return evaluateConditionWithOperatorEnum(sourceValue, operatorEnum, paramValue);
|
||||
} catch (Exception e) {
|
||||
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);
|
||||
}
|
||||
|
||||
// ========== 【触发器】相关工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查基础触发器参数是否有效
|
||||
*
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isBasicTriggerValid(IotSceneRuleDO.Trigger trigger) {
|
||||
return trigger != null && trigger.getType() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查触发器操作符和值是否有效
|
||||
*
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isTriggerOperatorAndValueValid(IotSceneRuleDO.Trigger trigger) {
|
||||
return StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录触发器匹配成功日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
*/
|
||||
public static void logTriggerMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
log.debug("[isMatched][message({}) trigger({}) 匹配触发器成功]", message.getRequestId(), trigger.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录触发器匹配失败日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
public static void logTriggerMatchFailure(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger, String reason) {
|
||||
log.debug("[isMatched][message({}) trigger({}) reason({}) 匹配触发器失败]", message.getRequestId(), trigger.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 【条件】相关工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查基础条件参数是否有效
|
||||
*
|
||||
* @param condition 触发条件
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isBasicConditionValid(IotSceneRuleDO.TriggerCondition condition) {
|
||||
return condition != null && condition.getType() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查条件操作符和参数是否有效
|
||||
*
|
||||
* @param condition 触发条件
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isConditionOperatorAndParamValid(IotSceneRuleDO.TriggerCondition condition) {
|
||||
return StrUtil.isNotBlank(condition.getOperator()) && StrUtil.isNotBlank(condition.getParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录条件匹配成功日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
*/
|
||||
public static void logConditionMatchSuccess(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
log.debug("[isMatched][message({}) condition({}) 匹配条件成功]", message.getRequestId(), condition.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录条件匹配失败日志
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @param reason 失败原因
|
||||
*/
|
||||
public static void logConditionMatchFailure(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition, String reason) {
|
||||
log.debug("[isMatched][message({}) condition({}) reason({}) 匹配条件失败]", message.getRequestId(), condition.getType(), reason);
|
||||
}
|
||||
|
||||
// ========== 【通用】工具方法 ==========
|
||||
|
||||
/**
|
||||
* 检查标识符是否匹配
|
||||
*
|
||||
* @param expectedIdentifier 期望的标识符
|
||||
* @param actualIdentifier 实际的标识符
|
||||
* @return 是否匹配
|
||||
*/
|
||||
public static boolean isIdentifierMatched(String expectedIdentifier, String actualIdentifier) {
|
||||
return StrUtil.isNotBlank(expectedIdentifier) && expectedIdentifier.equals(actualIdentifier);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ 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.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition.IotSceneRuleConditionMatcher;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger.IotSceneRuleTriggerMatcher;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -12,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 场景规则匹配器统一管理器
|
||||
@@ -27,21 +29,18 @@ public class IotSceneRuleMatcherManager {
|
||||
|
||||
/**
|
||||
* 触发器匹配器映射表
|
||||
* Key: 触发器类型枚举
|
||||
* Value: 对应的匹配器实例
|
||||
*/
|
||||
private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleMatcher> triggerMatchers;
|
||||
private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleTriggerMatcher> triggerMatchers;
|
||||
|
||||
/**
|
||||
* 条件匹配器映射表
|
||||
* Key: 条件类型枚举
|
||||
* Value: 对应的匹配器实例
|
||||
*/
|
||||
private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleMatcher> conditionMatchers;
|
||||
private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleConditionMatcher> conditionMatchers;
|
||||
|
||||
/**
|
||||
* 所有匹配器列表(按优先级排序)
|
||||
*/
|
||||
// TODO @puhui999:貌似 local variable 也可以
|
||||
private final List<IotSceneRuleMatcher> allMatchers;
|
||||
|
||||
public IotSceneRuleMatcherManager(List<IotSceneRuleMatcher> matchers) {
|
||||
@@ -60,50 +59,44 @@ public class IotSceneRuleMatcherManager {
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 分离触发器匹配器和条件匹配器
|
||||
List<IotSceneRuleMatcher> triggerMatchers = this.allMatchers.stream()
|
||||
.filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER)
|
||||
List<IotSceneRuleTriggerMatcher> triggerMatchers = this.allMatchers.stream()
|
||||
.filter(matcher -> matcher instanceof IotSceneRuleTriggerMatcher)
|
||||
.map(matcher -> (IotSceneRuleTriggerMatcher) matcher)
|
||||
.toList();
|
||||
List<IotSceneRuleMatcher> conditionMatchers = this.allMatchers.stream()
|
||||
.filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION)
|
||||
List<IotSceneRuleConditionMatcher> conditionMatchers = this.allMatchers.stream()
|
||||
.filter(matcher -> matcher instanceof IotSceneRuleConditionMatcher)
|
||||
.map(matcher -> (IotSceneRuleConditionMatcher) matcher)
|
||||
.toList();
|
||||
|
||||
// 构建触发器匹配器映射表
|
||||
// TODO @puhui999:convertMap()
|
||||
this.triggerMatchers = triggerMatchers.stream()
|
||||
.collect(Collectors.toMap(
|
||||
IotSceneRuleMatcher::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(
|
||||
IotSceneRuleMatcher::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()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,27 +107,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;
|
||||
}
|
||||
IotSceneRuleMatcher matcher = triggerMatchers.get(triggerType);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -148,28 +139,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;
|
||||
}
|
||||
|
||||
IotSceneRuleMatcher matcher = conditionMatchers.get(conditionType);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -181,12 +171,15 @@ public class IotSceneRuleMatcherManager {
|
||||
* @return 条件类型枚举
|
||||
*/
|
||||
private IotSceneRuleConditionTypeEnum findConditionTypeEnum(Integer typeValue) {
|
||||
// TODO @puhui999:是不是搞到枚举类里?
|
||||
return Arrays.stream(IotSceneRuleConditionTypeEnum.values())
|
||||
.filter(type -> type.getType().equals(typeValue))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// TODO @puhui999:下面两个方法,是不是也可以删除哈?
|
||||
|
||||
/**
|
||||
* 获取所有支持的触发器类型
|
||||
*
|
||||
@@ -205,65 +198,4 @@ public class IotSceneRuleMatcherManager {
|
||||
return new HashSet<>(conditionMatchers.keySet());
|
||||
}
|
||||
|
||||
// TODO @puhui999:用不到的方法,可以去掉先哈;
|
||||
|
||||
/**
|
||||
* 获取指定触发器类型的匹配器
|
||||
*
|
||||
* @param triggerType 触发器类型
|
||||
* @return 匹配器实例,如果不存在则返回 null
|
||||
*/
|
||||
public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) {
|
||||
return triggerMatchers.get(triggerType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定条件类型的匹配器
|
||||
*
|
||||
* @param conditionType 条件类型
|
||||
* @return 匹配器实例,如果不存在则返回 null
|
||||
*/
|
||||
public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) {
|
||||
return conditionMatchers.get(conditionType);
|
||||
}
|
||||
|
||||
// TODO @puhui999:是不是不用这个哈;直接 @Getter,单测直接处理;
|
||||
/**
|
||||
* 获取所有匹配器的统计信息
|
||||
*
|
||||
* @return 统计信息映射表
|
||||
*/
|
||||
public Map<String, Object> getMatcherStatistics() {
|
||||
Map<String, Object> statistics = new HashMap<>();
|
||||
statistics.put("totalMatchers", allMatchers.size());
|
||||
statistics.put("triggerMatchers", triggerMatchers.size());
|
||||
statistics.put("conditionMatchers", conditionMatchers.size());
|
||||
statistics.put("supportedTriggerTypes", getSupportedTriggerTypes());
|
||||
statistics.put("supportedConditionTypes", getSupportedConditionTypes());
|
||||
|
||||
// 触发器匹配器详情
|
||||
Map<String, Object> triggerMatcherDetails = new HashMap<>();
|
||||
triggerMatchers.forEach((type, matcher) -> {
|
||||
Map<String, Object> detail = new HashMap<>();
|
||||
detail.put("matcherName", matcher.getMatcherName());
|
||||
detail.put("priority", matcher.getPriority());
|
||||
detail.put("enabled", matcher.isEnabled());
|
||||
triggerMatcherDetails.put(type.name(), detail);
|
||||
});
|
||||
statistics.put("triggerMatcherDetails", triggerMatcherDetails);
|
||||
|
||||
// 条件匹配器详情
|
||||
Map<String, Object> conditionMatcherDetails = new HashMap<>();
|
||||
conditionMatchers.forEach((type, matcher) -> {
|
||||
Map<String, Object> detail = new HashMap<>();
|
||||
detail.put("matcherName", matcher.getMatcherName());
|
||||
detail.put("priority", matcher.getPriority());
|
||||
detail.put("enabled", matcher.isEnabled());
|
||||
conditionMatcherDetails.put(type.name(), detail);
|
||||
});
|
||||
statistics.put("conditionMatcherDetails", conditionMatcherDetails);
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
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.IotSceneRuleTriggerTypeEnum;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 定时触发器匹配器
|
||||
* <p>
|
||||
* 处理定时触发的触发器匹配逻辑
|
||||
* 注意:定时触发器不依赖设备消息,主要用于定时任务场景
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher {
|
||||
|
||||
@Override
|
||||
public MatcherType getMatcherType() {
|
||||
return MatcherType.TRIGGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.TIMER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 基础参数校验
|
||||
if (!isBasicTriggerValid(trigger)) {
|
||||
logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查 CRON 表达式是否存在
|
||||
if (StrUtil.isBlank(trigger.getCronExpression())) {
|
||||
logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 定时触发器通常不依赖具体的设备消息
|
||||
// 它是通过定时任务调度器触发的,这里主要是验证配置的有效性
|
||||
|
||||
// 4. 可以添加 CRON 表达式格式验证
|
||||
if (!isValidCronExpression(trigger.getCronExpression())) {
|
||||
logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
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;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 当前时间条件匹配器
|
||||
* <p>
|
||||
* 处理时间相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CurrentTimeConditionMatcher implements IotSceneRuleConditionMatcher {
|
||||
|
||||
/**
|
||||
* 时间格式化器 - HH:mm:ss
|
||||
*/
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 时间格式化器 - HH:mm
|
||||
*/
|
||||
private static final DateTimeFormatter TIME_FORMATTER_SHORT = DateTimeFormatter.ofPattern("HH:mm");
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.CURRENT_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查操作符和参数是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.3 验证操作符是否为支持的时间操作符
|
||||
String operator = condition.getOperator();
|
||||
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 executeTimeMatching(IotSceneRuleConditionOperatorEnum operatorEnum, String param) {
|
||||
try {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
if (isDateTimeOperator(operatorEnum)) {
|
||||
// 日期时间匹配(时间戳)
|
||||
long currentTimestamp = now.toEpochSecond(java.time.ZoneOffset.of("+8"));
|
||||
return matchDateTime(currentTimestamp, operatorEnum, param);
|
||||
} else {
|
||||
// 当日时间匹配(HH:mm:ss)
|
||||
return matchTime(now.toLocalTime(), operatorEnum, param);
|
||||
}
|
||||
} catch (Exception 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);
|
||||
}
|
||||
|
||||
// TODO @puhui999:switch 兼容下 jdk8
|
||||
/**
|
||||
* 匹配日期时间(时间戳)
|
||||
* 直接实现时间戳比较逻辑
|
||||
*/
|
||||
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) {
|
||||
Assert.isFalse(StrUtil.isBlank(timeStr), "时间字符串不能为空");
|
||||
|
||||
try {
|
||||
// 尝试不同的时间格式
|
||||
if (timeStr.length() == 5) { // HH:mm
|
||||
return LocalTime.parse(timeStr, TIME_FORMATTER_SHORT);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 40; // 较低优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备属性条件匹配器
|
||||
* <p>
|
||||
* 处理设备属性相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatcher {
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY;
|
||||
}
|
||||
|
||||
// TODO @puhui999:matches 会不会更好?参考的 org.hamcrest.Matcher jdk 接口
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(condition.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "标识符不匹配,期望: " + condition.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.3 检查操作符和参数是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1. 获取属性值
|
||||
Object propertyValue = message.getParams();
|
||||
if (propertyValue == null) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, condition.getOperator(), condition.getParam());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备属性条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 25; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
|
||||
|
||||
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.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备状态条件匹配器
|
||||
* <p>
|
||||
* 处理设备状态相关的子条件匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher {
|
||||
|
||||
@Override
|
||||
public IotSceneRuleConditionTypeEnum getSupportedConditionType() {
|
||||
return IotSceneRuleConditionTypeEnum.DEVICE_STATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicConditionValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "条件基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查操作符和参数是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isConditionOperatorAndParamValid(condition)) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "操作符或参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 获取设备状态值
|
||||
Object stateValue = message.getParams();
|
||||
if (stateValue == null) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, condition.getOperator(), condition.getParam());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchSuccess(message, condition);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "设备状态条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 30; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
|
||||
|
||||
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.IotSceneRuleConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcher;
|
||||
|
||||
/**
|
||||
* IoT 场景规则条件匹配器接口
|
||||
* <p>
|
||||
* 专门处理子条件的匹配逻辑,如设备状态、属性值、时间条件等
|
||||
* <p>
|
||||
* 条件匹配器负责判断设备消息是否满足场景规则的附加条件,
|
||||
* 在触发器匹配成功后进行进一步的条件筛选
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public interface IotSceneRuleConditionMatcher extends IotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 获取支持的条件类型
|
||||
*
|
||||
* @return 条件类型枚举
|
||||
*/
|
||||
IotSceneRuleConditionTypeEnum getSupportedConditionType();
|
||||
|
||||
/**
|
||||
* 检查条件是否匹配消息
|
||||
* <p>
|
||||
* 判断设备消息是否满足指定的触发条件
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param condition 触发条件
|
||||
* @return 是否匹配
|
||||
*/
|
||||
boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备事件上报触发器匹配器
|
||||
* <p>
|
||||
* 处理设备事件上报的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceEventPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.EVENT_POST.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " +
|
||||
IotDeviceMessageMethodEnum.EVENT_POST.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.3 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " +
|
||||
trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 对于事件触发器,通常不需要检查操作符和值,只要事件发生即匹配
|
||||
// 但如果配置了操作符和值,则需要进行条件匹配
|
||||
if (StrUtil.isNotBlank(trigger.getOperator()) && StrUtil.isNotBlank(trigger.getValue())) {
|
||||
Object eventData = message.getData();
|
||||
if (eventData == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中事件数据为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(eventData, trigger.getOperator(), trigger.getValue());
|
||||
if (!matched) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "事件数据条件不匹配");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 30; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备属性上报触发器匹配器
|
||||
* <p>
|
||||
* 处理设备属性数据上报的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " +
|
||||
IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.3 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " +
|
||||
trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.4 检查操作符和值是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 获取属性值
|
||||
Object propertyValue = message.getParams();
|
||||
if (propertyValue == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "属性值条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 20; // 中等优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备服务调用触发器匹配器
|
||||
* <p>
|
||||
* 处理设备服务调用的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceServiceInvokeTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " + IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.3 检查标识符是否匹配
|
||||
String messageIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (!IotSceneRuleMatcherHelper.isIdentifierMatched(trigger.getIdentifier(), messageIdentifier)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "标识符不匹配,期望: " + trigger.getIdentifier() + ", 实际: " + messageIdentifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 对于服务调用触发器,通常只需要匹配服务标识符即可
|
||||
// 不需要检查操作符和值,因为服务调用本身就是触发条件
|
||||
// TODO @puhui999: 服务调用时校验输入参数是否匹配条件
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 40; // 较低优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
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.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 设备状态更新触发器匹配器
|
||||
* <p>
|
||||
* 处理设备上下线状态变更的触发器匹配逻辑
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查消息方法是否匹配
|
||||
if (!IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod().equals(message.getMethod())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息方法不匹配,期望: " +
|
||||
IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod() + ", 实际: " + message.getMethod());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.3 检查操作符和值是否有效
|
||||
if (!IotSceneRuleMatcherHelper.isTriggerOperatorAndValueValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "操作符或值无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 获取设备状态值
|
||||
Object stateValue = message.getParams();
|
||||
if (stateValue == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
// TODO @puhui999: 状态匹配重新实现
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "状态值条件不匹配");
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 10; // 高优先级
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
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.IotSceneRuleMatcher;
|
||||
|
||||
/**
|
||||
* IoT 场景规则触发器匹配器接口
|
||||
* <p>
|
||||
* 专门处理主触发条件的匹配逻辑,如设备消息类型、定时器等
|
||||
* <p>
|
||||
* 触发器匹配器负责判断设备消息是否满足场景规则的主触发条件,
|
||||
* 是场景规则执行的第一道门槛
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public interface IotSceneRuleTriggerMatcher extends IotSceneRuleMatcher {
|
||||
|
||||
/**
|
||||
* 获取支持的触发器类型
|
||||
*
|
||||
* @return 触发器类型枚举
|
||||
*/
|
||||
IotSceneRuleTriggerTypeEnum getSupportedTriggerType();
|
||||
|
||||
/**
|
||||
* 检查触发器是否匹配消息
|
||||
* <p>
|
||||
* 判断设备消息是否满足指定的触发器条件
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param trigger 触发器配置
|
||||
* @return 是否匹配
|
||||
*/
|
||||
boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
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.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherHelper;
|
||||
import org.quartz.CronExpression;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 定时触发器匹配器
|
||||
* <p>
|
||||
* 处理定时触发的触发器匹配逻辑
|
||||
* 注意:定时触发器不依赖设备消息,主要用于定时任务场景
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
public class TimerTriggerMatcher implements IotSceneRuleTriggerMatcher {
|
||||
|
||||
@Override
|
||||
public IotSceneRuleTriggerTypeEnum getSupportedTriggerType() {
|
||||
return IotSceneRuleTriggerTypeEnum.TIMER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatched(IotDeviceMessage message, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1.1 基础参数校验
|
||||
if (!IotSceneRuleMatcherHelper.isBasicTriggerValid(trigger)) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "触发器基础参数无效");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.2 检查 CRON 表达式是否存在
|
||||
if (StrUtil.isBlank(trigger.getCronExpression())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "定时触发器缺少 CRON 表达式");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1.3 定时触发器通常不依赖具体的设备消息
|
||||
// 它是通过定时任务调度器触发的,这里主要是验证配置的有效性
|
||||
if (!CronExpression.isValidExpression(trigger.getCronExpression())) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "CRON 表达式格式无效: " + trigger.getCronExpression());
|
||||
return false;
|
||||
}
|
||||
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 50; // 最低优先级,因为定时触发器不依赖消息
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* IoT 场景规则触发器匹配器测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
// TODO @puhui999:public 都加下哈;
|
||||
|
||||
private IotSceneRuleMatcherManager matcherManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 创建所有匹配器实例
|
||||
List<IotSceneRuleMatcher> matchers = Arrays.asList(
|
||||
new DeviceStateUpdateTriggerMatcher(),
|
||||
new DevicePropertyPostTriggerMatcher(),
|
||||
new DeviceEventPostTriggerMatcher(),
|
||||
new DeviceServiceInvokeTriggerMatcher(),
|
||||
new TimerTriggerMatcher()
|
||||
);
|
||||
|
||||
// 初始化匹配器管理器
|
||||
matcherManager = new IotSceneRuleMatcherManager(matchers);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeviceStateUpdateTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-001")
|
||||
.method("thing.state.update") // TODO @puhui999:这里的枚举;
|
||||
.data(1) // 在线状态 TODO @puhui999:这里的枚举;
|
||||
.build();
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
|
||||
trigger.setOperator("="); // TODO @puhui999:这里的枚举;下面也是类似;
|
||||
trigger.setValue("1");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备状态更新触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDevicePropertyPostTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-002")
|
||||
.method("thing.property.post")
|
||||
.data(25.5) // 温度值
|
||||
.params(params)
|
||||
.build();
|
||||
// 模拟标识符
|
||||
params.put("identifier", "temperature");
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
|
||||
trigger.setIdentifier("temperature");
|
||||
trigger.setOperator(">");
|
||||
trigger.setValue("20");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备属性上报触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeviceEventPostTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-003")
|
||||
.method("thing.event.post")
|
||||
.data("alarm_data")
|
||||
.params(params)
|
||||
.build();
|
||||
// 模拟标识符
|
||||
params.put("identifier", "high_temperature_alarm");
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
|
||||
trigger.setIdentifier("high_temperature_alarm");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备事件上报触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeviceServiceInvokeTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-004")
|
||||
.method("thing.service.invoke")
|
||||
.msg("alarm_data")
|
||||
.params(params)
|
||||
.build();
|
||||
// 模拟标识符
|
||||
params.put("identifier", "restart_device");
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
|
||||
trigger.setIdentifier("restart_device");
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "设备服务调用触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimerTriggerMatcher() {
|
||||
// 1. 准备测试数据
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-005")
|
||||
.method("timer.trigger") // 定时触发器不依赖具体消息方法
|
||||
.build();
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
|
||||
trigger.setCronExpression("0 0 12 * * ?"); // 每天中午12点
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
assertTrue(matched, "定时触发器应该匹配");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidTriggerType() {
|
||||
// 1. 准备测试数据
|
||||
IotDeviceMessage message = IotDeviceMessage.builder()
|
||||
.requestId("test-006")
|
||||
.method("unknown.method")
|
||||
.build();
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(999); // 无效的触发器类型
|
||||
|
||||
// 2. 执行测试
|
||||
boolean matched = matcherManager.isMatched(message, trigger);
|
||||
|
||||
// 3. 验证结果
|
||||
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. 执行测试
|
||||
var supportedTypes = matcherManager.getSupportedTriggerTypes();
|
||||
|
||||
// 2. 验证结果
|
||||
assertNotNull(supportedTypes);
|
||||
assertEquals(5, supportedTypes.size());
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE));
|
||||
assertTrue(supportedTypes.contains(IotSceneRuleTriggerTypeEnum.TIMER));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link CurrentTimeConditionMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private CurrentTimeConditionMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new CurrentTimeConditionMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedConditionType() {
|
||||
// when & then
|
||||
assertEquals(IotSceneRuleConditionTypeEnum.CURRENT_TIME, matcher.getSupportedConditionType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(40, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
// ========== 时间戳条件测试 ==========
|
||||
|
||||
@Test
|
||||
public void testIsMatched_DateTimeGreaterThan_Success() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
long pastTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8"));
|
||||
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
|
||||
String.valueOf(pastTimestamp)
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_DateTimeGreaterThan_Failure() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
|
||||
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
|
||||
String.valueOf(futureTimestamp)
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_DateTimeLessThan_Success() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
long futureTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
|
||||
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.DATE_TIME_LESS_THAN.getOperator(),
|
||||
String.valueOf(futureTimestamp)
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_DateTimeBetween_Success() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
long startTimestamp = LocalDateTime.now().minusHours(1).toEpochSecond(ZoneOffset.of("+8"));
|
||||
long endTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
|
||||
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(),
|
||||
startTimestamp + "," + endTimestamp
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_DateTimeBetween_Failure() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
long startTimestamp = LocalDateTime.now().plusHours(1).toEpochSecond(ZoneOffset.of("+8"));
|
||||
long endTimestamp = LocalDateTime.now().plusHours(2).toEpochSecond(ZoneOffset.of("+8"));
|
||||
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.DATE_TIME_BETWEEN.getOperator(),
|
||||
startTimestamp + "," + endTimestamp
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
// ========== 当日时间条件测试 ==========
|
||||
|
||||
@Test
|
||||
public void testIsMatched_TimeGreaterThan_EarlyMorning() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(),
|
||||
"06:00:00" // 早上6点
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
// 结果取决于当前时间,如果当前时间大于6点则为true
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_TimeLessThan_LateNight() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.TIME_LESS_THAN.getOperator(),
|
||||
"23:59:59" // 晚上11点59分59秒
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
// 大部分情况下应该为true,除非在午夜前1秒运行测试
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_TimeBetween_AllDay() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
|
||||
"00:00:00,23:59:59" // 全天
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result); // 全天范围应该总是匹配
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_TimeBetween_WorkingHours() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
|
||||
"09:00:00,17:00:00" // 工作时间
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
// 结果取决于当前时间是否在工作时间内
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
// ========== 异常情况测试 ==========
|
||||
|
||||
@Test
|
||||
public void testIsMatched_NullCondition() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, null);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_NullConditionType() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(null);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_InvalidOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType());
|
||||
condition.setOperator("invalid_operator");
|
||||
condition.setParam("12:00:00");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_InvalidTimeFormat() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.TIME_GREATER_THAN.getOperator(),
|
||||
"invalid-time-format"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_InvalidTimestampFormat() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = createDateTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.DATE_TIME_GREATER_THAN.getOperator(),
|
||||
"invalid-timestamp"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_InvalidBetweenFormat() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.TriggerCondition condition = createTimeCondition(
|
||||
IotSceneRuleConditionOperatorEnum.TIME_BETWEEN.getOperator(),
|
||||
"09:00:00" // 缺少结束时间
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建日期时间条件
|
||||
*/
|
||||
private IotSceneRuleDO.TriggerCondition createDateTimeCondition(String operator, String param) {
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType());
|
||||
condition.setOperator(operator);
|
||||
condition.setParam(param);
|
||||
return condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建当日时间条件
|
||||
*/
|
||||
private IotSceneRuleDO.TriggerCondition createTimeCondition(String operator, String param) {
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.CURRENT_TIME.getType());
|
||||
condition.setOperator(operator);
|
||||
condition.setParam(param);
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link DevicePropertyConditionMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private DevicePropertyConditionMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DevicePropertyConditionMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedConditionType() {
|
||||
// when & then
|
||||
assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY, matcher.getSupportedConditionType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(20, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_TemperatureEquals() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
"25.5"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_HumidityGreaterThan() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("humidity", 75);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"humidity",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"70"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_PressureLessThan() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("pressure", 1010.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"pressure",
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
|
||||
"1020"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_StatusNotEquals() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("status", "active");
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"status",
|
||||
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
|
||||
"inactive"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_PropertyMismatch() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 15.0);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_PropertyNotFound() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"humidity", // 不存在的属性
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"50"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullCondition() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, null);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullConditionType() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(null);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingIdentifier() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
|
||||
condition.setIdentifier(null); // 缺少标识符
|
||||
condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
|
||||
condition.setParam("20");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingOperator() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
|
||||
condition.setIdentifier("temperature");
|
||||
condition.setOperator(null); // 缺少操作符
|
||||
condition.setParam("20");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingParam() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
|
||||
condition.setIdentifier("temperature");
|
||||
condition.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
|
||||
condition.setParam(null); // 缺少参数
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullMessage() {
|
||||
// given
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(null, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullDeviceProperties() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(null);
|
||||
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_GreaterThanOrEquals() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("voltage", 12.0);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"voltage",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
|
||||
"12.0"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_LessThanOrEquals() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("current", 2.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"current",
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
|
||||
"3.0"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_StringProperty() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("mode", "auto");
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"mode",
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
"auto"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_BooleanProperty() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.of("enabled", true);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"enabled",
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
"true"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_MultipleProperties() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 25.5)
|
||||
.put("humidity", 60)
|
||||
.put("status", "active")
|
||||
.put("enabled", true)
|
||||
.build();
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"humidity",
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
"60"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建设备消息
|
||||
*/
|
||||
private IotDeviceMessage createDeviceMessage(Map<String, Object> properties) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(properties);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建有效的条件
|
||||
*/
|
||||
private IotSceneRuleDO.TriggerCondition createValidCondition(String identifier, String operator, String param) {
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
|
||||
condition.setIdentifier(identifier);
|
||||
condition.setOperator(operator);
|
||||
condition.setParam(param);
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link DeviceStateConditionMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private DeviceStateConditionMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceStateConditionMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedConditionType() {
|
||||
// when & then
|
||||
assertEquals(IotSceneRuleConditionTypeEnum.DEVICE_STATE, matcher.getSupportedConditionType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(30, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_OnlineState() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_OfflineState() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.OFFLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_InactiveState() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.INACTIVE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_StateMismatch() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.OFFLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_NotEqualsOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.OFFLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_GreaterThanOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.OFFLINE.getState()); // 2
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString() // 1
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_LessThanOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.INACTIVE.getState()); // 0
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString() // 1
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullCondition() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, null);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullConditionType() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(null);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
|
||||
condition.setOperator(null);
|
||||
condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString());
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingParam() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
|
||||
condition.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
|
||||
condition.setParam(null);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullMessage() {
|
||||
// given
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(null, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullDeviceState() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(null);
|
||||
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_GreaterThanOrEqualsOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString() // 1
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_LessThanOrEqualsOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState()); // 1
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.OFFLINE.getState().toString() // 2
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_InvalidOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
|
||||
condition.setOperator("invalid_operator");
|
||||
condition.setParam(IotDeviceStateEnum.ONLINE.getState().toString());
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_InvalidParamFormat() {
|
||||
// given
|
||||
IotDeviceMessage message = createDeviceMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
"invalid_state_value"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, condition);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建设备消息
|
||||
*/
|
||||
private IotDeviceMessage createDeviceMessage(Integer deviceState) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(deviceState);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建有效的条件
|
||||
*/
|
||||
private IotSceneRuleDO.TriggerCondition createValidCondition(String operator, String param) {
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_STATE.getType());
|
||||
condition.setOperator(operator);
|
||||
condition.setParam(param);
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link DeviceEventPostTriggerMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private DeviceEventPostTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceEventPostTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType() {
|
||||
// when & then
|
||||
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST, matcher.getSupportedTriggerType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(30, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_AlarmEvent() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "alarm")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.put("message", "Temperature too high")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_ErrorEvent() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "error")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("code", 500)
|
||||
.put("description", "System error")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("error");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_InfoEvent() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "info")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("status", "normal")
|
||||
.put("timestamp", System.currentTimeMillis())
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("info");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_EventIdentifierMismatch() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "alarm")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("error"); // 不匹配的事件标识符
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_WrongMessageMethod() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "alarm")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法
|
||||
message.setParams(eventParams);
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingIdentifier() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "alarm")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
|
||||
trigger.setIdentifier(null); // 缺少标识符
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullMessageParams() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
|
||||
message.setParams(null);
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_InvalidMessageParams() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
|
||||
message.setParams("invalid-params"); // 不是 Map 类型
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingEventIdentifierInParams() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.build()) // 缺少 identifier 字段
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTrigger() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "alarm")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, null);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTriggerType() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "alarm")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(null);
|
||||
trigger.setIdentifier("alarm");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_ComplexEventValue() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "maintenance")
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("type", "scheduled")
|
||||
.put("duration", 120)
|
||||
.put("components", new String[]{"motor", "sensor"})
|
||||
.put("priority", "medium")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("maintenance");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_EmptyEventValue() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "heartbeat")
|
||||
.put("value", MapUtil.of()) // 空的事件值
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("heartbeat");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_CaseInsensitiveIdentifier() {
|
||||
// given
|
||||
Map<String, Object> eventParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "ALARM") // 大写
|
||||
.put("value", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("level", "high")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createEventPostMessage(eventParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("alarm"); // 小写
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
// 根据实际实现,这里可能需要调整期望结果
|
||||
// 如果实现是大小写敏感的,则应该为 false
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建事件上报消息
|
||||
*/
|
||||
private IotDeviceMessage createEventPostMessage(Map<String, Object> eventParams) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.EVENT_POST.getMethod());
|
||||
message.setParams(eventParams);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建有效的触发器
|
||||
*/
|
||||
private IotSceneRuleDO.Trigger createValidTrigger(String identifier) {
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_EVENT_POST.getType());
|
||||
trigger.setIdentifier(identifier);
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
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.IotSceneRuleTriggerTypeEnum;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link DevicePropertyPostTriggerMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private DevicePropertyPostTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DevicePropertyPostTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType() {
|
||||
// when & then
|
||||
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST, matcher.getSupportedTriggerType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(20, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_TemperatureProperty() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 25.5)
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_HumidityProperty() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("humidity", 60)
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"humidity",
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
"60"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_PropertyMismatch() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 15.0)
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_PropertyNotFound() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 25.5)
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"humidity", // 不存在的属性
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"50"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_WrongMessageMethod() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 25.5)
|
||||
.build();
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
|
||||
message.setParams(properties);
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingIdentifier() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 25.5)
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
|
||||
trigger.setIdentifier(null); // 缺少标识符
|
||||
trigger.setOperator(IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator());
|
||||
trigger.setValue("20");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullMessageParams() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
|
||||
message.setParams(null);
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_InvalidMessageParams() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
|
||||
message.setParams("invalid-params"); // 不是 Map 类型
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_LessThanOperator() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 15.0)
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"temperature",
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_NotEqualsOperator() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("status", "active")
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"status",
|
||||
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
|
||||
"inactive"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_MultipleProperties() {
|
||||
// given
|
||||
Map<String, Object> properties = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("temperature", 25.5)
|
||||
.put("humidity", 60)
|
||||
.put("status", "active")
|
||||
.build();
|
||||
IotDeviceMessage message = createPropertyPostMessage(properties);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
"humidity",
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
"60"
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建属性上报消息
|
||||
*/
|
||||
private IotDeviceMessage createPropertyPostMessage(Map<String, Object> properties) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
|
||||
message.setParams(properties);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建有效的触发器
|
||||
*/
|
||||
private IotSceneRuleDO.Trigger createValidTrigger(String identifier, String operator, String value) {
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_PROPERTY_POST.getType());
|
||||
trigger.setIdentifier(identifier);
|
||||
trigger.setOperator(operator);
|
||||
trigger.setValue(value);
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link DeviceServiceInvokeTriggerMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private DeviceServiceInvokeTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceServiceInvokeTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType() {
|
||||
// when & then
|
||||
// TODO @puhui999:单测按照现有项目的注释风格哈;类似 // 调用;// 断言
|
||||
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE, matcher.getSupportedTriggerType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(40, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_RestartService() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "restart")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_ConfigService() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "config")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("interval", 30)
|
||||
.put("enabled", true)
|
||||
.put("threshold", 75.5)
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("config");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_UpdateService() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "update")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("version", "1.2.3")
|
||||
.put("url", "http://example.com/firmware.bin")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("update");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_ServiceIdentifierMismatch() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "restart")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("config"); // 不匹配的服务标识符
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_WrongMessageMethod() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "restart")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()); // 错误的方法
|
||||
message.setParams(serviceParams);
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingIdentifier() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "restart")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
|
||||
trigger.setIdentifier(null); // 缺少标识符
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullMessageParams() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
|
||||
message.setParams(null);
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_InvalidMessageParams() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
|
||||
message.setParams("invalid-params"); // 不是 Map 类型
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingServiceIdentifierInParams() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build()) // 缺少 identifier 字段
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("restart");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTrigger() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "restart")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, null);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTriggerType() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "restart")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(null);
|
||||
trigger.setIdentifier("restart");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_EmptyInputData() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "ping")
|
||||
.put("inputData", MapUtil.of()) // 空的输入数据
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("ping");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_NoInputData() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "status")
|
||||
// 没有 inputData 字段
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("status");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_ComplexInputData() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "calibrate")
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("sensors", new String[]{"temperature", "humidity", "pressure"})
|
||||
.put("precision", 0.01)
|
||||
.put("duration", 300)
|
||||
.put("autoSave", true)
|
||||
.put("config", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "auto")
|
||||
.put("level", "high")
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("calibrate");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_CaseInsensitiveIdentifier() {
|
||||
// given
|
||||
Map<String, Object> serviceParams = MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("identifier", "RESTART") // 大写
|
||||
.put("inputData", MapUtil.builder(new HashMap<String, Object>())
|
||||
.put("mode", "soft")
|
||||
.build())
|
||||
.build();
|
||||
IotDeviceMessage message = createServiceInvokeMessage(serviceParams);
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("restart"); // 小写
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
// 根据实际实现,这里可能需要调整期望结果
|
||||
// 如果实现是大小写敏感的,则应该为 false
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建服务调用消息
|
||||
*/
|
||||
private IotDeviceMessage createServiceInvokeMessage(Map<String, Object> serviceParams) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod());
|
||||
message.setParams(serviceParams);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建有效的触发器
|
||||
*/
|
||||
private IotSceneRuleDO.Trigger createValidTrigger(String identifier) {
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_SERVICE_INVOKE.getType());
|
||||
trigger.setIdentifier(identifier);
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
||||
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
|
||||
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.IotSceneRuleTriggerTypeEnum;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link DeviceStateUpdateTriggerMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private DeviceStateUpdateTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceStateUpdateTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType() {
|
||||
// when & then
|
||||
assertEquals(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE, matcher.getSupportedTriggerType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(10, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_OnlineState() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_OfflineState() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.OFFLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.OFFLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_StateMismatch() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.OFFLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTrigger() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, null);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTriggerType() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(null);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_WrongMessageMethod() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod());
|
||||
message.setParams(IotDeviceStateEnum.ONLINE.getState());
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
|
||||
trigger.setOperator(null);
|
||||
trigger.setValue(IotDeviceStateEnum.ONLINE.getState().toString());
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_MissingValue() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
|
||||
trigger.setOperator(IotSceneRuleConditionOperatorEnum.EQUALS.getOperator());
|
||||
trigger.setValue(null);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullMessageParams() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
|
||||
message.setParams(null);
|
||||
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.ONLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_GreaterThanOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
IotDeviceStateEnum.INACTIVE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_NotEqualsOperator() {
|
||||
// given
|
||||
IotDeviceMessage message = createStateUpdateMessage(IotDeviceStateEnum.ONLINE.getState());
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger(
|
||||
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
|
||||
IotDeviceStateEnum.OFFLINE.getState().toString()
|
||||
);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建设备状态更新消息
|
||||
*/
|
||||
private IotDeviceMessage createStateUpdateMessage(Integer state) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setMethod(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod());
|
||||
message.setParams(state);
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建有效的触发器
|
||||
*/
|
||||
private IotSceneRuleDO.Trigger createValidTrigger(String operator, String value) {
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.DEVICE_STATE_UPDATE.getType());
|
||||
trigger.setOperator(operator);
|
||||
trigger.setValue(value);
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link TimerTriggerMatcher} 的单元测试类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class TimerTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
private TimerTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new TimerTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType() {
|
||||
// when & then
|
||||
assertEquals(IotSceneRuleTriggerTypeEnum.TIMER, matcher.getSupportedTriggerType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPriority() {
|
||||
// when & then
|
||||
assertEquals(50, matcher.getPriority());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// when & then
|
||||
assertTrue(matcher.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_ValidCronExpression() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * ?"); // 每天中午12点
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_EveryMinuteCron() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 * * * * ?"); // 每分钟
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_WeekdaysCron() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9 ? * MON-FRI"); // 工作日上午9点
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_InvalidCronExpression() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("invalid-cron-expression");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_EmptyCronExpression() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullCronExpression() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
|
||||
trigger.setCronExpression(null);
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTrigger() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, null);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_NullTriggerType() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(null);
|
||||
trigger.setCronExpression("0 0 12 * * ?");
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_ComplexCronExpression() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 15 10 ? * 6#3"); // 每月第三个星期五上午10:15
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_IncorrectCronFormat() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * *"); // 缺少字段
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_SpecificDateCron() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 0 1 1 ? 2025"); // 2025年1月1日午夜
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_EverySecondCron() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("* * * * * ?"); // 每秒
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Failure_InvalidCharactersCron() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 12 * * @ #"); // 包含无效字符
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsMatched_Success_RangeCron() {
|
||||
// given
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
IotSceneRuleDO.Trigger trigger = createValidTrigger("0 0 9-17 * * MON-FRI"); // 工作日9-17点
|
||||
|
||||
// when
|
||||
boolean result = matcher.isMatched(message, trigger);
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建有效的定时触发器
|
||||
*/
|
||||
private IotSceneRuleDO.Trigger createValidTrigger(String cronExpression) {
|
||||
IotSceneRuleDO.Trigger trigger = new IotSceneRuleDO.Trigger();
|
||||
trigger.setType(IotSceneRuleTriggerTypeEnum.TIMER.getType());
|
||||
trigger.setCronExpression(cronExpression);
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,15 @@ mybatis-plus:
|
||||
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
|
||||
type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
cn.iocoder.yudao.module.iot.service.rule.scene.matcher: DEBUG
|
||||
cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager: INFO
|
||||
cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition: DEBUG
|
||||
cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger: DEBUG
|
||||
root: WARN
|
||||
|
||||
--- #################### 定时任务相关配置 ####################
|
||||
|
||||
--- #################### 配置中心相关配置 ####################
|
||||
|
||||
@@ -1,4 +1,37 @@
|
||||
<configuration>
|
||||
<!-- 引用 Spring Boot 的 logback 基础配置 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 设置特定包的日志级别 -->
|
||||
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<!-- 设置 IotSceneRuleMatcherManager 的日志级别 -->
|
||||
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager" level="INFO"
|
||||
additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<!-- 设置所有匹配器的日志级别 -->
|
||||
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<logger name="cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<!-- 根日志级别 -->
|
||||
<root level="WARN">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
@@ -185,6 +185,7 @@ logging:
|
||||
cn.iocoder.yudao.module.erp.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.iot.dal.mysql: debug
|
||||
cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG
|
||||
cn.iocoder.yudao.module.iot.service.rule: debug
|
||||
cn.iocoder.yudao.module.ai.dal.mysql: debug
|
||||
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
|
||||
|
||||
|
||||
Reference in New Issue
Block a user