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,9 +26,9 @@ public interface ErrorCodeConstants {
26 ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在"); 26 ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
27 ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一"); 27 ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
28 ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除"); 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 // ========== 产品分类 1-050-004-000 ========== 33 // ========== 产品分类 1-050-004-000 ==========
34 ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在"); 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,4 +36,14 @@ public enum IotProductDeviceTypeEnum implements IntArrayValuable {
36 return ARRAYS; 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,7 +18,10 @@ import org.springframework.security.access.prepost.PreAuthorize;
18 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
19 import org.springframework.web.bind.annotation.*; 19 import org.springframework.web.bind.annotation.*;
20 20
  21 +import java.util.List;
  22 +
21 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 @Tag(name = "管理后台 - IoT 设备") 26 @Tag(name = "管理后台 - IoT 设备")
24 @RestController 27 @RestController
@@ -86,4 +89,14 @@ public class IotDeviceController { @@ -86,4 +89,14 @@ public class IotDeviceController {
86 return success(deviceService.getDeviceCountByProductId(productId)); 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 \ No newline at end of file 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,13 +10,25 @@ public class IotDeviceSaveReqVO {
10 @Schema(description = "设备编号", example = "177") 10 @Schema(description = "设备编号", example = "177")
11 private Long id; 11 private Long id;
12 12
  13 + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
  14 + private String deviceKey;
  15 +
13 @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") 16 @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
14 private String deviceName; 17 private String deviceName;
15 18
16 @Schema(description = "备注名称", example = "张三") 19 @Schema(description = "备注名称", example = "张三")
17 private String nickname; 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 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") 28 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
20 private Long productId; 29 private Long productId;
21 30
  31 + @Schema(description = "网关设备 ID", example = "16380")
  32 + private Long gatewayId;
  33 +
22 } 34 }
23 \ No newline at end of file 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,12 +121,12 @@ public class IotProductController {
121 } 121 }
122 122
123 @GetMapping("/simple-list") 123 @GetMapping("/simple-list")
124 - @Operation(summary = "获得所有产品列表")  
125 - @PreAuthorize("@ss.hasPermission('iot:product:query')") 124 + @Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
126 public CommonResult<List<IotProductRespVO>> getSimpleProductList() { 125 public CommonResult<List<IotProductRespVO>> getSimpleProductList() {
127 List<IotProductDO> list = productService.getProductList(); 126 List<IotProductDO> list = productService.getProductList();
128 return success(convertList(list, product -> // 只返回 id、name 字段 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 \ No newline at end of file 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,8 +38,8 @@ public class IotProductRespVO {
38 @ExcelProperty("产品图标") 38 @ExcelProperty("产品图标")
39 private String icon; 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 private String picUrl; 43 private String picUrl;
44 44
45 @Schema(description = "产品描述", example = "你猜") 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,7 +28,7 @@ public class IotProductSaveReqVO {
28 @Schema(description = "产品图标", example = "https://iocoder.cn/1.svg") 28 @Schema(description = "产品图标", example = "https://iocoder.cn/1.svg")
29 private String icon; 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 private String picUrl; 32 private String picUrl;
33 33
34 @Schema(description = "产品描述", example = "描述") 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 package cn.iocoder.yudao.module.iot.dal.dataobject.device; 1 package cn.iocoder.yudao.module.iot.dal.dataobject.device;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
4 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 5 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
5 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; 6 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
6 import com.baomidou.mybatisplus.annotation.KeySequence; 7 import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableField;
7 import com.baomidou.mybatisplus.annotation.TableId; 9 import com.baomidou.mybatisplus.annotation.TableId;
8 import com.baomidou.mybatisplus.annotation.TableName; 10 import com.baomidou.mybatisplus.annotation.TableName;
9 import lombok.*; 11 import lombok.*;
10 12
11 import java.math.BigDecimal; 13 import java.math.BigDecimal;
12 import java.time.LocalDateTime; 14 import java.time.LocalDateTime;
  15 +import java.util.Set;
13 16
14 /** 17 /**
15 * IoT 设备 DO 18 * IoT 设备 DO
16 * 19 *
17 * @author haohao 20 * @author haohao
18 */ 21 */
19 -@TableName("iot_device") 22 +@TableName(value = "iot_device", autoResultMap = true)
20 @KeySequence("iot_device_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 23 @KeySequence("iot_device_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
21 @Data 24 @Data
22 @EqualsAndHashCode(callSuper = true) 25 @EqualsAndHashCode(callSuper = true)
@@ -47,6 +50,17 @@ public class IotDeviceDO extends BaseDO { @@ -47,6 +50,17 @@ public class IotDeviceDO extends BaseDO {
47 * 设备序列号 50 * 设备序列号
48 */ 51 */
49 private String serialNumber; 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,13 +80,6 @@ public class IotDeviceDO extends BaseDO {
66 * 冗余 {@link IotProductDO#getDeviceType()} 80 * 冗余 {@link IotProductDO#getDeviceType()}
67 */ 81 */
68 private Integer deviceType; 82 private Integer deviceType;
69 -  
70 - /**  
71 - * 设备状态  
72 - * <p>  
73 - * 枚举 {@link IotDeviceStatusEnum}  
74 - */  
75 - private Integer status;  
76 /** 83 /**
77 * 网关设备编号 84 * 网关设备编号
78 * <p> 85 * <p>
@@ -83,6 +90,13 @@ public class IotDeviceDO extends BaseDO { @@ -83,6 +90,13 @@ public class IotDeviceDO extends BaseDO {
83 private Long gatewayId; 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 private LocalDateTime statusLastUpdateTime; 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,6 +7,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePa
7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
9 9
  10 +import java.util.List;
  11 +
10 /** 12 /**
11 * IoT 设备 Mapper 13 * IoT 设备 Mapper
12 * 14 *
@@ -51,4 +53,13 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; { @@ -51,4 +53,13 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
51 default Long selectCountByProductId(Long productId) { 53 default Long selectCountByProductId(Long productId) {
52 return selectCount(IotDeviceDO::getProductId, productId); 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 \ No newline at end of file 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,7 +21,7 @@ public interface IotProductCategoryMapper extends BaseMapperX&lt;IotProductCategory
21 return selectPage(reqVO, new LambdaQueryWrapperX<IotProductCategoryDO>() 21 return selectPage(reqVO, new LambdaQueryWrapperX<IotProductCategoryDO>()
22 .likeIfPresent(IotProductCategoryDO::getName, reqVO.getName()) 22 .likeIfPresent(IotProductCategoryDO::getName, reqVO.getName())
23 .betweenIfPresent(IotProductCategoryDO::getCreateTime, reqVO.getCreateTime()) 23 .betweenIfPresent(IotProductCategoryDO::getCreateTime, reqVO.getCreateTime())
24 - .orderByDesc(IotProductCategoryDO::getId)); 24 + .orderByAsc(IotProductCategoryDO::getSort));
25 } 25 }
26 26
27 default List<IotProductCategoryDO> selectListByStatus(Integer status) { 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 package cn.iocoder.yudao.module.iot.service.device; 1 package cn.iocoder.yudao.module.iot.service.device;
2 2
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO; 4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO; 5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
5 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO; 6 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
6 -import jakarta.validation.*;  
7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 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 * IoT 设备 Service 接口 14 * IoT 设备 Service 接口
@@ -53,6 +56,14 @@ public interface IotDeviceService { @@ -53,6 +56,14 @@ public interface IotDeviceService {
53 PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO); 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 * @param updateReqVO 更新信息 69 * @param updateReqVO 更新信息
@@ -75,4 +86,5 @@ public interface IotDeviceService { @@ -75,4 +86,5 @@ public interface IotDeviceService {
75 * @return 设备信息 86 * @return 设备信息
76 */ 87 */
77 IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName); 88 IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
  89 +
78 } 90 }
79 \ No newline at end of file 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 package cn.iocoder.yudao.module.iot.service.device; 1 package cn.iocoder.yudao.module.iot.service.device;
2 2
3 import cn.hutool.core.util.IdUtil; 3 import cn.hutool.core.util.IdUtil;
4 -import cn.hutool.core.util.StrUtil; 4 +import cn.hutool.core.util.RandomUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; 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,18 +12,17 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
12 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 12 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
13 import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper; 13 import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
14 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum; 14 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
  15 +import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
15 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 16 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
16 import jakarta.annotation.Resource; 17 import jakarta.annotation.Resource;
17 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
18 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
19 -import org.springframework.transaction.annotation.Transactional;  
20 import org.springframework.validation.annotation.Validated; 20 import org.springframework.validation.annotation.Validated;
21 21
22 -import java.security.SecureRandom; 22 +import javax.annotation.Nullable;
23 import java.time.LocalDateTime; 23 import java.time.LocalDateTime;
24 -import java.util.Base64; 24 +import java.util.List;
25 import java.util.Objects; 25 import java.util.Objects;
26 -import java.util.UUID;  
27 26
28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
29 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; 28 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
@@ -40,142 +39,64 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -40,142 +39,64 @@ public class IotDeviceServiceImpl implements IotDeviceService {
40 39
41 @Resource 40 @Resource
42 private IotDeviceMapper deviceMapper; 41 private IotDeviceMapper deviceMapper;
  42 +
43 @Resource 43 @Resource
44 private IotProductService productService; 44 private IotProductService productService;
45 45
46 - /**  
47 - * 创建 IoT 设备  
48 - *  
49 - * @param createReqVO 创建请求 VO  
50 - * @return 设备 ID  
51 - */  
52 @Override 46 @Override
53 - @Transactional(rollbackFor = Exception.class)  
54 public Long createDevice(IotDeviceSaveReqVO createReqVO) { 47 public Long createDevice(IotDeviceSaveReqVO createReqVO) {
55 // 1.1 校验产品是否存在 48 // 1.1 校验产品是否存在
56 IotProductDO product = productService.getProduct(createReqVO.getProductId()); 49 IotProductDO product = productService.getProduct(createReqVO.getProductId());
57 if (product == null) { 50 if (product == null) {
58 throw exception(PRODUCT_NOT_EXISTS); 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 // 2.1 转换 VO 为 DO 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 deviceMapper.insert(device); 79 deviceMapper.insert(device);
82 return device.getId(); 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 @Override 83 @Override
166 - @Transactional(rollbackFor = Exception.class)  
167 public void updateDevice(IotDeviceSaveReqVO updateReqVO) { 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 // 2. 更新到数据库 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 deviceMapper.updateById(updateObj); 96 deviceMapper.updateById(updateObj);
175 } 97 }
176 98
177 @Override 99 @Override
178 - @Transactional(rollbackFor = Exception.class)  
179 public void deleteDevice(Long id) { 100 public void deleteDevice(Long id) {
180 // 1.1 校验存在 101 // 1.1 校验存在
181 IotDeviceDO device = validateDeviceExists(id); 102 IotDeviceDO device = validateDeviceExists(id);
@@ -202,13 +123,24 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -202,13 +123,24 @@ public class IotDeviceServiceImpl implements IotDeviceService {
202 return device; 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 IotDeviceDO device = deviceMapper.selectById(id); 132 IotDeviceDO device = deviceMapper.selectById(id);
208 if (device == null) { 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 @Override 146 @Override
@@ -217,18 +149,23 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -217,18 +149,23 @@ public class IotDeviceServiceImpl implements IotDeviceService {
217 } 149 }
218 150
219 @Override 151 @Override
  152 + public List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType) {
  153 + return deviceMapper.selectList(deviceType);
  154 + }
  155 +
  156 + @Override
220 public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) { 157 public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
221 // 1. 校验存在 158 // 1. 校验存在
222 IotDeviceDO device = validateDeviceExists(updateReqVO.getId()); 159 IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
223 160
224 // 2.1 更新状态和更新时间 161 // 2.1 更新状态和更新时间
225 - IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class); 162 + IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
  163 + .setStatusLastUpdateTime(LocalDateTime.now());
226 // 2.2 更新状态相关时间 164 // 2.2 更新状态相关时间
227 if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus()) 165 if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
228 && Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) { 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 } else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) { 169 } else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
233 // 如果是上线,设置最后上线时间 170 // 如果是上线,设置最后上线时间
234 updateDevice.setLastOnlineTime(LocalDateTime.now()); 171 updateDevice.setLastOnlineTime(LocalDateTime.now());
@@ -236,10 +173,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -236,10 +173,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
236 // 如果是离线,设置最后离线时间 173 // 如果是离线,设置最后离线时间
237 updateDevice.setLastOfflineTime(LocalDateTime.now()); 174 updateDevice.setLastOfflineTime(LocalDateTime.now());
238 } 175 }
239 -  
240 - // 2.3 设置状态更新时间  
241 - updateDevice.setStatusLastUpdateTime(LocalDateTime.now());  
242 - // 2.4 更新到数据库 176 + // 2.3 更新到数据库
243 deviceMapper.updateById(updateDevice); 177 deviceMapper.updateById(updateDevice);
244 } 178 }
245 179
@@ -254,4 +188,42 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -254,4 +188,42 @@ public class IotDeviceServiceImpl implements IotDeviceService {
254 return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName); 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 \ No newline at end of file 230 \ No newline at end of file