Merge remote-tracking branch 'origin/feature/iot' into feature/iot

This commit is contained in:
haohao
2025-08-20 07:54:08 +08:00
36 changed files with 3640 additions and 1365 deletions

View File

@@ -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());
}
}

View File

@@ -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;
}
}
/**
* 执行规则场景的动作
*

View File

@@ -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);
}
}

View File

@@ -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 @puhui999if 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 @puhui9991日志格式 [][]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; // 较低优先级
}
}

View File

@@ -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; // 中等优先级
}
}

View File

@@ -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; // 中等优先级
}
}

View File

@@ -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; // 中等优先级
}
}

View File

@@ -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; // 较低优先级
}
}

View File

@@ -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; // 中等优先级
}
}

View File

@@ -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; // 高优先级
}
}

View File

@@ -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 @puhui999MatcherTypeEnum
// 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>

View File

@@ -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);
}
}

View File

@@ -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 @puhui999convertMap()
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;
}
}

View File

@@ -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 @puhui999CronExpression.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;
}
}

View File

@@ -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 @puhui999switch 兼容下 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; // 较低优先级
}
}

View File

@@ -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 @puhui999matches 会不会更好?参考的 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; // 中等优先级
}
}

View File

@@ -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; // 中等优先级
}
}

View File

@@ -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);
}

View File

@@ -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; // 中等优先级
}
}

View File

@@ -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; // 中等优先级
}
}

View File

@@ -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; // 较低优先级
}
}

View File

@@ -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; // 高优先级
}
}

View File

@@ -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);
}

View File

@@ -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; // 最低优先级,因为定时触发器不依赖消息
}
}

View File

@@ -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 @puhui999public 都加下哈;
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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################

View File

@@ -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>

View File

@@ -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 提示