Explorar o código

【功能新增】IoT:设备拓扑图的添加

YunaiV hai 6 meses
pai
achega
bc9b3715b1
Modificáronse 17 ficheiros con 161 adicións e 18 borrados
  1. 9 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java
  2. 2 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEventReportReqDTO.java
  3. 2 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDevicePropertyReportReqDTO.java
  4. 1 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterSubReqDTO.java
  5. 2 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceStateUpdateReqDTO.java
  6. 43 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceTopologyAddReqDTO.java
  7. 4 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java
  8. 2 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java
  9. 6 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
  10. 2 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
  11. 2 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/message/IotDeviceMessage.java
  12. 4 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
  13. 8 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
  14. 1 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java
  15. 7 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java
  16. 47 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java
  17. 19 4
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/upstream/IotDeviceUpstreamClient.java

+ 9 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java

@@ -62,6 +62,15 @@ public interface IotDeviceUpstreamApi {
     @PostMapping(PREFIX + "/register-sub")
     CommonResult<Boolean> registerSubDevice(@Valid @RequestBody IotDeviceRegisterSubReqDTO registerReqDTO);
 
+    // TODO @芋艿:这个需要 plugins 接入下
+    /**
+     * 注册设备拓扑
+     *
+     * @param addReqDTO 注册设备拓扑 DTO
+     */
+    @PostMapping(PREFIX + "/add-topology")
+    CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO);
+
     // ========== 插件相关 ==========
 
     /**

+ 2 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEventReportReqDTO.java

@@ -7,6 +7,8 @@ import java.util.Map;
 
 /**
  * IoT 设备【事件】上报 Request DTO
+ *
+ * @author 芋道源码
  */
 @Data
 public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {

+ 2 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDevicePropertyReportReqDTO.java

@@ -7,6 +7,8 @@ import java.util.Map;
 
 /**
  * IoT 设备【属性】上报 Request DTO
+ *
+ * @author 芋道源码
  */
 @Data
 public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {

+ 1 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterSubReqDTO.java

@@ -13,6 +13,7 @@ import java.util.List;
 @Data
 public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO {
 
+    // TODO @芋艿:看看要不要优化命名
     /**
      * 子设备数组
      */

+ 2 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceStateUpdateReqDTO.java

@@ -7,6 +7,8 @@ import lombok.Data;
 
 /**
  * IoT 设备【状态】更新 Request DTO
+ *
+ * @author 芋道源码
  */
 @Data
 public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {

+ 43 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceTopologyAddReqDTO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
+
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * IoT 设备【拓扑】添加 Request DTO
+ */
+@Data
+public class IotDeviceTopologyAddReqDTO extends IotDeviceUpstreamAbstractReqDTO {
+
+    // TODO @芋艿:看看要不要优化命名
+    /**
+     * 子设备数组
+     */
+    @NotEmpty(message = "子设备不能为空")
+    private List<IotDeviceRegisterSubReqDTO.Device> params;
+
+    /**
+     * 设备信息
+     */
+    @Data
+    public static class Device {
+
+        /**
+         * 产品标识
+         */
+        @NotEmpty(message = "产品标识不能为空")
+        private String productKey;
+
+        /**
+         * 设备名称
+         */
+        @NotEmpty(message = "设备名称不能为空")
+        private String deviceName;
+
+        // TODO @芋艿:阿里云还有 sign 签名
+
+    }
+
+}

+ 4 - 1
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java

@@ -30,7 +30,10 @@ public enum IotDeviceMessageIdentifierEnum {
 
     REGISTER_REGISTER("register"), // 上行
     REGISTER_REGISTER_SUB("register_sub"), // 上行
-    REGISTER_UNREGISTER_SUB("unregister_sub"),; // 下行
+    REGISTER_UNREGISTER_SUB("unregister_sub"), // 下行
+
+    TOPOLOGY_ADD("topology_add"), // 下行;
+    ;
 
     /**
      * 标志符

+ 2 - 1
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java

@@ -19,7 +19,8 @@ public enum IotDeviceMessageTypeEnum implements ArrayValuable<String> {
     SERVICE("service"), // 设备服务:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
     CONFIG("config"), // 设备配置:可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置
     OTA("ota"), // 设备 OTA:可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级
-    REGISTER("register"),; // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
+    REGISTER("register"), // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
+    TOPOLOGY("topology"),; // 设备拓扑:可参考 https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships 设备拓扑
 
     public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new);
 

+ 6 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java

@@ -55,6 +55,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
         return success(true);
     }
 
+    @Override
+    public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
+        deviceUpstreamService.addDeviceTopology(addReqDTO);
+        return success(true);
+    }
+
     // ========== 插件相关 ==========
 
     @Override

+ 2 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java

@@ -61,6 +61,8 @@ public class IotDeviceController {
         return success(true);
     }
 
+    // TODO @芋艿:参考阿里云:1)绑定网关;2)解绑网关
+
     @PutMapping("/update-group")
     @Operation(summary = "更新设备分组")
     @PreAuthorize("@ss.hasPermission('iot:device:update')")

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

@@ -9,8 +9,9 @@ import lombok.NoArgsConstructor;
 
 import java.time.LocalDateTime;
 
+// TODO @芋艿:参考阿里云的物模型,优化 IoT 上下行消息的设计,尽量保持一致(渐进式,不要一口气)!
 /**
- * 设备消息
+ * IoT 设备消息
  */
 @Data
 @NoArgsConstructor

+ 4 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java

@@ -30,12 +30,12 @@ public interface IotDeviceService {
      *
      * @param productKey 产品标识
      * @param deviceName 设备名称
-     * @param gatewayId 网关设备 ID
+     * @param gatewayId  网关设备 ID
      * @return 设备
      */
     IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey,
-                             @NotEmpty(message = "设备名称不能为空") String deviceName,
-                             Long gatewayId);
+            @NotEmpty(message = "设备名称不能为空") String deviceName,
+            Long gatewayId);
 
     /**
      * 更新设备
@@ -58,7 +58,7 @@ public interface IotDeviceService {
     /**
      * 更新设备状态
      *
-     * @param id   编号
+     * @param id    编号
      * @param state 状态
      */
     void updateDeviceState(Long id, Integer state);

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

@@ -81,8 +81,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     public IotDeviceDO createDevice(String productKey, String deviceName, Long gatewayId) {
         String deviceKey = generateDeviceKey();
         // 1.1 校验产品是否存在
-        IotProductDO product = TenantUtils.executeIgnore(() ->
-                productService.getProductByProductKey(productKey));
+        IotProductDO product = TenantUtils.executeIgnore(() -> productService.getProductByProductKey(productKey));
         if (product == null) {
             throw exception(PRODUCT_NOT_EXISTS);
         }
@@ -100,7 +99,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     }
 
     private void validateCreateDeviceParam(String productKey, String deviceName, String deviceKey,
-                                           Long gatewayId, IotProductDO product) {
+            Long gatewayId, IotProductDO product) {
         TenantUtils.executeIgnore(() -> {
             // 校验设备名称在同一产品下是否唯一
             if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
@@ -120,7 +119,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     }
 
     private void initDevice(IotDeviceDO device, IotProductDO product) {
-        device.setProductId(product.getId()).setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
+        device.setProductId(product.getId()).setProductKey(product.getProductKey())
+                .setDeviceType(product.getDeviceType());
         // 生成并设置必要的字段
         // TODO @芋艿:各种 mqtt 是不是可以简化!
         device.setDeviceSecret(generateDeviceSecret())
@@ -269,7 +269,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         // 2. 更新状态和时间
         IotDeviceDO updateObj = new IotDeviceDO().setId(id).setState(state);
         if (device.getOnlineTime() == null
-            && Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
+                && Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
             updateObj.setActiveTime(LocalDateTime.now());
         }
         if (Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
@@ -363,7 +363,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
                 // 2.1.1 校验字段是否符合要求
                 try {
                     ValidationUtils.validate(importDevice);
-                } catch (ConstraintViolationException ex){
+                } catch (ConstraintViolationException ex) {
                     respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
                     return;
                 }
@@ -427,7 +427,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     }
 
     @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
-    public void deleteDeviceCache0(IotDeviceDO device) {}
+    public void deleteDeviceCache0(IotDeviceDO device) {
+    }
 
     private IotDeviceServiceImpl getSelf() {
         return SpringUtil.getBean(getClass());

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

@@ -88,6 +88,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
         if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.OTA.getType())) {
             return otaUpgrade(downstreamReqVO, device, parentDevice);
         }
+        // TODO @芋艿:取消设备的网关的时,要不要下发 REGISTER_UNREGISTER_SUB ?
         throw new IllegalArgumentException("不支持的下行消息类型:" + downstreamReqVO);
     }
 

+ 7 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java

@@ -55,4 +55,11 @@ public interface IotDeviceUpstreamService {
      */
     void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO);
 
+    /**
+     * 添加设备拓扑
+     *
+     * @param addReqDTO 添加设备拓扑 DTO
+     */
+    void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO);
+
 }

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

@@ -220,6 +220,7 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
         if (CollUtil.isNotEmpty(registerReqDTO.getParams())) {
             registerReqDTO.getParams().forEach(subDevice -> registerDevice0(
                     subDevice.getProductKey(), subDevice.getDeviceName(), device.getId(), registerReqDTO));
+            // TODO @芋艿:后续要处理,每个设备是否成功
         }
 
         // 3. 发送设备消息
@@ -230,6 +231,52 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
         sendDeviceMessage(message, device);
     }
 
+    @Override
+    public void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
+        // 1.1 获得设备
+        log.info("[addDeviceTopology][添加设备拓扑: {}]", addReqDTO);
+        IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
+                addReqDTO.getProductKey(), addReqDTO.getDeviceName());
+        if (device == null) {
+            log.error("[addDeviceTopology][设备({}/{}) 不存在]",
+                    addReqDTO.getProductKey(), addReqDTO.getDeviceName());
+            return;
+        }
+        if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
+            log.error("[addDeviceTopology][设备({}/{}) 不是网关设备({}),无法进行拓扑添加]",
+                    addReqDTO.getProductKey(), addReqDTO.getDeviceName(), device);
+            return;
+        }
+        // 1.2 记录设备的最后时间
+        updateDeviceLastTime(device, addReqDTO);
+
+        // 2. 处理拓扑
+        if (CollUtil.isNotEmpty(addReqDTO.getParams())) {
+            TenantUtils.execute(device.getTenantId(), () -> {
+                addReqDTO.getParams().forEach(subDevice -> {
+                    IotDeviceDO subDeviceDO = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
+                            subDevice.getProductKey(), subDevice.getDeviceName());
+                    // TODO @芋艿:后续要处理,每个设备是否成功
+                    if (subDeviceDO == null) {
+                        log.error("[addDeviceTopology][子设备({}/{}) 不存在]",
+                                subDevice.getProductKey(), subDevice.getDeviceName());
+                        return;
+                    }
+                    deviceService.updateDeviceGateway(subDeviceDO.getId(), device.getId());
+                    log.info("[addDeviceTopology][子设备({}/{}) 添加到网关设备({}) 成功]",
+                            subDevice.getProductKey(), subDevice.getDeviceName(), device);
+                });
+            });
+        }
+
+        // 3. 发送设备消息
+        IotDeviceMessage message = BeanUtils.toBean(addReqDTO, IotDeviceMessage.class)
+                .setType(IotDeviceMessageTypeEnum.TOPOLOGY.getType())
+                .setIdentifier(IotDeviceMessageIdentifierEnum.TOPOLOGY_ADD.getIdentifier())
+                .setData(addReqDTO.getParams());
+        sendDeviceMessage(message, device);
+    }
+
     private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
         // 1. 【异步】记录设备与插件实例的映射
         pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());

+ 19 - 4
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/upstream/IotDeviceUpstreamClient.java

@@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin.common.upstream;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
 import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -42,6 +39,24 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
         return doPost(url, reportReqDTO);
     }
 
+    // TODO @芋艿:待实现
+    @Override
+    public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
+        return null;
+    }
+
+    // TODO @芋艿:待实现
+    @Override
+    public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
+        return null;
+    }
+
+    // TODO @芋艿:待实现
+    @Override
+    public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
+        return null;
+    }
+
     @Override
     public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
         String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property";