mirror of
https://gitee.com/zhijiantianya/ruoyi-vue-pro.git
synced 2025-12-26 08:26:44 +08:00
!1417 feat: 【IoT 物联网】实现场景规则定时触发器的注册、重构设备属性设置执行器、新增设备服务调用实现
Merge pull request !1417 from puhui999/feature/iot
This commit is contained in:
commit
b945970ce6
@ -17,11 +17,11 @@ 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.IotSceneRuleTriggerTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.framework.job.core.IotSchedulerManager;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.action.IotSceneRuleAction;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.matcher.IotSceneRuleMatcherManager;
|
||||
import cn.iocoder.yudao.module.iot.service.rule.scene.timer.IotSceneRuleTimerHandler;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -47,9 +47,6 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
|
||||
@Resource
|
||||
private IotSceneRuleMapper sceneRuleMapper;
|
||||
|
||||
// TODO @puhui999:定时任务,基于它调度;
|
||||
@Resource(name = "iotSchedulerManager")
|
||||
private IotSchedulerManager schedulerManager;
|
||||
@Resource
|
||||
private IotProductService productService;
|
||||
@Resource
|
||||
@ -59,11 +56,17 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
|
||||
private IotSceneRuleMatcherManager sceneRuleMatcherManager;
|
||||
@Resource
|
||||
private List<IotSceneRuleAction> sceneRuleActions;
|
||||
@Resource
|
||||
private IotSceneRuleTimerHandler timerHandler;
|
||||
|
||||
@Override
|
||||
public Long createSceneRule(IotSceneRuleSaveReqVO createReqVO) {
|
||||
IotSceneRuleDO sceneRule = BeanUtils.toBean(createReqVO, IotSceneRuleDO.class);
|
||||
sceneRuleMapper.insert(sceneRule);
|
||||
|
||||
// 注册定时触发器
|
||||
timerHandler.registerTimerTriggers(sceneRule);
|
||||
|
||||
return sceneRule.getId();
|
||||
}
|
||||
|
||||
@ -74,6 +77,9 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
|
||||
// 更新
|
||||
IotSceneRuleDO updateObj = BeanUtils.toBean(updateReqVO, IotSceneRuleDO.class);
|
||||
sceneRuleMapper.updateById(updateObj);
|
||||
|
||||
// 更新定时触发器
|
||||
timerHandler.updateTimerTriggers(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -83,12 +89,26 @@ public class IotSceneRuleServiceImpl implements IotSceneRuleService {
|
||||
// 更新状态
|
||||
IotSceneRuleDO updateObj = new IotSceneRuleDO().setId(id).setStatus(status);
|
||||
sceneRuleMapper.updateById(updateObj);
|
||||
|
||||
// 根据状态管理定时触发器
|
||||
if (CommonStatusEnum.isEnable(status)) {
|
||||
// 启用时,获取完整的场景规则信息并注册定时触发器
|
||||
IotSceneRuleDO sceneRule = sceneRuleMapper.selectById(id);
|
||||
if (sceneRule != null) {
|
||||
timerHandler.registerTimerTriggers(sceneRule);
|
||||
}
|
||||
} else {
|
||||
// 禁用时,暂停定时触发器
|
||||
timerHandler.pauseTimerTriggers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSceneRule(Long id) {
|
||||
// 校验存在
|
||||
validateSceneRuleExists(id);
|
||||
// 删除定时触发器
|
||||
timerHandler.unregisterTimerTriggers(id);
|
||||
// 删除
|
||||
sceneRuleMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.action;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
@ -9,8 +13,11 @@ import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 设备控制的 {@link IotSceneRuleAction} 实现类
|
||||
* IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@ -23,28 +30,108 @@ public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction {
|
||||
@Resource
|
||||
private IotDeviceMessageService deviceMessageService;
|
||||
|
||||
// TODO @puhui999:这里
|
||||
@Override
|
||||
public void execute(IotDeviceMessage message,
|
||||
IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) {
|
||||
//IotSceneRuleDO.ActionDeviceControl control = actionConfig.getDeviceControl();
|
||||
//Assert.notNull(control, "设备控制配置不能为空");
|
||||
//// 遍历每个设备,下发消息
|
||||
//control.getDeviceNames().forEach(deviceName -> {
|
||||
// IotDeviceDO device = deviceService.getDeviceFromCache(control.getProductKey(), deviceName);
|
||||
// if (device == null) {
|
||||
// log.error("[execute][message({}) actionConfig({}) 对应的设备不存在]", message, actionConfig);
|
||||
// return;
|
||||
// }
|
||||
// try {
|
||||
// // TODO @芋艿:@puhui999:这块可能要改,从 type => method
|
||||
// IotDeviceMessage downstreamMessage = deviceMessageService.sendDeviceMessage(IotDeviceMessage.requestOf(
|
||||
// control.getType() + control.getIdentifier(), control.getData()).setDeviceId(device.getId()));
|
||||
// log.info("[execute][message({}) actionConfig({}) 下发消息({})成功]", message, actionConfig, downstreamMessage);
|
||||
// } catch (Exception e) {
|
||||
// log.error("[execute][message({}) actionConfig({}) 下发消息失败]", message, actionConfig, e);
|
||||
// }
|
||||
//});
|
||||
// 1. 参数校验
|
||||
if (actionConfig.getDeviceId() == null) {
|
||||
log.error("[execute][规则场景({}) 动作配置({}) 设备编号不能为空]", rule.getId(), actionConfig);
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isEmpty(actionConfig.getIdentifier())) {
|
||||
log.error("[execute][规则场景({}) 动作配置({}) 属性标识符不能为空]", rule.getId(), actionConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 判断是否为全部设备
|
||||
if (IotDeviceDO.DEVICE_ID_ALL.equals(actionConfig.getDeviceId())) {
|
||||
executeForAllDevices(message, rule, actionConfig);
|
||||
} else {
|
||||
executeForSingleDevice(message, rule, actionConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单个设备执行属性设置
|
||||
*/
|
||||
private void executeForSingleDevice(IotDeviceMessage message,
|
||||
IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) {
|
||||
// 1. 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(actionConfig.getDeviceId());
|
||||
if (device == null) {
|
||||
log.error("[executeForSingleDevice][规则场景({}) 动作配置({}) 对应的设备({}) 不存在]",
|
||||
rule.getId(), actionConfig, actionConfig.getDeviceId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行属性设置
|
||||
executePropertySetForDevice(rule, actionConfig, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为产品下的所有设备执行属性设置
|
||||
*/
|
||||
private void executeForAllDevices(IotDeviceMessage message,
|
||||
IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) {
|
||||
// 1. 参数校验
|
||||
if (actionConfig.getProductId() == null) {
|
||||
log.error("[executeForAllDevices][规则场景({}) 动作配置({}) 产品编号不能为空]", rule.getId(), actionConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 获取产品下的所有设备
|
||||
List<IotDeviceDO> devices = deviceService.getDeviceListByProductId(actionConfig.getProductId());
|
||||
if (CollUtil.isEmpty(devices)) {
|
||||
log.warn("[executeForAllDevices][规则场景({}) 动作配置({}) 产品({}) 下没有设备]",
|
||||
rule.getId(), actionConfig, actionConfig.getProductId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 遍历所有设备执行属性设置
|
||||
for (IotDeviceDO device : devices) {
|
||||
executePropertySetForDevice(rule, actionConfig, device);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定设备执行属性设置
|
||||
*/
|
||||
private void executePropertySetForDevice(IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig, IotDeviceDO device) {
|
||||
// 1. 构建属性设置消息
|
||||
IotDeviceMessage downstreamMessage = buildPropertySetMessage(actionConfig, device);
|
||||
if (downstreamMessage == null) {
|
||||
log.error("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 构建属性设置消息失败]",
|
||||
rule.getId(), actionConfig, device.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 发送设备消息
|
||||
try {
|
||||
IotDeviceMessage result = deviceMessageService.sendDeviceMessage(downstreamMessage, device);
|
||||
log.info("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 属性设置消息({}) 发送成功]",
|
||||
rule.getId(), actionConfig, device.getId(), result.getId());
|
||||
} catch (Exception e) {
|
||||
log.error("[executePropertySetForDevice][规则场景({}) 动作配置({}) 设备({}) 属性设置消息发送失败]",
|
||||
rule.getId(), actionConfig, device.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建属性设置消息
|
||||
*
|
||||
* @param actionConfig 动作配置
|
||||
* @param device 设备信息
|
||||
* @return 设备消息
|
||||
*/
|
||||
private IotDeviceMessage buildPropertySetMessage(IotSceneRuleDO.Action actionConfig, IotDeviceDO device) {
|
||||
try {
|
||||
// 属性设置参数格式: {"properties": {"identifier": value}}
|
||||
Object params = Map.of("properties", Map.of(actionConfig.getIdentifier(), actionConfig.getParams()));
|
||||
return IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod(), params);
|
||||
} catch (Exception e) {
|
||||
log.error("[buildPropertySetMessage][构建属性设置消息异常]", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.action;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.dal.dataobject.device.IotDeviceDO;
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
|
||||
import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleActionTypeEnum;
|
||||
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
|
||||
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IoT 设备服务调用的 {@link IotSceneRuleAction} 实现类
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotDeviceServiceInvokeSceneRuleAction implements IotSceneRuleAction {
|
||||
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Resource
|
||||
private IotDeviceMessageService deviceMessageService;
|
||||
|
||||
@Override
|
||||
public void execute(IotDeviceMessage message,
|
||||
IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) {
|
||||
// 1. 参数校验
|
||||
if (actionConfig.getDeviceId() == null) {
|
||||
log.error("[execute][规则场景({}) 动作配置({}) 设备编号不能为空]", rule.getId(), actionConfig);
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isEmpty(actionConfig.getIdentifier())) {
|
||||
log.error("[execute][规则场景({}) 动作配置({}) 服务标识符不能为空]", rule.getId(), actionConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 判断是否为全部设备
|
||||
if (IotDeviceDO.DEVICE_ID_ALL.equals(actionConfig.getDeviceId())) {
|
||||
executeForAllDevices(message, rule, actionConfig);
|
||||
} else {
|
||||
executeForSingleDevice(message, rule, actionConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单个设备执行服务调用
|
||||
*/
|
||||
private void executeForSingleDevice(IotDeviceMessage message,
|
||||
IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) {
|
||||
// 1. 获取设备信息
|
||||
IotDeviceDO device = deviceService.getDeviceFromCache(actionConfig.getDeviceId());
|
||||
if (device == null) {
|
||||
log.error("[executeForSingleDevice][规则场景({}) 动作配置({}) 对应的设备({}) 不存在]",
|
||||
rule.getId(), actionConfig, actionConfig.getDeviceId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 执行服务调用
|
||||
executeServiceInvokeForDevice(rule, actionConfig, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为产品下的所有设备执行服务调用
|
||||
*/
|
||||
private void executeForAllDevices(IotDeviceMessage message,
|
||||
IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig) {
|
||||
// 1. 参数校验
|
||||
if (actionConfig.getProductId() == null) {
|
||||
log.error("[executeForAllDevices][规则场景({}) 动作配置({}) 产品编号不能为空]", rule.getId(), actionConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 获取产品下的所有设备
|
||||
List<IotDeviceDO> devices = deviceService.getDeviceListByProductId(actionConfig.getProductId());
|
||||
if (CollUtil.isEmpty(devices)) {
|
||||
log.warn("[executeForAllDevices][规则场景({}) 动作配置({}) 产品({}) 下没有设备]",
|
||||
rule.getId(), actionConfig, actionConfig.getProductId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 遍历所有设备执行服务调用
|
||||
for (IotDeviceDO device : devices) {
|
||||
executeServiceInvokeForDevice(rule, actionConfig, device);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定设备执行服务调用
|
||||
*/
|
||||
private void executeServiceInvokeForDevice(IotSceneRuleDO rule, IotSceneRuleDO.Action actionConfig, IotDeviceDO device) {
|
||||
// 1. 构建服务调用消息
|
||||
IotDeviceMessage downstreamMessage = buildServiceInvokeMessage(actionConfig, device);
|
||||
if (downstreamMessage == null) {
|
||||
log.error("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 构建服务调用消息失败]",
|
||||
rule.getId(), actionConfig, device.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 发送设备消息
|
||||
try {
|
||||
IotDeviceMessage result = deviceMessageService.sendDeviceMessage(downstreamMessage, device);
|
||||
log.info("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 服务调用消息({}) 发送成功]",
|
||||
rule.getId(), actionConfig, device.getId(), result.getId());
|
||||
} catch (Exception e) {
|
||||
log.error("[executeServiceInvokeForDevice][规则场景({}) 动作配置({}) 设备({}) 服务调用消息发送失败]",
|
||||
rule.getId(), actionConfig, device.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建服务调用消息
|
||||
*
|
||||
* @param actionConfig 动作配置
|
||||
* @param device 设备信息
|
||||
* @return 设备消息
|
||||
*/
|
||||
private IotDeviceMessage buildServiceInvokeMessage(IotSceneRuleDO.Action actionConfig, IotDeviceDO device) {
|
||||
try {
|
||||
// 服务调用参数格式: {"identifier": "serviceId", "params": {...}}
|
||||
Object params = Map.of(
|
||||
"identifier", actionConfig.getIdentifier(),
|
||||
"params", actionConfig.getParams() != null ? actionConfig.getParams() : Map.of()
|
||||
);
|
||||
return IotDeviceMessage.requestOf(IotDeviceMessageMethodEnum.SERVICE_INVOKE.getMethod(), params);
|
||||
} catch (Exception e) {
|
||||
log.error("[buildServiceInvokeMessage][构建服务调用消息异常]", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IotSceneRuleActionTypeEnum getType() {
|
||||
return IotSceneRuleActionTypeEnum.DEVICE_SERVICE_INVOKE;
|
||||
}
|
||||
|
||||
}
|
||||
@ -91,7 +91,7 @@ public final class IotSceneRuleMatcherHelper {
|
||||
Map<String, Object> springExpressionVariables = new HashMap<>();
|
||||
|
||||
// 设置源值
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, sourceValue);
|
||||
springExpressionVariables.put(IotSceneRuleConditionOperatorEnum.SPRING_EXPRESSION_SOURCE, StrUtil.toString(sourceValue));
|
||||
|
||||
// 处理参数值
|
||||
if (StrUtil.isNotBlank(paramValue)) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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;
|
||||
@ -7,6 +8,7 @@ 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>
|
||||
@ -43,10 +45,10 @@ public class DevicePropertyConditionMatcher implements IotSceneRuleConditionMatc
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1. 获取属性值
|
||||
Object propertyValue = message.getParams();
|
||||
// 2.1. 获取属性值 - 使用工具类方法正确提取属性值
|
||||
Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, condition.getIdentifier());
|
||||
if (propertyValue == null) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空");
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中属性值为空或未找到指定属性");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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;
|
||||
@ -35,8 +36,9 @@ public class DeviceStateConditionMatcher implements IotSceneRuleConditionMatcher
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 获取设备状态值
|
||||
Object stateValue = message.getParams();
|
||||
// 2.1 获取设备状态值 - 使用工具类方法正确提取状态值
|
||||
// 对于设备状态条件,状态值通过 getIdentifier 获取(实际是从 params.state 字段)
|
||||
String stateValue = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (stateValue == null) {
|
||||
IotSceneRuleMatcherHelper.logConditionMatchFailure(message, condition, "消息中设备状态值为空");
|
||||
return false;
|
||||
|
||||
@ -52,10 +52,10 @@ public class DevicePropertyPostTriggerMatcher implements IotSceneRuleTriggerMatc
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 获取属性值
|
||||
Object propertyValue = message.getParams();
|
||||
// 2.1 获取属性值 - 使用工具类方法正确提取属性值
|
||||
Object propertyValue = IotDeviceMessageUtils.extractPropertyValue(message, trigger.getIdentifier());
|
||||
if (propertyValue == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空");
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中属性值为空或未找到指定属性");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ 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;
|
||||
@ -43,16 +44,17 @@ public class DeviceStateUpdateTriggerMatcher implements IotSceneRuleTriggerMatch
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.1 获取设备状态值
|
||||
Object stateValue = message.getParams();
|
||||
if (stateValue == null) {
|
||||
// 2.1 获取设备状态值 - 使用工具类方法正确提取状态值
|
||||
// 对于状态更新消息,状态值通过 getIdentifier 获取(实际是从 params.state 字段)
|
||||
String stateIdentifier = IotDeviceMessageUtils.getIdentifier(message);
|
||||
if (stateIdentifier == null) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchFailure(message, trigger, "消息中设备状态值为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2.2 使用条件评估器进行匹配
|
||||
// TODO @puhui999: 状态匹配重新实现
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateValue, trigger.getOperator(), trigger.getValue());
|
||||
// 状态值通常是字符串或数字,直接使用标识符作为状态值
|
||||
boolean matched = IotSceneRuleMatcherHelper.evaluateCondition(stateIdentifier, trigger.getOperator(), trigger.getValue());
|
||||
if (matched) {
|
||||
IotSceneRuleMatcherHelper.logTriggerMatchSuccess(message, trigger);
|
||||
} else {
|
||||
|
||||
@ -0,0 +1,178 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.timer;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
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.framework.job.core.IotSchedulerManager;
|
||||
import cn.iocoder.yudao.module.iot.job.rule.IotSceneRuleJob;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
|
||||
|
||||
/**
|
||||
* IoT 场景规则定时触发器处理器
|
||||
* <p>
|
||||
* 负责管理定时触发器的注册、更新、删除等操作
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class IotSceneRuleTimerHandler {
|
||||
|
||||
@Resource(name = "iotSchedulerManager")
|
||||
private IotSchedulerManager schedulerManager;
|
||||
|
||||
/**
|
||||
* 注册场景规则的定时触发器
|
||||
*
|
||||
* @param sceneRule 场景规则
|
||||
*/
|
||||
public void registerTimerTriggers(IotSceneRuleDO sceneRule) {
|
||||
if (sceneRule == null || CollUtil.isEmpty(sceneRule.getTriggers())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤出定时触发器
|
||||
List<IotSceneRuleDO.Trigger> timerTriggers = filterList(sceneRule.getTriggers(),
|
||||
trigger -> ObjUtil.equals(trigger.getType(), IotSceneRuleTriggerTypeEnum.TIMER.getType()));
|
||||
|
||||
if (CollUtil.isEmpty(timerTriggers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册每个定时触发器
|
||||
timerTriggers.forEach(trigger -> registerSingleTimerTrigger(sceneRule, trigger));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新场景规则的定时触发器
|
||||
*
|
||||
* @param sceneRule 场景规则
|
||||
*/
|
||||
public void updateTimerTriggers(IotSceneRuleDO sceneRule) {
|
||||
if (sceneRule == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先删除旧的定时任务
|
||||
unregisterTimerTriggers(sceneRule.getId());
|
||||
|
||||
// 如果场景规则已禁用,则不重新注册
|
||||
if (CommonStatusEnum.isDisable(sceneRule.getStatus())) {
|
||||
log.info("[updateTimerTriggers][场景规则({}) 已禁用,不注册定时触发器]", sceneRule.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新注册定时触发器
|
||||
registerTimerTriggers(sceneRule);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销场景规则的定时触发器
|
||||
*
|
||||
* @param sceneRuleId 场景规则ID
|
||||
*/
|
||||
public void unregisterTimerTriggers(Long sceneRuleId) {
|
||||
if (sceneRuleId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String jobName = buildJobName(sceneRuleId);
|
||||
try {
|
||||
schedulerManager.deleteJob(jobName);
|
||||
log.info("[unregisterTimerTriggers][场景规则({}) 定时触发器注销成功]", sceneRuleId);
|
||||
} catch (SchedulerException e) {
|
||||
log.error("[unregisterTimerTriggers][场景规则({}) 定时触发器注销失败]", sceneRuleId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停场景规则的定时触发器
|
||||
*
|
||||
* @param sceneRuleId 场景规则ID
|
||||
*/
|
||||
public void pauseTimerTriggers(Long sceneRuleId) {
|
||||
if (sceneRuleId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String jobName = buildJobName(sceneRuleId);
|
||||
try {
|
||||
schedulerManager.pauseJob(jobName);
|
||||
log.info("[pauseTimerTriggers][场景规则({}) 定时触发器暂停成功]", sceneRuleId);
|
||||
} catch (SchedulerException e) {
|
||||
log.error("[pauseTimerTriggers][场景规则({}) 定时触发器暂停失败]", sceneRuleId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复场景规则的定时触发器
|
||||
*
|
||||
* @param sceneRuleId 场景规则ID
|
||||
*/
|
||||
public void resumeTimerTriggers(Long sceneRuleId) {
|
||||
if (sceneRuleId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String jobName = buildJobName(sceneRuleId);
|
||||
try {
|
||||
schedulerManager.resumeJob(jobName);
|
||||
log.info("[resumeTimerTriggers][场景规则({}) 定时触发器恢复成功]", sceneRuleId);
|
||||
} catch (SchedulerException e) {
|
||||
log.error("[resumeTimerTriggers][场景规则({}) 定时触发器恢复失败]", sceneRuleId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册单个定时触发器
|
||||
*
|
||||
* @param sceneRule 场景规则
|
||||
* @param trigger 定时触发器配置
|
||||
*/
|
||||
private void registerSingleTimerTrigger(IotSceneRuleDO sceneRule, IotSceneRuleDO.Trigger trigger) {
|
||||
// 1. 参数校验
|
||||
if (StrUtil.isBlank(trigger.getCronExpression())) {
|
||||
log.error("[registerSingleTimerTrigger][场景规则({}) 定时触发器缺少 CRON 表达式]", sceneRule.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 构建任务名称和数据
|
||||
String jobName = buildJobName(sceneRule.getId());
|
||||
|
||||
try {
|
||||
// 3. 注册定时任务
|
||||
schedulerManager.addOrUpdateJob(
|
||||
IotSceneRuleJob.class,
|
||||
jobName,
|
||||
trigger.getCronExpression(),
|
||||
IotSceneRuleJob.buildJobDataMap(sceneRule.getId())
|
||||
);
|
||||
|
||||
log.info("[registerSingleTimerTrigger][场景规则({}) 定时触发器注册成功,CRON: {}]",
|
||||
sceneRule.getId(), trigger.getCronExpression());
|
||||
} catch (SchedulerException e) {
|
||||
log.error("[registerSingleTimerTrigger][场景规则({}) 定时触发器注册失败,CRON: {}]",
|
||||
sceneRule.getId(), trigger.getCronExpression(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务名称
|
||||
*
|
||||
* @param sceneRuleId 场景规则ID
|
||||
* @return 任务名称
|
||||
*/
|
||||
private String buildJobName(Long sceneRuleId) {
|
||||
return "iot_scene_rule_timer_" + sceneRuleId;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* Matcher 测试基类
|
||||
* 提供通用的 Spring 测试配置
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
public abstract class BaseMatcherTest {
|
||||
|
||||
/**
|
||||
* 注入一下 SpringUtil,解析 EL 表达式时需要
|
||||
* {@link SpringExpressionUtils#parseExpression}
|
||||
*/
|
||||
@Configuration
|
||||
static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public SpringUtil springUtil() {
|
||||
return new SpringUtil();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
@ -20,11 +20,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class CurrentTimeConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
public class CurrentTimeConditionMatcherTest extends BaseMatcherTest {
|
||||
|
||||
@InjectMocks
|
||||
private CurrentTimeConditionMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new CurrentTimeConditionMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedConditionType() {
|
||||
// 调用
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
@ -21,11 +19,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
public class DevicePropertyConditionMatcherTest extends BaseMatcherTest {
|
||||
|
||||
@InjectMocks
|
||||
private DevicePropertyConditionMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DevicePropertyConditionMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedConditionType() {
|
||||
// 调用
|
||||
@ -41,27 +43,17 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
int result = matcher.getPriority();
|
||||
|
||||
// 断言
|
||||
assertEquals(20, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEnabled() {
|
||||
// 调用
|
||||
boolean result = matcher.isEnabled();
|
||||
|
||||
// 断言
|
||||
assertTrue(result);
|
||||
assertEquals(25, result); // 修正:实际返回值是 25
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches_temperatureEquals_success() {
|
||||
// 准备参数
|
||||
String propertyName = "temperature";
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "temperature";
|
||||
Double propertyValue = 25.5;
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
String.valueOf(propertyValue)
|
||||
);
|
||||
@ -75,14 +67,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_humidityGreaterThan_success() {
|
||||
// 准备参数
|
||||
String propertyName = "humidity";
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "humidity";
|
||||
Integer propertyValue = 75;
|
||||
Integer compareValue = 70;
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
String.valueOf(compareValue)
|
||||
);
|
||||
@ -96,14 +87,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_pressureLessThan_success() {
|
||||
// 准备参数
|
||||
String propertyName = "pressure";
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "pressure";
|
||||
Double propertyValue = 1010.5;
|
||||
Integer compareValue = 1020;
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN.getOperator(),
|
||||
String.valueOf(compareValue)
|
||||
);
|
||||
@ -117,14 +107,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_statusNotEquals_success() {
|
||||
// 准备参数
|
||||
String propertyName = "status";
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "status";
|
||||
String propertyValue = "active";
|
||||
String compareValue = "inactive";
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.NOT_EQUALS.getOperator(),
|
||||
compareValue
|
||||
);
|
||||
@ -138,14 +127,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_propertyMismatch_fail() {
|
||||
// 准备参数
|
||||
String propertyName = "temperature";
|
||||
// 准备参数:创建属性上报消息,值不满足条件
|
||||
String propertyIdentifier = "temperature";
|
||||
Double propertyValue = 15.0;
|
||||
Integer compareValue = 20;
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
String.valueOf(compareValue)
|
||||
);
|
||||
@ -158,14 +146,16 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches_propertyNotFound_fail() {
|
||||
// 准备参数
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
public void testMatches_identifierMismatch_fail() {
|
||||
// 准备参数:标识符不匹配
|
||||
String messageIdentifier = "temperature";
|
||||
String conditionIdentifier = "humidity";
|
||||
Double propertyValue = 25.5;
|
||||
IotDeviceMessage message = createPropertyPostMessage(messageIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
randomString(), // 随机不存在的属性名
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"50"
|
||||
conditionIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
String.valueOf(propertyValue)
|
||||
);
|
||||
|
||||
// 调用
|
||||
@ -178,8 +168,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
@Test
|
||||
public void testMatches_nullCondition_fail() {
|
||||
// 准备参数
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
|
||||
|
||||
// 调用
|
||||
boolean result = matcher.matches(message, null);
|
||||
@ -191,8 +180,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
@Test
|
||||
public void testMatches_nullConditionType_fail() {
|
||||
// 准备参数
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(null);
|
||||
|
||||
@ -206,8 +194,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
@Test
|
||||
public void testMatches_missingIdentifier_fail() {
|
||||
// 准备参数
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
|
||||
condition.setIdentifier(null); // 缺少标识符
|
||||
@ -224,8 +211,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
@Test
|
||||
public void testMatches_missingOperator_fail() {
|
||||
// 准备参数
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
|
||||
condition.setIdentifier("temperature");
|
||||
@ -242,8 +228,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
@Test
|
||||
public void testMatches_missingParam_fail() {
|
||||
// 准备参数
|
||||
Map<String, Object> properties = MapUtil.of("temperature", 25.5);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage("temperature", 25.5);
|
||||
IotSceneRuleDO.TriggerCondition condition = new IotSceneRuleDO.TriggerCondition();
|
||||
condition.setType(IotSceneRuleConditionTypeEnum.DEVICE_PROPERTY.getType());
|
||||
condition.setIdentifier("temperature");
|
||||
@ -275,7 +260,7 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_nullDeviceProperties_fail() {
|
||||
// 准备参数
|
||||
// 准备参数:消息的 params 为 null
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(null);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
@ -292,14 +277,79 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches_voltageGreaterThanOrEquals_success() {
|
||||
// 准备参数
|
||||
String propertyName = "voltage";
|
||||
Double propertyValue = 12.0;
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
public void testMatches_propertiesStructure_success() {
|
||||
// 测试使用 properties 结构的消息(真实的属性上报场景)
|
||||
String identifier = "temperature";
|
||||
Double propertyValue = 25.5;
|
||||
IotDeviceMessage message = createPropertyPostMessageWithProperties(identifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
identifier,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// 调用
|
||||
boolean result = matcher.matches(message, condition);
|
||||
|
||||
// 断言:修复后的实现应该能正确从 properties 中提取属性值
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches_simpleValueMessage_success() {
|
||||
// 测试简单值消息(params 直接是属性值)
|
||||
Double propertyValue = 25.5;
|
||||
IotDeviceMessage message = createSimpleValueMessage(propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
"any", // 对于简单值消息,标识符匹配会被跳过
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// 调用
|
||||
boolean result = matcher.matches(message, condition);
|
||||
|
||||
// 断言:修复后的实现应该能处理简单值消息
|
||||
// 但由于标识符匹配失败,结果为 false
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches_valueFieldStructure_success() {
|
||||
// 测试使用 value 字段的消息结构
|
||||
String identifier = "temperature";
|
||||
Double propertyValue = 25.5;
|
||||
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setDeviceId(randomLongId());
|
||||
message.setMethod("thing.event.post");
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("identifier", identifier);
|
||||
params.put("value", propertyValue);
|
||||
message.setParams(params);
|
||||
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
identifier,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN.getOperator(),
|
||||
"20"
|
||||
);
|
||||
|
||||
// 调用
|
||||
boolean result = matcher.matches(message, condition);
|
||||
|
||||
// 断言:修复后的实现应该能从 value 字段提取属性值
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches_voltageGreaterThanOrEquals_success() {
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "voltage";
|
||||
Double propertyValue = 12.0;
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.GREATER_THAN_OR_EQUALS.getOperator(),
|
||||
String.valueOf(propertyValue)
|
||||
);
|
||||
@ -313,14 +363,13 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_currentLessThanOrEquals_success() {
|
||||
// 准备参数
|
||||
String propertyName = "current";
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "current";
|
||||
Double propertyValue = 2.5;
|
||||
Double compareValue = 3.0;
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.LESS_THAN_OR_EQUALS.getOperator(),
|
||||
String.valueOf(compareValue)
|
||||
);
|
||||
@ -334,13 +383,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_stringProperty_success() {
|
||||
// 准备参数
|
||||
String propertyName = "mode";
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "mode";
|
||||
String propertyValue = "auto";
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
propertyValue
|
||||
);
|
||||
@ -354,13 +402,12 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
|
||||
@Test
|
||||
public void testMatches_booleanProperty_success() {
|
||||
// 准备参数
|
||||
String propertyName = "enabled";
|
||||
// 准备参数:创建属性上报消息
|
||||
String propertyIdentifier = "enabled";
|
||||
Boolean propertyValue = true;
|
||||
Map<String, Object> properties = MapUtil.of(propertyName, propertyValue);
|
||||
IotDeviceMessage message = createDeviceMessage(properties);
|
||||
IotDeviceMessage message = createPropertyPostMessage(propertyIdentifier, propertyValue);
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
propertyName,
|
||||
propertyIdentifier,
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
String.valueOf(propertyValue)
|
||||
);
|
||||
@ -372,40 +419,61 @@ public class DevicePropertyConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches_multipleProperties_success() {
|
||||
// 准备参数
|
||||
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);
|
||||
String targetProperty = "humidity";
|
||||
Integer targetValue = 60;
|
||||
IotSceneRuleDO.TriggerCondition condition = createValidCondition(
|
||||
targetProperty,
|
||||
IotSceneRuleConditionOperatorEnum.EQUALS.getOperator(),
|
||||
String.valueOf(targetValue)
|
||||
);
|
||||
|
||||
// 调用
|
||||
boolean result = matcher.matches(message, condition);
|
||||
|
||||
// 断言
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/**
|
||||
* 创建设备消息
|
||||
* 创建设备消息用于测试
|
||||
*
|
||||
* 支持的消息格式:
|
||||
* 1. 直接属性值:params 直接是属性值(适用于简单消息)
|
||||
* 2. 标识符+值:params 包含 identifier 和对应的属性值
|
||||
* 3. properties 结构:params.properties[identifier] = value
|
||||
* 4. data 结构:params.data[identifier] = value
|
||||
* 5. value 字段:params.value = value
|
||||
*/
|
||||
private IotDeviceMessage createDeviceMessage(Map<String, Object> properties) {
|
||||
private IotDeviceMessage createPropertyPostMessage(String identifier, Object value) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setDeviceId(randomLongId());
|
||||
message.setParams(properties);
|
||||
message.setMethod("thing.event.post"); // 使用事件上报方法
|
||||
|
||||
// 创建符合修复后逻辑的 params 结构
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("identifier", identifier);
|
||||
// 直接将属性值放在标识符对应的字段中
|
||||
params.put(identifier, value);
|
||||
message.setParams(params);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建使用 properties 结构的消息(模拟真实的属性上报消息)
|
||||
*/
|
||||
private IotDeviceMessage createPropertyPostMessageWithProperties(String identifier, Object value) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setDeviceId(randomLongId());
|
||||
message.setMethod("thing.property.post"); // 属性上报方法
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(identifier, value);
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("properties", properties);
|
||||
message.setParams(params);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建简单值消息(params 直接是属性值)
|
||||
*/
|
||||
private IotDeviceMessage createSimpleValueMessage(Object value) {
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setDeviceId(randomLongId());
|
||||
message.setMethod("thing.property.post");
|
||||
// 直接将属性值作为 params
|
||||
message.setParams(value);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
||||
@ -18,11 +18,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceStateConditionMatcherTest extends BaseMockitoUnitTest {
|
||||
public class DeviceStateConditionMatcherTest extends BaseMatcherTest {
|
||||
|
||||
@InjectMocks
|
||||
private DeviceStateConditionMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceStateConditionMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedConditionType() {
|
||||
// 调用
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceEventPostTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
public class DeviceEventPostTriggerMatcherTest extends BaseMatcherTest {
|
||||
|
||||
@InjectMocks
|
||||
private DeviceEventPostTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceEventPostTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType_success() {
|
||||
// 准备参数
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -24,11 +24,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DevicePropertyPostTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
public class DevicePropertyPostTriggerMatcherTest extends BaseMatcherTest {
|
||||
|
||||
@InjectMocks
|
||||
private DevicePropertyPostTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DevicePropertyPostTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType_success() {
|
||||
// 准备参数
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -22,11 +22,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceServiceInvokeTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
public class DeviceServiceInvokeTriggerMatcherTest extends BaseMatcherTest {
|
||||
|
||||
@InjectMocks
|
||||
private DeviceServiceInvokeTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceServiceInvokeTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType_success() {
|
||||
// 准备参数
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
|
||||
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
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 org.mockito.InjectMocks;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@ -18,11 +21,24 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class DeviceStateUpdateTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
@SpringJUnitConfig(DeviceStateUpdateTriggerMatcherTest.TestConfig.class)
|
||||
public class DeviceStateUpdateTriggerMatcherTest {
|
||||
|
||||
@Configuration
|
||||
static class TestConfig {
|
||||
@Bean
|
||||
public SpringUtil springUtil() {
|
||||
return new SpringUtil();
|
||||
}
|
||||
}
|
||||
|
||||
@InjectMocks
|
||||
private DeviceStateUpdateTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new DeviceStateUpdateTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType_success() {
|
||||
// 准备参数
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
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 cn.iocoder.yudao.module.iot.service.rule.scene.matcher.BaseMatcherTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
||||
@ -16,11 +16,15 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class TimerTriggerMatcherTest extends BaseMockitoUnitTest {
|
||||
public class TimerTriggerMatcherTest extends BaseMatcherTest {
|
||||
|
||||
@InjectMocks
|
||||
private TimerTriggerMatcher matcher;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
matcher = new TimerTriggerMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedTriggerType_success() {
|
||||
// 准备参数
|
||||
|
||||
@ -69,6 +69,83 @@ public class IotDeviceMessageUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从设备消息中提取指定标识符的属性值
|
||||
* - 支持多种消息格式和属性值提取策略
|
||||
* - 兼容现有的消息结构
|
||||
* - 提供统一的属性值提取接口
|
||||
* <p>
|
||||
* 支持的提取策略(按优先级顺序):
|
||||
* 1. 直接值:如果 params 不是 Map,直接返回该值(适用于简单消息)
|
||||
* 2. 标识符字段:从 params[identifier] 获取
|
||||
* 3. properties 结构:从 params.properties[identifier] 获取(标准属性上报)
|
||||
* 4. data 结构:从 params.data[identifier] 获取
|
||||
* 5. value 字段:从 params.value 获取(单值消息)
|
||||
* 6. 单值 Map:如果 Map 只包含 identifier 和一个值,返回该值
|
||||
*
|
||||
* @param message 设备消息
|
||||
* @param identifier 属性标识符
|
||||
* @return 属性值,如果未找到则返回 null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object extractPropertyValue(IotDeviceMessage message, String identifier) {
|
||||
Object params = message.getParams();
|
||||
if (params == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 策略1:如果 params 不是 Map,直接返回该值(适用于简单的单属性消息)
|
||||
if (!(params instanceof Map)) {
|
||||
return params;
|
||||
}
|
||||
|
||||
Map<String, Object> paramsMap = (Map<String, Object>) params;
|
||||
|
||||
// 策略2:直接通过标识符获取属性值
|
||||
Object directValue = paramsMap.get(identifier);
|
||||
if (directValue != null) {
|
||||
return directValue;
|
||||
}
|
||||
|
||||
// 策略3:从 properties 字段中获取(适用于标准属性上报消息)
|
||||
Object properties = paramsMap.get("properties");
|
||||
if (properties instanceof Map) {
|
||||
Map<String, Object> propertiesMap = (Map<String, Object>) properties;
|
||||
Object propertyValue = propertiesMap.get(identifier);
|
||||
if (propertyValue != null) {
|
||||
return propertyValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 策略4:从 data 字段中获取(适用于某些消息格式)
|
||||
Object data = paramsMap.get("data");
|
||||
if (data instanceof Map) {
|
||||
Map<String, Object> dataMap = (Map<String, Object>) data;
|
||||
Object dataValue = dataMap.get(identifier);
|
||||
if (dataValue != null) {
|
||||
return dataValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 策略5:从 value 字段中获取(适用于单值消息)
|
||||
Object value = paramsMap.get("value");
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 策略6:如果 Map 只有两个字段且包含 identifier,返回另一个字段的值
|
||||
if (paramsMap.size() == 2 && paramsMap.containsKey("identifier")) {
|
||||
for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
|
||||
if (!"identifier".equals(entry.getKey())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 未找到对应的属性值
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== Topic 相关 ==========
|
||||
|
||||
public static String buildMessageBusGatewayDeviceMessageTopic(String serverId) {
|
||||
|
||||
@ -0,0 +1,141 @@
|
||||
package cn.iocoder.yudao.module.iot.core.util;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* {@link IotDeviceMessageUtils} 的单元测试
|
||||
*
|
||||
* @author HUIHUI
|
||||
*/
|
||||
public class IotDeviceMessageUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_directValue() {
|
||||
// 测试直接值(非 Map)
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(25.5);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_directIdentifier() {
|
||||
// 测试直接通过标识符获取
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("temperature", 25.5);
|
||||
message.setParams(params);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_propertiesStructure() {
|
||||
// 测试 properties 结构
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("temperature", 25.5);
|
||||
properties.put("humidity", 60);
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("properties", properties);
|
||||
message.setParams(params);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_dataStructure() {
|
||||
// 测试 data 结构
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("temperature", 25.5);
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("data", data);
|
||||
message.setParams(params);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_valueField() {
|
||||
// 测试 value 字段
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("identifier", "temperature");
|
||||
params.put("value", 25.5);
|
||||
message.setParams(params);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_singleValueMap() {
|
||||
// 测试单值 Map(包含 identifier 和一个值)
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("identifier", "temperature");
|
||||
params.put("actualValue", 25.5);
|
||||
message.setParams(params);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_notFound() {
|
||||
// 测试未找到属性值
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("humidity", 60);
|
||||
message.setParams(params);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_nullParams() {
|
||||
// 测试 params 为 null
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
message.setParams(null);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractPropertyValue_priorityOrder() {
|
||||
// 测试优先级顺序:直接标识符 > properties > data > value
|
||||
IotDeviceMessage message = new IotDeviceMessage();
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("temperature", 20.0);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("temperature", 30.0);
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("temperature", 25.5); // 最高优先级
|
||||
params.put("properties", properties);
|
||||
params.put("data", data);
|
||||
params.put("value", 40.0);
|
||||
message.setParams(params);
|
||||
|
||||
Object result = IotDeviceMessageUtils.extractPropertyValue(message, "temperature");
|
||||
assertEquals(25.5, result); // 应该返回直接标识符的值
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user