Commit afaf98c44f76daa8a18fafa7410991a0b2485889

Authored by YunaiV
1 parent db9c4852

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

Showing 13 changed files with 249 additions and 147 deletions
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongSetTypeHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mybatis.core.type;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.string.StrUtils;
  5 +import org.apache.ibatis.type.JdbcType;
  6 +import org.apache.ibatis.type.MappedJdbcTypes;
  7 +import org.apache.ibatis.type.MappedTypes;
  8 +import org.apache.ibatis.type.TypeHandler;
  9 +
  10 +import java.sql.CallableStatement;
  11 +import java.sql.PreparedStatement;
  12 +import java.sql.ResultSet;
  13 +import java.sql.SQLException;
  14 +import java.util.List;
  15 +import java.util.Set;
  16 +
  17 +/**
  18 + * Set<Long> 的类型转换器实现类,对应数据库的 varchar 类型
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@MappedJdbcTypes(JdbcType.VARCHAR)
  23 +@MappedTypes(List.class)
  24 +public class LongSetTypeHandler implements TypeHandler<Set<Long>> {
  25 +
  26 + private static final String COMMA = ",";
  27 +
  28 + @Override
  29 + public void setParameter(PreparedStatement ps, int i, Set<Long> strings, JdbcType jdbcType) throws SQLException {
  30 + // 设置占位符
  31 + ps.setString(i, CollUtil.join(strings, COMMA));
  32 + }
  33 +
  34 + @Override
  35 + public Set<Long> getResult(ResultSet rs, String columnName) throws SQLException {
  36 + String value = rs.getString(columnName);
  37 + return getResult(value);
  38 + }
  39 +
  40 + @Override
  41 + public Set<Long> getResult(ResultSet rs, int columnIndex) throws SQLException {
  42 + String value = rs.getString(columnIndex);
  43 + return getResult(value);
  44 + }
  45 +
  46 + @Override
  47 + public Set<Long> getResult(CallableStatement cs, int columnIndex) throws SQLException {
  48 + String value = cs.getString(columnIndex);
  49 + return getResult(value);
  50 + }
  51 +
  52 + private Set<Long> getResult(String value) {
  53 + if (value == null) {
  54 + return null;
  55 + }
  56 + return StrUtils.splitToLongSet(value, COMMA);
  57 + }
  58 +}
... ...
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 {
26 26 ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
27 27 ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
28 28 ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
29   - ErrorCode DEVICE_NAME_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_003, "设备名称不能修改");
30   - ErrorCode DEVICE_PRODUCT_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_004, "产品不能修改");
31   - ErrorCode DEVICE_INVALID_DEVICE_STATUS = new ErrorCode(1_050_003_005, "无效的设备状态");
  29 + ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
  30 + ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
  31 + ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
32 32  
33 33 // ========== 产品分类 1-050-004-000 ==========
34 34 ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
... ...
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 {
36 36 return ARRAYS;
37 37 }
38 38  
  39 + /**
  40 + * 判断是否是网关
  41 + *
  42 + * @param type 类型
  43 + * @return 是否是网关
  44 + */
  45 + public static boolean isGateway(Integer type) {
  46 + return GATEWAY.getType().equals(type);
  47 + }
  48 +
39 49 }
... ...
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;
18 18 import org.springframework.validation.annotation.Validated;
19 19 import org.springframework.web.bind.annotation.*;
20 20  
  21 +import java.util.List;
  22 +
21 23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
22 25  
23 26 @Tag(name = "管理后台 - IoT 设备")
24 27 @RestController
... ... @@ -86,4 +89,14 @@ public class IotDeviceController {
86 89 return success(deviceService.getDeviceCountByProductId(productId));
87 90 }
88 91  
  92 + @GetMapping("/simple-list")
  93 + @Operation(summary = "获取设备的精简信息列表", description = "主要用于前端的下拉选项")
  94 + @Parameter(name = "deviceType", description = "设备类型", example = "1")
  95 + public CommonResult<List<IotDeviceRespVO>> getSimpleDeviceList(
  96 + @RequestParam(value = "deviceType", required = false) Integer deviceType) {
  97 + List<IotDeviceDO> list = deviceService.getDeviceList(deviceType);
  98 + return success(convertList(list, device -> // 只返回 id、name 字段
  99 + new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())));
  100 + }
  101 +
89 102 }
90 103 \ No newline at end of file
... ...
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 {
10 10 @Schema(description = "设备编号", example = "177")
11 11 private Long id;
12 12  
  13 + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
  14 + private String deviceKey;
  15 +
13 16 @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
14 17 private String deviceName;
15 18  
16 19 @Schema(description = "备注名称", example = "张三")
17 20 private String nickname;
18 21  
  22 + @Schema(description = "设备序列号", example = "123456")
  23 + private String serialNumber;
  24 +
  25 + @Schema(description = "设备图片", example = "https://iocoder.cn/1.png")
  26 + private String picUrl;
  27 +
19 28 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
20 29 private Long productId;
21 30  
  31 + @Schema(description = "网关设备 ID", example = "16380")
  32 + private Long gatewayId;
  33 +
22 34 }
23 35 \ No newline at end of file
... ...
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 {
121 121 }
122 122  
123 123 @GetMapping("/simple-list")
124   - @Operation(summary = "获得所有产品列表")
125   - @PreAuthorize("@ss.hasPermission('iot:product:query')")
  124 + @Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
126 125 public CommonResult<List<IotProductRespVO>> getSimpleProductList() {
127 126 List<IotProductDO> list = productService.getProductList();
128 127 return success(convertList(list, product -> // 只返回 id、name 字段
129   - new IotProductRespVO().setId(product.getId()).setName(product.getName())));
  128 + new IotProductRespVO().setId(product.getId()).setName(product.getName())
  129 + .setDeviceType(product.getDeviceType())));
130 130 }
131 131  
132 132 }
133 133 \ No newline at end of file
... ...
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 {
38 38 @ExcelProperty("产品图标")
39 39 private String icon;
40 40  
41   - @Schema(description = "产品图标", example = "https://iocoder.cn/1.png")
42   - @ExcelProperty("产品图标")
  41 + @Schema(description = "产品图片", example = "https://iocoder.cn/1.png")
  42 + @ExcelProperty("产品图片")
43 43 private String picUrl;
44 44  
45 45 @Schema(description = "产品描述", example = "你猜")
... ...
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 {
28 28 @Schema(description = "产品图标", example = "https://iocoder.cn/1.svg")
29 29 private String icon;
30 30  
31   - @Schema(description = "产品图标", example = "https://iocoder.cn/1.png")
  31 + @Schema(description = "产品图片", example = "https://iocoder.cn/1.png")
32 32 private String picUrl;
33 33  
34 34 @Schema(description = "产品描述", example = "描述")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
1 1 package cn.iocoder.yudao.module.iot.dal.dataobject.device;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
4 5 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
5 6 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
6 7 import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableField;
7 9 import com.baomidou.mybatisplus.annotation.TableId;
8 10 import com.baomidou.mybatisplus.annotation.TableName;
9 11 import lombok.*;
10 12  
11 13 import java.math.BigDecimal;
12 14 import java.time.LocalDateTime;
  15 +import java.util.Set;
13 16  
14 17 /**
15 18 * IoT 设备 DO
16 19 *
17 20 * @author haohao
18 21 */
19   -@TableName("iot_device")
  22 +@TableName(value = "iot_device", autoResultMap = true)
20 23 @KeySequence("iot_device_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
21 24 @Data
22 25 @EqualsAndHashCode(callSuper = true)
... ... @@ -47,6 +50,17 @@ public class IotDeviceDO extends BaseDO {
47 50 * 设备序列号
48 51 */
49 52 private String serialNumber;
  53 + /**
  54 + * 设备图片
  55 + */
  56 + private String picUrl;
  57 + /**
  58 + * 设备分组编号集合
  59 + *
  60 + * 关联 TODO 芋艿:
  61 + */
  62 + @TableField(typeHandler = LongSetTypeHandler.class)
  63 + private Set<Long> groupIds;
50 64  
51 65 /**
52 66 * 产品编号
... ... @@ -66,13 +80,6 @@ public class IotDeviceDO extends BaseDO {
66 80 * 冗余 {@link IotProductDO#getDeviceType()}
67 81 */
68 82 private Integer deviceType;
69   -
70   - /**
71   - * 设备状态
72   - * <p>
73   - * 枚举 {@link IotDeviceStatusEnum}
74   - */
75   - private Integer status;
76 83 /**
77 84 * 网关设备编号
78 85 * <p>
... ... @@ -83,6 +90,13 @@ public class IotDeviceDO extends BaseDO {
83 90 private Long gatewayId;
84 91  
85 92 /**
  93 + * 设备状态
  94 + * <p>
  95 + * 枚举 {@link IotDeviceStatusEnum}
  96 + */
  97 + private Integer status;
  98 +
  99 + /**
86 100 * 设备状态最后更新时间
87 101 */
88 102 private LocalDateTime statusLastUpdateTime;
... ...
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
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
  10 +import java.util.List;
  11 +
10 12 /**
11 13 * IoT 设备 Mapper
12 14 *
... ... @@ -51,4 +53,13 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
51 53 default Long selectCountByProductId(Long productId) {
52 54 return selectCount(IotDeviceDO::getProductId, productId);
53 55 }
  56 +
  57 + default IotDeviceDO selectByDeviceKey(String deviceKey) {
  58 + return selectOne(IotDeviceDO::getDeviceKey, deviceKey);
  59 + }
  60 +
  61 + default List<IotDeviceDO> selectList(Integer deviceType) {
  62 + return selectList(IotDeviceDO::getDeviceType, deviceType);
  63 + }
  64 +
54 65 }
55 66 \ No newline at end of file
... ...
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&lt;IotProductCategory
21 21 return selectPage(reqVO, new LambdaQueryWrapperX<IotProductCategoryDO>()
22 22 .likeIfPresent(IotProductCategoryDO::getName, reqVO.getName())
23 23 .betweenIfPresent(IotProductCategoryDO::getCreateTime, reqVO.getCreateTime())
24   - .orderByDesc(IotProductCategoryDO::getId));
  24 + .orderByAsc(IotProductCategoryDO::getSort));
25 25 }
26 26  
27 27 default List<IotProductCategoryDO> selectListByStatus(Integer status) {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
1 1 package cn.iocoder.yudao.module.iot.service.device;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
4 5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
5 6 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
6   -import jakarta.validation.*;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
8   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import jakarta.validation.Valid;
  9 +
  10 +import javax.annotation.Nullable;
  11 +import java.util.List;
9 12  
10 13 /**
11 14 * IoT 设备 Service 接口
... ... @@ -53,6 +56,14 @@ public interface IotDeviceService {
53 56 PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO);
54 57  
55 58 /**
  59 + * 获得设备列表
  60 + *
  61 + * @param deviceType 设备类型
  62 + * @return 设备列表
  63 + */
  64 + List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType);
  65 +
  66 + /**
56 67 * 更新设备状态
57 68 *
58 69 * @param updateReqVO 更新信息
... ... @@ -75,4 +86,5 @@ public interface IotDeviceService {
75 86 * @return 设备信息
76 87 */
77 88 IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
  89 +
78 90 }
79 91 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.service.device;
2 2  
3 3 import cn.hutool.core.util.IdUtil;
4   -import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.core.util.RandomUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 7 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
... ... @@ -12,18 +12,17 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
12 12 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
13 13 import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
14 14 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
  15 +import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
15 16 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
16 17 import jakarta.annotation.Resource;
17 18 import lombok.extern.slf4j.Slf4j;
18 19 import org.springframework.stereotype.Service;
19   -import org.springframework.transaction.annotation.Transactional;
20 20 import org.springframework.validation.annotation.Validated;
21 21  
22   -import java.security.SecureRandom;
  22 +import javax.annotation.Nullable;
23 23 import java.time.LocalDateTime;
24   -import java.util.Base64;
  24 +import java.util.List;
25 25 import java.util.Objects;
26   -import java.util.UUID;
27 26  
28 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
29 28 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
... ... @@ -40,142 +39,64 @@ public class IotDeviceServiceImpl implements IotDeviceService {
40 39  
41 40 @Resource
42 41 private IotDeviceMapper deviceMapper;
  42 +
43 43 @Resource
44 44 private IotProductService productService;
45 45  
46   - /**
47   - * 创建 IoT 设备
48   - *
49   - * @param createReqVO 创建请求 VO
50   - * @return 设备 ID
51   - */
52 46 @Override
53   - @Transactional(rollbackFor = Exception.class)
54 47 public Long createDevice(IotDeviceSaveReqVO createReqVO) {
55 48 // 1.1 校验产品是否存在
56 49 IotProductDO product = productService.getProduct(createReqVO.getProductId());
57 50 if (product == null) {
58 51 throw exception(PRODUCT_NOT_EXISTS);
59 52 }
60   - // 1.2 校验设备名称在同一产品下是否唯一
61   - if (StrUtil.isBlank(createReqVO.getDeviceName())) {
62   - createReqVO.setDeviceName(generateUniqueDeviceName(product.getProductKey()));
63   - } else {
64   - validateDeviceNameUnique(product.getProductKey(), createReqVO.getDeviceName());
  53 + // 1.2 校验设备标识是否唯一
  54 + if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
  55 + throw exception(DEVICE_KEY_EXISTS);
  56 + }
  57 + // 1.3 校验设备名称在同一产品下是否唯一
  58 + if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) {
  59 + throw exception(DEVICE_NAME_EXISTS);
  60 + }
  61 + // 1.4 校验父设备是否为合法网关
  62 + if (IotProductDeviceTypeEnum.isGateway(product.getDeviceType())
  63 + && createReqVO.getGatewayId() != null) {
  64 + validateGatewayDeviceExists(createReqVO.getGatewayId());
65 65 }
66 66  
67 67 // 2.1 转换 VO 为 DO
68   - IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class)
69   - .setProductKey(product.getProductKey())
70   - .setDeviceType(product.getDeviceType());
71   - // 2.2 生成并设置必要的字段
72   - device.setDeviceKey(generateUniqueDeviceKey());
73   - device.setDeviceSecret(generateDeviceSecret());
74   - device.setMqttClientId(generateMqttClientId());
75   - device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey()));
76   - device.setMqttPassword(generateMqttPassword());
77   - // 2.3 设置设备状态为未激活
78   - device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus());
79   - device.setStatusLastUpdateTime(LocalDateTime.now());
80   - // 2.4 插入到数据库
  68 + IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> {
  69 + o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
  70 + // 生成并设置必要的字段
  71 + o.setDeviceSecret(generateDeviceSecret())
  72 + .setMqttClientId(generateMqttClientId())
  73 + .setMqttUsername(generateMqttUsername(o.getDeviceName(), o.getProductKey()))
  74 + .setMqttPassword(generateMqttPassword());
  75 + // 设置设备状态为未激活
  76 + o.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus()).setStatusLastUpdateTime(LocalDateTime.now());
  77 + });
  78 + // 2.2 插入到数据库
81 79 deviceMapper.insert(device);
82 80 return device.getId();
83 81 }
84 82  
85   - /**
86   - * 校验设备名称在同一产品下是否唯一
87   - *
88   - * @param productKey 产品 Key
89   - * @param deviceName 设备名称
90   - */
91   - private void validateDeviceNameUnique(String productKey, String deviceName) {
92   - IotDeviceDO existingDevice = deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
93   - if (existingDevice != null) {
94   - throw exception(DEVICE_NAME_EXISTS);
95   - }
96   - }
97   -
98   - /**
99   - * 生成唯一的 deviceKey
100   - *
101   - * @return 生成的 deviceKey
102   - */
103   - private String generateUniqueDeviceKey() {
104   - return UUID.randomUUID().toString();
105   - }
106   -
107   - /**
108   - * 生成 deviceSecret
109   - *
110   - * @return 生成的 deviceSecret
111   - */
112   - private String generateDeviceSecret() {
113   - return IdUtil.fastSimpleUUID();
114   - }
115   -
116   - /**
117   - * 生成 MQTT Client ID
118   - *
119   - * @return 生成的 MQTT Client ID
120   - */
121   - private String generateMqttClientId() {
122   - return UUID.randomUUID().toString();
123   - }
124   -
125   - /**
126   - * 生成 MQTT Username
127   - *
128   - * @param deviceName 设备名称
129   - * @param productKey 产品 Key
130   - * @return 生成的 MQTT Username
131   - */
132   - private String generateMqttUsername(String deviceName, String productKey) {
133   - return deviceName + "&" + productKey;
134   - }
135   -
136   - /**
137   - * 生成 MQTT Password
138   - *
139   - * @return 生成的 MQTT Password
140   - */
141   - private String generateMqttPassword() {
142   - // TODO @浩浩:这里的 StrUtil 随机字符串?
143   - SecureRandom secureRandom = new SecureRandom();
144   - byte[] passwordBytes = new byte[32]; // 256 位的随机数
145   - secureRandom.nextBytes(passwordBytes);
146   - return Base64.getUrlEncoder().withoutPadding().encodeToString(passwordBytes);
147   - }
148   -
149   - /**
150   - * 生成唯一的 DeviceName
151   - *
152   - * @param productKey 产品标识
153   - * @return 生成的唯一 DeviceName
154   - */
155   - private String generateUniqueDeviceName(String productKey) {
156   - for (int i = 0; i < Short.MAX_VALUE; i++) {
157   - String deviceName = IdUtil.fastSimpleUUID().substring(0, 20);
158   - if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
159   - return deviceName;
160   - }
161   - }
162   - throw new IllegalArgumentException("生成 DeviceName 失败");
163   - }
164   -
165 83 @Override
166   - @Transactional(rollbackFor = Exception.class)
167 84 public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
168   - // 1. 校验存在
169   - validateDeviceExists(updateReqVO.getId());
  85 + updateReqVO.setDeviceKey(null).setDeviceName(null).setProductId(null); // 不允许更新
  86 + // 1.1 校验存在
  87 + IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
  88 + // 1.2 校验父设备是否为合法网关
  89 + if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
  90 + && updateReqVO.getGatewayId() != null) {
  91 + validateGatewayDeviceExists(updateReqVO.getGatewayId());
  92 + }
170 93  
171 94 // 2. 更新到数据库
172   - IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
173   - .setDeviceName(null).setProductId(null); // 设备名称 和 产品 ID 不能修改
  95 + IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
174 96 deviceMapper.updateById(updateObj);
175 97 }
176 98  
177 99 @Override
178   - @Transactional(rollbackFor = Exception.class)
179 100 public void deleteDevice(Long id) {
180 101 // 1.1 校验存在
181 102 IotDeviceDO device = validateDeviceExists(id);
... ... @@ -202,13 +123,24 @@ public class IotDeviceServiceImpl implements IotDeviceService {
202 123 return device;
203 124 }
204 125  
205   - @Override
206   - public IotDeviceDO getDevice(Long id) {
  126 + /**
  127 + * 校验网关设备是否存在
  128 + *
  129 + * @param id 设备 ID
  130 + */
  131 + private void validateGatewayDeviceExists(Long id) {
207 132 IotDeviceDO device = deviceMapper.selectById(id);
208 133 if (device == null) {
209   - throw exception(DEVICE_NOT_EXISTS);
  134 + throw exception(DEVICE_GATEWAY_NOT_EXISTS);
  135 + }
  136 + if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
  137 + throw exception(DEVICE_NOT_GATEWAY);
210 138 }
211   - return device;
  139 + }
  140 +
  141 + @Override
  142 + public IotDeviceDO getDevice(Long id) {
  143 + return deviceMapper.selectById(id);
212 144 }
213 145  
214 146 @Override
... ... @@ -217,18 +149,23 @@ public class IotDeviceServiceImpl implements IotDeviceService {
217 149 }
218 150  
219 151 @Override
  152 + public List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType) {
  153 + return deviceMapper.selectList(deviceType);
  154 + }
  155 +
  156 + @Override
220 157 public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
221 158 // 1. 校验存在
222 159 IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
223 160  
224 161 // 2.1 更新状态和更新时间
225   - IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
  162 + IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
  163 + .setStatusLastUpdateTime(LocalDateTime.now());
226 164 // 2.2 更新状态相关时间
227 165 if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
228 166 && Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
229 167 // 从未激活到在线,设置激活时间和最后上线时间
230   - updateDevice.setActiveTime(LocalDateTime.now());
231   - updateDevice.setLastOnlineTime(LocalDateTime.now());
  168 + updateDevice.setActiveTime(LocalDateTime.now()).setLastOnlineTime(LocalDateTime.now());
232 169 } else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
233 170 // 如果是上线,设置最后上线时间
234 171 updateDevice.setLastOnlineTime(LocalDateTime.now());
... ... @@ -236,10 +173,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
236 173 // 如果是离线,设置最后离线时间
237 174 updateDevice.setLastOfflineTime(LocalDateTime.now());
238 175 }
239   -
240   - // 2.3 设置状态更新时间
241   - updateDevice.setStatusLastUpdateTime(LocalDateTime.now());
242   - // 2.4 更新到数据库
  176 + // 2.3 更新到数据库
243 177 deviceMapper.updateById(updateDevice);
244 178 }
245 179  
... ... @@ -254,4 +188,42 @@ public class IotDeviceServiceImpl implements IotDeviceService {
254 188 return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
255 189 }
256 190  
  191 + /**
  192 + * 生成 deviceSecret
  193 + *
  194 + * @return 生成的 deviceSecret
  195 + */
  196 + private String generateDeviceSecret() {
  197 + return IdUtil.fastSimpleUUID();
  198 + }
  199 +
  200 + /**
  201 + * 生成 MQTT Client ID
  202 + *
  203 + * @return 生成的 MQTT Client ID
  204 + */
  205 + private String generateMqttClientId() {
  206 + return IdUtil.fastSimpleUUID();
  207 + }
  208 +
  209 + /**
  210 + * 生成 MQTT Username
  211 + *
  212 + * @param deviceName 设备名称
  213 + * @param productKey 产品 Key
  214 + * @return 生成的 MQTT Username
  215 + */
  216 + private String generateMqttUsername(String deviceName, String productKey) {
  217 + return deviceName + "&" + productKey;
  218 + }
  219 +
  220 + /**
  221 + * 生成 MQTT Password
  222 + *
  223 + * @return 生成的 MQTT Password
  224 + */
  225 + private String generateMqttPassword() {
  226 + return RandomUtil.randomString(32);
  227 + }
  228 +
257 229 }
258 230 \ No newline at end of file
... ...