Commit afaf98c44f76daa8a18fafa7410991a0b2485889
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<IotDeviceDO> { |
| 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<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 | ... | ... |