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] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E3=80=91IoT=EF=BC=9A=E5=A2=9E=E5=8A=A0=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8C=85=E5=90=AB?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=AE=9E=E4=BE=8B=E5=92=8C=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E7=9A=84=E5=AE=9A=E4=B9=89=E5=8F=8A=E7=9B=B8=E5=85=B3=E9=85=8D?= =?UTF-8?q?=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;