From e650e75271cf241721f19014dadb22b3b51e24f0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 30 Jan 2025 21:06:47 +0800 Subject: [PATCH] 【代码优化】IoT:设备上行时,增加 processId,用于设备与插件的映射 --- yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java | 4 ++-- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java | 10 +++++----- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInfoDO.java | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInstanceDO.java | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInfoDO.java | 92 -------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInstanceDO.java | 59 ----------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInfoMapper.java | 28 ++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInstanceMapper.java | 18 ++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java | 34 ---------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java | 36 ------------------------------------ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java | 10 ++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DevicePropertyRedisDAO.java | 7 +------ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/plugin/DevicePluginProcessIdRedisDAO.java | 25 +++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/PluginStart.java | 8 ++++---- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java | 4 ++-- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java | 10 +++++++--- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java | 4 ++-- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java | 6 ++++-- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoService.java | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoServiceImpl.java | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceService.java | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java | 88 ---------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java | 158 -------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java | 49 ------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java | 194 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/plugininstance/PluginInstanceMapper.xml | 2 +- yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/util/IotPluginCommonUtils.java | 31 +++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/pom.xml | 6 ++++++ yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java | 27 --------------------------- yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java | 30 ++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDevicePropertyReportVertxHandler.java | 7 +++---- 32 files changed, 855 insertions(+), 768 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInfoDO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInstanceDO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInfoDO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInstanceDO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInfoMapper.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInstanceMapper.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/plugin/DevicePluginProcessIdRedisDAO.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoService.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoServiceImpl.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceService.java create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java create mode 100644 yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/util/IotPluginCommonUtils.java delete mode 100644 yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java create mode 100644 yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java index abf3a8e..a0c8ce9 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceUpstreamAbstractReqDTO.java @@ -21,9 +21,9 @@ public abstract class IotDeviceUpstreamAbstractReqDTO { private String requestId; /** - * 插件标识 + * 插件实例的进程编号 */ - private String pluginKey; + private String processId; /** * 产品标识 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java index b916222..7d9472d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInfoController.java @@ -7,8 +7,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoImp import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoRespVO; import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoSaveReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInfoDO; -import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO; +import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInfoService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -27,7 +27,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; public class PluginInfoController { @Resource - private PluginInfoService pluginInfoService; + private IotPluginInfoService pluginInfoService; @PostMapping("/create") @Operation(summary = "创建插件信息") @@ -58,7 +58,7 @@ public class PluginInfoController { @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); + IotPluginInfoDO pluginInfo = pluginInfoService.getPluginInfo(id); return success(BeanUtils.toBean(pluginInfo, PluginInfoRespVO.class)); } @@ -66,7 +66,7 @@ public class PluginInfoController { @Operation(summary = "获得插件信息分页") @PreAuthorize("@ss.hasPermission('iot:plugin-info:query')") public CommonResult> getPluginInfoPage(@Valid PluginInfoPageReqVO pageReqVO) { - PageResult pageResult = pluginInfoService.getPluginInfoPage(pageReqVO); + PageResult pageResult = pluginInfoService.getPluginInfoPage(pageReqVO); return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class)); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInfoDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInfoDO.java new file mode 100644 index 0000000..f55a9f5 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInfoDO.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.plugin; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum; +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 IotPluginInfoDO extends BaseDO { + + /** + * 主键 ID + */ + @TableId + private Long id; + /** + * 插件包标识符 + */ + private String pluginKey; + /** + * 插件名称 + */ + private String name; + /** + * 描述 + */ + private String description; + /** + * 部署方式 + *

+ * 枚举 {@link IotPluginDeployTypeEnum} + */ + private Integer deployType; + // TODO @芋艿:如果是外置的插件,fileName 和 version 的选择~ + /** + * 插件包文件名 + */ + private String fileName; + /** + * 插件版本 + */ + private String version; + // TODO @芋艿:type 字典的定义 + /** + * 插件类型 + *

+ * 枚举 {@link IotPluginTypeEnum} + */ + private Integer type; + /** + * 设备插件协议类型 + */ + // TODO @芋艿:枚举字段 + private String protocol; + /** + * 状态 + *

+ * 枚举 {@link IotPluginStatusEnum} + */ + private Integer status; + // TODO @芋艿:configSchema、config 示例字段 + /** + * 插件配置项描述信息 + */ + private String configSchema; + /** + * 插件配置信息 + */ + private String config; + // TODO @芋艿:script 后续的使用 + /** + * 插件脚本 + */ + 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/plugin/IotPluginInstanceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInstanceDO.java new file mode 100644 index 0000000..e03aa9d --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInstanceDO.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.iot.dal.dataobject.plugin; + +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.*; + +import java.time.LocalDateTime; + +/** + * 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 IotPluginInstanceDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 插件编号 + *

+ * 关联 {@link IotPluginInfoDO#getId()} + */ + private Long pluginId; + /** + * 插件进程编号 + * + * 一般格式是:hostIp@processId@${uuid} + */ + private String processId; + + /** + * 插件实例所在 IP + */ + private String hostIp; + /** + * 设备下行端口 + */ + private Integer downstreamPort; + + /** + * 是否在线 + */ + private Boolean online; + /** + * 在线时间 + */ + private LocalDateTime onlineTime; + /** + * 离线时间 + */ + private LocalDateTime offlineTime; + /** + * 心跳时间 + * + * 目的:心路时间超过一定时间后,会被进行下线处理 + */ + private Long heartbeatTime; + +} \ 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/plugin/PluginInfoDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInfoDO.java deleted file mode 100644 index 5efff60..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInfoDO.java +++ /dev/null @@ -1,92 +0,0 @@ -package cn.iocoder.yudao.module.iot.dal.dataobject.plugin; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum; -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum; -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; - /** - * 插件包标识符 - */ - private String pluginKey; - /** - * 插件名称 - */ - private String name; - /** - * 描述 - */ - private String description; - /** - * 部署方式 - *

- * 枚举 {@link IotPluginDeployTypeEnum} - */ - private Integer deployType; - // TODO @芋艿:如果是外置的插件,fileName 和 version 的选择~ - /** - * 插件包文件名 - */ - private String fileName; - /** - * 插件版本 - */ - private String version; - // TODO @芋艿:type 字典的定义 - /** - * 插件类型 - *

- * 枚举 {@link IotPluginTypeEnum} - */ - private Integer type; - /** - * 设备插件协议类型 - */ - // TODO @芋艿:枚举字段 - private String protocol; - /** - * 状态 - *

- * 枚举 {@link IotPluginStatusEnum} - */ - private Integer status; - // TODO @芋艿:configSchema、config 示例字段 - /** - * 插件配置项描述信息 - */ - private String configSchema; - /** - * 插件配置信息 - */ - private String config; - // TODO @芋艿:script 后续的使用 - /** - * 插件脚本 - */ - 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/plugin/PluginInstanceDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInstanceDO.java deleted file mode 100644 index d2da1c2..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/PluginInstanceDO.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.iot.dal.dataobject.plugin; - -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_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; - // TODO @芋艿:找一个更好的字段名 - /** - * 插件主程序 ID - */ - private String mainId; - /** - * 插件 ID - *

- * 关联 {@link PluginInfoDO#getId()} - */ - private Long pluginId; - - /** - * 插件主程序所在 IP - */ - private String ip; - // TODO @芋艿:这个 port 是否有必要记录 - /** - * 插件主程序端口 - */ - private Integer port; - // TODO @芋艿:downstreamPort 增加,目的:用于下行 - - // TODO @haohao:字段改成 heartbeatTime,LocalDateTime - /** - * 心跳时间,心路时间超过 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/plugin/IotPluginInfoMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInfoMapper.java new file mode 100644 index 0000000..300339f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInfoMapper.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.plugin; + +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.plugin.vo.info.PluginInfoPageReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface IotPluginInfoMapper extends BaseMapperX { + + default PageResult selectPage(PluginInfoPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(IotPluginInfoDO::getName, reqVO.getName()) + .eqIfPresent(IotPluginInfoDO::getStatus, reqVO.getStatus()) + .orderByDesc(IotPluginInfoDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(IotPluginInfoDO::getStatus, status) + .orderByAsc(IotPluginInfoDO::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/plugin/IotPluginInstanceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInstanceMapper.java new file mode 100644 index 0000000..3e47eab --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInstanceMapper.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.iot.dal.mysql.plugin; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface IotPluginInstanceMapper extends BaseMapperX { + + // TODO @芋艿:方法名,重构 + default IotPluginInstanceDO selectByMainIdAndPluginId(String mainId, Long pluginId) { + return selectOne(new LambdaQueryWrapperX() + .eq(IotPluginInstanceDO::getProcessId, mainId) + .eq(IotPluginInstanceDO::getPluginId, pluginId)); + } + +} \ 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/plugin/PluginInfoMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java deleted file mode 100644 index 7198e9e..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInfoMapper.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.iot.dal.mysql.plugin; - -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.controller.admin.plugin.vo.info.PluginInfoPageReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInfoDO; - -import java.util.List; - -import org.apache.ibatis.annotations.Mapper; - -/** - * IoT 插件信息 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface PluginInfoMapper extends BaseMapperX { - - default PageResult selectPage(PluginInfoPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(PluginInfoDO::getName, reqVO.getName()) - .eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus()) - .orderByDesc(PluginInfoDO::getId)); - } - - default List selectListByStatus(Integer status) { - return selectList(new LambdaQueryWrapperX() - .eq(PluginInfoDO::getStatus, status) - .orderByAsc(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/plugin/PluginInstanceMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java deleted file mode 100644 index e5a187b..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.iot.dal.mysql.plugin; - -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.controller.admin.plugin.vo.instance.PluginInstancePageReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInstanceDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * IoT 插件实例 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface PluginInstanceMapper extends BaseMapperX { - - default PluginInstanceDO selectByMainIdAndPluginId(String mainId, Long pluginId) { - return selectOne(new LambdaQueryWrapperX() - .eq(PluginInstanceDO::getMainId, mainId) - .eq(PluginInstanceDO::getPluginId, pluginId)); - } - - // TODO @haohao:这个还需要么?相关不用的 VO 可以删除 - 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/dal/redis/RedisKeyConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java index 8cff06c..8145ff7 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/RedisKeyConstants.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.dal.redis; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO; /** * Iot Redis Key 枚举类 @@ -42,4 +43,13 @@ public interface RedisKeyConstants { */ String THING_MODEL_LIST = "thing_model_list"; + /** + * 设备插件的插件进程编号的映射,采用 HASH 结构 + * + * KEY 格式:device_plugin_instance_process_ids + * HASH KEY:${deviceKey} + * VALUE:插件进程编号,对应 {@link IotPluginInstanceDO#getProcessId()} 字段 + */ + String DEVICE_PLUGIN_INSTANCE_PROCESS_IDS = "device_plugin_instance_process_ids"; + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DevicePropertyRedisDAO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DevicePropertyRedisDAO.java index a07972b..0f1196a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DevicePropertyRedisDAO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/device/DevicePropertyRedisDAO.java @@ -33,7 +33,7 @@ public class DevicePropertyRedisDAO { entry -> JsonUtils.parseObject((String) entry.getValue(), IotDevicePropertyDO.class)); } - public void set(String deviceKey, Map properties) { + public void putAll(String deviceKey, Map properties) { if (CollUtil.isEmpty(properties)) { return; } @@ -43,11 +43,6 @@ public class DevicePropertyRedisDAO { entry -> JsonUtils.toJsonString(entry.getValue()))); } - public void delete(String deviceKey) { - String redisKey = formatKey(deviceKey); - stringRedisTemplate.delete(redisKey); - } - private static String formatKey(String deviceKey) { return String.format(DEVICE_PROPERTY, deviceKey); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/plugin/DevicePluginProcessIdRedisDAO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/plugin/DevicePluginProcessIdRedisDAO.java new file mode 100644 index 0000000..32559d7 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/plugin/DevicePluginProcessIdRedisDAO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.iot.dal.redis.plugin; + +import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +/** + * 设备插件的插件进程编号的缓存的 Redis DAO + */ +@Repository +public class DevicePluginProcessIdRedisDAO { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + public void put(String deviceKey, String processId) { + stringRedisTemplate.opsForHash().put(RedisKeyConstants.DEVICE_PLUGIN_INSTANCE_PROCESS_IDS, deviceKey, processId); + } + + public String get(String deviceKey) { + return (String) stringRedisTemplate.opsForHash().get(RedisKeyConstants.DEVICE_PLUGIN_INSTANCE_PROCESS_IDS, deviceKey); + } + +} \ 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/core/PluginStart.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/PluginStart.java index 680d7c3..76b0fa6 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/PluginStart.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/PluginStart.java @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.iot.framework.plugin.core; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInfoDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO; import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; -import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService; +import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInfoService; import lombok.extern.slf4j.Slf4j; import org.pf4j.spring.SpringPluginManager; import org.springframework.boot.ApplicationArguments; @@ -20,7 +20,7 @@ import java.util.List; public class PluginStart implements ApplicationRunner { @Resource - private PluginInfoService pluginInfoService; + private IotPluginInfoService pluginInfoService; @Resource private SpringPluginManager pluginManager; @@ -28,7 +28,7 @@ public class PluginStart implements ApplicationRunner { @Override public void run(ApplicationArguments args) { TenantUtils.executeIgnore(() -> { // 1. 忽略租户上下文执行 - List pluginInfoList = pluginInfoService + List pluginInfoList = pluginInfoService .getPluginInfoListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); // 2. 获取运行中的插件列表 if (CollUtil.isEmpty(pluginInfoList)) { // 3. 检查插件列表是否为空 log.info("[run] 没有需要启动的插件"); // 4. 日志记录没有插件需要启动 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java index fbcfea3..ae6ed42 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.iot.job.plugin; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService; +import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -18,7 +18,7 @@ import java.util.concurrent.TimeUnit; public class PluginInstancesJob { @Resource - private PluginInstanceService pluginInstanceService; + private IotPluginInstanceService pluginInstanceService; @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS) public void updatePluginInstances() { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java index 6667c39..ca0263e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java @@ -20,6 +20,7 @@ import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; +import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -43,6 +44,8 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { private IotDeviceService deviceService; @Resource private IotDevicePropertyService devicePropertyService; + @Resource + private IotPluginInstanceService pluginInstanceService; @Resource private IotDeviceProducer deviceProducer; @@ -144,10 +147,11 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { } private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { - // 1. TODO 芋艿:插件状态 + // 1. 【异步】记录设备与插件实例的映射 + pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId()); - // 2. 更新设备的最后时间 - devicePropertyService.updateDeviceReportTime(device.getDeviceKey(), LocalDateTime.now()); + // 2. 【异步】更新设备的最后时间 + devicePropertyService.updateDeviceReportTimeAsync(device.getDeviceKey(), LocalDateTime.now()); } private void sendDeviceMessage(IotDeviceMessage message, IotDeviceDO device) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java index ac23c1d..2f06268 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyService.java @@ -61,11 +61,11 @@ public interface IotDevicePropertyService { Set getDeviceKeysByReportTime(LocalDateTime maxReportTime); /** - * 更新设备上报时间 + * 异步更新设备上报时间 * * @param deviceKey 设备标识 * @param reportTime 上报时间 */ - void updateDeviceReportTime(String deviceKey, LocalDateTime reportTime); + void updateDeviceReportTimeAsync(String deviceKey, LocalDateTime reportTime); } \ 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/data/IotDevicePropertyServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java index 9bcdde0..4bd25a0 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/data/IotDevicePropertyServiceImpl.java @@ -27,6 +27,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -151,7 +152,7 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { LocalDateTimeUtil.toEpochMilli(message.getReportTime())); // 3.2 保存设备属性【日志】 - deviceDataRedisDAO.set(message.getDeviceKey(), convertMap(properties.entrySet(), Map.Entry::getKey, + deviceDataRedisDAO.putAll(message.getDeviceKey(), convertMap(properties.entrySet(), Map.Entry::getKey, entry -> IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build())); } @@ -190,7 +191,8 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { } @Override - public void updateDeviceReportTime(String deviceKey, LocalDateTime reportTime) { + @Async + public void updateDeviceReportTimeAsync(String deviceKey, LocalDateTime reportTime) { deviceReportTimeRedisDAO.update(deviceKey, reportTime); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoService.java new file mode 100644 index 0000000..5ee7dba --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoService.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.iot.service.plugin; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; +import jakarta.validation.Valid; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * IoT 插件信息 Service 接口 + * + * @author 芋道源码 + */ +public interface IotPluginInfoService { + + /** + * 创建插件信息 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPluginInfo(@Valid PluginInfoSaveReqVO createReqVO); + + /** + * 更新插件信息 + * + * @param updateReqVO 更新信息 + */ + void updatePluginInfo(@Valid PluginInfoSaveReqVO updateReqVO); + + /** + * 删除插件信息 + * + * @param id 编号 + */ + void deletePluginInfo(Long id); + + /** + * 获得插件信息 + * + * @param id 编号 + * @return 插件信息 + */ + IotPluginInfoDO getPluginInfo(Long id); + + /** + * 获得插件信息分页 + * + * @param pageReqVO 分页查询 + * @return 插件信息分页 + */ + PageResult getPluginInfoPage(PluginInfoPageReqVO pageReqVO); + + /** + * 上传插件的 JAR 包 + * + * @param id 插件id + * @param file 文件 + */ + void uploadFile(Long id, MultipartFile file); + + /** + * 更新插件的状态 + * + * @param id 插件id + * @param status 状态 {@link IotPluginStatusEnum} + */ + void updatePluginStatus(Long id, Integer status); + + /** + * 获得插件信息列表 + * + * @return 插件信息列表 + */ + List getPluginInfoList(); + + /** + * 根据状态获得插件信息列表 + * + * @param status 状态 {@link IotPluginStatusEnum} + * @return 插件信息列表 + */ + List getPluginInfoListByStatus(Integer status); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoServiceImpl.java new file mode 100644 index 0000000..258392b --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoServiceImpl.java @@ -0,0 +1,158 @@ +package cn.iocoder.yudao.module.iot.service.plugin; + +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.plugin.vo.info.PluginInfoPageReqVO; +import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO; +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInfoMapper; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.spring.SpringPluginManager; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_DELETE_FAILED_RUNNING; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_NOT_EXISTS; + +/** + * IoT 插件信息 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class IotPluginInfoServiceImpl implements IotPluginInfoService { + + @Resource + private IotPluginInfoMapper pluginInfoMapper; + + @Resource + private IotPluginInstanceService pluginInstanceService; + + @Resource + private SpringPluginManager pluginManager; + + @Override + public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) { + IotPluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, IotPluginInfoDO.class); + pluginInfoMapper.insert(pluginInfo); + return pluginInfo.getId(); + } + + @Override + public void updatePluginInfo(PluginInfoSaveReqVO updateReqVO) { + // 校验存在 + validatePluginInfoExists(updateReqVO.getId()); + // 更新 + IotPluginInfoDO updateObj = BeanUtils.toBean(updateReqVO, IotPluginInfoDO.class); + pluginInfoMapper.updateById(updateObj); + } + + @Override + public void deletePluginInfo(Long id) { + // 1.1 校验存在 + IotPluginInfoDO pluginInfoDO = validatePluginInfoExists(id); + // 1.2 停止插件 + if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { + throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING); + } + + // 2. 卸载插件 + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDO.getPluginKey()); + + // 3. 删除插件文件 + pluginInstanceService.deletePluginFile(pluginInfoDO); + + // 4. 删除插件信息 + pluginInfoMapper.deleteById(id); + } + + private IotPluginInfoDO validatePluginInfoExists(Long id) { + IotPluginInfoDO pluginInfo = pluginInfoMapper.selectById(id); + if (pluginInfo == null) { + throw exception(PLUGIN_INFO_NOT_EXISTS); + } + return pluginInfo; + } + + @Override + public IotPluginInfoDO getPluginInfo(Long id) { + return pluginInfoMapper.selectById(id); + } + + @Override + public PageResult getPluginInfoPage(PluginInfoPageReqVO pageReqVO) { + return pluginInfoMapper.selectPage(pageReqVO); + } + + @Override + public void uploadFile(Long id, MultipartFile file) { + // 1. 校验插件信息是否存在 + IotPluginInfoDO pluginInfoDo = validatePluginInfoExists(id); + + // 2. 停止并卸载旧的插件 + pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); + + // 3 上传新的插件文件,更新插件启用状态文件 + String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); + + // 4. 更新插件信息 + updatePluginInfo(pluginInfoDo, pluginKeyNew, file); + } + + /** + * 更新插件信息 + * + * @param pluginInfoDo 插件信息 + * @param pluginKeyNew 插件标识符 + * @param file 文件 + */ + private void updatePluginInfo(IotPluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { + // 创建新的插件信息对象并链式设置属性 + IotPluginInfoDO updatedPluginInfo = new IotPluginInfoDO() + .setId(pluginInfoDo.getId()) + .setPluginKey(pluginKeyNew) + .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) + .setFileName(file.getOriginalFilename()) + .setScript("") + .setConfigSchema(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()) + .setVersion(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion()) + .setDescription(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()); + + // 执行更新 + pluginInfoMapper.updateById(updatedPluginInfo); + } + + @Override + public void updatePluginStatus(Long id, Integer status) { + // 1. 校验插件信息是否存在 + IotPluginInfoDO pluginInfoDo = validatePluginInfoExists(id); + + // 2. 更新插件状态 + pluginInstanceService.updatePluginStatus(pluginInfoDo, status); + + // 3. 更新数据库中的插件状态 + IotPluginInfoDO updatedPluginInfo = new IotPluginInfoDO(); + updatedPluginInfo.setId(id); + updatedPluginInfo.setStatus(status); + pluginInfoMapper.updateById(updatedPluginInfo); + } + + @Override + public List getPluginInfoList() { + return pluginInfoMapper.selectList(); + } + + @Override + public List getPluginInfoListByStatus(Integer status) { + return pluginInfoMapper.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/plugin/IotPluginInstanceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceService.java new file mode 100644 index 0000000..381bab1 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceService.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.iot.service.plugin; + +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO; +import org.springframework.web.multipart.MultipartFile; + +/** + * IoT 插件实例 Service 接口 + * + * @author 芋道源码 + */ +public interface IotPluginInstanceService { + + // TODO @芋艿:这个是否应该放到 plugin 主动心跳,而是 server 自己心跳 + /** + * 上报插件实例(心跳) + */ + void reportPluginInstances(); + + /** + * 停止并卸载插件 + * + * @param pluginKey 插件标识符 + */ + void stopAndUnloadPlugin(String pluginKey); + + /** + * 删除插件文件 + * + * @param pluginInfoDo 插件信息 + */ + void deletePluginFile(IotPluginInfoDO pluginInfoDo); + + /** + * 上传并加载新的插件文件 + * + * @param file 插件文件 + * @return 插件标识符 + */ + String uploadAndLoadNewPlugin(MultipartFile file); + + /** + * 更新插件状态 + * + * @param pluginInfoDo 插件信息 + * @param status 新状态 + */ + void updatePluginStatus(IotPluginInfoDO pluginInfoDo, Integer status); + + // ========== 设备与插件的映射操作 ========== + + /** + * 更新设备对应的插件实例的进程编号 + * + * @param deviceKey 设备 Key + * @param processId 进程编号 + */ + void updateDevicePluginInstanceProcessIdAsync(String deviceKey, String processId); + +} \ 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/plugin/IotPluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java new file mode 100644 index 0000000..8973a33 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java @@ -0,0 +1,207 @@ +package cn.iocoder.yudao.module.iot.service.plugin; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO; +import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO; +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInfoMapper; +import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInstanceMapper; +import cn.iocoder.yudao.module.iot.dal.redis.plugin.DevicePluginProcessIdRedisDAO; +import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.pf4j.PluginState; +import org.pf4j.PluginWrapper; +import org.pf4j.spring.SpringPluginManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +/** + * IoT 插件实例 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class IotPluginInstanceServiceImpl implements IotPluginInstanceService { + + // TODO @haohao:mac@uuid + public static final String MAIN_ID = IdUtil.fastSimpleUUID(); + + // TODO @haohao:不直接操作,通过 Service 哈 + @Resource + private IotPluginInfoMapper pluginInfoMapper; + + @Resource + private IotPluginInstanceMapper pluginInstanceMapper; + + @Resource + private DevicePluginProcessIdRedisDAO devicePluginProcessIdRedisDAO; + + @Resource + private SpringPluginManager pluginManager; + + @Value("${pf4j.pluginsDir}") + private String pluginsDir; + @Value("${server.port:48080}") + private int port; + + @Override + public void stopAndUnloadPlugin(String pluginKey) { + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + if (plugin == null) { + log.warn("插件不存在或已卸载: {}", pluginKey); + return; + } + if (plugin.getPluginState().equals(PluginState.STARTED)) { + pluginManager.stopPlugin(pluginKey); // 停止插件 + log.info("已停止插件: {}", pluginKey); + } + pluginManager.unloadPlugin(pluginKey); // 卸载插件 + log.info("已卸载插件: {}", pluginKey); + } + + @Override + public void deletePluginFile(IotPluginInfoDO pluginInfoDO) { + File file = new File(pluginsDir, pluginInfoDO.getFileName()); + if (!file.exists()) { + return; + } + try { + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 + if (!file.delete()) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); + } + } catch (InterruptedException e) { + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); + } + } + + @Override + public String uploadAndLoadNewPlugin(MultipartFile file) { + String pluginKeyNew; + // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载 + Path pluginsPath = Paths.get(pluginsDir); + try { + FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录 + String filename = file.getOriginalFilename(); + if (filename != null) { + Path jarPath = pluginsPath.resolve(filename); + Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 + pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 + log.info("已加载插件: {}", pluginKeyNew); + } else { + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); + } + } catch (IOException e) { + log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e); + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); + } catch (Exception e) { + log.error("[uploadAndLoadNewPlugin][加载插件失败]", e); + throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); + } + return pluginKeyNew; + } + + @Override + public void updatePluginStatus(IotPluginInfoDO pluginInfoDo, Integer status) { + String pluginKey = pluginInfoDo.getPluginKey(); + PluginWrapper plugin = pluginManager.getPlugin(pluginKey); + + if (plugin == null) { + // 插件不存在且状态为停止,抛出异常 + if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { + throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID); + } + return; + } + + // 启动插件 + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) + && plugin.getPluginState() != PluginState.STARTED) { + pluginManager.startPlugin(pluginKey); + log.info("已启动插件: {}", pluginKey); + } + // 停止插件 + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) + && plugin.getPluginState() == PluginState.STARTED) { + pluginManager.stopPlugin(pluginKey); + log.info("已停止插件: {}", pluginKey); + } + } + + @Override + public void reportPluginInstances() { + // 1.1 获取 pf4j 插件列表 + List plugins = pluginManager.getPlugins(); + + // 1.2 获取插件信息列表并转换为 Map 以便快速查找 + List pluginInfos = pluginInfoMapper.selectList(); + Map pluginInfoMap = pluginInfos.stream() + .collect(Collectors.toMap(IotPluginInfoDO::getPluginKey, Function.identity())); + + // 1.3 获取本机 IP 和 MAC 地址,mac@uuid + String ip = NetUtil.getLocalhostStr(); + String mac = NetUtil.getLocalMacAddress(); + String mainId = mac + "@" + MAIN_ID; + + // 2. 遍历插件列表,并保存为插件实例 + for (PluginWrapper plugin : plugins) { + String pluginKey = plugin.getPluginId(); + + // 2.1 查找插件信息 + IotPluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); + if (pluginInfo == null) { + log.error("插件信息不存在,pluginKey = {}", pluginKey); + continue; + } + + // 2.2 情况一:如果插件实例不存在,则创建 + IotPluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, + pluginInfo.getId()); + // TODO @芋艿:稍后优化; +// if (pluginInstance == null) { +// // 4.4 如果插件实例不存在,则创建 +// pluginInstance = PluginInstanceDO.builder() +// .pluginId(pluginInfo.getId()) +// .mainId(MAIN_ID + "-" + mac) +// .hostIp(ip) +// .port(port) +// .heartbeatAt(System.currentTimeMillis()) +// .build(); +// pluginInstanceMapper.insert(pluginInstance); +// } else { +// // 2.2 情况二:如果存在,则更新 heartbeatAt +// PluginInstanceDO updatePluginInstance = PluginInstanceDO.builder() +// .id(pluginInstance.getId()) +// .heartbeatAt(System.currentTimeMillis()) +// .build(); +// pluginInstanceMapper.updateById(updatePluginInstance); +// } + } + } + + // ========== 设备与插件的映射操作 ========== + + @Override + public void updateDevicePluginInstanceProcessIdAsync(String deviceKey, String processId) { + devicePluginProcessIdRedisDAO.put(deviceKey, processId); + } + +} \ 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/plugin/PluginInfoService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java deleted file mode 100644 index 7e04555..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java +++ /dev/null @@ -1,88 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.plugin; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoPageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoSaveReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInfoDO; -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; -import jakarta.validation.Valid; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -/** - * 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 uploadFile(Long id, MultipartFile file); - - /** - * 更新插件的状态 - * - * @param id 插件id - * @param status 状态 {@link IotPluginStatusEnum} - */ - void updatePluginStatus(Long id, Integer status); - - /** - * 获得插件信息列表 - * - * @return 插件信息列表 - */ - List getPluginInfoList(); - - /** - * 根据状态获得插件信息列表 - * - * @param status 状态 {@link IotPluginStatusEnum} - * @return 插件信息列表 - */ - List getPluginInfoListByStatus(Integer status); -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java deleted file mode 100644 index 7d58f49..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java +++ /dev/null @@ -1,158 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.plugin; - -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.plugin.vo.info.PluginInfoPageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoSaveReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInfoDO; -import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper; -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.pf4j.spring.SpringPluginManager; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_DELETE_FAILED_RUNNING; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INFO_NOT_EXISTS; - -/** - * IoT 插件信息 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class PluginInfoServiceImpl implements PluginInfoService { - - @Resource - private PluginInfoMapper pluginInfoMapper; - - @Resource - private PluginInstanceService pluginInstanceService; - - @Resource - private SpringPluginManager pluginManager; - - @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) { - // 1.1 校验存在 - PluginInfoDO pluginInfoDO = validatePluginInfoExists(id); - // 1.2 停止插件 - if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { - throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING); - } - - // 2. 卸载插件 - pluginInstanceService.stopAndUnloadPlugin(pluginInfoDO.getPluginKey()); - - // 3. 删除插件文件 - pluginInstanceService.deletePluginFile(pluginInfoDO); - - // 4. 删除插件信息 - 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 uploadFile(Long id, MultipartFile file) { - // 1. 校验插件信息是否存在 - PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); - - // 2. 停止并卸载旧的插件 - pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); - - // 3 上传新的插件文件,更新插件启用状态文件 - String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); - - // 4. 更新插件信息 - updatePluginInfo(pluginInfoDo, pluginKeyNew, file); - } - - /** - * 更新插件信息 - * - * @param pluginInfoDo 插件信息 - * @param pluginKeyNew 插件标识符 - * @param file 文件 - */ - private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) { - // 创建新的插件信息对象并链式设置属性 - PluginInfoDO updatedPluginInfo = new PluginInfoDO() - .setId(pluginInfoDo.getId()) - .setPluginKey(pluginKeyNew) - .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) - .setFileName(file.getOriginalFilename()) - .setScript("") - .setConfigSchema(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()) - .setVersion(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion()) - .setDescription(pluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription()); - - // 执行更新 - pluginInfoMapper.updateById(updatedPluginInfo); - } - - @Override - public void updatePluginStatus(Long id, Integer status) { - // 1. 校验插件信息是否存在 - PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); - - // 2. 更新插件状态 - pluginInstanceService.updatePluginStatus(pluginInfoDo, status); - - // 3. 更新数据库中的插件状态 - PluginInfoDO updatedPluginInfo = new PluginInfoDO(); - updatedPluginInfo.setId(id); - updatedPluginInfo.setStatus(status); - pluginInfoMapper.updateById(updatedPluginInfo); - } - - @Override - public List getPluginInfoList() { - return pluginInfoMapper.selectList(); - } - - @Override - public List getPluginInfoListByStatus(Integer status) { - return pluginInfoMapper.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/plugin/PluginInstanceService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java deleted file mode 100644 index 4854b2c..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.plugin; - -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInfoDO; -import org.springframework.web.multipart.MultipartFile; - -/** - * IoT 插件实例 Service 接口 - * - * @author 芋道源码 - */ -public interface PluginInstanceService { - - // TODO @芋艿:这个是否应该放到 plugin 主动心跳,而是 server 自己心跳 - /** - * 上报插件实例(心跳) - */ - void reportPluginInstances(); - - /** - * 停止并卸载插件 - * - * @param pluginKey 插件标识符 - */ - void stopAndUnloadPlugin(String pluginKey); - - /** - * 删除插件文件 - * - * @param pluginInfoDo 插件信息 - */ - void deletePluginFile(PluginInfoDO pluginInfoDo); - - /** - * 上传并加载新的插件文件 - * - * @param file 插件文件 - * @return 插件标识符 - */ - String uploadAndLoadNewPlugin(MultipartFile file); - - /** - * 更新插件状态 - * - * @param pluginInfoDo 插件信息 - * @param status 新状态 - */ - void updatePluginStatus(PluginInfoDO pluginInfoDo, 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/plugin/PluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java deleted file mode 100644 index 8e6771f..0000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java +++ /dev/null @@ -1,194 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.plugin; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.net.NetUtil; -import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInfoDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.PluginInstanceDO; -import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInfoMapper; -import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper; -import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants; -import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.pf4j.PluginState; -import org.pf4j.PluginWrapper; -import org.pf4j.spring.SpringPluginManager; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.IOException; -import java.nio.file.*; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; - -/** - * IoT 插件实例 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class PluginInstanceServiceImpl implements PluginInstanceService { - - // TODO @haohao:mac@uuid - public static final String MAIN_ID = IdUtil.fastSimpleUUID(); - - @Resource - private PluginInfoMapper pluginInfoMapper; - - @Resource - private PluginInstanceMapper pluginInstanceMapper; - - @Resource - private SpringPluginManager pluginManager; - - @Value("${pf4j.pluginsDir}") - private String pluginsDir; - @Value("${server.port:48080}") - private int port; - - @Override - public void stopAndUnloadPlugin(String pluginKey) { - PluginWrapper plugin = pluginManager.getPlugin(pluginKey); - if (plugin == null) { - log.warn("插件不存在或已卸载: {}", pluginKey); - return; - } - if (plugin.getPluginState().equals(PluginState.STARTED)) { - pluginManager.stopPlugin(pluginKey); // 停止插件 - log.info("已停止插件: {}", pluginKey); - } - pluginManager.unloadPlugin(pluginKey); // 卸载插件 - log.info("已卸载插件: {}", pluginKey); - } - - @Override - public void deletePluginFile(PluginInfoDO pluginInfoDO) { - File file = new File(pluginsDir, pluginInfoDO.getFileName()); - if (!file.exists()) { - return; - } - try { - TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 - if (!file.delete()) { - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); - } - } catch (InterruptedException e) { - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); - } - } - - @Override - public String uploadAndLoadNewPlugin(MultipartFile file) { - String pluginKeyNew; - // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载 - Path pluginsPath = Paths.get(pluginsDir); - try { - FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录 - String filename = file.getOriginalFilename(); - if (filename != null) { - Path jarPath = pluginsPath.resolve(filename); - Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 - pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 - log.info("已加载插件: {}", pluginKeyNew); - } else { - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); - } - } catch (IOException e) { - log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e); - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); - } catch (Exception e) { - log.error("[uploadAndLoadNewPlugin][加载插件失败]", e); - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); - } - return pluginKeyNew; - } - - @Override - public void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) { - String pluginKey = pluginInfoDo.getPluginKey(); - PluginWrapper plugin = pluginManager.getPlugin(pluginKey); - - if (plugin == null) { - // 插件不存在且状态为停止,抛出异常 - if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { - throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID); - } - return; - } - - // 启动插件 - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) - && plugin.getPluginState() != PluginState.STARTED) { - pluginManager.startPlugin(pluginKey); - log.info("已启动插件: {}", pluginKey); - } - // 停止插件 - else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) - && plugin.getPluginState() == PluginState.STARTED) { - pluginManager.stopPlugin(pluginKey); - log.info("已停止插件: {}", pluginKey); - } - } - - @Override - public void reportPluginInstances() { - // 1.1 获取 pf4j 插件列表 - List plugins = pluginManager.getPlugins(); - - // 1.2 获取插件信息列表并转换为 Map 以便快速查找 - List pluginInfos = pluginInfoMapper.selectList(); - Map pluginInfoMap = pluginInfos.stream() - .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); - - // 1.3 获取本机 IP 和 MAC 地址,mac@uuid - String ip = NetUtil.getLocalhostStr(); - String mac = NetUtil.getLocalMacAddress(); - String mainId = mac + "@" + MAIN_ID; - - // 2. 遍历插件列表,并保存为插件实例 - for (PluginWrapper plugin : plugins) { - String pluginKey = plugin.getPluginId(); - - // 2.1 查找插件信息 - PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); - if (pluginInfo == null) { - log.error("插件信息不存在,pluginKey = {}", pluginKey); - continue; - } - - // 2.2 情况一:如果插件实例不存在,则创建 - PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, - pluginInfo.getId()); - if (pluginInstance == null) { - // 4.4 如果插件实例不存在,则创建 - pluginInstance = PluginInstanceDO.builder() - .pluginId(pluginInfo.getId()) - .mainId(MAIN_ID + "-" + mac) - .ip(ip) - .port(port) - .heartbeatAt(System.currentTimeMillis()) - .build(); - pluginInstanceMapper.insert(pluginInstance); - } else { - // 2.2 情况二:如果存在,则更新 heartbeatAt - PluginInstanceDO updatePluginInstance = PluginInstanceDO.builder() - .id(pluginInstance.getId()) - .heartbeatAt(System.currentTimeMillis()) - .build(); - pluginInstanceMapper.updateById(updatePluginInstance); - } - } - } - -} \ 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 index 007a417..df38e85 100644 --- 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 @@ -1,6 +1,6 @@ - + + + org.springframework.boot + spring-boot-starter-web + + io.vertx diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java deleted file mode 100644 index 8f0c5c7..0000000 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.iot.plugin.http; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; - -/** - * 独立运行入口 - */ -@Slf4j -@SpringBootApplication -public class HttpPluginSpringbootApplication { - - public static void main(String[] args) { - ConfigurableApplicationContext context = SpringApplication.run(HttpPluginSpringbootApplication.class, args); - - // 手动获取 VertxService 并启动 - // TODO @haohao:可以放在 bean 的 init 里么?回复:会和插件模式冲突 @芋艿,测试下 - // TODO @haohao:貌似去掉,没有问题额。。。 -// IotDeviceUpstreamServer vertxService = context.getBean(IotDeviceUpstreamServer.class); -// vertxService.start(); - - log.info("[main][独立模式启动完成]"); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java new file mode 100644 index 0000000..0aa0850 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.iot.plugin.http; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * 独立运行入口 + */ +@Slf4j +@SpringBootApplication +public class IotHttpPluginApplication { + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(IotHttpPluginApplication.class); + application.setWebApplicationType(WebApplicationType.NONE); + ConfigurableApplicationContext context = application.run(args); + + // 手动获取 VertxService 并启动 + // TODO @haohao:可以放在 bean 的 init 里么?回复:会和插件模式冲突 @芋艿,测试下 + // TODO @haohao:貌似去掉,没有问题额。。。 +// IotDeviceUpstreamServer vertxService = context.getBean(IotDeviceUpstreamServer.class); +// vertxService.start(); + + log.info("[main][独立模式启动完成]"); + } + +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDevicePropertyReportVertxHandler.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDevicePropertyReportVertxHandler.java index e35d13b..6bf600b 100644 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDevicePropertyReportVertxHandler.java +++ b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDevicePropertyReportVertxHandler.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO; import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; import io.vertx.core.Handler; import io.vertx.ext.web.RequestBody; import io.vertx.ext.web.RoutingContext; @@ -50,19 +51,17 @@ public class IotDevicePropertyReportVertxHandler implements Handler) requestBody.asJsonObject().getMap().get("properties"))); -- libgit2 0.21.4