Explorar o código

【功能完善】IoT: 更新插件管理功能,重构插件标识符为 pluginKey,删除 PluginInstanceController,添加插件实例定时更新任务,优化插件信息获取接口。

安浩浩 hai 7 meses
pai
achega
24a660b5c2
Modificáronse 14 ficheiros con 174 adicións e 202 borrados
  1. 1 0
      plugins/enabled.txt
  2. 0 80
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInstanceController.java
  3. 3 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java
  4. 2 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java
  5. 2 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java
  6. 4 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java
  7. 6 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java
  8. 4 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java
  9. 24 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/listener/CustomPluginStateListener.java
  10. 29 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java
  11. 3 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java
  12. 26 29
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java
  13. 1 40
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java
  14. 69 39
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java

+ 1 - 0
plugins/enabled.txt

@@ -1 +1,2 @@
+http-plugin
 http-plugin@0.0.1

+ 0 - 80
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/PluginInstanceController.java

@@ -1,80 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.plugin;
-
-import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
-import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
-import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstancePageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstanceRespVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstanceSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
-import cn.iocoder.yudao.module.iot.service.plugin.PluginInstanceService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.annotation.Resource;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.Valid;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-import java.io.IOException;
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-
-@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<Long> createPluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO createReqVO) {
-        return success(pluginInstanceService.createPluginInstance(createReqVO));
-    }
-
-    @PutMapping("/update")
-    @Operation(summary = "更新IoT 插件实例")
-    @PreAuthorize("@ss.hasPermission('iot:plugin-instance:update')")
-    public CommonResult<Boolean> 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<Boolean> 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<PluginInstanceRespVO> 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<PageResult<PluginInstanceRespVO>> getPluginInstancePage(@Valid PluginInstancePageReqVO pageReqVO) {
-        PageResult<PluginInstanceDO> pageResult = pluginInstanceService.getPluginInstancePage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, PluginInstanceRespVO.class));
-    }
-
-}

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoRespVO.java

@@ -16,9 +16,9 @@ public class PluginInfoRespVO {
     @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 = "24627")
+    @ExcelProperty("插件包标识符")
+    private String pluginKey;
 
     @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
     @ExcelProperty("插件名称")

+ 2 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugin/vo/PluginInfoSaveReqVO.java

@@ -10,8 +10,8 @@ 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 = "24627")
+    private String pluginKey;
 
     @Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
     private String name;

+ 2 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininfo/PluginInfoDO.java

@@ -29,12 +29,10 @@ public class PluginInfoDO extends BaseDO {
      */
     @TableId
     private Long id;
-    // TODO @haohao:这个是不是改成类似 key 之类的字段哈?
-    // 回复:默认是 pluginId,可以不用改
     /**
-     * 插件包 ID
+     * 插件包标识符
      */
-    private String pluginId;
+    private String pluginKey;
     /**
      * 插件名称
      */

+ 4 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java

@@ -1,12 +1,13 @@
 package cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
 
-// TODO @haohao:一些必要的关联、枚举
+
 /**
  * IoT 插件实例 DO
  *
@@ -33,6 +34,8 @@ public class PluginInstanceDO extends BaseDO {
     private String mainId;
     /**
      * 插件id
+     * <p>
+     * 关联 {@link PluginInfoDO#getId()}
      */
     private Long pluginId;
     /**

+ 6 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/PluginInstanceMapper.java

@@ -15,6 +15,12 @@ import org.apache.ibatis.annotations.Mapper;
 @Mapper
 public interface PluginInstanceMapper extends BaseMapperX<PluginInstanceDO> {
 
+    default PluginInstanceDO selectByMainIdAndPluginId(String mainId, Long pluginId) {
+        return selectOne(new LambdaQueryWrapperX<PluginInstanceDO>()
+                .eq(PluginInstanceDO::getMainId, mainId)
+                .eq(PluginInstanceDO::getPluginId, pluginId));
+    }
+
     default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>()
                 .eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId())

+ 4 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.framework.plugin;
 
 import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
 import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
+import cn.iocoder.yudao.module.iot.framework.plugin.listener.CustomPluginStateListener;
 import lombok.extern.slf4j.Slf4j;
 import org.pf4j.spring.SpringPluginManager;
 import org.springframework.context.annotation.Bean;
@@ -30,7 +31,9 @@ public class UnifiedConfiguration {
     @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER)
     public SpringPluginManager pluginManager() {
         log.info("[init][实例化 SpringPluginManager]");
-        return new SpringPluginManager();
+        SpringPluginManager springPluginManager = new SpringPluginManager();
+        springPluginManager.addPluginStateListener(new CustomPluginStateListener());
+        return springPluginManager;
     }
 
 }

+ 24 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/listener/CustomPluginStateListener.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.iot.framework.plugin.listener;
+
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.PluginStateEvent;
+import org.pf4j.PluginStateListener;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class CustomPluginStateListener implements PluginStateListener {
+
+    @Override
+    public void pluginStateChanged(PluginStateEvent event) {
+        // 1. 获取插件ID
+        String pluginId = event.getPlugin().getPluginId();
+        // 2. 获取插件旧状态
+        String oldState = event.getOldState().toString();
+        // 3. 获取插件新状态
+        String newState = event.getPluginState().toString();
+        // 4. 打印日志信息
+        log.info("插件的状态 '{}' 已更改为 '{}' 至 '{}'", pluginId, oldState, newState);
+    }
+
+}

+ 29 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java

@@ -0,0 +1,29 @@
+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 org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 插件实例 Job
+ *
+ * @author 芋道源码
+ */
+@Component
+public class PluginInstancesJob {
+
+    @Resource
+    private PluginInstanceService pluginInstanceService;
+
+    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
+    public void updatePluginInstances() {
+        TenantUtils.executeIgnore(() -> {
+            pluginInstanceService.updatePluginInstances();
+        });
+    }
+}

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java

@@ -71,9 +71,9 @@ public interface PluginInfoService {
     void updatePluginStatus(Long id, Integer status);
 
     /**
-     * 获得启用的插件列表
+     * 获得插件信息列表
      *
-     * @return 插件列表-插件id
+     * @return 插件信息列表
      */
-    List<String> getEnabledPlugins();
+    List<PluginInfoDO> getPluginInfoList();
 }

+ 26 - 29
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java

@@ -76,7 +76,7 @@ public class PluginInfoServiceImpl implements PluginInfoService {
         }
 
         // 卸载插件
-        PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginId());
+        PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginKey());
         if (plugin != null) {
             // 查询插件是否是启动状态
             if (plugin.getPluginState().equals(PluginState.STARTED)) {
@@ -127,30 +127,30 @@ public class PluginInfoServiceImpl implements PluginInfoService {
         // 1. 校验插件信息是否存在
         PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
 
-        // 2. 获取插件 ID
-        String pluginId = pluginInfoDo.getPluginId();
+        // 2. 获取插件标识
+        String pluginKey = pluginInfoDo.getPluginKey();
 
         // 3. 停止并卸载旧的插件
-        stopAndUnloadPlugin(pluginId);
+        stopAndUnloadPlugin(pluginKey);
 
         // 4. 上传新的插件文件
-        String pluginIdNew = uploadAndLoadNewPlugin(file);
+        String pluginKeyNew = uploadAndLoadNewPlugin(file);
 
         // 5. 更新插件启用状态文件
-        updatePluginStatusFile(pluginIdNew, false);
+        updatePluginStatusFile(pluginKeyNew, false);
 
         // 6. 更新插件信息
-        updatePluginInfo(pluginInfoDo, pluginIdNew, file);
+        updatePluginInfo(pluginInfoDo, pluginKeyNew, file);
     }
 
     // 停止并卸载旧的插件
-    private void stopAndUnloadPlugin(String pluginId) {
-        PluginWrapper plugin = pluginManager.getPlugin(pluginId);
+    private void stopAndUnloadPlugin(String pluginKey) {
+        PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
         if (plugin != null) {
             if (plugin.getPluginState().equals(PluginState.STARTED)) {
-                pluginManager.stopPlugin(pluginId); // 停止插件
+                pluginManager.stopPlugin(pluginKey); // 停止插件
             }
-            pluginManager.unloadPlugin(pluginId); // 卸载插件
+            pluginManager.unloadPlugin(pluginKey); // 卸载插件
         }
     }
 
@@ -175,18 +175,18 @@ public class PluginInfoServiceImpl implements PluginInfoService {
     }
 
     // 更新插件状态文件
-    private void updatePluginStatusFile(String pluginIdNew, boolean isEnabled) {
+    private void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) {
         Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt");
         Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt");
         Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath;
         Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath;
 
         try {
-            PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew);
+            PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew);
             if (pluginWrapper == null) {
                 throw exception(PLUGIN_INSTALL_FAILED);
             }
-            String pluginInfo = pluginIdNew + "@" + pluginWrapper.getDescriptor().getVersion();
+            String pluginInfo = pluginKeyNew + "@" + pluginWrapper.getDescriptor().getVersion();
             List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath)
                     : new ArrayList<>();
             List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath)
@@ -209,13 +209,13 @@ public class PluginInfoServiceImpl implements PluginInfoService {
     }
 
     // 更新插件信息
-    private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginIdNew, MultipartFile file) {
-        pluginInfoDo.setPluginId(pluginIdNew);
+    private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginKeyNew, MultipartFile file) {
+        pluginInfoDo.setPluginKey(pluginKeyNew);
         pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
         pluginInfoDo.setFileName(file.getOriginalFilename());
         pluginInfoDo.setScript("");
 
-        PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginIdNew).getDescriptor();
+        PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginKeyNew).getDescriptor();
         pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription());
         pluginInfoDo.setVersion(pluginDescriptor.getVersion());
         pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
@@ -232,23 +232,23 @@ public class PluginInfoServiceImpl implements PluginInfoService {
             throw exception(PLUGIN_STATUS_INVALID);
         }
 
-        // 3. 获取插件ID和插件实例
-        String pluginId = pluginInfoDo.getPluginId();
-        PluginWrapper plugin = pluginManager.getPlugin(pluginId);
+        // 3. 获取插件标识和插件实例
+        String pluginKey = pluginInfoDo.getPluginKey();
+        PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
 
         // 4. 根据状态更新插件
         if (plugin != null) {
             // 4.1 如果目标状态是运行且插件未启动,则启动插件
             if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
                     && plugin.getPluginState() != PluginState.STARTED) {
-                pluginManager.startPlugin(pluginId);
-                updatePluginStatusFile(pluginId, true); // 更新插件状态文件为启用
+                pluginManager.startPlugin(pluginKey);
+                updatePluginStatusFile(pluginKey, true); // 更新插件状态文件为启用
             }
             // 4.2 如果目标状态是停止且插件已启动,则停止插件
             else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
                     && plugin.getPluginState() == PluginState.STARTED) {
-                pluginManager.stopPlugin(pluginId);
-                updatePluginStatusFile(pluginId, false); // 更新插件状态文件为禁用
+                pluginManager.stopPlugin(pluginKey);
+                updatePluginStatusFile(pluginKey, false); // 更新插件状态文件为禁用
             }
         } else {
             // 5. 插件不存在且状态为停止,抛出异常
@@ -263,11 +263,8 @@ public class PluginInfoServiceImpl implements PluginInfoService {
     }
 
     @Override
-    public List<String> getEnabledPlugins() {
-        return pluginInfoMapper.selectList().stream()
-                .filter(pluginInfoDO -> IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus()))
-                .map(PluginInfoDO::getPluginId)
-                .toList();
+    public List<PluginInfoDO> getPluginInfoList() {
+        return pluginInfoMapper.selectList(null);
     }
 
 }

+ 1 - 40
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java

@@ -1,11 +1,5 @@
 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.PluginInstancePageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstanceSaveReqVO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
-import jakarta.validation.Valid;
-
 /**
  * IoT 插件实例 Service 接口
  *
@@ -13,42 +7,9 @@ import jakarta.validation.Valid;
  */
 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<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO);
+    void updatePluginInstances();
 
 }

+ 69 - 39
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java

@@ -1,17 +1,19 @@
 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.PluginInstancePageReqVO;
-import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInstanceSaveReqVO;
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
 import cn.iocoder.yudao.module.iot.dal.mysql.plugin.PluginInstanceMapper;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+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 static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS;
+import java.util.List;
 
 /**
  * IoT 插件实例 Service 实现类
@@ -20,51 +22,79 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTAN
  */
 @Service
 @Validated
+@Slf4j
 public class PluginInstanceServiceImpl implements PluginInstanceService {
 
+    /**
+     * 主程序id
+     */
+    public static final String MAIN_ID = IdUtil.fastSimpleUUID();
+
+    @Resource
+    private PluginInfoService pluginInfoService;
     @Resource
     private PluginInstanceMapper pluginInstanceMapper;
+    @Resource
+    private SpringPluginManager pluginManager;
 
-    @Override
-    public Long createPluginInstance(PluginInstanceSaveReqVO createReqVO) {
-        // 插入
-        PluginInstanceDO pluginInstance = BeanUtils.toBean(createReqVO, PluginInstanceDO.class);
-        pluginInstanceMapper.insert(pluginInstance);
-        // 返回
-        return pluginInstance.getId();
-    }
+    @Value("${server.port:48080}")
+    private int port;
 
-    @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);
-    }
+    public void updatePluginInstances() {
+        // 1. 查询 pf4j 插件列表
+        List<PluginWrapper> plugins = pluginManager.getPlugins();
 
-    private void validatePluginInstanceExists(Long id) {
-        if (pluginInstanceMapper.selectById(id) == null) {
-            throw exception(PLUGIN_INSTANCE_NOT_EXISTS);
-        }
-    }
+        // 2. 查询插件信息列表
+        List<PluginInfoDO> pluginInfos = pluginInfoService.getPluginInfoList();
 
-    @Override
-    public PluginInstanceDO getPluginInstance(Long id) {
-        return pluginInstanceMapper.selectById(id);
+        // 动态获取主程序的 IP 和端口
+        String mainIp = getLocalIpAddress();
+
+        // 3. 遍历插件列表,并保存为插件实例
+        for (PluginWrapper plugin : plugins) {
+            String pluginKey = plugin.getPluginId();
+            PluginInfoDO pluginInfo = pluginInfos.stream()
+                    .filter(pluginInfoDO -> pluginInfoDO.getPluginKey().equals(pluginKey))
+                    .findFirst()
+                    .orElse(null);
+
+            // 4. 如果插件信息不存在,则跳过
+            if (pluginInfo == null) {
+                continue;
+            }
+
+            // 5. 查询插件实例
+            PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(MAIN_ID, pluginInfo.getId());
+
+            // 6. 如果插件实例不存在,则创建
+            if (pluginInstance == null) {
+                pluginInstance = new PluginInstanceDO();
+                pluginInstance.setPluginId(pluginInfo.getId());
+                pluginInstance.setMainId(MAIN_ID);
+                pluginInstance.setIp(mainIp);
+                pluginInstance.setPort(port);
+                pluginInstance.setHeartbeatAt(System.currentTimeMillis());
+                pluginInstanceMapper.insert(pluginInstance);
+            } else {
+                // 7. 如果插件实例存在,则更新
+                pluginInstance.setHeartbeatAt(System.currentTimeMillis());
+                pluginInstanceMapper.updateById(pluginInstance);
+            }
+        }
     }
 
-    @Override
-    public PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO) {
-        return pluginInstanceMapper.selectPage(pageReqVO);
+    private String getLocalIpAddress() {
+        try {
+            List<String> ipList = NetUtil.localIpv4s().stream()
+                    .filter(ip -> !ip.startsWith("0.0") && !ip.startsWith("127.") && !ip.startsWith("169.254") && !ip.startsWith("255.255.255.255"))
+                    .toList();
+            return ipList.isEmpty() ? "127.0.0.1" : ipList.get(0);
+        } catch (Exception e) {
+            log.error("获取本地IP地址失败", e);
+            return "127.0.0.1"; // 默认值
+        }
     }
 
 }