Browse Source

【功能新增】IoT:设备注册 sub register 逻辑

YunaiV 6 months ago
parent
commit
4254c06c37
11 changed files with 176 additions and 48 deletions
  1. 9 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java
  2. 1 1
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterReqDTO.java
  3. 42 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterSubReqDTO.java
  4. 2 2
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java
  5. 10 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java
  6. 6 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
  7. 1 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java
  8. 14 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
  9. 31 30
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
  10. 8 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java
  11. 52 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java

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

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

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

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
 import lombok.Data;
 
 /**
- * IoT 设备【注册】注册 Request DTO
+ * IoT 设备【注册】自己 Request DTO
  *
  * @author 芋道源码
  */

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

@@ -0,0 +1,42 @@
+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
+ *
+ * @author 芋道源码
+ */
+@Data
+public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO {
+
+    /**
+     * 子设备数组
+     */
+    @NotEmpty(message = "子设备不能为空")
+    private List<Device> params;
+
+    /**
+     * 设备信息
+     */
+    @Data
+    public static class Device {
+
+        /**
+         * 产品标识
+         */
+        @NotEmpty(message = "产品标识不能为空")
+        private String productKey;
+
+        /**
+         * 设备名称
+         */
+        @NotEmpty(message = "设备名称不能为空")
+        private String deviceName;
+
+    }
+
+}

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

@@ -29,8 +29,8 @@ public enum IotDeviceMessageIdentifierEnum {
     OTA_REPORT("report"), // 上行
 
     REGISTER_REGISTER("register"), // 上行
-    REGISTER_SUB_REGISTER("sub_register"), // 上行
-    REGISTER_SUB_UNREGISTER("sub_unregister"),; // 下行
+    REGISTER_REGISTER_SUB("register_sub"), // 上行
+    REGISTER_UNREGISTER_SUB("unregister_sub"),; // 下行
 
     /**
      * 标志符

+ 10 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java

@@ -46,4 +46,14 @@ public enum IotProductDeviceTypeEnum implements ArrayValuable<Integer> {
         return GATEWAY.getType().equals(type);
     }
 
+    /**
+     * 判断是否是网关子设备
+     *
+     * @param type 类型
+     * @return 是否是网关子设备
+     */
+    public static boolean isGatewaySub(Integer type) {
+        return GATEWAY_SUB.getType().equals(type);
+    }
+
 }

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

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

+ 1 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.Size;
 import lombok.Data;
 
@@ -18,8 +17,7 @@ public class IotDeviceSaveReqVO {
     @Size(max = 50, message = "设备编号长度不能超过 50 个字符")
     private String deviceKey;
 
-    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
-    @NotEmpty(message = "设备名称不能为空")
+    @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.AUTO, example = "王五")
     private String deviceName;
 
     @Schema(description = "备注名称", example = "张三")

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

@@ -30,10 +30,12 @@ public interface IotDeviceService {
      *
      * @param productKey 产品标识
      * @param deviceName 设备名称
+     * @param gatewayId 网关设备 ID
      * @return 设备
      */
     IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey,
-                             @NotEmpty(message = "设备名称不能为空") String deviceName);
+                             @NotEmpty(message = "设备名称不能为空") String deviceName,
+                             Long gatewayId);
 
     /**
      * 更新设备
@@ -42,6 +44,17 @@ public interface IotDeviceService {
      */
     void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO);
 
+    // TODO @芋艿:先这么实现。未来看情况,要不要自己实现
+    /**
+     * 更新设备的所属网关
+     *
+     * @param id        编号
+     * @param gatewayId 网关设备 ID
+     */
+    default void updateDeviceGateway(Long id, Long gatewayId) {
+        updateDevice(new IotDeviceSaveReqVO().setId(id).setGatewayId(gatewayId));
+    }
+
     /**
      * 更新设备状态
      *

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

@@ -64,22 +64,10 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         if (product == null) {
             throw exception(PRODUCT_NOT_EXISTS);
         }
-        // 1.2 校验设备标识是否唯一
-        TenantUtils.executeIgnore(() -> {
-            if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
-                throw exception(PRODUCT_KEY_EXISTS);
-            }
-        });
-        // 1.3 校验设备名称在同一产品下是否唯一
-        if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceName()) != null) {
-            throw exception(DEVICE_NAME_EXISTS);
-        }
-        // 1.4 校验父设备是否为合法网关
-        if (IotProductDeviceTypeEnum.isGateway(product.getDeviceType())
-                && createReqVO.getGatewayId() != null) {
-            validateGatewayDeviceExists(createReqVO.getGatewayId());
-        }
-        // 1.5 校验分组存在
+        // 1.2 统一校验
+        validateCreateDeviceParam(product.getProductKey(), createReqVO.getDeviceName(), createReqVO.getDeviceKey(),
+                createReqVO.getGatewayId(), product);
+        // 1.3 校验分组存在
         deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
 
         // 2. 插入到数据库
@@ -90,34 +78,47 @@ public class IotDeviceServiceImpl implements IotDeviceService {
     }
 
     @Override
-    public IotDeviceDO createDevice(String productKey, String deviceName) {
+    public IotDeviceDO createDevice(String productKey, String deviceName, Long gatewayId) {
+        String deviceKey = generateDeviceKey();
         // 1.1 校验产品是否存在
         IotProductDO product = TenantUtils.executeIgnore(() ->
                 productService.getProductByProductKey(productKey));
         if (product == null) {
             throw exception(PRODUCT_NOT_EXISTS);
         }
-        // 1.2 校验设备标识是否唯一
-        String deviceKey = generateDeviceKey();
-        TenantUtils.executeIgnore(() -> {
-            if (deviceMapper.selectByDeviceKey(deviceKey) != null) {
-                throw exception(PRODUCT_KEY_EXISTS);
-            }
-        });
         return TenantUtils.execute(product.getTenantId(), () -> {
-            // 1.3 校验设备名称在同一产品下是否唯一
-            if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), deviceName) != null) {
-                throw exception(DEVICE_NAME_EXISTS);
-            }
+            // 1.2 校验设备名称在同一产品下是否唯一
+            validateCreateDeviceParam(productKey, deviceName, deviceKey, gatewayId, product);
 
             // 2. 插入到数据库
-            IotDeviceDO device = new IotDeviceDO().setDeviceName(deviceName).setDeviceKey(deviceKey);
+            IotDeviceDO device = new IotDeviceDO().setDeviceName(deviceName).setDeviceKey(deviceKey)
+                    .setGatewayId(gatewayId);
             initDevice(device, product);
             deviceMapper.insert(device);
             return device;
         });
     }
 
+    private void validateCreateDeviceParam(String productKey, String deviceName, String deviceKey,
+                                           Long gatewayId, IotProductDO product) {
+        TenantUtils.executeIgnore(() -> {
+            // 校验设备名称在同一产品下是否唯一
+            if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
+                throw exception(DEVICE_NAME_EXISTS);
+            }
+            // 校验设备标识是否唯一
+            if (deviceMapper.selectByDeviceKey(deviceKey) != null) {
+                throw exception(DEVICE_KEY_EXISTS);
+            }
+        });
+
+        // 校验父设备是否为合法网关
+        if (IotProductDeviceTypeEnum.isGatewaySub(product.getDeviceType())
+                && gatewayId != null) {
+            validateGatewayDeviceExists(gatewayId);
+        }
+    }
+
     private void initDevice(IotDeviceDO device, IotProductDO product) {
         device.setProductId(product.getId()).setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
         // 生成并设置必要的字段
@@ -136,7 +137,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         // 1.1 校验存在
         IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
         // 1.2 校验父设备是否为合法网关
-        if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
+        if (IotProductDeviceTypeEnum.isGatewaySub(device.getDeviceType())
                 && updateReqVO.getGatewayId() != null) {
             validateGatewayDeviceExists(updateReqVO.getGatewayId());
         }

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

@@ -1,9 +1,6 @@
 package cn.iocoder.yudao.module.iot.service.device.control;
 
-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.IotDeviceRegisterReqDTO;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
 import jakarta.validation.Valid;
 
@@ -51,4 +48,11 @@ public interface IotDeviceUpstreamService {
      */
     void registerDevice(IotDeviceRegisterReqDTO registerReqDTO);
 
+    /**
+     * 注册子设备
+     *
+     * @param registerReqDTO 注册子设备 DTO
+     */
+    void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO);
+
 }

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

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.iot.service.device.control;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjUtil;
@@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
 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.device.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
 import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
@@ -164,20 +166,30 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
 
     @Override
     public void registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
-        // 1.1 注册设备
         log.info("[registerDevice][注册设备: {}]", registerReqDTO);
-        IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
-                registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
-        boolean register = device == null;
+        registerDevice0(registerReqDTO.getProductKey(), registerReqDTO.getDeviceName(), null, registerReqDTO);
+    }
+
+    private void registerDevice0(String productKey, String deviceName, Long gatewayId,
+                                 IotDeviceUpstreamAbstractReqDTO registerReqDTO) {
+        // 1.1 注册设备
+        IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
+        boolean registerNew = device == null;
         if (device == null) {
-            device = deviceService.createDevice(registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
-            log.info("[registerDevice][请求({}) 成功注册设备({})]", registerReqDTO, device);
+            device = deviceService.createDevice(productKey, deviceName, gatewayId);
+            log.info("[registerDevice0][消息({}) 设备({}/{}) 成功注册]", registerReqDTO, productKey, device);
+        } else if (gatewayId != null && ObjUtil.notEqual(device.getGatewayId(), gatewayId)) {
+            Long deviceId = device.getId();
+            TenantUtils.execute(device.getTenantId(),
+                    () -> deviceService.updateDeviceGateway(deviceId, gatewayId));
+            log.info("[registerDevice0][消息({}) 设备({}/{}) 更新网关设备编号({})]",
+                    registerReqDTO, productKey, device, gatewayId);
         }
         // 1.2 记录设备的最后时间
         updateDeviceLastTime(device, registerReqDTO);
 
         // 2. 发送设备消息
-        if (register) {
+        if (registerNew) {
             IotDeviceMessage message = BeanUtils.toBean(registerReqDTO, IotDeviceMessage.class)
                     .setType(IotDeviceMessageTypeEnum.REGISTER.getType())
                     .setIdentifier(IotDeviceMessageIdentifierEnum.REGISTER_REGISTER.getIdentifier());
@@ -185,6 +197,39 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
         }
     }
 
+    @Override
+    public void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
+        // 1.1 注册子设备
+        log.info("[registerSubDevice][注册子设备: {}]", registerReqDTO);
+        IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
+                registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
+        if (device == null) {
+            log.error("[registerSubDevice][设备({}/{}) 不存在]",
+                    registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
+            return;
+        }
+        if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
+            log.error("[registerSubDevice][设备({}/{}) 不是网关设备({}),无法进行注册]",
+                    registerReqDTO.getProductKey(), registerReqDTO.getDeviceName(), device);
+            return;
+        }
+        // 1.2 记录设备的最后时间
+        updateDeviceLastTime(device, registerReqDTO);
+
+        // 2. 处理子设备
+        if (CollUtil.isNotEmpty(registerReqDTO.getParams())) {
+            registerReqDTO.getParams().forEach(subDevice -> registerDevice0(
+                    subDevice.getProductKey(), subDevice.getDeviceName(), device.getId(), registerReqDTO));
+        }
+
+        // 3. 发送设备消息
+        IotDeviceMessage message = BeanUtils.toBean(registerReqDTO, IotDeviceMessage.class)
+                .setType(IotDeviceMessageTypeEnum.REGISTER.getType())
+                .setIdentifier(IotDeviceMessageIdentifierEnum.REGISTER_REGISTER_SUB.getIdentifier())
+                .setData(registerReqDTO.getParams());
+        sendDeviceMessage(message, device);
+    }
+
     private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
         // 1. 【异步】记录设备与插件实例的映射
         pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());