فهرست منبع

【功能完善】IoT:设备新增、修改支持更多字段

YunaiV 8 ماه پیش
والد
کامیت
afaf98c44f
13فایلهای تغییر یافته به همراه249 افزوده شده و 147 حذف شده
  1. 58 0
      yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongSetTypeHandler.java
  2. 3 3
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
  3. 10 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java
  4. 13 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
  5. 12 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java
  6. 3 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java
  7. 2 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
  8. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
  9. 22 8
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
  10. 11 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
  11. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductCategoryMapper.java
  12. 14 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
  13. 99 127
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java

+ 58 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongSetTypeHandler.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.framework.mybatis.core.type;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+import org.apache.ibatis.type.TypeHandler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Set<Long> 的类型转换器实现类,对应数据库的 varchar 类型
+ *
+ * @author 芋道源码
+ */
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes(List.class)
+public class LongSetTypeHandler implements TypeHandler<Set<Long>> {
+
+    private static final String COMMA = ",";
+
+    @Override
+    public void setParameter(PreparedStatement ps, int i, Set<Long> strings, JdbcType jdbcType) throws SQLException {
+        // 设置占位符
+        ps.setString(i, CollUtil.join(strings, COMMA));
+    }
+
+    @Override
+    public Set<Long> getResult(ResultSet rs, String columnName) throws SQLException {
+        String value = rs.getString(columnName);
+        return getResult(value);
+    }
+
+    @Override
+    public Set<Long> getResult(ResultSet rs, int columnIndex) throws SQLException {
+        String value = rs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    @Override
+    public Set<Long> getResult(CallableStatement cs, int columnIndex) throws SQLException {
+        String value = cs.getString(columnIndex);
+        return getResult(value);
+    }
+
+    private Set<Long> getResult(String value) {
+        if (value == null) {
+            return null;
+        }
+        return StrUtils.splitToLongSet(value, COMMA);
+    }
+}

+ 3 - 3
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java

@@ -26,9 +26,9 @@ public interface ErrorCodeConstants {
     ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
     ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
     ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
-    ErrorCode DEVICE_NAME_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_003, "设备名称不能修改");
-    ErrorCode DEVICE_PRODUCT_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_004, "产品不能修改");
-    ErrorCode DEVICE_INVALID_DEVICE_STATUS = new ErrorCode(1_050_003_005, "无效的设备状态");
+    ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
+    ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
+    ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
 
     // ========== 产品分类 1-050-004-000 ==========
     ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");

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

@@ -36,4 +36,14 @@ public enum IotProductDeviceTypeEnum implements IntArrayValuable {
         return ARRAYS;
     }
 
+    /**
+     * 判断是否是网关
+     *
+     * @param type 类型
+     * @return 是否是网关
+     */
+    public static boolean isGateway(Integer type) {
+        return GATEWAY.getType().equals(type);
+    }
+
 }

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

@@ -18,7 +18,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
 @Tag(name = "管理后台 - IoT 设备")
 @RestController
@@ -86,4 +89,14 @@ public class IotDeviceController {
         return success(deviceService.getDeviceCountByProductId(productId));
     }
 
+    @GetMapping("/simple-list")
+    @Operation(summary = "获取设备的精简信息列表", description = "主要用于前端的下拉选项")
+    @Parameter(name = "deviceType", description = "设备类型", example = "1")
+    public CommonResult<List<IotDeviceRespVO>> getSimpleDeviceList(
+            @RequestParam(value = "deviceType", required = false) Integer deviceType) {
+        List<IotDeviceDO> list = deviceService.getDeviceList(deviceType);
+        return success(convertList(list, device -> // 只返回 id、name 字段
+                new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())));
+    }
+
 }

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

@@ -10,13 +10,25 @@ public class IotDeviceSaveReqVO {
     @Schema(description = "设备编号", example = "177")
     private Long id;
 
+    @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
+    private String deviceKey;
+
     @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
     private String deviceName;
 
     @Schema(description = "备注名称", example = "张三")
     private String nickname;
 
+    @Schema(description = "设备序列号", example = "123456")
+    private String serialNumber;
+
+    @Schema(description = "设备图片", example = "https://iocoder.cn/1.png")
+    private String picUrl;
+
     @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
     private Long productId;
 
+    @Schema(description = "网关设备 ID", example = "16380")
+    private Long gatewayId;
+
 }

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java

@@ -121,12 +121,12 @@ public class IotProductController {
     }
 
     @GetMapping("/simple-list")
-    @Operation(summary = "获得所有产品列表")
-    @PreAuthorize("@ss.hasPermission('iot:product:query')")
+    @Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
     public CommonResult<List<IotProductRespVO>> getSimpleProductList() {
         List<IotProductDO> list = productService.getProductList();
         return success(convertList(list, product -> // 只返回 id、name 字段
-                new IotProductRespVO().setId(product.getId()).setName(product.getName())));
+                new IotProductRespVO().setId(product.getId()).setName(product.getName())
+                        .setDeviceType(product.getDeviceType())));
     }
 
 }

+ 2 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java

@@ -38,8 +38,8 @@ public class IotProductRespVO {
     @ExcelProperty("产品图标")
     private String icon;
 
-    @Schema(description = "产品图", example = "https://iocoder.cn/1.png")
-    @ExcelProperty("产品图")
+    @Schema(description = "产品图", example = "https://iocoder.cn/1.png")
+    @ExcelProperty("产品图")
     private String picUrl;
 
     @Schema(description = "产品描述", example = "你猜")

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java

@@ -28,7 +28,7 @@ public class IotProductSaveReqVO {
     @Schema(description = "产品图标", example = "https://iocoder.cn/1.svg")
     private String icon;
 
-    @Schema(description = "产品图", example = "https://iocoder.cn/1.png")
+    @Schema(description = "产品图", example = "https://iocoder.cn/1.png")
     private String picUrl;
 
     @Schema(description = "产品描述", example = "描述")

+ 22 - 8
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java

@@ -1,22 +1,25 @@
 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.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.Set;
 
 /**
  * IoT 设备 DO
  *
  * @author haohao
  */
-@TableName("iot_device")
+@TableName(value = "iot_device", autoResultMap = true)
 @KeySequence("iot_device_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
 @Data
 @EqualsAndHashCode(callSuper = true)
@@ -47,6 +50,17 @@ public class IotDeviceDO extends BaseDO {
      * 设备序列号
      */
     private String serialNumber;
+    /**
+     * 设备图片
+     */
+    private String picUrl;
+    /**
+     * 设备分组编号集合
+     *
+     * 关联 TODO 芋艿:
+     */
+    @TableField(typeHandler = LongSetTypeHandler.class)
+    private Set<Long> groupIds;
 
     /**
      * 产品编号
@@ -66,13 +80,6 @@ public class IotDeviceDO extends BaseDO {
      * 冗余 {@link IotProductDO#getDeviceType()}
      */
     private Integer deviceType;
-
-    /**
-     * 设备状态
-     * <p>
-     * 枚举 {@link IotDeviceStatusEnum}
-     */
-    private Integer status;
     /**
      * 网关设备编号
      * <p>
@@ -82,6 +89,13 @@ public class IotDeviceDO extends BaseDO {
      */
     private Long gatewayId;
 
+    /**
+     * 设备状态
+     * <p>
+     * 枚举 {@link IotDeviceStatusEnum}
+     */
+    private Integer status;
+
     /**
      * 设备状态最后更新时间
      */

+ 11 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java

@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePa
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 /**
  * IoT 设备 Mapper
  *
@@ -51,4 +53,13 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
     default Long selectCountByProductId(Long productId) {
         return selectCount(IotDeviceDO::getProductId, productId);
     }
+
+    default IotDeviceDO selectByDeviceKey(String deviceKey) {
+        return selectOne(IotDeviceDO::getDeviceKey, deviceKey);
+    }
+
+    default List<IotDeviceDO> selectList(Integer deviceType) {
+        return selectList(IotDeviceDO::getDeviceType, deviceType);
+    }
+
 }

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductCategoryMapper.java

@@ -21,7 +21,7 @@ public interface IotProductCategoryMapper extends BaseMapperX<IotProductCategory
         return selectPage(reqVO, new LambdaQueryWrapperX<IotProductCategoryDO>()
                 .likeIfPresent(IotProductCategoryDO::getName, reqVO.getName())
                 .betweenIfPresent(IotProductCategoryDO::getCreateTime, reqVO.getCreateTime())
-                .orderByDesc(IotProductCategoryDO::getId));
+                .orderByAsc(IotProductCategoryDO::getSort));
     }
 
     default List<IotProductCategoryDO> selectListByStatus(Integer status) {

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

@@ -1,11 +1,14 @@
 package cn.iocoder.yudao.module.iot.service.device;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
-import jakarta.validation.*;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import jakarta.validation.Valid;
+
+import javax.annotation.Nullable;
+import java.util.List;
 
 /**
  * IoT 设备 Service 接口
@@ -52,6 +55,14 @@ public interface IotDeviceService {
      */
     PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO);
 
+    /**
+     * 获得设备列表
+     *
+     * @param deviceType 设备类型
+     * @return 设备列表
+     */
+    List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType);
+
     /**
      * 更新设备状态
      *
@@ -75,4 +86,5 @@ public interface IotDeviceService {
      * @return 设备信息
      */
     IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
+
 }

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

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.iot.service.device;
 
 import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.RandomUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
@@ -12,18 +12,17 @@ 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.mysql.device.IotDeviceMapper;
 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
+import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import java.security.SecureRandom;
+import javax.annotation.Nullable;
 import java.time.LocalDateTime;
-import java.util.Base64;
+import java.util.List;
 import java.util.Objects;
-import java.util.UUID;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
@@ -40,142 +39,64 @@ public class IotDeviceServiceImpl implements IotDeviceService {
 
     @Resource
     private IotDeviceMapper deviceMapper;
+
     @Resource
     private IotProductService productService;
 
-    /**
-     * 创建 IoT 设备
-     *
-     * @param createReqVO 创建请求 VO
-     * @return 设备 ID
-     */
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public Long createDevice(IotDeviceSaveReqVO createReqVO) {
         // 1.1 校验产品是否存在
         IotProductDO product = productService.getProduct(createReqVO.getProductId());
         if (product == null) {
             throw exception(PRODUCT_NOT_EXISTS);
         }
-        // 1.2 校验设备名称在同一产品下是否唯一
-        if (StrUtil.isBlank(createReqVO.getDeviceName())) {
-            createReqVO.setDeviceName(generateUniqueDeviceName(product.getProductKey()));
-        } else {
-            validateDeviceNameUnique(product.getProductKey(), createReqVO.getDeviceName());
+        // 1.2 校验设备标识是否唯一
+        if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
+            throw exception(DEVICE_KEY_EXISTS);
+        }
+        // 1.3 校验设备名称在同一产品下是否唯一
+        if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) {
+            throw exception(DEVICE_NAME_EXISTS);
+        }
+        // 1.4 校验父设备是否为合法网关
+        if (IotProductDeviceTypeEnum.isGateway(product.getDeviceType())
+            && createReqVO.getGatewayId() != null) {
+            validateGatewayDeviceExists(createReqVO.getGatewayId());
         }
 
         // 2.1 转换 VO 为 DO
-        IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class)
-                .setProductKey(product.getProductKey())
-                .setDeviceType(product.getDeviceType());
-        // 2.2 生成并设置必要的字段
-        device.setDeviceKey(generateUniqueDeviceKey());
-        device.setDeviceSecret(generateDeviceSecret());
-        device.setMqttClientId(generateMqttClientId());
-        device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey()));
-        device.setMqttPassword(generateMqttPassword());
-        // 2.3 设置设备状态为未激活
-        device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus());
-        device.setStatusLastUpdateTime(LocalDateTime.now());
-        // 2.4 插入到数据库
+        IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> {
+            o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
+            // 生成并设置必要的字段
+            o.setDeviceSecret(generateDeviceSecret())
+                    .setMqttClientId(generateMqttClientId())
+                    .setMqttUsername(generateMqttUsername(o.getDeviceName(), o.getProductKey()))
+                    .setMqttPassword(generateMqttPassword());
+            // 设置设备状态为未激活
+            o.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus()).setStatusLastUpdateTime(LocalDateTime.now());
+        });
+        // 2.2 插入到数据库
         deviceMapper.insert(device);
         return device.getId();
     }
 
-    /**
-     * 校验设备名称在同一产品下是否唯一
-     *
-     * @param productKey 产品 Key
-     * @param deviceName 设备名称
-     */
-    private void validateDeviceNameUnique(String productKey, String deviceName) {
-        IotDeviceDO existingDevice = deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
-        if (existingDevice != null) {
-            throw exception(DEVICE_NAME_EXISTS);
-        }
-    }
-
-    /**
-     * 生成唯一的 deviceKey
-     *
-     * @return 生成的 deviceKey
-     */
-    private String generateUniqueDeviceKey() {
-        return UUID.randomUUID().toString();
-    }
-
-    /**
-     * 生成 deviceSecret
-     *
-     * @return 生成的 deviceSecret
-     */
-    private String generateDeviceSecret() {
-        return IdUtil.fastSimpleUUID();
-    }
-
-    /**
-     * 生成 MQTT Client ID
-     *
-     * @return 生成的 MQTT Client ID
-     */
-    private String generateMqttClientId() {
-        return UUID.randomUUID().toString();
-    }
-
-    /**
-     * 生成 MQTT Username
-     *
-     * @param deviceName 设备名称
-     * @param productKey 产品 Key
-     * @return 生成的 MQTT Username
-     */
-    private String generateMqttUsername(String deviceName, String productKey) {
-        return deviceName + "&" + productKey;
-    }
-
-    /**
-     * 生成 MQTT Password
-     *
-     * @return 生成的 MQTT Password
-     */
-    private String generateMqttPassword() {
-        // TODO @浩浩:这里的 StrUtil 随机字符串?
-        SecureRandom secureRandom = new SecureRandom();
-        byte[] passwordBytes = new byte[32]; // 256 位的随机数
-        secureRandom.nextBytes(passwordBytes);
-        return Base64.getUrlEncoder().withoutPadding().encodeToString(passwordBytes);
-    }
-
-    /**
-     * 生成唯一的 DeviceName
-     *
-     * @param productKey 产品标识
-     * @return 生成的唯一 DeviceName
-     */
-    private String generateUniqueDeviceName(String productKey) {
-        for (int i = 0; i < Short.MAX_VALUE; i++) {
-            String deviceName = IdUtil.fastSimpleUUID().substring(0, 20);
-            if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
-                return deviceName;
-            }
-        }
-        throw new IllegalArgumentException("生成 DeviceName 失败");
-    }
-
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
-        // 1. 校验存在
-        validateDeviceExists(updateReqVO.getId());
+        updateReqVO.setDeviceKey(null).setDeviceName(null).setProductId(null); // 不允许更新
+        // 1.1 校验存在
+        IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
+        // 1.2 校验父设备是否为合法网关
+        if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
+                && updateReqVO.getGatewayId() != null) {
+            validateGatewayDeviceExists(updateReqVO.getGatewayId());
+        }
 
         // 2. 更新到数据库
-        IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
-                .setDeviceName(null).setProductId(null); // 设备名称 和 产品 ID 不能修改
+        IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
         deviceMapper.updateById(updateObj);
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public void deleteDevice(Long id) {
         // 1.1 校验存在
         IotDeviceDO device = validateDeviceExists(id);
@@ -202,13 +123,24 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         return device;
     }
 
-    @Override
-    public IotDeviceDO getDevice(Long id) {
+    /**
+     * 校验网关设备是否存在
+     *
+     * @param id 设备 ID
+     */
+    private void validateGatewayDeviceExists(Long id) {
         IotDeviceDO device = deviceMapper.selectById(id);
         if (device == null) {
-            throw exception(DEVICE_NOT_EXISTS);
+            throw exception(DEVICE_GATEWAY_NOT_EXISTS);
+        }
+        if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
+            throw exception(DEVICE_NOT_GATEWAY);
         }
-        return device;
+    }
+
+    @Override
+    public IotDeviceDO getDevice(Long id) {
+        return deviceMapper.selectById(id);
     }
 
     @Override
@@ -216,19 +148,24 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         return deviceMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType) {
+        return deviceMapper.selectList(deviceType);
+    }
+
     @Override
     public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
         // 1. 校验存在
         IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
 
         // 2.1 更新状态和更新时间
-        IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
+        IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
+                .setStatusLastUpdateTime(LocalDateTime.now());
         // 2.2 更新状态相关时间
         if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
                 && Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
             // 从未激活到在线,设置激活时间和最后上线时间
-            updateDevice.setActiveTime(LocalDateTime.now());
-            updateDevice.setLastOnlineTime(LocalDateTime.now());
+            updateDevice.setActiveTime(LocalDateTime.now()).setLastOnlineTime(LocalDateTime.now());
         } else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
             // 如果是上线,设置最后上线时间
             updateDevice.setLastOnlineTime(LocalDateTime.now());
@@ -236,10 +173,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
             // 如果是离线,设置最后离线时间
             updateDevice.setLastOfflineTime(LocalDateTime.now());
         }
-
-        // 2.3 设置状态更新时间
-        updateDevice.setStatusLastUpdateTime(LocalDateTime.now());
-        // 2.4 更新到数据库
+        // 2.3 更新到数据库
         deviceMapper.updateById(updateDevice);
     }
 
@@ -254,4 +188,42 @@ public class IotDeviceServiceImpl implements IotDeviceService {
         return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
     }
 
+    /**
+     * 生成 deviceSecret
+     *
+     * @return 生成的 deviceSecret
+     */
+    private String generateDeviceSecret() {
+        return IdUtil.fastSimpleUUID();
+    }
+
+    /**
+     * 生成 MQTT Client ID
+     *
+     * @return 生成的 MQTT Client ID
+     */
+    private String generateMqttClientId() {
+        return IdUtil.fastSimpleUUID();
+    }
+
+    /**
+     * 生成 MQTT Username
+     *
+     * @param deviceName 设备名称
+     * @param productKey 产品 Key
+     * @return 生成的 MQTT Username
+     */
+    private String generateMqttUsername(String deviceName, String productKey) {
+        return deviceName + "&" + productKey;
+    }
+
+    /**
+     * 生成 MQTT Password
+     *
+     * @return 生成的 MQTT Password
+     */
+    private String generateMqttPassword() {
+        return RandomUtil.randomString(32);
+    }
+
 }