Explorar o código

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

YunaiV hai 6 meses
pai
achega
48cfcdadc1
Modificáronse 17 ficheiros con 306 adicións e 115 borrados
  1. 31 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java
  2. 2 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
  3. 40 33
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotRuleSceneDO.java
  4. 7 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java
  5. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java
  6. 0 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/package-info.java
  7. 5 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/message/IotDeviceMessage.java
  8. 5 8
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
  9. 3 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamService.java
  10. 23 17
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java
  11. 23 21
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java
  12. 5 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneService.java
  13. 69 11
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneServiceImpl.java
  14. 29 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneAction.java
  15. 56 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneDeviceControlAction.java
  16. 5 9
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
  17. 2 0
      yudao-server/src/main/resources/application.yaml

+ 31 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotRuleSceneActionTypeEnum.java

@@ -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 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java

@@ -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,主键,自增

+ 40 - 33
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/rule/IotRuleSceneDO.java

@@ -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,60 +170,60 @@ public class IotRuleSceneDO extends BaseDO {
     }
 
     /**
-     * 执行器
+     * 执行器配置
      */
     @Data
-    public static class Actuator {
+    public static class ActionConfig {
 
         /**
          * 执行类型
          *
-         * TODO @芋艿:control、alert、webhook(待定)
+         * 枚举 {@link IotRuleSceneActionTypeEnum}
          */
         private Integer type;
 
         /**
-         * 产品标识
-         *
-         * 关联 {@link IotProductDO#getProductKey()}
-         */
-        private String productKey;
-        /**
-         * 设备名称数组
-         *
-         * 关联 {@link IotDeviceDO#getDeviceName()}
-         */
-        private List<String> deviceNames;
-
-        /**
-         * 控制数组
+         * 设备控制
          *
-         * TODO 芋艿:类型的情况下
+         * 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DEVICE_CONTROL} 时,必填
          */
-        private List<ActuatorControl> controls;
+        private ActionDeviceControl deviceControl;
 
         /**
          * 数据桥接编号
          *
-         * TODO 芋艿:暂定!
+         * 当 {@link #type} 为 {@link IotRuleSceneActionTypeEnum#DATA_BRIDGE} 时,必填
          * TODO 芋艿:关联
          */
-        private Long bridgeId;
+        private Long dataBridgeId;
 
     }
 
     /**
-     * 执行控制
+     * 执行设备控制
      */
     @Data
-    public static class ActuatorControl {
+    public static class ActionDeviceControl {
+
+        /**
+         * 产品标识
+         *
+         * 关联 {@link IotProductDO#getProductKey()}
+         */
+        private String productKey;
+        /**
+         * 设备名称数组
+         *
+         * 关联 {@link IotDeviceDO#getDeviceName()}
+         */
+        private List<String> deviceNames;
 
         /**
          * 消息类型
          *
          * 枚举 {@link IotDeviceMessageTypeEnum#PROPERTY}、{@link IotDeviceMessageTypeEnum#SERVICE}
          */
-        private Integer type;
+        private String type;
         /**
          * 消息标识符
          *

+ 7 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java

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

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotRuleSceneMessageHandler.java

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

+ 0 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/package-info.java

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

+ 5 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/message/IotDeviceMessage.java

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

+ 5 - 8
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java

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

+ 3 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamService.java

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

+ 23 - 17
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java

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

+ 23 - 21
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java

@@ -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;
-        }
-
-        // 2. 更新设备状态
-        TenantUtils.executeIgnore(() ->
-                deviceService.updateDeviceState(device.getId(), updateReqDTO.getState()));
-
-        // 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);
+        TenantUtils.execute(device.getTenantId(), () -> {
+            // 1.2 记录设备的最后时间
+            updateDeviceLastTime(device, updateReqDTO);
+            // 1.3 当前状态一致,不处理
+            if (Objects.equals(device.getState(), updateReqDTO.getState())) {
+                return;
+            }
+
+            // 2. 更新设备状态
+            deviceService.updateDeviceState(device.getId(), updateReqDTO.getState());
+
+            // 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);
+        });
     }
 
     @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());
         }

+ 5 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneService.java

@@ -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 场景,执行规则场景
 
 }

+ 69 - 11
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/IotRuleSceneServiceImpl.java

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

+ 29 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneAction.java

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

+ 56 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneDeviceControlAction.java

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

+ 5 - 9
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java

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

+ 2 - 0
yudao-server/src/main/resources/application.yaml

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