review:【iot 物联网】场景联动相关实现

This commit is contained in:
YunaiV
2025-08-16 20:37:22 +08:00
parent f88d30f103
commit 6791c62858
13 changed files with 65 additions and 62 deletions

View File

@@ -60,7 +60,7 @@ public enum IotSceneRuleTriggerTypeEnum implements ArrayValuable<Integer> {
return ARRAYS;
}
// TODO @puhui999可以参考下别的枚举哈方法名和实现都可以更简洁of(String type) { firstMatch
/**
* 根据类型值查找触发器类型枚举
*

View File

@@ -45,7 +45,6 @@ public class IotRedisRuleAction extends
// 2. 根据数据结构类型执行不同的操作
String messageJson = JsonUtils.toJsonString(message);
IotRedisDataStructureEnum dataStructure = getDataStructureByType(config.getDataStructure());
switch (dataStructure) {
case STREAM:
executeStream(redisTemplate, config, messageJson);

View File

@@ -58,12 +58,15 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
@Resource
private IotSceneRuleMapper sceneRuleMapper;
// TODO @puhui999定时任务基于它调度
@Resource(name = "iotSchedulerManager")
private IotSchedulerManager schedulerManager;
@Resource
private IotProductService productService;
@Resource
private IotDeviceService deviceService;
// TODO @puhui999sceneRuleMatcherManager 变量名
@Resource
private IotSceneRuleMatcherManager matcherManager;
@Resource
@@ -135,7 +138,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
return sceneRuleMapper.selectListByStatus(status);
}
// TODO 芋艿,缓存待实现
// TODO 芋艿,缓存待实现 @puhui999
@Override
@TenantIgnore // 忽略租户隔离:因为 IotSceneRuleMessageHandler 调用时,一般未传递租户,所以需要忽略
public List<IotSceneRuleDO> getSceneRuleListByProductIdAndDeviceIdFromCache(Long productId, Long deviceId) {
@@ -177,7 +180,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
@Override
public void executeSceneRuleByDevice(IotDeviceMessage message) {
// TODO @芋艿:这里的 tenantId通过设备获取
// TODO @芋艿:这里的 tenantId通过设备获取@puhui999
TenantUtils.execute(message.getTenantId(), () -> {
// 1. 获得设备匹配的规则场景
List<IotSceneRuleDO> sceneRules = getMatchedSceneRuleListByMessage(message);
@@ -223,7 +226,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
*/
private List<IotSceneRuleDO> getMatchedSceneRuleListByMessage(IotDeviceMessage message) {
// 1. 匹配设备
// TODO @芋艿:可能需要 getSelf(); 缓存
// TODO @芋艿:可能需要 getSelf(); 缓存 @puhui999
// 1.1 通过 deviceId 获取设备信息
IotDeviceDO device = deviceService.getDeviceFromCache(message.getDeviceId());
if (device == null) {
@@ -342,7 +345,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
private boolean isTriggerConditionMatched(IotDeviceMessage message, IotSceneRuleDO.TriggerCondition condition,
IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
try {
// 使用重构后的条件匹配管理器进行匹配
return matcherManager.isConditionMatched(message, condition);
} catch (Exception e) {
log.error("[isTriggerConditionMatched][规则场景编号({}) 的触发器({}) 条件匹配异常]",
@@ -351,8 +353,7 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
}
}
// TODO @芋艿:【可优化】可以考虑增加下单测,边界太多了。
// TODO @puhui999下面还需要么
/**
* 判断触发器的条件参数是否匹配
*

View File

@@ -24,6 +24,8 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
@Slf4j
public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher {
// TODO @puhui999这个是不是也是【通用】条件哈
/**
* 评估条件是否匹配
*
@@ -32,6 +34,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher
* @param paramValue 参数值(来自条件配置)
* @return 是否匹配
*/
@SuppressWarnings("DataFlowIssue")
protected boolean evaluateCondition(Object sourceValue, String operator, String paramValue) {
try {
// 1. 校验操作符是否合法
@@ -48,6 +51,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher
// 处理参数值
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,
@@ -68,7 +72,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher
}
}
// ========== 触发器相关工具方法 ==========
// ========== 触发器相关工具方法 ==========
/**
* 检查基础触发器参数是否有效
@@ -111,7 +115,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher
log.debug("[{}][消息({}) 匹配触发器({}) 失败: {}]", getMatcherName(), message.getRequestId(), trigger.getType(), reason);
}
// ========== 条件相关工具方法 ==========
// ========== 条件相关工具方法 ==========
/**
* 检查基础条件参数是否有效
@@ -154,7 +158,7 @@ public abstract class AbstractIotSceneRuleMatcher implements IotSceneRuleMatcher
log.debug("[{}][消息({}) 匹配条件({}) 失败: {}]", getMatcherName(), message.getRequestId(), condition.getType(), reason);
}
// ========== 通用工具方法 ==========
// ========== 通用工具方法 ==========
/**
* 检查标识符是否匹配

View File

@@ -56,15 +56,11 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
return false;
}
// 3. 获取当前时间
// 3. 根据操作符类型进行不同的时间匹配
LocalDateTime now = LocalDateTime.now();
// 4. 根据操作符类型进行不同的时间匹配
String operator = condition.getOperator();
String param = condition.getParam();
boolean matched = false;
boolean matched;
try {
if (operator.startsWith("date_time_")) {
// 日期时间匹配(时间戳)
@@ -82,13 +78,11 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
} else {
logConditionMatchFailure(message, condition, "时间条件不匹配");
}
} catch (Exception e) {
log.error("[CurrentTimeConditionMatcher][时间条件匹配异常] operator: {}, param: {}", operator, param, e);
logConditionMatchFailure(message, condition, "时间条件匹配异常: " + e.getMessage());
matched = false;
}
return matched;
}
@@ -107,21 +101,20 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
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);
@@ -138,6 +131,7 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
}
}
} catch (Exception e) {
// TODO @puhui9991日志格式 [][]2方法名不对哈
log.error("[CurrentTimeConditionMatcher][时间解析异常] param: {}", param, e);
return false;
}
@@ -147,10 +141,10 @@ public class CurrentTimeConditionMatcher extends AbstractIotSceneRuleMatcher {
* 解析时间字符串
*/
private LocalTime parseTime(String timeStr) {
// TODO @puhui999可以用 hutool Assert 类简化
if (StrUtil.isBlank(timeStr)) {
throw new IllegalArgumentException("时间字符串不能为空");
}
// 尝试不同的时间格式
try {
if (timeStr.length() == 5) { // HH:mm

View File

@@ -26,6 +26,7 @@ public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher
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. 基础参数校验
@@ -56,13 +57,11 @@ public class DevicePropertyConditionMatcher extends AbstractIotSceneRuleMatcher
// 5. 使用条件评估器进行匹配
boolean matched = evaluateCondition(propertyValue, condition.getOperator(), condition.getParam());
if (matched) {
logConditionMatchSuccess(message, condition);
} else {
logConditionMatchFailure(message, condition, "设备属性条件不匹配");
}
return matched;
}

View File

@@ -20,6 +20,7 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatche
/**
* 设备属性上报消息方法
*/
// TODO @puhui999是不是不用枚举哈直接使用 IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()
private static final String DEVICE_PROPERTY_POST_METHOD = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod();
@Override
@@ -68,13 +69,11 @@ public class DevicePropertyPostTriggerMatcher extends AbstractIotSceneRuleMatche
// 6. 使用条件评估器进行匹配
boolean matched = evaluateCondition(propertyValue, trigger.getOperator(), trigger.getValue());
if (matched) {
logTriggerMatchSuccess(message, trigger);
} else {
logTriggerMatchFailure(message, trigger, "属性值条件不匹配");
}
return matched;
}

View File

@@ -49,13 +49,11 @@ public class DeviceStateConditionMatcher extends AbstractIotSceneRuleMatcher {
// 4. 使用条件评估器进行匹配
boolean matched = evaluateCondition(stateValue, condition.getOperator(), condition.getParam());
if (matched) {
logConditionMatchSuccess(message, condition);
} else {
logConditionMatchFailure(message, condition, "设备状态条件不匹配");
}
return matched;
}

View File

@@ -16,6 +16,7 @@ import org.springframework.stereotype.Component;
@Component
public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher {
// TODO @puhui999是不是不用枚举哈
/**
* 设备状态更新消息方法
*/
@@ -60,13 +61,11 @@ public class DeviceStateUpdateTriggerMatcher extends AbstractIotSceneRuleMatcher
// 5. 使用条件评估器进行匹配
boolean matched = evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
if (matched) {
logTriggerMatchSuccess(message, trigger);
} else {
logTriggerMatchFailure(message, trigger, "状态值条件不匹配");
}
return matched;
}

View File

@@ -18,18 +18,23 @@ import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleTriggerTypeEnum;
*/
public interface IotSceneRuleMatcher {
// TODO @puhui999MatcherTypeEnum
// TODO @puhui999可以考虑根据类型新建 trigger、condition 包,然后把对应的实现类放进去哈;
/**
* 匹配器类型枚举
*/
enum MatcherType {
/**
* 触发器匹配器 - 用于匹配主触发条件
*/
TRIGGER,
/**
* 条件匹配器 - 用于匹配子条件
*/
CONDITION
}
/**
@@ -39,6 +44,10 @@ public interface IotSceneRuleMatcher {
*/
MatcherType getMatcherType();
// TODO @puhui999【重要】有个思路IotSceneRuleMatcher 拆分成 2 种 mather 接口;然后 AbstractIotSceneRuleMatcher 是个 Helper 工具类;
// TODO @puhui999是不是和 AbstractSceneRuleMatcher 一样,分下块;
/**
* 获取支持的触发器类型(仅触发器匹配器需要实现)
*
@@ -90,6 +99,7 @@ public interface IotSceneRuleMatcher {
return 100;
}
// TODO @puhui999如果目前没自定义体感可以删除哈
/**
* 获取匹配器名称,用于日志和调试
*

View File

@@ -30,14 +30,14 @@ public class IotSceneRuleMatcherManager {
* Key: 触发器类型枚举
* Value: 对应的匹配器实例
*/
private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleMatcher> triggerMatcherMap;
private final Map<IotSceneRuleTriggerTypeEnum, IotSceneRuleMatcher> triggerMatchers;
/**
* 条件匹配器映射表
* Key: 条件类型枚举
* Value: 对应的匹配器实例
*/
private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleMatcher> conditionMatcherMap;
private final Map<IotSceneRuleConditionTypeEnum, IotSceneRuleMatcher> conditionMatchers;
/**
* 所有匹配器列表(按优先级排序)
@@ -47,8 +47,8 @@ public class IotSceneRuleMatcherManager {
public IotSceneRuleMatcherManager(List<IotSceneRuleMatcher> matchers) {
if (CollUtil.isEmpty(matchers)) {
log.warn("[IotSceneRuleMatcherManager][没有找到任何匹配器]");
this.triggerMatcherMap = new HashMap<>();
this.conditionMatcherMap = new HashMap<>();
this.triggerMatchers = new HashMap<>();
this.conditionMatchers = new HashMap<>();
this.allMatchers = new ArrayList<>();
return;
}
@@ -63,13 +63,13 @@ public class IotSceneRuleMatcherManager {
List<IotSceneRuleMatcher> triggerMatchers = this.allMatchers.stream()
.filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.TRIGGER)
.toList();
List<IotSceneRuleMatcher> conditionMatchers = this.allMatchers.stream()
.filter(matcher -> matcher.getMatcherType() == IotSceneRuleMatcher.MatcherType.CONDITION)
.toList();
// 构建触发器匹配器映射表
this.triggerMatcherMap = triggerMatchers.stream()
// TODO @puhui999convertMap()
this.triggerMatchers = triggerMatchers.stream()
.collect(Collectors.toMap(
IotSceneRuleMatcher::getSupportedTriggerType,
Function.identity(),
@@ -81,9 +81,8 @@ public class IotSceneRuleMatcherManager {
},
LinkedHashMap::new
));
// 构建条件匹配器映射表
this.conditionMatcherMap = conditionMatchers.stream()
this.conditionMatchers = conditionMatchers.stream()
.collect(Collectors.toMap(
IotSceneRuleMatcher::getSupportedConditionType,
Function.identity(),
@@ -96,16 +95,13 @@ public class IotSceneRuleMatcherManager {
LinkedHashMap::new
));
// 日志输出初始化信息
log.info("[IotSceneRuleMatcherManager][初始化完成,共加载 {} 个匹配器,其中触发器匹配器 {} 个,条件匹配器 {} 个]",
this.allMatchers.size(), this.triggerMatcherMap.size(), this.conditionMatcherMap.size());
// 记录触发器匹配器详情
this.triggerMatcherMap.forEach((type, matcher) ->
this.allMatchers.size(), this.triggerMatchers.size(), this.conditionMatchers.size());
this.triggerMatchers.forEach((type, matcher) ->
log.info("[IotSceneRuleMatcherManager][触发器匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
type, matcher.getMatcherName(), matcher.getPriority()));
// 记录条件匹配器详情
this.conditionMatcherMap.forEach((type, matcher) ->
this.conditionMatchers.forEach((type, matcher) ->
log.info("[IotSceneRuleMatcherManager][条件匹配器] 类型: {}, 匹配器: {}, 优先级: {}",
type, matcher.getMatcherName(), matcher.getPriority()));
}
@@ -118,19 +114,17 @@ 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);
return false;
}
// 根据触发器类型查找对应的匹配器
IotSceneRuleTriggerTypeEnum triggerType = findTriggerTypeEnum(trigger.getType());
if (triggerType == null) {
log.warn("[isMatched][未知的触发器类型: {}]", trigger.getType());
return false;
}
IotSceneRuleMatcher matcher = triggerMatcherMap.get(triggerType);
IotSceneRuleMatcher matcher = triggerMatchers.get(triggerType);
if (matcher == null) {
log.warn("[isMatched][触发器类型({})没有对应的匹配器]", triggerType);
return false;
@@ -165,7 +159,7 @@ public class IotSceneRuleMatcherManager {
return false;
}
IotSceneRuleMatcher matcher = conditionMatcherMap.get(conditionType);
IotSceneRuleMatcher matcher = conditionMatchers.get(conditionType);
if (matcher == null) {
log.warn("[isConditionMatched][条件类型({})没有对应的匹配器]", conditionType);
return false;
@@ -199,7 +193,7 @@ public class IotSceneRuleMatcherManager {
* @return 支持的触发器类型列表
*/
public Set<IotSceneRuleTriggerTypeEnum> getSupportedTriggerTypes() {
return new HashSet<>(triggerMatcherMap.keySet());
return new HashSet<>(triggerMatchers.keySet());
}
/**
@@ -208,9 +202,11 @@ public class IotSceneRuleMatcherManager {
* @return 支持的条件类型列表
*/
public Set<IotSceneRuleConditionTypeEnum> getSupportedConditionTypes() {
return new HashSet<>(conditionMatcherMap.keySet());
return new HashSet<>(conditionMatchers.keySet());
}
// TODO @puhui999用不到的方法可以去掉先哈
/**
* 获取指定触发器类型的匹配器
*
@@ -218,7 +214,7 @@ public class IotSceneRuleMatcherManager {
* @return 匹配器实例,如果不存在则返回 null
*/
public IotSceneRuleMatcher getTriggerMatcher(IotSceneRuleTriggerTypeEnum triggerType) {
return triggerMatcherMap.get(triggerType);
return triggerMatchers.get(triggerType);
}
/**
@@ -228,9 +224,10 @@ public class IotSceneRuleMatcherManager {
* @return 匹配器实例,如果不存在则返回 null
*/
public IotSceneRuleMatcher getConditionMatcher(IotSceneRuleConditionTypeEnum conditionType) {
return conditionMatcherMap.get(conditionType);
return conditionMatchers.get(conditionType);
}
// TODO @puhui999是不是不用这个哈直接 @Getter单测直接处理
/**
* 获取所有匹配器的统计信息
*
@@ -239,14 +236,14 @@ public class IotSceneRuleMatcherManager {
public Map<String, Object> getMatcherStatistics() {
Map<String, Object> statistics = new HashMap<>();
statistics.put("totalMatchers", allMatchers.size());
statistics.put("triggerMatchers", triggerMatcherMap.size());
statistics.put("conditionMatchers", conditionMatcherMap.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<>();
triggerMatcherMap.forEach((type, matcher) -> {
triggerMatchers.forEach((type, matcher) -> {
Map<String, Object> detail = new HashMap<>();
detail.put("matcherName", matcher.getMatcherName());
detail.put("priority", matcher.getPriority());
@@ -257,7 +254,7 @@ public class IotSceneRuleMatcherManager {
// 条件匹配器详情
Map<String, Object> conditionMatcherDetails = new HashMap<>();
conditionMatcherMap.forEach((type, matcher) -> {
conditionMatchers.forEach((type, matcher) -> {
Map<String, Object> detail = new HashMap<>();
detail.put("matcherName", matcher.getMatcherName());
detail.put("priority", matcher.getPriority());

View File

@@ -61,6 +61,7 @@ public class TimerTriggerMatcher extends AbstractIotSceneRuleMatcher {
* @return 是否有效
*/
private boolean isValidCronExpression(String cronExpression) {
// TODO @puhui999CronExpression.isValidExpression(cronExpression);
try {
// 简单的 CRON 表达式格式验证
// 标准 CRON 表达式应该有 6 或 7 个字段(秒 分 时 日 月 周 [年]

View File

@@ -20,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.*;
*/
public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
// TODO @puhui999public 都加下哈;
private IotSceneRuleMatcherManager matcherManager;
@BeforeEach
@@ -42,13 +44,13 @@ public class IotSceneRuleTriggerMatcherTest extends BaseMockitoUnitTest {
// 1. 准备测试数据
IotDeviceMessage message = IotDeviceMessage.builder()
.requestId("test-001")
.method("thing.state.update")
.data(1) // 在线状态
.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("=");
trigger.setOperator("="); // TODO @puhui999这里的枚举下面也是类似
trigger.setValue("1");
// 2. 执行测试