From afaf98c44f76daa8a18fafa7410991a0b2485889 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 14 Dec 2024 16:12:24 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=81=E4=BF=AE=E6=94=B9=E6=94=AF=E6=8C=81=E6=9B=B4?= =?UTF-8?q?=E5=A4=9A=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/core/type/LongSetTypeHandler.java | 58 +++++ .../module/iot/enums/ErrorCodeConstants.java | 6 +- .../product/IotProductDeviceTypeEnum.java | 10 + .../admin/device/IotDeviceController.java | 13 + .../device/vo/device/IotDeviceSaveReqVO.java | 12 + .../admin/product/IotProductController.java | 6 +- .../product/vo/product/IotProductRespVO.java | 4 +- .../vo/product/IotProductSaveReqVO.java | 2 +- .../dal/dataobject/device/IotDeviceDO.java | 30 ++- .../iot/dal/mysql/device/IotDeviceMapper.java | 11 + .../product/IotProductCategoryMapper.java | 2 +- .../iot/service/device/IotDeviceService.java | 16 +- .../service/device/IotDeviceServiceImpl.java | 226 ++++++++---------- 13 files changed, 249 insertions(+), 147 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongSetTypeHandler.java diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongSetTypeHandler.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/LongSetTypeHandler.java new file mode 100644 index 0000000000..58d82ecf3f --- /dev/null +++ b/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 的类型转换器实现类,对应数据库的 varchar 类型 + * + * @author 芋道源码 + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes(List.class) +public class LongSetTypeHandler implements TypeHandler> { + + private static final String COMMA = ","; + + @Override + public void setParameter(PreparedStatement ps, int i, Set strings, JdbcType jdbcType) throws SQLException { + // 设置占位符 + ps.setString(i, CollUtil.join(strings, COMMA)); + } + + @Override + public Set getResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return getResult(value); + } + + @Override + public Set getResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return getResult(value); + } + + @Override + public Set getResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return getResult(value); + } + + private Set getResult(String value) { + if (value == null) { + return null; + } + return StrUtils.splitToLongSet(value, COMMA); + } +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index d1d350d5eb..0d8b7d8bcc 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/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, "产品分类不存在"); diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java index 99b75f3fbd..ef1432804a 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java +++ b/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); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java index ba68e02328..2dee57d6d9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java +++ b/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> getSimpleDeviceList( + @RequestParam(value = "deviceType", required = false) Integer deviceType) { + List list = deviceService.getDeviceList(deviceType); + return success(convertList(list, device -> // 只返回 id、name 字段 + new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()))); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java index 85fde9d3d9..e4f23d97e2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java +++ b/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; + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java index c34571471b..2d8c856400 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java +++ b/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> getSimpleProductList() { List 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()))); } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java index 6637a67ff0..f674651d51 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java +++ b/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 = "你猜") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java index 31890beb7d..268ab7c6fa 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java +++ b/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 = "描述") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java index d3f6547a1a..6028c25fe0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java +++ b/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 groupIds; /** * 产品编号 @@ -66,13 +80,6 @@ public class IotDeviceDO extends BaseDO { * 冗余 {@link IotProductDO#getDeviceType()} */ private Integer deviceType; - - /** - * 设备状态 - *

- * 枚举 {@link IotDeviceStatusEnum} - */ - private Integer status; /** * 网关设备编号 *

@@ -82,6 +89,13 @@ public class IotDeviceDO extends BaseDO { */ private Long gatewayId; + /** + * 设备状态 + *

+ * 枚举 {@link IotDeviceStatusEnum} + */ + private Integer status; + /** * 设备状态最后更新时间 */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index 4d8315eb3c..65af1f17ac 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/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 { default Long selectCountByProductId(Long productId) { return selectCount(IotDeviceDO::getProductId, productId); } + + default IotDeviceDO selectByDeviceKey(String deviceKey) { + return selectOne(IotDeviceDO::getDeviceKey, deviceKey); + } + + default List selectList(Integer deviceType) { + return selectList(IotDeviceDO::getDeviceType, deviceType); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductCategoryMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductCategoryMapper.java index 8b1744af07..70ad56da80 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductCategoryMapper.java +++ b/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() .likeIfPresent(IotProductCategoryDO::getName, reqVO.getName()) .betweenIfPresent(IotProductCategoryDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(IotProductCategoryDO::getId)); + .orderByAsc(IotProductCategoryDO::getSort)); } default List selectListByStatus(Integer status) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java index ed4c6a60ce..3569c75977 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java +++ b/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 getDevicePage(IotDevicePageReqVO pageReqVO); + /** + * 获得设备列表 + * + * @param deviceType 设备类型 + * @return 设备列表 + */ + List getDeviceList(@Nullable Integer deviceType); + /** * 更新设备状态 * @@ -75,4 +86,5 @@ public interface IotDeviceService { * @return 设备信息 */ IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName); + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index bb302a22f7..c882b7420a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/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); } - return device; + if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) { + throw exception(DEVICE_NOT_GATEWAY); + } + } + + @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 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); + } + } \ No newline at end of file From 9041de2da57bd7fac4af894d5f44f82e20e58cf3 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 14 Dec 2024 16:34:26 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E7=AD=9B=E9=80=89=E5=8E=BB=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/vo/device/IotDevicePageReqVO.java | 52 +------------------ .../iot/dal/mysql/device/IotDeviceMapper.java | 16 +----- 2 files changed, 4 insertions(+), 64 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java index 90ded80898..791ee34b60 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java @@ -8,11 +8,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @Schema(description = "管理后台 - IoT 设备分页 Request VO") @Data @@ -20,11 +15,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ @ToString(callSuper = true) public class IotDevicePageReqVO extends PageParam { - // TODO @芋艿:需要去掉一些多余的字段; - - @Schema(description = "设备唯一标识符", example = "24602") - private String deviceKey; - @Schema(description = "设备名称", example = "王五") private String deviceName; @@ -34,53 +24,15 @@ public class IotDevicePageReqVO extends PageParam { @Schema(description = "产品编号", example = "26202") private Long productId; - @Schema(description = "产品标识") - private String productKey; - @Schema(description = "设备类型", example = "1") @InEnum(IotProductDeviceTypeEnum.class) private Integer deviceType; - @Schema(description = "网关设备 ID", example = "16380") - private Long gatewayId; - @Schema(description = "设备状态", example = "1") @InEnum(IotDeviceStatusEnum.class) private Integer status; - @Schema(description = "设备状态最后更新时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] statusLastUpdateTime; - - @Schema(description = "最后上线时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] lastOnlineTime; - - @Schema(description = "最后离线时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] lastOfflineTime; - - @Schema(description = "设备激活时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] activeTime; - - @Schema(description = "设备密钥,用于设备认证,需安全存储") - private String deviceSecret; - - @Schema(description = "MQTT 客户端 ID", example = "24602") - private String mqttClientId; - - @Schema(description = "MQTT 用户名", example = "芋艿") - private String mqttUsername; - - @Schema(description = "MQTT 密码") - private String mqttPassword; - - @Schema(description = "认证类型(如一机一密、动态注册)", example = "2") - private String authType; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; + @Schema(description = "设备分组编号", example = "1024") + private Long groupId; } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index 65af1f17ac..0459c964d9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.dal.mysql.device; +import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -17,27 +18,14 @@ import java.util.List; @Mapper public interface IotDeviceMapper extends BaseMapperX { - // TODO @haohao:可能多余的查询条件,要去掉哈 default PageResult selectPage(IotDevicePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(IotDeviceDO::getDeviceKey, reqVO.getDeviceKey()) .likeIfPresent(IotDeviceDO::getDeviceName, reqVO.getDeviceName()) .eqIfPresent(IotDeviceDO::getProductId, reqVO.getProductId()) - .eqIfPresent(IotDeviceDO::getProductKey, reqVO.getProductKey()) .eqIfPresent(IotDeviceDO::getDeviceType, reqVO.getDeviceType()) .likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname()) - .eqIfPresent(IotDeviceDO::getGatewayId, reqVO.getGatewayId()) .eqIfPresent(IotDeviceDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(IotDeviceDO::getStatusLastUpdateTime, reqVO.getStatusLastUpdateTime()) - .betweenIfPresent(IotDeviceDO::getLastOnlineTime, reqVO.getLastOnlineTime()) - .betweenIfPresent(IotDeviceDO::getLastOfflineTime, reqVO.getLastOfflineTime()) - .betweenIfPresent(IotDeviceDO::getActiveTime, reqVO.getActiveTime()) - .eqIfPresent(IotDeviceDO::getDeviceSecret, reqVO.getDeviceSecret()) - .eqIfPresent(IotDeviceDO::getMqttClientId, reqVO.getMqttClientId()) - .likeIfPresent(IotDeviceDO::getMqttUsername, reqVO.getMqttUsername()) - .eqIfPresent(IotDeviceDO::getMqttPassword, reqVO.getMqttPassword()) - .eqIfPresent(IotDeviceDO::getAuthType, reqVO.getAuthType()) - .betweenIfPresent(IotDeviceDO::getCreateTime, reqVO.getCreateTime()) + .apply(ObjectUtil.isNotNull(reqVO.getGroupId()), "FIND_IN_SET(" + reqVO.getGroupId() + ",group_ids) > 0") .orderByDesc(IotDeviceDO::getId)); } From b143bc177f8fccb778eb60a2f470cc99f33a77a2 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 14 Dec 2024 17:00:58 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E5=88=86?= =?UTF-8?q?=E7=BB=84=E7=9A=84=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/iot/enums/ErrorCodeConstants.java | 3 + .../vo/group/IotDeviceGroupPageReqVO.java | 27 +++++++ .../device/vo/group/IotDeviceGroupRespVO.java | 27 +++++++ .../vo/group/IotDeviceGroupSaveReqVO.java | 26 +++++++ .../dataobject/device/IotDeviceGroupDO.java | 44 ++++++++++++ .../mysql/device/IotDeviceGroupMapper.java | 25 +++++++ .../service/device/IotDeviceGroupService.java | 54 ++++++++++++++ .../device/IotDeviceGroupServiceImpl.java | 70 +++++++++++++++++++ 8 files changed, 276 insertions(+) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupPageReqVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupSaveReqVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceGroupDO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index 0d8b7d8bcc..55e560757a 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -33,4 +33,7 @@ public interface ErrorCodeConstants { // ========== 产品分类 1-050-004-000 ========== ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在"); + // ========== 设备分组 1-050-005-000 ========== + ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在"); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupPageReqVO.java new file mode 100644 index 0000000000..1490f2894e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupPageReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - IoT 设备分组分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class IotDeviceGroupPageReqVO extends PageParam { + + @Schema(description = "分组名字", example = "李四") + private String name; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java new file mode 100644 index 0000000000..50306cb766 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IoT 设备分组 Response VO") +@Data +public class IotDeviceGroupRespVO { + + @Schema(description = "分组 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3583") + private Long id; + + @Schema(description = "分组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private String name; + + @Schema(description = "分组状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "分组描述", example = "你说的对") + private String description; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupSaveReqVO.java new file mode 100644 index 0000000000..491cd9366b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupSaveReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - IoT 设备分组新增/修改 Request VO") +@Data +public class IotDeviceGroupSaveReqVO { + + @Schema(description = "分组 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3583") + private Long id; + + @Schema(description = "分组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotEmpty(message = "分组名字不能为空") + private String name; + + @Schema(description = "分组状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "分组状态不能为空") + private Integer status; + + @Schema(description = "分组描述", example = "你说的对") + private String description; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceGroupDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceGroupDO.java new file mode 100644 index 0000000000..44c471216d --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceGroupDO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.device; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IoT 设备分组 DO + * + * @author 芋道源码 + */ +@TableName("iot_device_group") +@KeySequence("iot_device_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class IotDeviceGroupDO extends BaseDO { + + /** + * 分组 ID + */ + @TableId + private Long id; + /** + * 分组名字 + */ + private String name; + /** + * 分组状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 分组描述 + */ + private String description; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java new file mode 100644 index 0000000000..492f7ee3f4 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.device; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupPageReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * IoT 设备分组 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface IotDeviceGroupMapper extends BaseMapperX { + + default PageResult selectPage(IotDeviceGroupPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(IotDeviceGroupDO::getName, reqVO.getName()) + .betweenIfPresent(IotDeviceGroupDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(IotDeviceGroupDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java new file mode 100644 index 0000000000..f4966b18f9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java @@ -0,0 +1,54 @@ +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.group.IotDeviceGroupPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; +import jakarta.validation.Valid; + +/** + * IoT 设备分组 Service 接口 + * + * @author 芋道源码 + */ +public interface IotDeviceGroupService { + + /** + * 创建IoT 设备分组 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDeviceGroup(@Valid IotDeviceGroupSaveReqVO createReqVO); + + /** + * 更新IoT 设备分组 + * + * @param updateReqVO 更新信息 + */ + void updateDeviceGroup(@Valid IotDeviceGroupSaveReqVO updateReqVO); + + /** + * 删除IoT 设备分组 + * + * @param id 编号 + */ + void deleteDeviceGroup(Long id); + + /** + * 获得IoT 设备分组 + * + * @param id 编号 + * @return IoT 设备分组 + */ + IotDeviceGroupDO getDeviceGroup(Long id); + + /** + * 获得IoT 设备分组分页 + * + * @param pageReqVO 分页查询 + * @return IoT 设备分组分页 + */ + PageResult getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java new file mode 100644 index 0000000000..957775c5e0 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.iot.service.device; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; +import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceGroupMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_GROUP_NOT_EXISTS; + +/** + * IoT 设备分组 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class IotDeviceGroupServiceImpl implements IotDeviceGroupService { + + @Resource + private IotDeviceGroupMapper deviceGroupMapper; + + @Override + public Long createDeviceGroup(IotDeviceGroupSaveReqVO createReqVO) { + // 插入 + IotDeviceGroupDO deviceGroup = BeanUtils.toBean(createReqVO, IotDeviceGroupDO.class); + deviceGroupMapper.insert(deviceGroup); + // 返回 + return deviceGroup.getId(); + } + + @Override + public void updateDeviceGroup(IotDeviceGroupSaveReqVO updateReqVO) { + // 校验存在 + validateDeviceGroupExists(updateReqVO.getId()); + // 更新 + IotDeviceGroupDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceGroupDO.class); + deviceGroupMapper.updateById(updateObj); + } + + @Override + public void deleteDeviceGroup(Long id) { + // 校验存在 + validateDeviceGroupExists(id); + // 删除 + deviceGroupMapper.deleteById(id); + } + + private void validateDeviceGroupExists(Long id) { + if (deviceGroupMapper.selectById(id) == null) { + throw exception(DEVICE_GROUP_NOT_EXISTS); + } + } + + @Override + public IotDeviceGroupDO getDeviceGroup(Long id) { + return deviceGroupMapper.selectById(id); + } + + @Override + public PageResult getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO) { + return deviceGroupMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file From b5ac5261394db19b535fdbe18ee1aac2a892baae Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 14 Dec 2024 18:41:46 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=E5=A2=9E=E5=8A=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=88=86=E7=BB=84=E9=80=89=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/iot/enums/ErrorCodeConstants.java | 2 +- .../device/IotDeviceGroupController.java | 88 +++++++++++++++++++ .../device/vo/device/IotDeviceRespVO.java | 4 + .../device/vo/device/IotDeviceSaveReqVO.java | 5 ++ .../device/vo/group/IotDeviceGroupRespVO.java | 3 + .../dal/dataobject/device/IotDeviceDO.java | 2 +- .../mysql/device/IotDeviceGroupMapper.java | 6 ++ .../iot/dal/mysql/device/IotDeviceMapper.java | 6 ++ .../service/device/IotDeviceGroupService.java | 48 ++++++++-- .../device/IotDeviceGroupServiceImpl.java | 25 +++++- .../iot/service/device/IotDeviceService.java | 8 ++ .../service/device/IotDeviceServiceImpl.java | 13 +++ 12 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index 55e560757a..b75effb105 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -35,5 +35,5 @@ public interface ErrorCodeConstants { // ========== 设备分组 1-050-005-000 ========== ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在"); - + ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除"); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java new file mode 100644 index 0000000000..d19cf7fc9f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceGroupController.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceGroupService; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +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 +@RequestMapping("/iot/device-group") +@Validated +public class IotDeviceGroupController { + + @Resource + private IotDeviceGroupService deviceGroupService; + @Resource + private IotDeviceService deviceService; + + @PostMapping("/create") + @Operation(summary = "创建设备分组") + @PreAuthorize("@ss.hasPermission('iot:device-group:create')") + public CommonResult createDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO createReqVO) { + return success(deviceGroupService.createDeviceGroup(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新设备分组") + @PreAuthorize("@ss.hasPermission('iot:device-group:update')") + public CommonResult updateDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO updateReqVO) { + deviceGroupService.updateDeviceGroup(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除设备分组") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:device-group:delete')") + public CommonResult deleteDeviceGroup(@RequestParam("id") Long id) { + deviceGroupService.deleteDeviceGroup(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得设备分组") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('iot:device-group:query')") + public CommonResult getDeviceGroup(@RequestParam("id") Long id) { + IotDeviceGroupDO deviceGroup = deviceGroupService.getDeviceGroup(id); + return success(BeanUtils.toBean(deviceGroup, IotDeviceGroupRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得设备分组分页") + @PreAuthorize("@ss.hasPermission('iot:device-group:query')") + public CommonResult> getDeviceGroupPage(@Valid IotDeviceGroupPageReqVO pageReqVO) { + PageResult pageResult = deviceGroupService.getDeviceGroupPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, IotDeviceGroupRespVO.class, + group -> group.setDeviceCount(deviceService.getDeviceCountByGroupId(group.getId())))); + } + + @GetMapping("/simple-list") + @Operation(summary = "获取设备分组的精简信息列表", description = "只包含被开启的分组,主要用于前端的下拉选项") + public CommonResult> getSimpleDeviceGroupList() { + List list = deviceGroupService.getDeviceGroupListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, group -> // 只返回 id、name 字段 + new IotDeviceGroupRespVO().setId(group.getId()).setName(group.getName()))); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java index fd2fbaa686..9214adaf2d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.util.Set; @Schema(description = "管理后台 - IoT 设备 Response VO") @Data @@ -23,6 +24,9 @@ public class IotDeviceRespVO { @ExcelProperty("设备名称备") private String deviceName; + @Schema(description = "设备分组编号数组", example = "1,2") + private Set groupIds; + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") @ExcelProperty("产品编号") private Long productId; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java index e4f23d97e2..97220d57a2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.util.Set; + @Schema(description = "管理后台 - IoT 设备新增/修改 Request VO") @Data public class IotDeviceSaveReqVO { @@ -25,6 +27,9 @@ public class IotDeviceSaveReqVO { @Schema(description = "设备图片", example = "https://iocoder.cn/1.png") private String picUrl; + @Schema(description = "设备分组编号数组", example = "1,2") + private Set groupIds; + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202") private Long productId; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java index 50306cb766..4fd5415028 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/group/IotDeviceGroupRespVO.java @@ -24,4 +24,7 @@ public class IotDeviceGroupRespVO { @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; + @Schema(description = "设备数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Long deviceCount; + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java index 6028c25fe0..f396855f13 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java @@ -57,7 +57,7 @@ public class IotDeviceDO extends BaseDO { /** * 设备分组编号集合 * - * 关联 TODO 芋艿: + * 关联 {@link IotDeviceGroupDO#getId()} */ @TableField(typeHandler = LongSetTypeHandler.class) private Set groupIds; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java index 492f7ee3f4..605ce5270a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGro import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + /** * IoT 设备分组 Mapper * @@ -22,4 +24,8 @@ public interface IotDeviceGroupMapper extends BaseMapperX { .orderByDesc(IotDeviceGroupDO::getId)); } + default List selectListByStatus(Integer status) { + return selectList(IotDeviceGroupDO::getStatus, status); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index 0459c964d9..59794c4edf 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java @@ -50,4 +50,10 @@ public interface IotDeviceMapper extends BaseMapperX { return selectList(IotDeviceDO::getDeviceType, deviceType); } + default Long selectCountByGroupId(Long groupId) { + return selectCount(new LambdaQueryWrapperX() + .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0") + .orderByDesc(IotDeviceDO::getId)); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java index f4966b18f9..5219f2d4bd 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java @@ -1,11 +1,17 @@ package cn.iocoder.yudao.module.iot.service.device; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; import jakarta.validation.Valid; +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + /** * IoT 设备分组 Service 接口 * @@ -14,7 +20,7 @@ import jakarta.validation.Valid; public interface IotDeviceGroupService { /** - * 创建IoT 设备分组 + * 创建设备分组 * * @param createReqVO 创建信息 * @return 编号 @@ -22,33 +28,61 @@ public interface IotDeviceGroupService { Long createDeviceGroup(@Valid IotDeviceGroupSaveReqVO createReqVO); /** - * 更新IoT 设备分组 + * 更新设备分组 * * @param updateReqVO 更新信息 */ void updateDeviceGroup(@Valid IotDeviceGroupSaveReqVO updateReqVO); /** - * 删除IoT 设备分组 + * 删除设备分组 * * @param id 编号 */ void deleteDeviceGroup(Long id); /** - * 获得IoT 设备分组 + * 校验设备分组是否存在 + * + * @param ids 设备分组 ID 数组 + */ + default List validateDeviceGroupExists(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return List.of(); + } + return convertList(ids, this::validateDeviceGroupExists); + } + + /** + * 校验设备分组是否存在 + * + * @param id 设备分组 ID + * @return 设备分组 + */ + IotDeviceGroupDO validateDeviceGroupExists(Long id); + + /** + * 获得设备分组 * * @param id 编号 - * @return IoT 设备分组 + * @return 设备分组 */ IotDeviceGroupDO getDeviceGroup(Long id); /** - * 获得IoT 设备分组分页 + * 获得设备分组分页 * * @param pageReqVO 分页查询 - * @return IoT 设备分组分页 + * @return 设备分组分页 */ PageResult getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO); + /** + * 获得设备分组列表 + * + * @param status 状态 + * @return 设备分组列表 + */ + List getDeviceGroupListByStatus(Integer status); + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java index 957775c5e0..cfa24784b7 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java @@ -10,7 +10,10 @@ import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.util.List; + import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_GROUP_NOT_EXISTS; /** @@ -25,6 +28,9 @@ public class IotDeviceGroupServiceImpl implements IotDeviceGroupService { @Resource private IotDeviceGroupMapper deviceGroupMapper; + @Resource + private IotDeviceService deviceService; + @Override public Long createDeviceGroup(IotDeviceGroupSaveReqVO createReqVO) { // 插入 @@ -45,16 +51,24 @@ public class IotDeviceGroupServiceImpl implements IotDeviceGroupService { @Override public void deleteDeviceGroup(Long id) { - // 校验存在 + // 1.1 校验存在 validateDeviceGroupExists(id); + // 1.2 校验是否存在设备 + if (deviceService.getDeviceCountByGroupId(id) > 0) { + throw exception(DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS); + } + // 删除 deviceGroupMapper.deleteById(id); } - private void validateDeviceGroupExists(Long id) { - if (deviceGroupMapper.selectById(id) == null) { + @Override + public IotDeviceGroupDO validateDeviceGroupExists(Long id) { + IotDeviceGroupDO group = deviceGroupMapper.selectById(id); + if (group == null) { throw exception(DEVICE_GROUP_NOT_EXISTS); } + return group; } @Override @@ -67,4 +81,9 @@ public class IotDeviceGroupServiceImpl implements IotDeviceGroupService { return deviceGroupMapper.selectPage(pageReqVO); } + @Override + public List getDeviceGroupListByStatus(Integer status) { + return deviceGroupMapper.selectListByStatus(status); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java index 3569c75977..8ae81e6119 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java @@ -78,6 +78,14 @@ public interface IotDeviceService { */ Long getDeviceCountByProductId(Long productId); + /** + * 获得设备数量 + * + * @param groupId 分组编号 + * @return 设备数量 + */ + Long getDeviceCountByGroupId(Long groupId); + /** * 根据产品 key 和设备名称,获得设备信息 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index c882b7420a..482dc606e5 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java @@ -16,6 +16,7 @@ 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.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -42,6 +43,9 @@ public class IotDeviceServiceImpl implements IotDeviceService { @Resource private IotProductService productService; + @Resource + @Lazy // 延迟加载,解决循环依赖 + private IotDeviceGroupService deviceGroupService; @Override public Long createDevice(IotDeviceSaveReqVO createReqVO) { @@ -63,6 +67,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { && createReqVO.getGatewayId() != null) { validateGatewayDeviceExists(createReqVO.getGatewayId()); } + // 1.5 校验分组存在 + deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds()); // 2.1 转换 VO 为 DO IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> { @@ -90,6 +96,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { && updateReqVO.getGatewayId() != null) { validateGatewayDeviceExists(updateReqVO.getGatewayId()); } + // 1.3 校验分组存在 + deviceGroupService.validateDeviceGroupExists(updateReqVO.getGroupIds()); // 2. 更新到数据库 IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class); @@ -182,6 +190,11 @@ public class IotDeviceServiceImpl implements IotDeviceService { return deviceMapper.selectCountByProductId(productId); } + @Override + public Long getDeviceCountByGroupId(Long groupId) { + return deviceMapper.selectCountByGroupId(groupId); + } + @Override @TenantIgnore public IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName) { From 345065815945b5512a7a87bb3becd99e6229e99d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 14 Dec 2024 18:56:51 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=E5=A2=9E=E5=8A=A0=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/iot/enums/DictTypeConstants.java | 3 +++ .../admin/device/IotDeviceController.java | 19 ++++++++++++++ .../device/vo/device/IotDeviceRespVO.java | 25 ++++++++++++++----- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java index b5c34f14b7..c8e6c70c1f 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java @@ -13,4 +13,7 @@ public class DictTypeConstants { public static final String PROTOCOL_TYPE = "iot_protocol_type"; public static final String DATA_FORMAT = "iot_data_format"; public static final String VALIDATE_TYPE = "iot_validate_type"; + + public static final String DEVICE_STATUS = "iot_device_status"; + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java index 2dee57d6d9..affdd0a4d9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java @@ -1,8 +1,11 @@ package cn.iocoder.yudao.module.iot.controller.admin.device; +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceRespVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO; @@ -13,13 +16,16 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.io.IOException; import java.util.List; +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -81,6 +87,19 @@ public class IotDeviceController { return success(BeanUtils.toBean(pageResult, IotDeviceRespVO.class)); } + @GetMapping("/export-excel") + @Operation(summary = "导出设备 Excel") + @PreAuthorize("@ss.hasPermission('iot:device:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportDeviceExcel(@Valid IotDevicePageReqVO exportReqVO, + HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + CommonResult> result = getDevicePage(exportReqVO); + // 导出 Excel + ExcelUtils.write(response, "设备.xls", "数据", IotDeviceRespVO.class, + result.getData().getList()); + } + @GetMapping("/count") @Operation(summary = "获得设备数量") @Parameter(name = "productId", description = "产品编号", example = "1") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java index 9214adaf2d..1516637590 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; @@ -8,6 +10,8 @@ import lombok.Data; import java.time.LocalDateTime; import java.util.Set; +import static cn.iocoder.yudao.module.iot.enums.DictTypeConstants.DEVICE_STATUS; + @Schema(description = "管理后台 - IoT 设备 Response VO") @Data @ExcelIgnoreUnannotated @@ -21,9 +25,21 @@ public class IotDeviceRespVO { private String deviceKey; @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") - @ExcelProperty("设备名称备") + @ExcelProperty("设备名称") private String deviceName; + @Schema(description = "设备备注名称", example = "张三") + @ExcelProperty("设备备注名称") + private String nickname; + + @Schema(description = "设备序列号", example = "1024") + @ExcelProperty("设备序列号") + private String serialNumber; + + @Schema(description = "设备图片", example = "我是一名码农") + @ExcelProperty("设备图片") + private String picUrl; + @Schema(description = "设备分组编号数组", example = "1,2") private Set groupIds; @@ -39,15 +55,12 @@ public class IotDeviceRespVO { @ExcelProperty("设备类型") private Integer deviceType; - @Schema(description = "设备备注名称", example = "张三") - @ExcelProperty("设备备注名称") - private String nickname; - @Schema(description = "网关设备 ID", example = "16380") private Long gatewayId; @Schema(description = "设备状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @ExcelProperty("设备状态") + @ExcelProperty(value = "设备状态", converter = DictConvert.class) + @DictFormat(DEVICE_STATUS) private Integer status; @Schema(description = "设备状态最后更新时间") From b02e396aff2885e402f598d300ad923d559396fd Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 14 Dec 2024 19:12:42 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=E5=A2=9E=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/IotDeviceController.java | 12 ++++++++- .../iot/service/device/IotDeviceService.java | 10 +++++++- .../service/device/IotDeviceServiceImpl.java | 25 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java index affdd0a4d9..417df29841 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java @@ -23,6 +23,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; @@ -62,7 +63,7 @@ public class IotDeviceController { } @DeleteMapping("/delete") - @Operation(summary = "删除设备") + @Operation(summary = "删除单个设备") @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('iot:device:delete')") public CommonResult deleteDevice(@RequestParam("id") Long id) { @@ -70,6 +71,15 @@ public class IotDeviceController { return success(true); } + @DeleteMapping("/delete-list") + @Operation(summary = "删除多个设备") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('iot:device:delete')") + public CommonResult deleteDeviceList(@RequestParam("ids") Collection ids) { + deviceService.deleteDeviceList(ids); + return success(true); + } + @GetMapping("/get") @Operation(summary = "获得设备") @Parameter(name = "id", description = "编号", required = true, example = "1024") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java index 8ae81e6119..a76f09486b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import jakarta.validation.Valid; import javax.annotation.Nullable; +import java.util.Collection; import java.util.List; /** @@ -33,12 +34,19 @@ public interface IotDeviceService { void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO); /** - * 删除设备 + * 删除单个设备 * * @param id 编号 */ void deleteDevice(Long id); + /** + * 删除多个设备 + * + * @param ids 编号数组 + */ + void deleteDeviceList(Collection ids); + /** * 获得设备 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index 482dc606e5..3af71593b4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.device; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.RandomUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -18,10 +19,12 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Nullable; import java.time.LocalDateTime; +import java.util.Collection; import java.util.List; import java.util.Objects; @@ -117,6 +120,28 @@ public class IotDeviceServiceImpl implements IotDeviceService { deviceMapper.deleteById(id); } + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteDeviceList(Collection ids) { + // 1.1 校验存在 + if (CollUtil.isEmpty(ids)) { + return; + } + List devices = deviceMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(devices)) { + return; + } + // 1.2 校验网关设备是否存在 + for (IotDeviceDO device : devices) { + if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(device.getId()) > 0) { + throw exception(DEVICE_HAS_CHILDREN); + } + } + + // 2. 删除设备 + deviceMapper.deleteByIds(ids); + } + /** * 校验设备是否存在 * From 39ba4e72da281a90715baab359a8995627747f7a Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 14 Dec 2024 19:43:22 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=95=8C=E9=9D=A2=E5=A2=9E=E5=8A=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=88=86=E7=BB=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/device/IotDeviceController.java | 17 +++++++++----- .../vo/device/IotDeviceUpdateGroupReqVO.java | 21 ++++++++++++++++++ .../iot/service/device/IotDeviceService.java | 22 +++++++++++++------ .../service/device/IotDeviceServiceImpl.java | 20 ++++++++++++++++- 4 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUpdateGroupReqVO.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java index 417df29841..35b4abe937 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java @@ -6,10 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceRespVO; -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 cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import io.swagger.v3.oas.annotations.Operation; @@ -62,6 +59,14 @@ public class IotDeviceController { return success(true); } + @PutMapping("/update-group") + @Operation(summary = "更新设备分组") + @PreAuthorize("@ss.hasPermission('iot:device:update')") + public CommonResult updateDeviceGroup(@Valid @RequestBody IotDeviceUpdateGroupReqVO updateReqVO) { + deviceService.updateDeviceGroup(updateReqVO); + return success(true); + } + @DeleteMapping("/delete") @Operation(summary = "删除单个设备") @Parameter(name = "id", description = "编号", required = true) @@ -102,7 +107,7 @@ public class IotDeviceController { @PreAuthorize("@ss.hasPermission('iot:device:export')") @ApiAccessLog(operateType = EXPORT) public void exportDeviceExcel(@Valid IotDevicePageReqVO exportReqVO, - HttpServletResponse response) throws IOException { + HttpServletResponse response) throws IOException { exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); CommonResult> result = getDevicePage(exportReqVO); // 导出 Excel @@ -125,7 +130,7 @@ public class IotDeviceController { @RequestParam(value = "deviceType", required = false) Integer deviceType) { List list = deviceService.getDeviceList(deviceType); return success(convertList(list, device -> // 只返回 id、name 字段 - new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()))); + new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()))); } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUpdateGroupReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUpdateGroupReqVO.java new file mode 100644 index 0000000000..bf66fbf98f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceUpdateGroupReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - IoT 设备更新分组 Request VO") +@Data +public class IotDeviceUpdateGroupReqVO { + + @Schema(description = "设备编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + @NotEmpty(message = "设备编号列表不能为空") + private Set ids; + + @Schema(description = "分组编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") + @NotEmpty(message = "分组编号列表不能为空") + private Set groupIds; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java index a76f09486b..42101626c0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java @@ -4,6 +4,7 @@ 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 cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import jakarta.validation.Valid; @@ -33,6 +34,20 @@ public interface IotDeviceService { */ void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO); + /** + * 更新设备状态 + * + * @param updateReqVO 更新信息 + */ + void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO); + + /** + * 更新设备分组 + * + * @param updateReqVO 更新信息 + */ + void updateDeviceGroup(@Valid IotDeviceUpdateGroupReqVO updateReqVO); + /** * 删除单个设备 * @@ -71,13 +86,6 @@ public interface IotDeviceService { */ List getDeviceList(@Nullable Integer deviceType); - /** - * 更新设备状态 - * - * @param updateReqVO 更新信息 - */ - void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO); - /** * 获得设备数量 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index 3af71593b4..29944eae7b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; 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 cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO; 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; @@ -29,6 +30,7 @@ import java.util.List; import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; /** @@ -67,7 +69,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { } // 1.4 校验父设备是否为合法网关 if (IotProductDeviceTypeEnum.isGateway(product.getDeviceType()) - && createReqVO.getGatewayId() != null) { + && createReqVO.getGatewayId() != null) { validateGatewayDeviceExists(createReqVO.getGatewayId()); } // 1.5 校验分组存在 @@ -107,6 +109,22 @@ public class IotDeviceServiceImpl implements IotDeviceService { deviceMapper.updateById(updateObj); } + @Override + @Transactional(rollbackFor = Exception.class) + public void updateDeviceGroup(IotDeviceUpdateGroupReqVO updateReqVO) { + // 1.1 校验设备存在 + List devices = deviceMapper.selectBatchIds(updateReqVO.getIds()); + if (CollUtil.isEmpty(devices)) { + return; + } + // 1.2 校验分组存在 + deviceGroupService.validateDeviceGroupExists(updateReqVO.getGroupIds()); + + // 3. 更新设备分组 + deviceMapper.updateBatch(convertList(devices, device -> new IotDeviceDO() + .setId(device.getId()).setGroupIds(updateReqVO.getGroupIds()))); + } + @Override public void deleteDevice(Long id) { // 1.1 校验存在 From 555310de66d18250116f14d2cf93f42155cc3862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Sat, 14 Dec 2024 21:51:17 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91IoT=EF=BC=9A=E5=A2=9E=E5=8A=A0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8C=85?= =?UTF-8?q?=E5=90=AB=E6=8F=92=E4=BB=B6=E5=AE=9E=E4=BE=8B=E5=92=8C=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E5=AE=9A=E4=B9=89=E5=8F=8A=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 8 + yudao-module-iot/pom.xml | 1 + .../module/iot/enums/ErrorCodeConstants.java | 13 +- .../enums/plugin/IotPluginDeployTypeEnum.java | 53 ++++ .../iot/enums/plugin/IotPluginStatusEnum.java | 57 ++++ .../iot/enums/plugin/IotPluginTypeEnum.java | 53 ++++ yudao-module-iot/yudao-module-iot-biz/pom.xml | 6 + .../plugininfo/PluginInfoController.java | 98 +++++++ .../plugininfo/vo/PluginInfoPageReqVO.java | 57 ++++ .../admin/plugininfo/vo/PluginInfoRespVO.java | 70 +++++ .../plugininfo/vo/PluginInfoSaveReqVO.java | 49 ++++ .../PluginInstanceController.java | 94 ++++++ .../vo/PluginInstancePageReqVO.java | 36 +++ .../vo/PluginInstanceRespVO.java | 43 +++ .../vo/PluginInstanceSaveReqVO.java | 35 +++ .../dataobject/plugininfo/PluginInfoDO.java | 78 +++++ .../plugininstance/PluginInstanceDO.java | 51 ++++ .../mysql/plugininfo/PluginInfoMapper.java | 36 +++ .../plugininstance/PluginInstanceMapper.java | 29 ++ .../framework/plugin/SpringConfiguration.java | 15 + .../service/plugininfo/PluginInfoService.java | 72 +++++ .../plugininfo/PluginInfoServiceImpl.java | 267 ++++++++++++++++++ .../plugininstance/PluginInstanceService.java | 54 ++++ .../PluginInstanceServiceImpl.java | 70 +++++ .../mapper/plugininfo/PluginInfoMapper.xml | 12 + .../plugininstance/PluginInstanceMapper.xml | 12 + .../plugininfo/PluginInfoServiceImplTest.java | 171 +++++++++++ .../PluginInstanceServiceImplTest.java | 150 ++++++++++ .../yudao-module-iot-plugin/pom.xml | 31 ++ .../yudao/module/iot/plugin/package-info.java | 6 + 30 files changed, 1726 insertions(+), 1 deletion(-) create mode 100644 yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java create mode 100644 yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java create mode 100644 yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoSaveReqVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/PluginInstanceController.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininfo/PluginInfoMapper.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininstance/PluginInstanceMapper.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceService.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininfo/PluginInfoMapper.xml create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java create mode 100644 yudao-module-iot/yudao-module-iot-plugin/pom.xml create mode 100644 yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/package-info.java diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 486fe124b3..6eaa89dfe7 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -66,6 +66,7 @@ 2.7.0 3.0.6 1.2.5 + 0.9.0 3.5.0 4.11.0 @@ -605,6 +606,13 @@ ${mqtt.version} + + + org.pf4j + pf4j-spring + ${pf4j-spring.version} + + diff --git a/yudao-module-iot/pom.xml b/yudao-module-iot/pom.xml index 069af1699b..d9002abea5 100644 --- a/yudao-module-iot/pom.xml +++ b/yudao-module-iot/pom.xml @@ -10,6 +10,7 @@ yudao-module-iot-api yudao-module-iot-biz + yudao-module-iot-plugin 4.0.0 diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index b75effb105..5f58109b36 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -36,4 +36,15 @@ public interface ErrorCodeConstants { // ========== 设备分组 1-050-005-000 ========== ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在"); ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除"); -} + + // ========== 插件信息 1-050-006-000 ========== + ErrorCode PLUGIN_INFO_NOT_EXISTS = new ErrorCode(1_050_006_000, "插件信息不存在"); + ErrorCode PLUGIN_INSTALL_FAILED = new ErrorCode(1_050_006_001, "插件安装失败"); + ErrorCode PLUGIN_INSTALL_FAILED_FILE_NAME_NOT_MATCH = new ErrorCode(1_050_006_002, "插件安装失败,文件名与原插件id不匹配"); + ErrorCode PLUGIN_INFO_DELETE_FAILED_RUNNING = new ErrorCode(1_050_006_003, "请先停止插件"); + ErrorCode PLUGIN_STATUS_INVALID = new ErrorCode(1_050_006_004, "插件状态无效"); + + // ========== 插件实例 1-050-007-000 ========== + ErrorCode PLUGIN_INSTANCE_NOT_EXISTS = new ErrorCode(1_050_007_000, "插件实例不存在"); + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java new file mode 100644 index 0000000000..fd15514c46 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.iot.enums.plugin; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IoT 部署方式枚举 + * + * @author haohao + */ +@Getter +public enum IotPluginDeployTypeEnum implements IntArrayValuable { + + UPLOAD(0, "上传jar"), + ALONE(1, "独立运行"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); + + /** + * 部署方式 + */ + private final Integer deployType; + + /** + * 部署方式名 + */ + private final String name; + + IotPluginDeployTypeEnum(Integer deployType, String name) { + this.deployType = deployType; + this.name = name; + } + + public static IotPluginDeployTypeEnum fromDeployType(Integer deployType) { + for (IotPluginDeployTypeEnum value : values()) { + if (value.getDeployType().equals(deployType)) { + return value; + } + } + return null; + } + + public static boolean isValidDeployType(Integer deployType) { + return fromDeployType(deployType) != null; + } + + @Override + public int[] array() { + return ARRAYS; + } +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java new file mode 100644 index 0000000000..25b1720225 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.iot.enums.plugin; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IoT 插件状态枚举 + * + * @author haohao + */ +@Getter +public enum IotPluginStatusEnum implements IntArrayValuable { + + STOPPED(0, "停止"), + RUNNING(1, "运行"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginStatusEnum::getStatus).toArray(); + + /** + * 状态 + */ + private final Integer status; + + /** + * 状态名 + */ + private final String name; + + IotPluginStatusEnum(Integer status, String name) { + this.status = status; + this.name = name; + } + + public static IotPluginStatusEnum fromState(Integer state) { + for (IotPluginStatusEnum value : values()) { + if (value.getStatus().equals(state)) { + return value; + } + } + return null; + } + + public static boolean isValidState(Integer state) { + return fromState(state) != null; + } + + public static boolean contains(Integer status) { + return Arrays.stream(values()).anyMatch(e -> e.getStatus().equals(status)); + } + + @Override + public int[] array() { + return new int[0]; + } +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java new file mode 100644 index 0000000000..9e1e5d7f05 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.iot.enums.plugin; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; + +import java.util.Arrays; + +/** + * IoT 插件类型枚举 + * + * @author haohao + */ +@Getter +public enum IotPluginTypeEnum implements IntArrayValuable { + + NORMAL(0, "普通插件"), + DEVICE(1, "设备插件"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + + /** + * 类型名 + */ + private final String name; + + IotPluginTypeEnum(Integer type, String name) { + this.type = type; + this.name = name; + } + + public static IotPluginTypeEnum fromType(Integer type) { + for (IotPluginTypeEnum value : values()) { + if (value.getType().equals(type)) { + return value; + } + } + return null; + } + + public static boolean isValidType(Integer type) { + return fromType(type) != null; + } + + @Override + public int[] array() { + return ARRAYS; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index 013287d2b9..b003e1785a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -69,6 +69,12 @@ org.eclipse.paho org.eclipse.paho.client.mqttv3 + + + + org.pf4j + pf4j-spring + diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java new file mode 100644 index 0000000000..d87a14590b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java @@ -0,0 +1,98 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininfo; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import cn.iocoder.yudao.module.iot.service.plugininfo.PluginInfoService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY; + +@Tag(name = "管理后台 - IoT 插件信息") +@RestController +@RequestMapping("/iot/plugin-info") +@Validated +public class PluginInfoController { + + @Resource + private PluginInfoService pluginInfoService; + + @PostMapping("/create") + @Operation(summary = "创建插件信息") + @PreAuthorize("@ss.hasPermission('iot:plugin-info:create')") + public CommonResult createPluginInfo(@Valid @RequestBody PluginInfoSaveReqVO createReqVO) { + return success(pluginInfoService.createPluginInfo(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新插件信息") + @PreAuthorize("@ss.hasPermission('iot:plugin-info:update')") + public CommonResult updatePluginInfo(@Valid @RequestBody PluginInfoSaveReqVO updateReqVO) { + pluginInfoService.updatePluginInfo(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除插件信息") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:plugin-info:delete')") + public CommonResult deletePluginInfo(@RequestParam("id") Long id) { + pluginInfoService.deletePluginInfo(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得插件信息") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('iot:plugin-info:query')") + public CommonResult getPluginInfo(@RequestParam("id") Long id) { + PluginInfoDO pluginInfo = pluginInfoService.getPluginInfo(id); + return success(BeanUtils.toBean(pluginInfo, PluginInfoRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得插件信息分页") + @PreAuthorize("@ss.hasPermission('iot:plugin-info:query')") + public CommonResult> getPluginInfoPage(@Valid PluginInfoPageReqVO pageReqVO) { + PageResult pageResult = pluginInfoService.getPluginInfoPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class)); + } + + @RequestMapping(value = "/update-jar", + method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题 + @Operation(summary = "上传Jar包") + public CommonResult uploadJar( + @RequestParam("id") Long id, + @RequestParam("jar") MultipartFile file) throws Exception { + if (file.isEmpty()) { + throw exception(FILE_IS_EMPTY); + } + pluginInfoService.uploadJar(id, file); + return success(true); + } + + // 修改插件状态 + @PutMapping("/update-status") + @Operation(summary = "修改插件状态") + @PreAuthorize("@ss.hasPermission('iot:plugin-info:update')") + public CommonResult updateUserStatus(@Valid @RequestBody PluginInfoSaveReqVO reqVO) { + pluginInfoService.updatePluginStatus(reqVO.getId(), reqVO.getStatus()); + return success(true); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java new file mode 100644 index 0000000000..1ddb8e1ffc --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - IoT 插件信息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PluginInfoPageReqVO extends PageParam { + + @Schema(description = "插件包id", example = "24627") + private String pluginId; + + @Schema(description = "插件名称", example = "赵六") + private String name; + + @Schema(description = "描述", example = "你猜") + private String description; + + @Schema(description = "部署方式", example = "2") + private Integer deployType; + + @Schema(description = "插件包文件名") + private String file; + + @Schema(description = "插件版本") + private String version; + + @Schema(description = "插件类型", example = "2") + private Integer type; + + @Schema(description = "设备插件协议类型") + private String protocol; + + @Schema(description = "状态") + private Integer status; + + @Schema(description = "插件配置项描述信息") + private String configSchema; + + @Schema(description = "插件配置信息") + private String config; + + @Schema(description = "插件脚本") + private String script; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java new file mode 100644 index 0000000000..1e9f2b7dd6 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; + +@Schema(description = "管理后台 - IoT 插件信息 Response VO") +@Data +@ExcelIgnoreUnannotated +public class PluginInfoRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") + @ExcelProperty("主键ID") + private Long id; + + @Schema(description = "插件包id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627") + @ExcelProperty("插件包id") + private String pluginId; + + @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @ExcelProperty("插件名称") + private String name; + + @Schema(description = "描述", example = "你猜") + @ExcelProperty("描述") + private String description; + + @Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("部署方式") + private Integer deployType; + + @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("插件包文件名") + private String file; + + @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("插件版本") + private String version; + + @Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("插件类型") + private Integer type; + + @Schema(description = "设备插件协议类型") + @ExcelProperty("设备插件协议类型") + private String protocol; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("状态") + private Integer status; + + @Schema(description = "插件配置项描述信息") + @ExcelProperty("插件配置项描述信息") + private String configSchema; + + @Schema(description = "插件配置信息") + @ExcelProperty("插件配置信息") + private String config; + + @Schema(description = "插件脚本") + @ExcelProperty("插件脚本") + private String script; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoSaveReqVO.java new file mode 100644 index 0000000000..8ce254a3ae --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoSaveReqVO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Schema(description = "管理后台 - IoT 插件信息新增/修改 Request VO") +@Data +public class PluginInfoSaveReqVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") + private Long id; + + @Schema(description = "插件包id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627") + private String pluginId; + + @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + private String name; + + @Schema(description = "描述", example = "你猜") + private String description; + + @Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer deployType; + + @Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED) + private String file; + + @Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED) + private String version; + + @Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; + + @Schema(description = "设备插件协议类型") + private String protocol; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer status; + + @Schema(description = "插件配置项描述信息") + private String configSchema; + + @Schema(description = "插件配置信息") + private String config; + + @Schema(description = "插件脚本") + private String script; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/PluginInstanceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/PluginInstanceController.java new file mode 100644 index 0000000000..9382f8c6d7 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/PluginInstanceController.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininstance; + +import org.springframework.web.bind.annotation.*; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import jakarta.validation.*; +import jakarta.servlet.http.*; +import java.util.*; +import java.io.IOException; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; + +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; + +import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; +import cn.iocoder.yudao.module.iot.service.plugininstance.PluginInstanceService; + +@Tag(name = "管理后台 - IoT 插件实例") +@RestController +@RequestMapping("/iot/plugin-instance") +@Validated +public class PluginInstanceController { + + @Resource + private PluginInstanceService pluginInstanceService; + + @PostMapping("/create") + @Operation(summary = "创建IoT 插件实例") + @PreAuthorize("@ss.hasPermission('iot:plugin-instance:create')") + public CommonResult createPluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO createReqVO) { + return success(pluginInstanceService.createPluginInstance(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新IoT 插件实例") + @PreAuthorize("@ss.hasPermission('iot:plugin-instance:update')") + public CommonResult updatePluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO updateReqVO) { + pluginInstanceService.updatePluginInstance(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除IoT 插件实例") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('iot:plugin-instance:delete')") + public CommonResult deletePluginInstance(@RequestParam("id") Long id) { + pluginInstanceService.deletePluginInstance(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得IoT 插件实例") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')") + public CommonResult getPluginInstance(@RequestParam("id") Long id) { + PluginInstanceDO pluginInstance = pluginInstanceService.getPluginInstance(id); + return success(BeanUtils.toBean(pluginInstance, PluginInstanceRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得IoT 插件实例分页") + @PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')") + public CommonResult> getPluginInstancePage(@Valid PluginInstancePageReqVO pageReqVO) { + PageResult pageResult = pluginInstanceService.getPluginInstancePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, PluginInstanceRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出IoT 插件实例 Excel") + @PreAuthorize("@ss.hasPermission('iot:plugin-instance:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportPluginInstanceExcel(@Valid PluginInstancePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = pluginInstanceService.getPluginInstancePage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "IoT 插件实例.xls", "数据", PluginInstanceRespVO.class, + BeanUtils.toBean(list, PluginInstanceRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java new file mode 100644 index 0000000000..2e678fa9dd --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - IoT 插件实例分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PluginInstancePageReqVO extends PageParam { + + @Schema(description = "插件主程序id", example = "23738") + private String mainId; + + @Schema(description = "插件id", example = "26498") + private Long pluginId; + + @Schema(description = "插件主程序所在ip") + private String ip; + + @Schema(description = "插件主程序端口") + private Integer port; + + @Schema(description = "心跳时间,心路时间超过30秒需要剔除") + private Long heartbeatAt; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java new file mode 100644 index 0000000000..e71ff29045 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; + +@Schema(description = "管理后台 - IoT 插件实例 Response VO") +@Data +@ExcelIgnoreUnannotated +public class PluginInstanceRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23864") + @ExcelProperty("主键ID") + private Long id; + + @Schema(description = "插件主程序id", requiredMode = Schema.RequiredMode.REQUIRED, example = "23738") + @ExcelProperty("插件主程序id") + private String mainId; + + @Schema(description = "插件id", requiredMode = Schema.RequiredMode.REQUIRED, example = "26498") + @ExcelProperty("插件id") + private Long pluginId; + + @Schema(description = "插件主程序所在ip", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("插件主程序所在ip") + private String ip; + + @Schema(description = "插件主程序端口", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("插件主程序端口") + private Integer port; + + @Schema(description = "心跳时间,心路时间超过30秒需要剔除", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("心跳时间,心路时间超过30秒需要剔除") + private Long heartbeatAt; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java new file mode 100644 index 0000000000..8d927045d5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - IoT 插件实例新增/修改 Request VO") +@Data +public class PluginInstanceSaveReqVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23864") + private Long id; + + @Schema(description = "插件主程序id", requiredMode = Schema.RequiredMode.REQUIRED, example = "23738") + @NotEmpty(message = "插件主程序id不能为空") + private String mainId; + + @Schema(description = "插件id", requiredMode = Schema.RequiredMode.REQUIRED, example = "26498") + @NotNull(message = "插件id不能为空") + private Long pluginId; + + @Schema(description = "插件主程序所在ip", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "插件主程序所在ip不能为空") + private String ip; + + @Schema(description = "插件主程序端口", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "插件主程序端口不能为空") + private Integer port; + + @Schema(description = "心跳时间,心路时间超过30秒需要剔除", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "心跳时间,心路时间超过30秒需要剔除不能为空") + private Long heartbeatAt; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java new file mode 100644 index 0000000000..07b2cc7ff9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * IoT 插件信息 DO + * + * @author 芋道源码 + */ +@TableName("iot_plugin_info") +@KeySequence("iot_plugin_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PluginInfoDO extends BaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + /** + * 插件包id + */ + private String pluginId; + /** + * 插件名称 + */ + private String name; + /** + * 描述 + */ + private String description; + /** + * 部署方式 + */ + private Integer deployType; + /** + * 插件包文件名 + */ + private String file; + /** + * 插件版本 + */ + private String version; + /** + * 插件类型 + */ + private Integer type; + /** + * 设备插件协议类型 + */ + private String protocol; + /** + * 状态 + */ + private Integer status; + /** + * 插件配置项描述信息 + */ + private String configSchema; + /** + * 插件配置信息 + */ + private String config; + /** + * 插件脚本 + */ + private String script; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java new file mode 100644 index 0000000000..79dd762c70 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * IoT 插件实例 DO + * + * @author 芋道源码 + */ +@TableName("iot_plugin_instance") +@KeySequence("iot_plugin_instance_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PluginInstanceDO extends BaseDO { + + /** + * 主键ID + */ + @TableId + private Long id; + /** + * 插件主程序id + */ + private String mainId; + /** + * 插件id + */ + private Long pluginId; + /** + * 插件主程序所在ip + */ + private String ip; + /** + * 插件主程序端口 + */ + private Integer port; + /** + * 心跳时间,心路时间超过30秒需要剔除 + */ + private Long heartbeatAt; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininfo/PluginInfoMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininfo/PluginInfoMapper.java new file mode 100644 index 0000000000..69c0bd3929 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininfo/PluginInfoMapper.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.plugininfo; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import org.apache.ibatis.annotations.Mapper; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.*; + +/** + * IoT 插件信息 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface PluginInfoMapper extends BaseMapperX { + + default PageResult selectPage(PluginInfoPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PluginInfoDO::getPluginId, reqVO.getPluginId()) + .likeIfPresent(PluginInfoDO::getName, reqVO.getName()) + .eqIfPresent(PluginInfoDO::getDescription, reqVO.getDescription()) + .eqIfPresent(PluginInfoDO::getDeployType, reqVO.getDeployType()) + .eqIfPresent(PluginInfoDO::getFile, reqVO.getFile()) + .eqIfPresent(PluginInfoDO::getVersion, reqVO.getVersion()) + .eqIfPresent(PluginInfoDO::getType, reqVO.getType()) + .eqIfPresent(PluginInfoDO::getProtocol, reqVO.getProtocol()) + .eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus()) + .eqIfPresent(PluginInfoDO::getConfigSchema, reqVO.getConfigSchema()) + .eqIfPresent(PluginInfoDO::getConfig, reqVO.getConfig()) + .eqIfPresent(PluginInfoDO::getScript, reqVO.getScript()) + .betweenIfPresent(PluginInfoDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PluginInfoDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininstance/PluginInstanceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininstance/PluginInstanceMapper.java new file mode 100644 index 0000000000..6c7f1d231c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugininstance/PluginInstanceMapper.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.plugininstance; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; +import org.apache.ibatis.annotations.Mapper; +import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*; + +/** + * IoT 插件实例 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface PluginInstanceMapper extends BaseMapperX { + + default PageResult selectPage(PluginInstancePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId()) + .eqIfPresent(PluginInstanceDO::getPluginId, reqVO.getPluginId()) + .eqIfPresent(PluginInstanceDO::getIp, reqVO.getIp()) + .eqIfPresent(PluginInstanceDO::getPort, reqVO.getPort()) + .eqIfPresent(PluginInstanceDO::getHeartbeatAt, reqVO.getHeartbeatAt()) + .betweenIfPresent(PluginInstanceDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PluginInstanceDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java new file mode 100644 index 0000000000..f43b69c12c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.iot.framework.plugin; + +import org.pf4j.spring.SpringPluginManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringConfiguration { + + @Bean + public SpringPluginManager pluginManager() { + return new SpringPluginManager(); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java new file mode 100644 index 0000000000..f9ae486772 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.iot.service.plugininfo; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import jakarta.validation.Valid; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; + +/** + * IoT 插件信息 Service 接口 + * + * @author 芋道源码 + */ +public interface PluginInfoService { + + /** + * 创建IoT 插件信息 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPluginInfo(@Valid PluginInfoSaveReqVO createReqVO); + + /** + * 更新IoT 插件信息 + * + * @param updateReqVO 更新信息 + */ + void updatePluginInfo(@Valid PluginInfoSaveReqVO updateReqVO); + + /** + * 删除IoT 插件信息 + * + * @param id 编号 + */ + void deletePluginInfo(Long id); + + /** + * 获得IoT 插件信息 + * + * @param id 编号 + * @return IoT 插件信息 + */ + PluginInfoDO getPluginInfo(Long id); + + /** + * 获得IoT 插件信息分页 + * + * @param pageReqVO 分页查询 + * @return IoT 插件信息分页 + */ + PageResult getPluginInfoPage(PluginInfoPageReqVO pageReqVO); + + /** + * 上传插件的 JAR 包 + * + * @param id 插件id + * @param file 文件 + */ + void uploadJar(Long id, MultipartFile file); + + /** + * 更新插件的状态 + * + * @param id 插件id + * @param status 状态 + */ + void updatePluginStatus(Long id, Integer status); +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java new file mode 100644 index 0000000000..65ad6ad1db --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java @@ -0,0 +1,267 @@ +package cn.iocoder.yudao.module.iot.service.plugininfo; + +import cn.hutool.core.io.IoUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.infra.api.file.FileApi; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginDescriptor; +import org.pf4j.PluginState; +import org.pf4j.PluginWrapper; +import org.pf4j.spring.SpringPluginManager; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.file.Path; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; + +/** + * IoT 插件信息 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class PluginInfoServiceImpl implements PluginInfoService { + + @Resource + private PluginInfoMapper pluginInfoMapper; + + @Resource + private SpringPluginManager pluginManager; + + @Resource + private FileApi fileApi; + + @Override + public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { + // 插入 + PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class); + pluginInfoMapper.insert(pluginInfo); + // 返回 + return pluginInfo.getId(); + } + + @Override + public void updatePluginInfo(PluginInfoSaveReqVO updateReqVO) { + // 校验存在 + validatePluginInfoExists(updateReqVO.getId()); + // 更新 + PluginInfoDO updateObj = BeanUtils.toBean(updateReqVO, PluginInfoDO.class); + pluginInfoMapper.updateById(updateObj); + } + + @Override + public void deletePluginInfo(Long id) { + // 校验存在 + PluginInfoDO pluginInfoDO = validatePluginInfoExists(id); + + // 停止插件 + if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { + throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING); + } + + // 卸载插件 + PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginId()); + if (plugin != null) { + // 查询插件是否是启动状态 + if (plugin.getPluginState().equals(PluginState.STARTED)) { + // 停止插件 + pluginManager.stopPlugin(plugin.getPluginId()); + } + // 卸载插件 + pluginManager.unloadPlugin(plugin.getPluginId()); + } + + // 删除 + pluginInfoMapper.deleteById(id); + } + + private PluginInfoDO validatePluginInfoExists(Long id) { + PluginInfoDO pluginInfo = pluginInfoMapper.selectById(id); + if (pluginInfo == null) { + throw exception(PLUGIN_INFO_NOT_EXISTS); + } + return pluginInfo; + } + + @Override + public PluginInfoDO getPluginInfo(Long id) { + return pluginInfoMapper.selectById(id); + } + + @Override + public PageResult getPluginInfoPage(PluginInfoPageReqVO pageReqVO) { + return pluginInfoMapper.selectPage(pageReqVO); + } + + @Override + public void uploadJar(Long id, MultipartFile file) { + // 1. 校验存在 + PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); + + // 2. 判断文件名称与插件 ID 是否匹配 + String pluginId = pluginInfoDo.getPluginId(); + + // 3. 停止卸载旧的插件 + // 3.1. 获取插件信息 + PluginWrapper plugin = pluginManager.getPlugin(pluginId); + if (plugin != null) { + // 3.2. 如果插件状态是启动的,停止插件 + if (plugin.getPluginState().equals(PluginState.STARTED)) { + pluginManager.stopPlugin(pluginId); + } + // 3.3. 卸载插件 + pluginManager.unloadPlugin(pluginId); + } + + // 4. 上传插件 + String pluginIdNew; + try { + String path = fileApi.createFile(IoUtil.readBytes(file.getInputStream())); + Path pluginPath = Path.of(path); + pluginIdNew = pluginManager.loadPlugin(pluginPath); + } catch (Exception e) { + throw exception(PLUGIN_INSTALL_FAILED); + } + + PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew); + if (pluginWrapper == null) { + throw exception(PLUGIN_INSTALL_FAILED); + } + + + // 5. 读取配置文件和脚本 + String configJson = ""; + String script = ""; + + pluginInfoDo.setPluginId(pluginIdNew); + pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus()); + pluginInfoDo.setFile(file.getOriginalFilename()); + pluginInfoDo.setConfigSchema(configJson); + pluginInfoDo.setScript(script); + + PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); + pluginInfoDo.setVersion(pluginDescriptor.getVersion()); + pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription()); + pluginInfoMapper.updateById(pluginInfoDo); + + + // 5. 读取配置文件和脚本 +// String configJson = ""; + // String script = ""; +// try (JarFile jarFile = new JarFile(pluginInfoUpdate.getPluginPath())) { +// // 5.1 获取config文件在jar包中的路径 +// String configFile = "classes/config.json"; +// JarEntry configEntry = jarFile.getJarEntry(configFile); +// +// if (configEntry != null) { +// // 5.2 读取配置文件 +// configJson = IoUtil.read(jarFile.getInputStream(configEntry), Charset.defaultCharset()); +// log.info("configJson:{}", configJson); +// } +// +// // 5.3 读取script.js脚本 +// String scriptFile = "classes/script.js"; +// JarEntry scriptEntity = jarFile.getJarEntry(scriptFile); +// if (scriptEntity != null) { +// // 5.4 读取脚本文件 +// script = IoUtil.read(jarFile.getInputStream(scriptEntity), Charset.defaultCharset()); +// log.info("script:{}", script); +// } +// } catch (Exception e) { +// throw exception(PLUGIN_INSTALL_FAILED); +// } + + +// PluginState pluginState = pluginInfoUpdate.getPluginState(); +// if (pluginState == PluginState.STARTED) { +// pluginInfoDo.setStatus(IotPluginStatusEnum.RUNNING.getStatus()); +// } +// pluginInfoDo.setPluginId(pluginInfoUpdate.getPluginId()); +// pluginInfoDo.setFile(file.getOriginalFilename()); +// pluginInfoDo.setConfigSchema(configJson); +// pluginInfoDo.setScript(script); +// +// PluginDescriptor pluginDescriptor = pluginInfoUpdate.getPluginDescriptor(); +// pluginInfoDo.setVersion(pluginDescriptor.getPluginVersion()); +// pluginInfoDo.setDescription(pluginDescriptor.getDescription()); +// pluginInfoMapper.updateById(pluginInfoDo); + } + + @Override + public void updatePluginStatus(Long id, Integer status) { + // 1. 校验存在 + PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); + + // 插件状态无效 + if (!IotPluginStatusEnum.contains(status)) { + throw exception(PLUGIN_STATUS_INVALID); + } + + // 插件包为空 +// String pluginId = pluginInfoDo.getPluginId(); +// if (StrUtil.isBlank(pluginId)) { +// throw exception(PLUGIN_INFO_NOT_EXISTS); +// } +// com.gitee.starblues.core.PluginInfo pluginInfo = pluginOperator.getPluginInfo(pluginId); +// if (pluginInfo != null) { +// if (pluginInfoDo.getStatus().equals(IotPluginStatusEnum.RUNNING.getStatus()) && pluginInfo.getPluginState() != PluginState.STARTED) { +// // 启动插件 +// pluginOperator.start(pluginId); +// } else if (pluginInfoDo.getStatus().equals(IotPluginStatusEnum.STOPPED.getStatus()) && pluginInfo.getPluginState() == PluginState.STARTED) { +// // 停止插件 +// pluginOperator.stop(pluginId); +// } +// } else { +// // 已经停止,未获取到插件 +// if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { +// throw exception(PLUGIN_STATUS_INVALID); +// } +// } + pluginInfoDo.setStatus(status); + pluginInfoMapper.updateById(pluginInfoDo); + } + + @PostConstruct + public void init() { + Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS); + } + + @SneakyThrows + private void startPlugins() { +// while (!pluginOperator.inited()) { +// Thread.sleep(1000L); +// } + + for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) { + if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { + continue; + } + log.info("start plugin:{}", pluginInfoDO.getPluginId()); + try { +// com.gitee.starblues.core.PluginInfo plugin = pluginOperator.getPluginInfo(pluginInfoDO.getPluginId()); +// if (plugin != null) { +// pluginOperator.start(plugin.getPluginId()); +// } + } catch (Exception e) { + log.error("start plugin error", e); + } + } + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceService.java new file mode 100644 index 0000000000..0789c46381 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceService.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.iot.service.plugininstance; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; +import jakarta.validation.Valid; + +/** + * IoT 插件实例 Service 接口 + * + * @author 芋道源码 + */ +public interface PluginInstanceService { + + /** + * 创建IoT 插件实例 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPluginInstance(@Valid PluginInstanceSaveReqVO createReqVO); + + /** + * 更新IoT 插件实例 + * + * @param updateReqVO 更新信息 + */ + void updatePluginInstance(@Valid PluginInstanceSaveReqVO updateReqVO); + + /** + * 删除IoT 插件实例 + * + * @param id 编号 + */ + void deletePluginInstance(Long id); + + /** + * 获得IoT 插件实例 + * + * @param id 编号 + * @return IoT 插件实例 + */ + PluginInstanceDO getPluginInstance(Long id); + + /** + * 获得IoT 插件实例分页 + * + * @param pageReqVO 分页查询 + * @return IoT 插件实例分页 + */ + PageResult getPluginInstancePage(PluginInstancePageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java new file mode 100644 index 0000000000..405efe1636 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.iot.service.plugininstance; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; +import cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS; + +/** + * IoT 插件实例 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class PluginInstanceServiceImpl implements PluginInstanceService { + + @Resource + private PluginInstanceMapper pluginInstanceMapper; + + @Override + public Long createPluginInstance(PluginInstanceSaveReqVO createReqVO) { + // 插入 + PluginInstanceDO pluginInstance = BeanUtils.toBean(createReqVO, PluginInstanceDO.class); + pluginInstanceMapper.insert(pluginInstance); + // 返回 + return pluginInstance.getId(); + } + + @Override + public void updatePluginInstance(PluginInstanceSaveReqVO updateReqVO) { + // 校验存在 + validatePluginInstanceExists(updateReqVO.getId()); + // 更新 + PluginInstanceDO updateObj = BeanUtils.toBean(updateReqVO, PluginInstanceDO.class); + pluginInstanceMapper.updateById(updateObj); + } + + @Override + public void deletePluginInstance(Long id) { + // 校验存在 + validatePluginInstanceExists(id); + // 删除 + pluginInstanceMapper.deleteById(id); + } + + private void validatePluginInstanceExists(Long id) { + if (pluginInstanceMapper.selectById(id) == null) { + throw exception(PLUGIN_INSTANCE_NOT_EXISTS); + } + } + + @Override + public PluginInstanceDO getPluginInstance(Long id) { + return pluginInstanceMapper.selectById(id); + } + + @Override + public PageResult getPluginInstancePage(PluginInstancePageReqVO pageReqVO) { + return pluginInstanceMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininfo/PluginInfoMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininfo/PluginInfoMapper.xml new file mode 100644 index 0000000000..f24f7e14ce --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininfo/PluginInfoMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml new file mode 100644 index 0000000000..2d297c785b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java new file mode 100644 index 0000000000..d921161f8e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java @@ -0,0 +1,171 @@ +package cn.iocoder.yudao.module.iot.service.plugininfo; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import jakarta.annotation.Resource; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; + +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; +import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import org.springframework.context.annotation.Import; + +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link PluginInfoServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import(PluginInfoServiceImpl.class) +public class PluginInfoServiceImplTest extends BaseDbUnitTest { + + @Resource + private PluginInfoServiceImpl pluginInfoService; + + @Resource + private PluginInfoMapper pluginInfoMapper; + + @Test + public void testCreatePluginInfo_success() { + // 准备参数 + PluginInfoSaveReqVO createReqVO = randomPojo(PluginInfoSaveReqVO.class).setId(null); + + // 调用 + Long pluginInfoId = pluginInfoService.createPluginInfo(createReqVO); + // 断言 + assertNotNull(pluginInfoId); + // 校验记录的属性是否正确 + PluginInfoDO pluginInfo = pluginInfoMapper.selectById(pluginInfoId); + assertPojoEquals(createReqVO, pluginInfo, "id"); + } + + @Test + public void testUpdatePluginInfo_success() { + // mock 数据 + PluginInfoDO dbPluginInfo = randomPojo(PluginInfoDO.class); + pluginInfoMapper.insert(dbPluginInfo);// @Sql: 先插入出一条存在的数据 + // 准备参数 + PluginInfoSaveReqVO updateReqVO = randomPojo(PluginInfoSaveReqVO.class, o -> { + o.setId(dbPluginInfo.getId()); // 设置更新的 ID + }); + + // 调用 + pluginInfoService.updatePluginInfo(updateReqVO); + // 校验是否更新正确 + PluginInfoDO pluginInfo = pluginInfoMapper.selectById(updateReqVO.getId()); // 获取最新的 + assertPojoEquals(updateReqVO, pluginInfo); + } + + @Test + public void testUpdatePluginInfo_notExists() { + // 准备参数 + PluginInfoSaveReqVO updateReqVO = randomPojo(PluginInfoSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> pluginInfoService.updatePluginInfo(updateReqVO), PLUGIN_INFO_NOT_EXISTS); + } + + @Test + public void testDeletePluginInfo_success() { + // mock 数据 + PluginInfoDO dbPluginInfo = randomPojo(PluginInfoDO.class); + pluginInfoMapper.insert(dbPluginInfo);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbPluginInfo.getId(); + + // 调用 + pluginInfoService.deletePluginInfo(id); + // 校验数据不存在了 + assertNull(pluginInfoMapper.selectById(id)); + } + + @Test + public void testDeletePluginInfo_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> pluginInfoService.deletePluginInfo(id), PLUGIN_INFO_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetPluginInfoPage() { + // mock 数据 + PluginInfoDO dbPluginInfo = randomPojo(PluginInfoDO.class, o -> { // 等会查询到 + o.setPluginId(null); + o.setName(null); + o.setDescription(null); + o.setDeployType(null); + o.setFile(null); + o.setVersion(null); + o.setType(null); + o.setProtocol(null); + o.setStatus(null); + o.setConfigSchema(null); + o.setConfig(null); + o.setScript(null); + o.setCreateTime(null); + }); + pluginInfoMapper.insert(dbPluginInfo); + // 测试 pluginId 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setPluginId(null))); + // 测试 name 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setName(null))); + // 测试 description 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setDescription(null))); + // 测试 deployType 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setDeployType(null))); + // 测试 file 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setFile(null))); + // 测试 version 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setVersion(null))); + // 测试 type 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setType(null))); + // 测试 protocol 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setProtocol(null))); + // 测试 state 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setStatus(null))); + // 测试 configSchema 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setConfigSchema(null))); + // 测试 config 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setConfig(null))); + // 测试 script 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setScript(null))); + // 测试 createTime 不匹配 + pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setCreateTime(null))); + // 准备参数 + PluginInfoPageReqVO reqVO = new PluginInfoPageReqVO(); + reqVO.setPluginId(null); + reqVO.setName(null); + reqVO.setDescription(null); + reqVO.setDeployType(null); + reqVO.setFile(null); + reqVO.setVersion(null); + reqVO.setType(null); + reqVO.setProtocol(null); + reqVO.setStatus(null); + reqVO.setConfigSchema(null); + reqVO.setConfig(null); + reqVO.setScript(null); + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + + // 调用 + PageResult pageResult = pluginInfoService.getPluginInfoPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbPluginInfo, pageResult.getList().get(0)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java new file mode 100644 index 0000000000..fe6235bded --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java @@ -0,0 +1,150 @@ +package cn.iocoder.yudao.module.iot.service.plugininstance; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; + +import jakarta.annotation.Resource; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; + +import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; +import cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Import; +import java.util.*; +import java.time.LocalDateTime; + +import static cn.hutool.core.util.RandomUtil.*; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * {@link PluginInstanceServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import(PluginInstanceServiceImpl.class) +public class PluginInstanceServiceImplTest extends BaseDbUnitTest { + + @Resource + private PluginInstanceServiceImpl pluginInstanceService; + + @Resource + private PluginInstanceMapper pluginInstanceMapper; + + @Test + public void testCreatePluginInstance_success() { + // 准备参数 + PluginInstanceSaveReqVO createReqVO = randomPojo(PluginInstanceSaveReqVO.class).setId(null); + + // 调用 + Long pluginInstanceId = pluginInstanceService.createPluginInstance(createReqVO); + // 断言 + assertNotNull(pluginInstanceId); + // 校验记录的属性是否正确 + PluginInstanceDO pluginInstance = pluginInstanceMapper.selectById(pluginInstanceId); + assertPojoEquals(createReqVO, pluginInstance, "id"); + } + + @Test + public void testUpdatePluginInstance_success() { + // mock 数据 + PluginInstanceDO dbPluginInstance = randomPojo(PluginInstanceDO.class); + pluginInstanceMapper.insert(dbPluginInstance);// @Sql: 先插入出一条存在的数据 + // 准备参数 + PluginInstanceSaveReqVO updateReqVO = randomPojo(PluginInstanceSaveReqVO.class, o -> { + o.setId(dbPluginInstance.getId()); // 设置更新的 ID + }); + + // 调用 + pluginInstanceService.updatePluginInstance(updateReqVO); + // 校验是否更新正确 + PluginInstanceDO pluginInstance = pluginInstanceMapper.selectById(updateReqVO.getId()); // 获取最新的 + assertPojoEquals(updateReqVO, pluginInstance); + } + + @Test + public void testUpdatePluginInstance_notExists() { + // 准备参数 + PluginInstanceSaveReqVO updateReqVO = randomPojo(PluginInstanceSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> pluginInstanceService.updatePluginInstance(updateReqVO), PLUGIN_INSTANCE_NOT_EXISTS); + } + + @Test + public void testDeletePluginInstance_success() { + // mock 数据 + PluginInstanceDO dbPluginInstance = randomPojo(PluginInstanceDO.class); + pluginInstanceMapper.insert(dbPluginInstance);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbPluginInstance.getId(); + + // 调用 + pluginInstanceService.deletePluginInstance(id); + // 校验数据不存在了 + assertNull(pluginInstanceMapper.selectById(id)); + } + + @Test + public void testDeletePluginInstance_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> pluginInstanceService.deletePluginInstance(id), PLUGIN_INSTANCE_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetPluginInstancePage() { + // mock 数据 + PluginInstanceDO dbPluginInstance = randomPojo(PluginInstanceDO.class, o -> { // 等会查询到 + o.setMainId(null); + o.setPluginId(null); + o.setIp(null); + o.setPort(null); + o.setHeartbeatAt(null); + o.setCreateTime(null); + }); + pluginInstanceMapper.insert(dbPluginInstance); + // 测试 mainId 不匹配 + pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setMainId(null))); + // 测试 pluginId 不匹配 + pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setPluginId(null))); + // 测试 ip 不匹配 + pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setIp(null))); + // 测试 port 不匹配 + pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setPort(null))); + // 测试 heartbeatAt 不匹配 + pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setHeartbeatAt(null))); + // 测试 createTime 不匹配 + pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setCreateTime(null))); + // 准备参数 + PluginInstancePageReqVO reqVO = new PluginInstancePageReqVO(); + reqVO.setMainId(null); + reqVO.setPluginId(null); + reqVO.setIp(null); + reqVO.setPort(null); + reqVO.setHeartbeatAt(null); + reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + + // 调用 + PageResult pageResult = pluginInstanceService.getPluginInstancePage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbPluginInstance, pageResult.getList().get(0)); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugin/pom.xml b/yudao-module-iot/yudao-module-iot-plugin/pom.xml new file mode 100644 index 0000000000..49c3215810 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/pom.xml @@ -0,0 +1,31 @@ + + + + yudao-module-iot + cn.iocoder.boot + ${revision} + + 4.0.0 + yudao-module-iot-plugin + jar + + ${project.artifactId} + + 物联网 模块 - 插件 + + + + + cn.iocoder.boot + yudao-common + + + + org.pf4j + pf4j-spring + + + + diff --git a/yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/package-info.java b/yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/package-info.java new file mode 100644 index 0000000000..567dcb038b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/package-info.java @@ -0,0 +1,6 @@ +/** + * 占位 + * + * TODO 芋艿:后续删除 + */ +package cn.iocoder.yudao.module.iot.plugin; From dea8883f8243b5bd2d67c5213c9b9ac76a24d9dc Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 15 Dec 2024 08:38:12 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91IoT=EF=BC=9A=E6=8F=92=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=9B=B8=E5=85=B3=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/plugin/IotPluginDeployTypeEnum.java | 2 +- .../iot/enums/plugin/IotPluginStatusEnum.java | 9 +- .../iot/enums/plugin/IotPluginTypeEnum.java | 13 +- .../plugininfo/PluginInfoController.java | 2 - .../plugininfo/vo/PluginInfoPageReqVO.java | 8 +- .../admin/plugininfo/vo/PluginInfoRespVO.java | 13 +- .../vo/PluginInstancePageReqVO.java | 1 + .../dataobject/plugininfo/PluginInfoDO.java | 9 +- .../plugininstance/PluginInstanceDO.java | 14 +- .../service/device/IotDeviceDataService.java | 1 - .../plugininfo/PluginInfoServiceImplTest.java | 171 ------------------ .../PluginInstanceServiceImplTest.java | 150 --------------- 12 files changed, 39 insertions(+), 354 deletions(-) delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java index fd15514c46..11898f6831 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java @@ -13,7 +13,7 @@ import java.util.Arrays; @Getter public enum IotPluginDeployTypeEnum implements IntArrayValuable { - UPLOAD(0, "上传jar"), + UPLOAD(0, "上传 jar"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈 ALONE(1, "独立运行"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java index 25b1720225..9aba854b0f 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginStatusEnum.java @@ -42,6 +42,11 @@ public enum IotPluginStatusEnum implements IntArrayValuable { return null; } + @Override + public int[] array() { + return ARRAYS; + } + public static boolean isValidState(Integer state) { return fromState(state) != null; } @@ -50,8 +55,4 @@ public enum IotPluginStatusEnum implements IntArrayValuable { return Arrays.stream(values()).anyMatch(e -> e.getStatus().equals(status)); } - @Override - public int[] array() { - return new int[0]; - } } diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java index 9e1e5d7f05..27368d2688 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginTypeEnum.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.enums.plugin; import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; @@ -10,6 +11,7 @@ import java.util.Arrays; * * @author haohao */ +@AllArgsConstructor @Getter public enum IotPluginTypeEnum implements IntArrayValuable { @@ -28,11 +30,12 @@ public enum IotPluginTypeEnum implements IntArrayValuable { */ private final String name; - IotPluginTypeEnum(Integer type, String name) { - this.type = type; - this.name = name; + @Override + public int[] array() { + return ARRAYS; } + // TODO @haohao:可以使用 hutool 简化 public static IotPluginTypeEnum fromType(Integer type) { for (IotPluginTypeEnum value : values()) { if (value.getType().equals(type)) { @@ -46,8 +49,4 @@ public enum IotPluginTypeEnum implements IntArrayValuable { return fromType(type) != null; } - @Override - public int[] array() { - return ARRAYS; - } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java index d87a14590b..b7efd088ef 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java @@ -20,7 +20,6 @@ import org.springframework.web.multipart.MultipartFile; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY; @Tag(name = "管理后台 - IoT 插件信息") @@ -86,7 +85,6 @@ public class PluginInfoController { return success(true); } - // 修改插件状态 @PutMapping("/update-status") @Operation(summary = "修改插件状态") @PreAuthorize("@ss.hasPermission('iot:plugin-info:update')") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java index 1ddb8e1ffc..a3b36da9df 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoPageReqVO.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum; import lombok.*; import io.swagger.v3.oas.annotations.media.Schema; import cn.iocoder.yudao.framework.common.pojo.PageParam; @@ -8,13 +10,12 @@ import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +// TODO @haohao:只查询必要字段哈 @Schema(description = "管理后台 - IoT 插件信息分页 Request VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) public class PluginInfoPageReqVO extends PageParam { - @Schema(description = "插件包id", example = "24627") + @Schema(description = "插件包 ID ", example = "24627") private String pluginId; @Schema(description = "插件名称", example = "赵六") @@ -33,6 +34,7 @@ public class PluginInfoPageReqVO extends PageParam { private String version; @Schema(description = "插件类型", example = "2") + @InEnum(IotPluginTypeEnum.class) private Integer type; @Schema(description = "设备插件协议类型") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java index 1e9f2b7dd6..6f081764a4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoRespVO.java @@ -1,22 +1,23 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; import java.time.LocalDateTime; -import com.alibaba.excel.annotation.*; @Schema(description = "管理后台 - IoT 插件信息 Response VO") @Data @ExcelIgnoreUnannotated public class PluginInfoRespVO { - @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") - @ExcelProperty("主键ID") + @Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") + @ExcelProperty("主键 ID") private Long id; - @Schema(description = "插件包id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627") - @ExcelProperty("插件包id") + @Schema(description = "插件包 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627") + @ExcelProperty("插件包 ID") private String pluginId; @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java index 2e678fa9dd..9fccdc0aec 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstancePageReqVO.java @@ -8,6 +8,7 @@ import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +// TODO @haohao:建议搞个 plugin,然后里面分 info 和 instance。另外,是不是 info => config 会好点,插件配置? @Schema(description = "管理后台 - IoT 插件实例分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java index 07b2cc7ff9..043cdb9c81 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java @@ -22,12 +22,13 @@ import lombok.*; public class PluginInfoDO extends BaseDO { /** - * 主键ID + * 主键 ID */ @TableId private Long id; + // TODO @haohao:这个是不是改成类似 key 之类的字段哈? /** - * 插件包id + * 插件包 ID */ private String pluginId; /** @@ -41,10 +42,12 @@ public class PluginInfoDO extends BaseDO { /** * 部署方式 */ + // TODO @haohao:枚举 private Integer deployType; /** * 插件包文件名 */ + // TODO @haohao:是不是叫 fileName 哈?避免后续有别的字段,类似 fileUrl? private String file; /** * 插件版本 @@ -53,6 +56,7 @@ public class PluginInfoDO extends BaseDO { /** * 插件类型 */ + // TODO @haohao:枚举 private Integer type; /** * 设备插件协议类型 @@ -61,6 +65,7 @@ public class PluginInfoDO extends BaseDO { /** * 状态 */ + // TODO @haohao:枚举 private Integer status; /** * 插件配置项描述信息 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java index 79dd762c70..17f295a55f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance; -import lombok.*; -import java.util.*; -import java.time.LocalDateTime; -import java.time.LocalDateTime; -import com.baomidou.mybatisplus.annotation.*; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; +// TODO @haohao:一些必要的关联、枚举 /** * IoT 插件实例 DO * @@ -28,7 +28,7 @@ public class PluginInstanceDO extends BaseDO { @TableId private Long id; /** - * 插件主程序id + * 插件主程序 ID */ private String mainId; /** @@ -44,7 +44,7 @@ public class PluginInstanceDO extends BaseDO { */ private Integer port; /** - * 心跳时间,心路时间超过30秒需要剔除 + * 心跳时间,心路时间超过 30 秒需要剔除 */ private Long heartbeatAt; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java index 4f390ca3be..70fd23014e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java @@ -15,7 +15,6 @@ import java.util.Map; */ public interface IotDeviceDataService { - /** * 保存设备数据 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java deleted file mode 100644 index d921161f8e..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImplTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.plugininfo; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import jakarta.annotation.Resource; - -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; - -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.*; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; -import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper; -import cn.iocoder.yudao.framework.common.pojo.PageResult; - -import org.springframework.context.annotation.Import; - -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; -import static org.junit.jupiter.api.Assertions.*; - -/** - * {@link PluginInfoServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(PluginInfoServiceImpl.class) -public class PluginInfoServiceImplTest extends BaseDbUnitTest { - - @Resource - private PluginInfoServiceImpl pluginInfoService; - - @Resource - private PluginInfoMapper pluginInfoMapper; - - @Test - public void testCreatePluginInfo_success() { - // 准备参数 - PluginInfoSaveReqVO createReqVO = randomPojo(PluginInfoSaveReqVO.class).setId(null); - - // 调用 - Long pluginInfoId = pluginInfoService.createPluginInfo(createReqVO); - // 断言 - assertNotNull(pluginInfoId); - // 校验记录的属性是否正确 - PluginInfoDO pluginInfo = pluginInfoMapper.selectById(pluginInfoId); - assertPojoEquals(createReqVO, pluginInfo, "id"); - } - - @Test - public void testUpdatePluginInfo_success() { - // mock 数据 - PluginInfoDO dbPluginInfo = randomPojo(PluginInfoDO.class); - pluginInfoMapper.insert(dbPluginInfo);// @Sql: 先插入出一条存在的数据 - // 准备参数 - PluginInfoSaveReqVO updateReqVO = randomPojo(PluginInfoSaveReqVO.class, o -> { - o.setId(dbPluginInfo.getId()); // 设置更新的 ID - }); - - // 调用 - pluginInfoService.updatePluginInfo(updateReqVO); - // 校验是否更新正确 - PluginInfoDO pluginInfo = pluginInfoMapper.selectById(updateReqVO.getId()); // 获取最新的 - assertPojoEquals(updateReqVO, pluginInfo); - } - - @Test - public void testUpdatePluginInfo_notExists() { - // 准备参数 - PluginInfoSaveReqVO updateReqVO = randomPojo(PluginInfoSaveReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> pluginInfoService.updatePluginInfo(updateReqVO), PLUGIN_INFO_NOT_EXISTS); - } - - @Test - public void testDeletePluginInfo_success() { - // mock 数据 - PluginInfoDO dbPluginInfo = randomPojo(PluginInfoDO.class); - pluginInfoMapper.insert(dbPluginInfo);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbPluginInfo.getId(); - - // 调用 - pluginInfoService.deletePluginInfo(id); - // 校验数据不存在了 - assertNull(pluginInfoMapper.selectById(id)); - } - - @Test - public void testDeletePluginInfo_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> pluginInfoService.deletePluginInfo(id), PLUGIN_INFO_NOT_EXISTS); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetPluginInfoPage() { - // mock 数据 - PluginInfoDO dbPluginInfo = randomPojo(PluginInfoDO.class, o -> { // 等会查询到 - o.setPluginId(null); - o.setName(null); - o.setDescription(null); - o.setDeployType(null); - o.setFile(null); - o.setVersion(null); - o.setType(null); - o.setProtocol(null); - o.setStatus(null); - o.setConfigSchema(null); - o.setConfig(null); - o.setScript(null); - o.setCreateTime(null); - }); - pluginInfoMapper.insert(dbPluginInfo); - // 测试 pluginId 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setPluginId(null))); - // 测试 name 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setName(null))); - // 测试 description 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setDescription(null))); - // 测试 deployType 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setDeployType(null))); - // 测试 file 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setFile(null))); - // 测试 version 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setVersion(null))); - // 测试 type 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setType(null))); - // 测试 protocol 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setProtocol(null))); - // 测试 state 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setStatus(null))); - // 测试 configSchema 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setConfigSchema(null))); - // 测试 config 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setConfig(null))); - // 测试 script 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setScript(null))); - // 测试 createTime 不匹配 - pluginInfoMapper.insert(cloneIgnoreId(dbPluginInfo, o -> o.setCreateTime(null))); - // 准备参数 - PluginInfoPageReqVO reqVO = new PluginInfoPageReqVO(); - reqVO.setPluginId(null); - reqVO.setName(null); - reqVO.setDescription(null); - reqVO.setDeployType(null); - reqVO.setFile(null); - reqVO.setVersion(null); - reqVO.setType(null); - reqVO.setProtocol(null); - reqVO.setStatus(null); - reqVO.setConfigSchema(null); - reqVO.setConfig(null); - reqVO.setScript(null); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = pluginInfoService.getPluginInfoPage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbPluginInfo, pageResult.getList().get(0)); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java deleted file mode 100644 index fe6235bded..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImplTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.plugininstance; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; - -import jakarta.annotation.Resource; - -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; - -import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO; -import cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper; -import cn.iocoder.yudao.framework.common.pojo.PageResult; - -import jakarta.annotation.Resource; -import org.springframework.context.annotation.Import; -import java.util.*; -import java.time.LocalDateTime; - -import static cn.hutool.core.util.RandomUtil.*; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; -import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; -import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*; -import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * {@link PluginInstanceServiceImpl} 的单元测试类 - * - * @author 芋道源码 - */ -@Import(PluginInstanceServiceImpl.class) -public class PluginInstanceServiceImplTest extends BaseDbUnitTest { - - @Resource - private PluginInstanceServiceImpl pluginInstanceService; - - @Resource - private PluginInstanceMapper pluginInstanceMapper; - - @Test - public void testCreatePluginInstance_success() { - // 准备参数 - PluginInstanceSaveReqVO createReqVO = randomPojo(PluginInstanceSaveReqVO.class).setId(null); - - // 调用 - Long pluginInstanceId = pluginInstanceService.createPluginInstance(createReqVO); - // 断言 - assertNotNull(pluginInstanceId); - // 校验记录的属性是否正确 - PluginInstanceDO pluginInstance = pluginInstanceMapper.selectById(pluginInstanceId); - assertPojoEquals(createReqVO, pluginInstance, "id"); - } - - @Test - public void testUpdatePluginInstance_success() { - // mock 数据 - PluginInstanceDO dbPluginInstance = randomPojo(PluginInstanceDO.class); - pluginInstanceMapper.insert(dbPluginInstance);// @Sql: 先插入出一条存在的数据 - // 准备参数 - PluginInstanceSaveReqVO updateReqVO = randomPojo(PluginInstanceSaveReqVO.class, o -> { - o.setId(dbPluginInstance.getId()); // 设置更新的 ID - }); - - // 调用 - pluginInstanceService.updatePluginInstance(updateReqVO); - // 校验是否更新正确 - PluginInstanceDO pluginInstance = pluginInstanceMapper.selectById(updateReqVO.getId()); // 获取最新的 - assertPojoEquals(updateReqVO, pluginInstance); - } - - @Test - public void testUpdatePluginInstance_notExists() { - // 准备参数 - PluginInstanceSaveReqVO updateReqVO = randomPojo(PluginInstanceSaveReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> pluginInstanceService.updatePluginInstance(updateReqVO), PLUGIN_INSTANCE_NOT_EXISTS); - } - - @Test - public void testDeletePluginInstance_success() { - // mock 数据 - PluginInstanceDO dbPluginInstance = randomPojo(PluginInstanceDO.class); - pluginInstanceMapper.insert(dbPluginInstance);// @Sql: 先插入出一条存在的数据 - // 准备参数 - Long id = dbPluginInstance.getId(); - - // 调用 - pluginInstanceService.deletePluginInstance(id); - // 校验数据不存在了 - assertNull(pluginInstanceMapper.selectById(id)); - } - - @Test - public void testDeletePluginInstance_notExists() { - // 准备参数 - Long id = randomLongId(); - - // 调用, 并断言异常 - assertServiceException(() -> pluginInstanceService.deletePluginInstance(id), PLUGIN_INSTANCE_NOT_EXISTS); - } - - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGetPluginInstancePage() { - // mock 数据 - PluginInstanceDO dbPluginInstance = randomPojo(PluginInstanceDO.class, o -> { // 等会查询到 - o.setMainId(null); - o.setPluginId(null); - o.setIp(null); - o.setPort(null); - o.setHeartbeatAt(null); - o.setCreateTime(null); - }); - pluginInstanceMapper.insert(dbPluginInstance); - // 测试 mainId 不匹配 - pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setMainId(null))); - // 测试 pluginId 不匹配 - pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setPluginId(null))); - // 测试 ip 不匹配 - pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setIp(null))); - // 测试 port 不匹配 - pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setPort(null))); - // 测试 heartbeatAt 不匹配 - pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setHeartbeatAt(null))); - // 测试 createTime 不匹配 - pluginInstanceMapper.insert(cloneIgnoreId(dbPluginInstance, o -> o.setCreateTime(null))); - // 准备参数 - PluginInstancePageReqVO reqVO = new PluginInstancePageReqVO(); - reqVO.setMainId(null); - reqVO.setPluginId(null); - reqVO.setIp(null); - reqVO.setPort(null); - reqVO.setHeartbeatAt(null); - reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - - // 调用 - PageResult pageResult = pluginInstanceService.getPluginInstancePage(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbPluginInstance, pageResult.getList().get(0)); - } - -} \ No newline at end of file From 92c2717d461cc117ce0d2c595d3705606a58c287 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 15 Dec 2024 10:46:33 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91IoT=EF=BC=9A=E8=AE=BE=E5=A4=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=89=B9=E9=87=8F=E5=AF=BC?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/iot/enums/ErrorCodeConstants.java | 1 + .../admin/device/IotDeviceController.java | 26 ++++++ .../vo/device/IotDeviceImportExcelVO.java | 37 ++++++++ .../vo/device/IotDeviceImportRespVO.java | 23 +++++ .../mysql/device/IotDeviceGroupMapper.java | 4 + .../iot/dal/mysql/device/IotDeviceMapper.java | 4 + .../dal/mysql/product/IotProductMapper.java | 2 +- .../service/device/IotDeviceGroupService.java | 8 ++ .../device/IotDeviceGroupServiceImpl.java | 5 + .../iot/service/device/IotDeviceService.java | 13 ++- .../service/device/IotDeviceServiceImpl.java | 93 +++++++++++++++++-- .../service/product/IotProductService.java | 16 ++++ .../product/IotProductServiceImpl.java | 22 +++-- 13 files changed, 239 insertions(+), 15 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index 5f58109b36..633b3cb4f2 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -29,6 +29,7 @@ public interface ErrorCodeConstants { 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, "设备不是网关设备"); + ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!"); // ========== 产品分类 1-050-004-000 ========== ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在"); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java index 35b4abe937..e0c214bc19 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java @@ -18,8 +18,10 @@ import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -133,4 +135,28 @@ public class IotDeviceController { new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName()))); } + @PostMapping("/import") + @Operation(summary = "导入设备") + @PreAuthorize("@ss.hasPermission('iot:device:import')") + public CommonResult importDevice( + @RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) + throws Exception { + List list = ExcelUtils.read(file, IotDeviceImportExcelVO.class); + return success(deviceService.importDevice(list, updateSupport)); + } + + @GetMapping("/get-import-template") + @Operation(summary = "获得导入设备模板") + public void importTemplate(HttpServletResponse response) throws IOException { + // 手动创建导出 demo + List list = Arrays.asList( + IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110") + .productKey("1de24640dfe").groupNames("灰度分组,生产分组").build(), + IotDeviceImportExcelVO.builder().deviceName("biubiu") + .productKey("YzvHxd4r67sT4s2B").groupNames("").build()); + // 输出 + ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java new file mode 100644 index 0000000000..710e74263d --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * 设备 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) // 设置 chain = false,避免设备导入有问题 +public class IotDeviceImportExcelVO { + + @ExcelProperty("设备名称") + @NotEmpty(message = "设备名称不能为空") + private String deviceName; + + @ExcelProperty("父设备名称") + @Schema(description = "父设备名称", example = "网关001") + private String parentDeviceName; + + @ExcelProperty("产品标识") + @NotEmpty(message = "产品标识不能为空") + private String productKey; + + @ExcelProperty("设备分组") + private String groupNames; + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java new file mode 100644 index 0000000000..bf52b123f9 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportRespVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - IoT 设备导入 Response VO") +@Data +@Builder +public class IotDeviceImportRespVO { + + @Schema(description = "创建成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List createDeviceNames; + + @Schema(description = "更新成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List updateDeviceNames; + + @Schema(description = "导入失败的设备集合,key为设备名称,value为失败原因", requiredMode = Schema.RequiredMode.REQUIRED) + private Map failureDeviceNames; +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java index 605ce5270a..1f80ae4558 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceGroupMapper.java @@ -28,4 +28,8 @@ public interface IotDeviceGroupMapper extends BaseMapperX { return selectList(IotDeviceGroupDO::getStatus, status); } + default IotDeviceGroupDO selectByName(String name) { + return selectOne(IotDeviceGroupDO::getName, name); + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java index 59794c4edf..893d7eefda 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java @@ -29,6 +29,10 @@ public interface IotDeviceMapper extends BaseMapperX { .orderByDesc(IotDeviceDO::getId)); } + default IotDeviceDO selectByDeviceName(String deviceName) { + return selectOne(IotDeviceDO::getDeviceName, deviceName); + } + default IotDeviceDO selectByProductKeyAndDeviceName(String productKey, String deviceName) { return selectOne(IotDeviceDO::getProductKey, productKey, IotDeviceDO::getDeviceName, deviceName); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java index eac1d29e8b..aef149ae72 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java @@ -23,7 +23,7 @@ public interface IotProductMapper extends BaseMapperX { } default IotProductDO selectByProductKey(String productKey) { - return selectOne(new LambdaQueryWrapperX().eq(IotProductDO::getProductKey, productKey)); + return selectOne(IotProductDO::getProductKey, productKey); } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java index 5219f2d4bd..45e6ab25ef 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupService.java @@ -69,6 +69,14 @@ public interface IotDeviceGroupService { */ IotDeviceGroupDO getDeviceGroup(Long id); + /** + * 获得设备分组 + * + * @param name 名称 + * @return 设备分组 + */ + IotDeviceGroupDO getDeviceGroupByName(String name); + /** * 获得设备分组分页 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java index cfa24784b7..06e5cb11de 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceGroupServiceImpl.java @@ -76,6 +76,11 @@ public class IotDeviceGroupServiceImpl implements IotDeviceGroupService { return deviceGroupMapper.selectById(id); } + @Override + public IotDeviceGroupDO getDeviceGroupByName(String name) { + return deviceGroupMapper.selectByName(name); + } + @Override public PageResult getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO) { return deviceGroupMapper.selectPage(pageReqVO); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java index 42101626c0..b8a3511cdd 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java @@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSa import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportRespVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportExcelVO; import jakarta.validation.Valid; import javax.annotation.Nullable; @@ -71,7 +73,7 @@ public interface IotDeviceService { IotDeviceDO getDevice(Long id); /** - * 获得设备分页 + * ��得设备分页 * * @param pageReqVO 分页查询 * @return IoT 设备分页 @@ -111,4 +113,13 @@ public interface IotDeviceService { */ IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName); + /** + * 导入设备 + * + * @param importDevices 导入设备列表 + * @param updateSupport 是否支持更新 + * @return 导入结果 + */ + IotDeviceImportRespVO importDevice(List importDevices, boolean updateSupport); + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java index 29944eae7b..a43521ff92 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java @@ -3,20 +3,22 @@ package cn.iocoder.yudao.module.iot.service.device; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; -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 cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; 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 jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -25,9 +27,7 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Nullable; import java.time.LocalDateTime; -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -244,6 +244,15 @@ public class IotDeviceServiceImpl implements IotDeviceService { return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName); } + /** + * 生成 deviceKey + * + * @return 生成的 deviceKey + */ + private String generateDeviceKey() { + return RandomUtil.randomString(16); + } + /** * 生成 deviceSecret * @@ -282,4 +291,74 @@ public class IotDeviceServiceImpl implements IotDeviceService { return RandomUtil.randomString(32); } + @Override + @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 + public IotDeviceImportRespVO importDevice(List importDevices, boolean updateSupport) { + // 1. 参数校验 + if (CollUtil.isEmpty(importDevices)) { + throw exception(DEVICE_IMPORT_LIST_IS_EMPTY); + } + + // 2. 遍历,逐个创建 or 更新 + IotDeviceImportRespVO respVO = IotDeviceImportRespVO.builder().createDeviceNames(new ArrayList<>()) + .updateDeviceNames(new ArrayList<>()).failureDeviceNames(new LinkedHashMap<>()).build(); + importDevices.forEach(importDevice -> { + try { + // 2.1.1 校验字段是否符合要求 + try { + ValidationUtils.validate(importDevice); + } catch (ConstraintViolationException ex){ + respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage()); + return; + } + // 2.1.2 校验产品是否存在 + IotProductDO product = productService.validateProductExists(importDevice.getProductKey()); + // 2.1.3 校验父设备是否存在 + Long gatewayId = null; + if (StrUtil.isNotEmpty(importDevice.getParentDeviceName())) { + IotDeviceDO gatewayDevice = deviceMapper.selectByDeviceName(importDevice.getParentDeviceName()); + if (gatewayDevice == null) { + throw exception(DEVICE_GATEWAY_NOT_EXISTS); + } + if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) { + throw exception(DEVICE_NOT_GATEWAY); + } + gatewayId = gatewayDevice.getId(); + } + // 2.1.4 校验设备分组是否存在 + Set groupIds = new HashSet<>(); + if (StrUtil.isNotEmpty(importDevice.getGroupNames())) { + String[] groupNames = importDevice.getGroupNames().split(","); + for (String groupName : groupNames) { + IotDeviceGroupDO group = deviceGroupService.getDeviceGroupByName(groupName); + if (group == null) { + throw exception(DEVICE_GROUP_NOT_EXISTS); + } + groupIds.add(group.getId()); + } + } + + // 2.2.1 判断如果不存在,在进行插入 + IotDeviceDO existDevice = deviceMapper.selectByDeviceName(importDevice.getDeviceName()); + if (existDevice == null) { + createDevice(new IotDeviceSaveReqVO() + .setDeviceName(importDevice.getDeviceName()).setDeviceKey(generateDeviceKey()) + .setProductId(product.getId()).setGatewayId(gatewayId).setGroupIds(groupIds)); + respVO.getCreateDeviceNames().add(importDevice.getDeviceName()); + return; + } + // 2.2.2 如果存在,判断是否允许更新 + if (updateSupport) { + throw exception(DEVICE_KEY_EXISTS); + } + updateDevice(new IotDeviceSaveReqVO().setId(existDevice.getId()) + .setGatewayId(gatewayId).setGroupIds(groupIds)); + respVO.getUpdateDeviceNames().add(importDevice.getDeviceName()); + } catch (ServiceException ex) { + respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage()); + } + }); + return respVO; + } + } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java index bcbf13ef45..9a2e96f671 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java @@ -45,6 +45,22 @@ public interface IotProductService { */ IotProductDO getProduct(Long id); + /** + * 校验产品存在 + * + * @param id 编号 + * @return 产品 + */ + IotProductDO validateProductExists(Long id); + + /** + * 校验产品存在 + * + * @param productKey 产品 key + * @return 产品 + */ + IotProductDO validateProductExists(String productKey); + /** * 获得产品分页 * diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java index b1d2d666ba..e7c1443883 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java @@ -71,16 +71,26 @@ public class IotProductServiceImpl implements IotProductService { productMapper.deleteById(id); } - private IotProductDO validateProductExists(Long id) { - IotProductDO iotProductDO = productMapper.selectById(id); - if (iotProductDO == null) { + @Override + public IotProductDO validateProductExists(Long id) { + IotProductDO product = productMapper.selectById(id); + if (product == null) { throw exception(PRODUCT_NOT_EXISTS); } - return iotProductDO; + return product; } - private void validateProductStatus(IotProductDO iotProductDO) { - if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) { + @Override + public IotProductDO validateProductExists(String productKey) { + IotProductDO product = productMapper.selectByProductKey(productKey); + if (product == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + return product; + } + + private void validateProductStatus(IotProductDO product) { + if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) { throw exception(PRODUCT_STATUS_NOT_DELETE); } }