【代码优化】IoT:实现规则 IotRuleSceneDeviceControlAction 执行器
This commit is contained in:
parent
4f84182dab
commit
48cfcdadc1
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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,主键,自增
|
||||
|
|
|
@ -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 @芋艿:control、alert、webhook(待定)
|
||||
* 枚举 {@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;
|
||||
/**
|
||||
* 消息标识符
|
||||
*
|
||||
|
|
|
@ -17,7 +17,7 @@ public interface RedisKeyConstants {
|
|||
* HASH KEY:identifier 属性标识
|
||||
* 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";
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ public class IotRuleSceneMessageHandler {
|
|||
@Async
|
||||
public void onMessage(IotDeviceMessage message) {
|
||||
log.info("[onMessage][消息内容({})]", message);
|
||||
ruleSceneService.executeRuleScene(message);
|
||||
ruleSceneService.executeRuleSceneByDevice(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* TODO 芋艿:未来实现一个 IotRuleMessageConsumer
|
||||
*/
|
||||
package cn.iocoder.yudao.module.iot.mq.consumer.rule;
|
|
@ -67,4 +67,9 @@ public class IotDeviceMessage {
|
|||
*/
|
||||
private LocalDateTime reportTime;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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 场景,执行规则场景
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue