【代码优化】IoT:实现规则 IotRuleSceneDeviceControlAction 执行器

This commit is contained in:
YunaiV 2025-02-03 12:05:13 +08:00
parent 4f84182dab
commit 48cfcdadc1
17 changed files with 306 additions and 115 deletions

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.iot.enums.rule;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* Iot 规则场景的触发类型枚举
*
* 设备触发定时触发
*/
@RequiredArgsConstructor
@Getter
public enum IotRuleSceneActionTypeEnum implements ArrayValuable<Integer> {
DEVICE_CONTROL(1), // 设备执行
ALERT(2), // 告警执行
DATA_BRIDGE(3); // 桥接执行
private final Integer type;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotRuleSceneActionTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@ -27,7 +28,7 @@ import java.util.Set;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDeviceDO extends BaseDO {
public class IotDeviceDO extends TenantBaseDO {
/**
* 设备 ID主键自增

View File

@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.rule;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
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.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -30,7 +32,7 @@ import java.util.Map;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotRuleSceneDO extends BaseDO {
public class IotRuleSceneDO extends TenantBaseDO {
/**
* 场景编号
@ -56,24 +58,26 @@ public class IotRuleSceneDO extends BaseDO {
* 触发器数组
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<Trigger> triggers;
private List<TriggerConfig> triggers;
// TODO @芋艿需要调研下 https://help.aliyun.com/zh/iot/user-guide/scene-orchestration-1?spm=a2c4g.11186623.help-menu-30520.d_2_4_5_0.45413908fxCSVa
/**
* 执行器数组
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<Actuator> actuators;
private List<ActionConfig> actions;
/**
* 触发器
* 触发器配置
*/
@Data
public static class Trigger {
public static class TriggerConfig {
/**
* 触发类型
*
* 枚举 {@link cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum}
* 枚举 {@link IotRuleSceneTriggerTypeEnum}
*/
private Integer type;
@ -93,14 +97,15 @@ public class IotRuleSceneDO extends BaseDO {
/**
* 触发条件数组
*
* TODO @芋艿注释说明
* {@link #type} {@link IotRuleSceneTriggerTypeEnum#DEVICE} 必填
* 条件与条件之间的关系
*/
private List<TriggerCondition> conditions;
/**
* CRON 表达式
*
* TODO @芋艿注释说明
* {@link #type} {@link IotRuleSceneTriggerTypeEnum#TIMER} 必填
*/
private String cronExpression;
@ -127,6 +132,8 @@ public class IotRuleSceneDO extends BaseDO {
/**
* 参数数组
*
* 参数与参数之间的关系
*/
private List<TriggerConditionParameter> parameters;
@ -163,18 +170,41 @@ public class IotRuleSceneDO extends BaseDO {
}
/**
* 执行器
* 执行器配置
*/
@Data
public static class Actuator {
public static class ActionConfig {
/**
* 执行类型
*
* TODO @芋艿controlalertwebhook待定
* 枚举 {@link IotRuleSceneActionTypeEnum}
*/
private Integer type;
/**
* 设备控制
*
* {@link #type} {@link IotRuleSceneActionTypeEnum#DEVICE_CONTROL} 必填
*/
private ActionDeviceControl deviceControl;
/**
* 数据桥接编号
*
* {@link #type} {@link IotRuleSceneActionTypeEnum#DATA_BRIDGE} 必填
* TODO 芋艿关联
*/
private Long dataBridgeId;
}
/**
* 执行设备控制
*/
@Data
public static class ActionDeviceControl {
/**
* 产品标识
*
@ -188,35 +218,12 @@ public class IotRuleSceneDO extends BaseDO {
*/
private List<String> deviceNames;
/**
* 控制数组
*
* TODO 芋艿类型的情况下
*/
private List<ActuatorControl> controls;
/**
* 数据桥接编号
*
* TODO 芋艿暂定
* TODO 芋艿关联
*/
private Long bridgeId;
}
/**
* 执行器控制
*/
@Data
public static class ActuatorControl {
/**
* 消息类型
*
* 枚举 {@link IotDeviceMessageTypeEnum#PROPERTY}{@link IotDeviceMessageTypeEnum#SERVICE}
*/
private Integer type;
private String type;
/**
* 消息标识符
*

View File

@ -17,7 +17,7 @@ public interface RedisKeyConstants {
* HASH KEYidentifier 属性标识
* VALUE 数据类型String(JSON) {@link IotDevicePropertyDO}
*/
String DEVICE_PROPERTY = "device_property:%s";
String DEVICE_PROPERTY = "iot:device_property:%s";
/**
* 设备的最后上报时间采用 ZSET 结构
@ -25,23 +25,23 @@ public interface RedisKeyConstants {
* KEY 格式{deviceKey}
* SCORE上报时间
*/
String DEVICE_REPORT_TIMES = "device_report_times";
String DEVICE_REPORT_TIMES = "iot:device_report_times";
/**
* 设备信息的数据缓存使用 Spring Cache 操作
* 设备信息的数据缓存使用 Spring Cache 操作忽略租户
*
* KEY 格式device_${productKey}_${deviceKey}
* VALUE 数据类型String(JSON)
*/
String DEVICE = "device";
String DEVICE = "iot:device";
/**
* 物模型的数据缓存使用 Spring Cache 操作
* 物模型的数据缓存使用 Spring Cache 操作忽略租户
*
* KEY 格式thing_model_${productKey}
* VALUE 数据类型String 数组(JSON) {@link cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO} 列表
*/
String THING_MODEL_LIST = "thing_model_list";
String THING_MODEL_LIST = "iot:thing_model_list";
/**
* 设备插件的插件进程编号的映射采用 HASH 结构
@ -50,6 +50,6 @@ public interface RedisKeyConstants {
* HASH KEY${deviceKey}
* VALUE插件进程编号对应 {@link IotPluginInstanceDO#getProcessId()} 字段
*/
String DEVICE_PLUGIN_INSTANCE_PROCESS_IDS = "device_plugin_instance_process_ids";
String DEVICE_PLUGIN_INSTANCE_PROCESS_IDS = "iot:device_plugin_instance_process_ids";
}

View File

@ -24,7 +24,7 @@ public class IotRuleSceneMessageHandler {
@Async
public void onMessage(IotDeviceMessage message) {
log.info("[onMessage][消息内容({})]", message);
ruleSceneService.executeRuleScene(message);
ruleSceneService.executeRuleSceneByDevice(message);
}
}

View File

@ -1,4 +0,0 @@
/**
* TODO 芋艿未来实现一个 IotRuleMessageConsumer
*/
package cn.iocoder.yudao.module.iot.mq.consumer.rule;

View File

@ -67,4 +67,9 @@ public class IotDeviceMessage {
*/
private LocalDateTime reportTime;
/**
* 租户编号
*/
private Long tenantId;
}

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
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.device.vo.device.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
@ -261,13 +262,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
}
@Override
public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
// 保证在 @CacheEvict 之前忽略租户
return TenantUtils.executeIgnore(() -> getSelf().getDeviceByProductKeyAndDeviceNameFromCache0(productKey, deviceName));
}
@Cacheable(value = RedisKeyConstants.DEVICE, key = "#productKey + '_' + #deviceName", unless = "#result == null")
public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache0(String productKey, String deviceName) {
@TenantIgnore // 忽略租户信息跨租户 productKey + deviceName 是唯一的
public IotDeviceDO getDeviceByProductKeyAndDeviceNameFromCache(String productKey, String deviceName) {
return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
}
@ -389,8 +386,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
}
private void deleteDeviceCache(IotDeviceDO device) {
// 保证 @CacheEvict 之前忽略租户
TenantUtils.executeIgnore(() -> getSelf().deleteDeviceCache0(device));
// 保证 Spring AOP 触发
getSelf().deleteDeviceCache0(device);
}
private void deleteDeviceCache(List<IotDeviceDO> devices) {

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.iot.service.device.control;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import jakarta.validation.Valid;
/**
@ -16,7 +17,8 @@ public interface IotDeviceDownstreamService {
* 设备下行可用于设备模拟
*
* @param downstreamReqVO 设备下行请求 VO
* @return 下发消息
*/
void downstreamDevice(@Valid IotDeviceDownstreamReqVO downstreamReqVO);
IotDeviceMessage downstreamDevice(@Valid IotDeviceDownstreamReqVO downstreamReqVO);
}

View File

@ -54,7 +54,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
private IotDeviceProducer deviceProducer;
@Override
public void downstreamDevice(IotDeviceDownstreamReqVO downstreamReqVO) {
public IotDeviceMessage downstreamDevice(IotDeviceDownstreamReqVO downstreamReqVO) {
// 校验设备是否存在
IotDeviceDO device = deviceService.validateDeviceExists(downstreamReqVO.getId());
// TODO 芋艿父设备的处理
@ -62,31 +62,28 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
// 服务调用
if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.SERVICE.getType())) {
invokeDeviceService(downstreamReqVO, device, parentDevice);
return;
return invokeDeviceService(downstreamReqVO, device, parentDevice);
}
// 属性相关
if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) {
// 属性设置
if (Objects.equals(downstreamReqVO.getIdentifier(),
IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier())) {
setDeviceProperty(downstreamReqVO, device, parentDevice);
return;
return setDeviceProperty(downstreamReqVO, device, parentDevice);
}
// 属性设置
if (Objects.equals(downstreamReqVO.getIdentifier(),
IotDeviceMessageIdentifierEnum.PROPERTY_GET.getIdentifier())) {
getDeviceProperty(downstreamReqVO, device, parentDevice);
return;
return getDeviceProperty(downstreamReqVO, device, parentDevice);
}
}
// 配置下发
if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.CONFIG.getType())
&& Objects.equals(downstreamReqVO.getIdentifier(), IotDeviceMessageIdentifierEnum.CONFIG_SET.getIdentifier())) {
setDeviceConfig(downstreamReqVO, device, parentDevice);
return;
return setDeviceConfig(downstreamReqVO, device, parentDevice);
}
// TODO 芋艿ota 升级
return null;
}
/**
@ -95,9 +92,10 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
* @param downstreamReqVO 下行请求
* @param device 设备
* @param parentDevice 父设备
* @return 下发消息
*/
@SuppressWarnings("unchecked")
private void invokeDeviceService(IotDeviceDownstreamReqVO downstreamReqVO,
private IotDeviceMessage invokeDeviceService(IotDeviceDownstreamReqVO downstreamReqVO,
IotDeviceDO device, IotDeviceDO parentDevice) {
// 1. 参数校验
if (!(downstreamReqVO.getData() instanceof Map<?, ?>)) {
@ -124,6 +122,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
device.getDeviceKey(), reqDTO, result);
throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg());
}
return message;
}
/**
@ -132,10 +131,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
* @param downstreamReqVO 下行请求
* @param device 设备
* @param parentDevice 父设备
* @return 下发消息
*/
@SuppressWarnings("unchecked")
private void setDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO,
IotDeviceDO device, IotDeviceDO parentDevice) {
private IotDeviceMessage setDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO,
IotDeviceDO device, IotDeviceDO parentDevice) {
// 1. 参数校验
if (!(downstreamReqVO.getData() instanceof Map<?, ?>)) {
throw new ServiceException(BAD_REQUEST.getCode(), "data 不是 Map 类型");
@ -162,6 +162,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
device.getDeviceKey(), reqDTO, result);
throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg());
}
return message;
}
/**
@ -170,10 +171,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
* @param downstreamReqVO 下行请求
* @param device 设备
* @param parentDevice 父设备
* @return 下发消息
*/
@SuppressWarnings("unchecked")
private void getDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO,
IotDeviceDO device, IotDeviceDO parentDevice) {
private IotDeviceMessage getDeviceProperty(IotDeviceDownstreamReqVO downstreamReqVO,
IotDeviceDO device, IotDeviceDO parentDevice) {
// 1. 参数校验
if (!(downstreamReqVO.getData() instanceof List<?>)) {
throw new ServiceException(BAD_REQUEST.getCode(), "data 不是 List 类型");
@ -200,6 +202,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
device.getDeviceKey(), reqDTO, result);
throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg());
}
return message;
}
/**
@ -208,10 +211,11 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
* @param downstreamReqVO 下行请求
* @param device 设备
* @param parentDevice 父设备
* @return 下发消息
*/
@SuppressWarnings({"unchecked", "unused"})
private void setDeviceConfig(IotDeviceDownstreamReqVO downstreamReqVO,
IotDeviceDO device, IotDeviceDO parentDevice) {
private IotDeviceMessage setDeviceConfig(IotDeviceDownstreamReqVO downstreamReqVO,
IotDeviceDO device, IotDeviceDO parentDevice) {
// 1. 参数转换无需校验
Map<String, Object> config = JsonUtils.parseObject(device.getConfig(), Map.class);
@ -235,6 +239,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
device.getDeviceKey(), reqDTO, result);
throw exception(DEVICE_DOWNSTREAM_FAILED, result.getMsg());
}
return message;
}
/**
@ -275,7 +280,8 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device, Integer code) {
// 1. 完善消息
message.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())
.setDeviceKey(device.getDeviceKey());
.setDeviceKey(device.getDeviceKey())
.setTenantId(device.getTenantId());
Assert.notNull(message.getRequestId(), "requestId 不能为空");
if (message.getReportTime() == null) {
message.setReportTime(LocalDateTime.now());

View File

@ -98,26 +98,27 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
updateReqDTO.getProductKey(), updateReqDTO.getDeviceName());
return;
}
// 1.2 记录设备的最后时间
updateDeviceLastTime(device, updateReqDTO);
// 1.3 当前状态一致不处理
if (Objects.equals(device.getState(), updateReqDTO.getState())) {
return;
}
TenantUtils.execute(device.getTenantId(), () -> {
// 1.2 记录设备的最后时间
updateDeviceLastTime(device, updateReqDTO);
// 1.3 当前状态一致不处理
if (Objects.equals(device.getState(), updateReqDTO.getState())) {
return;
}
// 2. 更新设备状态
TenantUtils.executeIgnore(() ->
deviceService.updateDeviceState(device.getId(), updateReqDTO.getState()));
// 2. 更新设备状态
deviceService.updateDeviceState(device.getId(), updateReqDTO.getState());
// 3. TODO 芋艿子设备的关联
// 3. TODO 芋艿子设备的关联
// 4. 发送设备消息
IotDeviceMessage message = BeanUtils.toBean(updateReqDTO, IotDeviceMessage.class)
.setType(IotDeviceMessageTypeEnum.STATE.getType())
.setIdentifier(ObjUtil.equals(updateReqDTO.getState(), IotDeviceStateEnum.ONLINE.getState())
? IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier()
: IotDeviceMessageIdentifierEnum.STATE_OFFLINE.getIdentifier());
sendDeviceMessage(message, device);
// 4. 发送设备消息
IotDeviceMessage message = BeanUtils.toBean(updateReqDTO, IotDeviceMessage.class)
.setType(IotDeviceMessageTypeEnum.STATE.getType())
.setIdentifier(ObjUtil.equals(updateReqDTO.getState(), IotDeviceStateEnum.ONLINE.getState())
? IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier()
: IotDeviceMessageIdentifierEnum.STATE_OFFLINE.getIdentifier());
sendDeviceMessage(message, device);
});
}
@Override
@ -174,7 +175,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
// 1. 完善消息
message.setDeviceKey(device.getDeviceKey());
message.setDeviceKey(device.getDeviceKey())
.setTenantId(device.getTenantId());
if (StrUtil.isEmpty(message.getRequestId())) {
message.setRequestId(IdUtil.fastSimpleUUID());
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.iot.service.rule;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import java.util.List;
@ -22,10 +23,12 @@ public interface IotRuleSceneService {
List<IotRuleSceneDO> getRuleSceneListByProductKeyAndDeviceNameFromCache(String productKey, String deviceName);
/**
* 执行规则场景
* 基于 {@link IotRuleSceneTriggerTypeEnum#DEVICE} 场景执行规则场景
*
* @param message 消息
*/
void executeRuleScene(IotDeviceMessage message);
void executeRuleSceneByDevice(IotDeviceMessage message);
// TODO @芋艿基于 timer 场景执行规则场景
}

View File

@ -11,13 +11,16 @@ 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.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
import cn.iocoder.yudao.module.iot.dal.mysql.rule.IotRuleSceneMapper;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageIdentifierEnum;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceMessageTypeEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerConditionParameterOperatorEnum;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneTriggerTypeEnum;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.service.rule.action.IotRuleSceneAction;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -43,6 +46,9 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
@Resource
private IotRuleSceneMapper ruleSceneMapper;
@Resource
private List<IotRuleSceneAction> ruleSceneActions;
// TODO 芋艿缓存待实现
@Override
@TenantIgnore // 忽略租户隔离因为 IotRuleSceneMessageHandler 调用时一般未传递租户所以需要忽略
@ -50,7 +56,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
if (true) {
IotRuleSceneDO ruleScene01 = new IotRuleSceneDO();
ruleScene01.setTriggers(CollUtil.newArrayList());
IotRuleSceneDO.Trigger trigger01 = new IotRuleSceneDO.Trigger();
IotRuleSceneDO.TriggerConfig trigger01 = new IotRuleSceneDO.TriggerConfig();
trigger01.setType(IotRuleSceneTriggerTypeEnum.DEVICE.getType());
trigger01.setConditions(CollUtil.newArrayList());
// 属性
@ -120,7 +126,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
condition02.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier());
condition02.setParameters(CollUtil.newArrayList());
trigger01.getConditions().add(condition02);
// TODO 芋艿事件
// 事件
IotRuleSceneDO.TriggerCondition condition03 = new IotRuleSceneDO.TriggerCondition();
condition03.setType(IotDeviceMessageTypeEnum.EVENT.getType());
condition03.setIdentifier("xxx");
@ -131,13 +137,28 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
parameter030.setValue("1");
trigger01.getConditions().add(condition03);
ruleScene01.getTriggers().add(trigger01);
// 动作
ruleScene01.setActions(CollUtil.newArrayList());
IotRuleSceneDO.ActionConfig action01 = new IotRuleSceneDO.ActionConfig();
action01.setType(IotRuleSceneActionTypeEnum.DEVICE_CONTROL.getType());
IotRuleSceneDO.ActionDeviceControl actionDeviceControl01 = new IotRuleSceneDO.ActionDeviceControl();
actionDeviceControl01.setProductKey("4aymZgOTOOCrDKRT");
actionDeviceControl01.setDeviceNames(ListUtil.of("small"));
actionDeviceControl01.setType(IotDeviceMessageTypeEnum.PROPERTY.getType());
actionDeviceControl01.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier());
actionDeviceControl01.setData(MapUtil.<String, Object>builder()
.put("power", 1)
.put("color", "red")
.build());
action01.setDeviceControl(actionDeviceControl01);
ruleScene01.getActions().add(action01);
return ListUtil.toList(ruleScene01);
}
List<IotRuleSceneDO> list = ruleSceneMapper.selectList();
// TODO @芋艿需要考虑开启状态
return filterList(list, ruleScene -> {
for (IotRuleSceneDO.Trigger trigger : ruleScene.getTriggers()) {
for (IotRuleSceneDO.TriggerConfig trigger : ruleScene.getTriggers()) {
if (ObjUtil.notEqual(trigger.getProductKey(), productKey)) {
continue;
}
@ -151,12 +172,17 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
}
@Override
public void executeRuleScene(IotDeviceMessage message) {
// 1. 获得设备匹配的规则场景
List<IotRuleSceneDO> ruleScenes = getMatchedRuleSceneList(message);
if (CollUtil.isEmpty(ruleScenes)) {
return;
}
public void executeRuleSceneByDevice(IotDeviceMessage message) {
TenantUtils.execute(message.getTenantId(), () -> {
// 1. 获得设备匹配的规则场景
List<IotRuleSceneDO> ruleScenes = getMatchedRuleSceneList(message);
if (CollUtil.isEmpty(ruleScenes)) {
return;
}
// 2. 执行规则场景
executeRuleSceneAction(message, ruleScenes);
});
}
/**
@ -176,7 +202,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
// 2. 匹配 trigger 触发器的条件
return filterList(ruleScenes, ruleScene -> {
for (IotRuleSceneDO.Trigger trigger : ruleScene.getTriggers()) {
for (IotRuleSceneDO.TriggerConfig trigger : ruleScene.getTriggers()) {
// 2.1 非设备触发不匹配
if (ObjUtil.notEqual(trigger.getType(), IotRuleSceneTriggerTypeEnum.DEVICE.getType())) {
return false;
@ -219,7 +245,7 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
*/
@SuppressWarnings({"unchecked", "DataFlowIssue"})
private boolean isTriggerConditionParameterMatched(IotDeviceMessage message, IotRuleSceneDO.TriggerConditionParameter parameter,
IotRuleSceneDO ruleScene, IotRuleSceneDO.Trigger trigger) {
IotRuleSceneDO ruleScene, IotRuleSceneDO.TriggerConfig trigger) {
// 1.1 校验操作符是否合法
IotRuleSceneTriggerConditionParameterOperatorEnum operator =
IotRuleSceneTriggerConditionParameterOperatorEnum.operatorOf(parameter.getOperator());
@ -266,4 +292,36 @@ public class IotRuleSceneServiceImpl implements IotRuleSceneService {
}
}
/**
* 执行规则场景的动作
*
* @param message 设备消息
* @param ruleScenes 规则场景列表
*/
private void executeRuleSceneAction(IotDeviceMessage message, List<IotRuleSceneDO> ruleScenes) {
// 1. 遍历规则场景
ruleScenes.forEach(ruleScene -> {
// 2. 遍历规则场景的动作
ruleScene.getActions().forEach(actionConfig -> {
// 3.1 获取对应的动作 Action 数组
List<IotRuleSceneAction> actions = filterList(ruleSceneActions,
action -> action.getType().getType().equals(actionConfig.getType()));
if (CollUtil.isEmpty(actions)) {
return;
}
// 3.2 执行动作
actions.forEach(action -> {
try {
action.execute(message, actionConfig);
log.info("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 成功]",
message, ruleScene.getId(), actionConfig);
} catch (Exception e) {
log.error("[executeRuleSceneAction][消息({}) 规则场景编号({}) 的执行动作({}) 执行异常]",
message, ruleScene.getId(), actionConfig, e);
}
});
});
});
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.iot.service.rule.action;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
/**
* IOT 规则场景的场景执行器接口
*
* @author 芋道源码
*/
public interface IotRuleSceneAction {
/**
* 执行场景
*
* @param message 消息
* @param config 配置
*/
void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config);
/**
* 获得类型
*
* @return 类型
*/
IotRuleSceneActionTypeEnum getType();
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.iot.service.rule.action;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* IoT 设备控制的 {@link IotRuleSceneAction} 实现类
*
* @author 芋道源码
*/
@Component
@Slf4j
public class IotRuleSceneDeviceControlAction implements IotRuleSceneAction {
@Resource
private IotDeviceDownstreamService deviceDownstreamService;
@Resource
private IotDeviceService deviceService;
@Override
public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) {
IotRuleSceneDO.ActionDeviceControl control = config.getDeviceControl();
Assert.notNull(control, "设备控制配置不能为空");
// 遍历每个设备下发消息
control.getDeviceNames().forEach(deviceName -> {
IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(control.getProductKey(), deviceName);
if (device == null) {
log.error("[execute][message({}) config({}) 对应的设备不存在]", message, config);
return;
}
try {
IotDeviceMessage downstreamMessage = deviceDownstreamService.downstreamDevice(new IotDeviceDownstreamReqVO()
.setId(device.getId()).setType(control.getType()).setIdentifier(control.getIdentifier())
.setData(control.getData()));
log.info("[execute][message({}) config({}) 下发消息({})成功]", message, config, downstreamMessage);
} catch (Exception e) {
log.error("[execute][message({}) config({}) 下发消息失败]", message, config, e);
}
});
}
@Override
public IotRuleSceneActionTypeEnum getType() {
return IotRuleSceneActionTypeEnum.DEVICE_CONTROL;
}
}

View File

@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelEvent;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelParam;
import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelService;
@ -135,13 +135,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
}
@Override
public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) {
// 保证在 @CacheEvict 之前忽略租户
return TenantUtils.executeIgnore(() -> getSelf().getThingModelListByProductKeyFromCache0(productKey));
}
@Cacheable(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")
public List<IotThingModelDO> getThingModelListByProductKeyFromCache0(String productKey) {
@TenantIgnore // 忽略租户信息跨租户 productKey 是唯一的
public List<IotThingModelDO> getThingModelListByProductKeyFromCache(String productKey) {
return thingModelMapper.selectListByProductKey(productKey);
}
@ -354,8 +350,8 @@ public class IotThingModelServiceImpl implements IotThingModelService {
}
private void deleteThingModelListCache(String productKey) {
// 保证 @CacheEvict 之前忽略租户
TenantUtils.executeIgnore(() -> getSelf().deleteThingModelListCache0(productKey));
// 保证 Spring AOP 触发
getSelf().deleteThingModelListCache0(productKey);
}
@CacheEvict(value = RedisKeyConstants.THING_MODEL_LIST, key = "#productKey")

View File

@ -311,6 +311,8 @@ yudao:
- mail_account
- mail_template
- sms_template
- iot:device
- iot:thing_model_list
sms-code: # 短信验证码相关的配置项
expire-times: 10m
send-frequency: 1m