Browse Source

【功能新增】IoT:增加插件支持,包含插件API和示例控制器

安浩浩 8 months ago
parent
commit
290fcd94d5
18 changed files with 637 additions and 92 deletions
  1. 1 0
      yudao-module-iot/pom.xml
  2. 8 0
      yudao-module-iot/yudao-module-iot-biz/pom.xml
  3. 40 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/Greetings.java
  4. 134 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java
  5. 34 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/WhazzupGreeting.java
  6. 8 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java
  7. 2 4
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java
  8. 48 72
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java
  9. 30 0
      yudao-module-iot/yudao-module-iot-plugin-api/pom.xml
  10. 27 0
      yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java
  11. 1 1
      yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
  12. 5 0
      yudao-module-iot/yudao-module-iot-plugin/plugin.properties
  13. 133 15
      yudao-module-iot/yudao-module-iot-plugin/pom.xml
  14. 37 0
      yudao-module-iot/yudao-module-iot-plugin/src/main/assembly/assembly.xml
  15. 22 0
      yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/IoTHttpPluginController.java
  16. 33 0
      yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/IoTPlugin.java
  17. 16 0
      yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/IotPluginConfig.java
  18. 58 0
      yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/WelcomePlugin.java

+ 1 - 0
yudao-module-iot/pom.xml

@@ -11,6 +11,7 @@
         <module>yudao-module-iot-api</module>
         <module>yudao-module-iot-biz</module>
         <module>yudao-module-iot-plugin</module>
+        <module>yudao-module-iot-plugin-api</module>
     </modules>
     <modelVersion>4.0.0</modelVersion>
 

+ 8 - 0
yudao-module-iot/yudao-module-iot-biz/pom.xml

@@ -25,6 +25,13 @@
             <version>${revision}</version>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-iot-plugin-api</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
@@ -75,6 +82,7 @@
             <groupId>org.pf4j</groupId>
             <artifactId>pf4j-spring</artifactId>
         </dependency>
+
     </dependencies>
 
 </project>

+ 40 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/Greetings.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
+
+import cn.iocoder.yudao.module.iot.api.Greeting;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 打招呼 测试用例
+ */
+@Component
+public class Greetings {
+
+    @Autowired
+    private List<Greeting> greetings;
+
+    public void printGreetings() {
+        System.out.printf("找到扩展点的 %d 个扩展 '%s'%n", greetings.size(), Greeting.class.getName());
+        for (Greeting greeting : greetings) {
+            System.out.println(">>> " + greeting.getGreeting());
+        }
+    }
+
+}

+ 134 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java

@@ -0,0 +1,134 @@
+package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
+
+import jakarta.annotation.Resource;
+import org.pf4j.spring.SpringPluginManager;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.security.PermitAll;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 插件 Controller 测试用例
+ */
+@RestController
+@RequestMapping("/iot/plugins")
+public class PluginController {
+
+    @Resource
+    private ApplicationContext applicationContext;
+    @Resource
+    private SpringPluginManager springPluginManager;
+    @Resource
+    private Greetings greetings;
+
+    @Value("${pf4j.pluginsDir}")
+    private String pluginsDir;
+
+    /**
+     * 上传插件 JAR 文件并加载插件
+     *
+     * @param file 上传的 JAR 文件
+     * @return 上传结果
+     */
+    @PermitAll
+    @PostMapping("/upload")
+    public ResponseEntity<String> uploadPlugin(@RequestParam("file") MultipartFile file) {
+        if (file.isEmpty()) {
+            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件为空");
+        }
+
+        // 确保插件目录存在
+        Path pluginsPath = Paths.get(pluginsDir);
+        try {
+            if (!Files.exists(pluginsPath)) {
+                Files.createDirectories(pluginsPath);
+            }
+
+            // 保存上传的 JAR 文件到插件目录
+            String filename = file.getOriginalFilename();
+            if (filename == null || (!filename.endsWith(".jar") && !filename.endsWith(".zip"))) {
+                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("上传的文件不是 JAR 或 ZIP 文件");
+            }
+
+            Path jarPath = pluginsPath.resolve(filename);
+
+            Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
+
+            // 加载插件
+            String pluginId = springPluginManager.loadPlugin(jarPath.toAbsolutePath());
+
+            // 启动插件
+            springPluginManager.startPlugin(pluginId);
+
+            return ResponseEntity.ok("插件上传并加载成功");
+        } catch (IOException e) {
+            e.printStackTrace();
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传插件时发生错误: " + e.getMessage());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("加载插件时发生错误: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 卸载指定插件
+     *
+     * @param pluginId 插件 ID
+     * @return 卸载结果
+     */
+    @PermitAll
+    @DeleteMapping("/unload/{pluginId}")
+    public ResponseEntity<String> unloadPlugin(@PathVariable String pluginId) {
+        if (springPluginManager.getPlugins().stream().noneMatch(plugin -> plugin.getDescriptor().getPluginId().equals(pluginId))) {
+            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("插件未加载: " + pluginId);
+        }
+
+        springPluginManager.stopPlugin(pluginId);
+        springPluginManager.unloadPlugin(pluginId);
+
+        // 删除插件 JAR 文件(可选)
+//            PluginWrapper plugin = pluginManager.getPlugin(pluginId);
+//            PluginDescriptor descriptor = plugin.getDescriptor();
+//            Path jarPath = Paths.get(pluginsDir).resolve(descriptor.getPluginId() + ".jar");
+//            Files.deleteIfExists(jarPath);
+
+        return ResponseEntity.ok("插件卸载成功: " + pluginId);
+    }
+
+    /**
+     * 列出所有已加载的插件
+     *
+     * @return 插件列表
+     */
+    @PermitAll
+    @GetMapping("/list")
+    public ResponseEntity<List<String>> listPlugins() {
+        List<String> plugins = springPluginManager.getPlugins().stream()
+                .map(plugin -> plugin.getDescriptor().getPluginId())
+                .collect(Collectors.toList());
+        return ResponseEntity.ok(plugins);
+    }
+
+    /**
+     * 打印问候语
+     *
+     * @return 1
+     */
+    @PermitAll
+    @GetMapping("/printGreetings")
+    public ResponseEntity<Integer> printGreetings() {
+        greetings.printGreetings();
+        return ResponseEntity.ok(1);
+    }
+}

+ 34 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/WhazzupGreeting.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
+
+import cn.iocoder.yudao.module.iot.api.Greeting;
+import org.pf4j.Extension;
+import org.springframework.stereotype.Component;
+
+/**
+ * 打招呼 测试用例
+ */
+@Extension
+@Component
+public class WhazzupGreeting implements Greeting {
+
+    @Override
+    public String getGreeting() {
+        return "Whazzup";
+    }
+
+}

+ 8 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java

@@ -1,8 +1,10 @@
 package cn.iocoder.yudao.module.iot.framework.plugin;
 
+import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.Greetings;
 import org.pf4j.spring.SpringPluginManager;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.DependsOn;
 
 @Configuration
 public class SpringConfiguration {
@@ -12,4 +14,10 @@ public class SpringConfiguration {
         return new SpringPluginManager();
     }
 
+    @Bean
+    @DependsOn("pluginManager")
+    public Greetings greetings() {
+        return new Greetings();
+    }
+
 }

+ 2 - 4
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java

@@ -7,8 +7,6 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
 import jakarta.validation.Valid;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.InputStream;
-
 /**
  * IoT 插件信息 Service 接口
  *
@@ -57,7 +55,7 @@ public interface PluginInfoService {
     /**
      * 上传插件的 JAR 包
      *
-     * @param id 插件id
+     * @param id   插件id
      * @param file 文件
      */
     void uploadJar(Long id, MultipartFile file);
@@ -65,7 +63,7 @@ public interface PluginInfoService {
     /**
      * 更新插件的状态
      *
-     * @param id 插件id
+     * @param id     插件id
      * @param status 状态
      */
     void updatePluginStatus(Long id, Integer status);

+ 48 - 72
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java

@@ -17,13 +17,18 @@ import org.pf4j.PluginDescriptor;
 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.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
@@ -47,6 +52,9 @@ public class PluginInfoServiceImpl implements PluginInfoService {
     @Resource
     private FileApi fileApi;
 
+    @Value("${pf4j.pluginsDir}")
+    private String pluginsDir;
+
     @Override
     public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
         // 插入
@@ -132,7 +140,7 @@ public class PluginInfoServiceImpl implements PluginInfoService {
         // 4. 上传插件
         String pluginIdNew;
         try {
-            String path = fileApi.createFile(IoUtil.readBytes(file.getInputStream()));
+            String path = fileApi.createFile(pluginsDir, IoUtil.readBytes(file.getInputStream()));
             Path pluginPath = Path.of(path);
             pluginIdNew = pluginManager.loadPlugin(pluginPath);
         } catch (Exception e) {
@@ -144,10 +152,31 @@ public class PluginInfoServiceImpl implements PluginInfoService {
             throw exception(PLUGIN_INSTALL_FAILED);
         }
 
-
         // 5. 读取配置文件和脚本
         String configJson = "";
         String script = "";
+        try (JarFile jarFile = new JarFile(pluginWrapper.getPluginPath().toFile())) {
+            // 5.1 获取config文件在jar包中的路径
+            String configFile = "classes/config.json";
+            JarEntry configEntry = jarFile.getJarEntry(configFile);
+
+            if (configEntry != null) {
+                // 5.2 读取配置文件
+                configJson = IoUtil.readUtf8(jarFile.getInputStream(configEntry));
+                log.info("configJson:{}", configJson);
+            }
+
+            // 5.3 读取script.js脚本
+            String scriptFile = "classes/script.js";
+            JarEntry scriptEntity = jarFile.getJarEntry(scriptFile);
+            if (scriptEntity != null) {
+                // 5.4 读取脚本文件
+                script = IoUtil.readUtf8(jarFile.getInputStream(scriptEntity));
+                log.info("script:{}", script);
+            }
+        } catch (Exception e) {
+            throw exception(PLUGIN_INSTALL_FAILED);
+        }
 
         pluginInfoDo.setPluginId(pluginIdNew);
         pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
@@ -159,48 +188,6 @@ public class PluginInfoServiceImpl implements PluginInfoService {
         pluginInfoDo.setVersion(pluginDescriptor.getVersion());
         pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
         pluginInfoMapper.updateById(pluginInfoDo);
-
-
-        // 5. 读取配置文件和脚本
-//        String configJson = "";
-        //       String script = "";
-//        try (JarFile jarFile = new JarFile(pluginInfoUpdate.getPluginPath())) {
-//            // 5.1 获取config文件在jar包中的路径
-//            String configFile = "classes/config.json";
-//            JarEntry configEntry = jarFile.getJarEntry(configFile);
-//
-//            if (configEntry != null) {
-//                // 5.2 读取配置文件
-//                configJson = IoUtil.read(jarFile.getInputStream(configEntry), Charset.defaultCharset());
-//                log.info("configJson:{}", configJson);
-//            }
-//
-//            // 5.3 读取script.js脚本
-//            String scriptFile = "classes/script.js";
-//            JarEntry scriptEntity = jarFile.getJarEntry(scriptFile);
-//            if (scriptEntity != null) {
-//                // 5.4 读取脚本文件
-//                script = IoUtil.read(jarFile.getInputStream(scriptEntity), Charset.defaultCharset());
-//                log.info("script:{}", script);
-//            }
-//        } catch (Exception e) {
-//            throw exception(PLUGIN_INSTALL_FAILED);
-//        }
-
-
-//        PluginState pluginState = pluginInfoUpdate.getPluginState();
-//        if (pluginState == PluginState.STARTED) {
-//            pluginInfoDo.setStatus(IotPluginStatusEnum.RUNNING.getStatus());
-//        }
-//        pluginInfoDo.setPluginId(pluginInfoUpdate.getPluginId());
-//        pluginInfoDo.setFile(file.getOriginalFilename());
-//        pluginInfoDo.setConfigSchema(configJson);
-//        pluginInfoDo.setScript(script);
-//
-//        PluginDescriptor pluginDescriptor = pluginInfoUpdate.getPluginDescriptor();
-//        pluginInfoDo.setVersion(pluginDescriptor.getPluginVersion());
-//        pluginInfoDo.setDescription(pluginDescriptor.getDescription());
-//        pluginInfoMapper.updateById(pluginInfoDo);
     }
 
     @Override
@@ -213,26 +200,22 @@ public class PluginInfoServiceImpl implements PluginInfoService {
             throw exception(PLUGIN_STATUS_INVALID);
         }
 
-        // 插件包为空
-//        String pluginId = pluginInfoDo.getPluginId();
-//        if (StrUtil.isBlank(pluginId)) {
-//            throw exception(PLUGIN_INFO_NOT_EXISTS);
-//        }
-//        com.gitee.starblues.core.PluginInfo pluginInfo = pluginOperator.getPluginInfo(pluginId);
-//        if (pluginInfo != null) {
-//            if (pluginInfoDo.getStatus().equals(IotPluginStatusEnum.RUNNING.getStatus()) && pluginInfo.getPluginState() != PluginState.STARTED) {
-//                // 启动插件
-//                pluginOperator.start(pluginId);
-//            } else if (pluginInfoDo.getStatus().equals(IotPluginStatusEnum.STOPPED.getStatus()) && pluginInfo.getPluginState() == PluginState.STARTED) {
-//                // 停止插件
-//                pluginOperator.stop(pluginId);
-//            }
-//        } else {
-//            // 已经停止,未获取到插件
-//            if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
-//                throw exception(PLUGIN_STATUS_INVALID);
-//            }
-//        }
+        String pluginId = pluginInfoDo.getPluginId();
+        PluginWrapper plugin = pluginManager.getPlugin(pluginId);
+        if (plugin != null) {
+            if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) {
+                // 启动插件
+                pluginManager.startPlugin(pluginId);
+            } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) {
+                // 停止插件
+                pluginManager.stopPlugin(pluginId);
+            }
+        } else {
+            // 已经停止,未获取到插件
+            if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
+                throw exception(PLUGIN_STATUS_INVALID);
+            }
+        }
         pluginInfoDo.setStatus(status);
         pluginInfoMapper.updateById(pluginInfoDo);
     }
@@ -244,20 +227,13 @@ public class PluginInfoServiceImpl implements PluginInfoService {
 
     @SneakyThrows
     private void startPlugins() {
-//        while (!pluginOperator.inited()) {
-//            Thread.sleep(1000L);
-//        }
-
         for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
             if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
                 continue;
             }
             log.info("start plugin:{}", pluginInfoDO.getPluginId());
             try {
-//                com.gitee.starblues.core.PluginInfo plugin = pluginOperator.getPluginInfo(pluginInfoDO.getPluginId());
-//                if (plugin != null) {
-//                    pluginOperator.start(plugin.getPluginId());
-//                }
+                pluginManager.startPlugin(pluginInfoDO.getPluginId());
             } catch (Exception e) {
                 log.error("start plugin error", e);
             }

+ 30 - 0
yudao-module-iot/yudao-module-iot-plugin-api/pom.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>cn.iocoder.boot</groupId>
+    <artifactId>yudao-module-iot-plugin-api</artifactId>
+    <version>0.0.1</version>
+    <packaging>jar</packaging>
+
+    <name>${project.artifactId}</name>
+    <description>
+        物联网 模块插件 API,暴露给其它模块调用
+    </description>
+
+    <properties>
+        <pf4j-spring.version>0.9.0</pf4j-spring.version>
+    </properties>
+
+    <dependencies>
+        <!-- PF4J Spring 集成 -->
+        <dependency>
+            <groupId>org.pf4j</groupId>
+            <artifactId>pf4j-spring</artifactId>
+            <version>${pf4j-spring.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 27 - 0
yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.iocoder.yudao.module.iot.api;
+
+import org.pf4j.ExtensionPoint;
+
+/**
+ * @author Decebal Suiu
+ */
+public interface Greeting extends ExtensionPoint {
+
+    String getGreeting();
+
+}

+ 1 - 1
yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/package-info.java → yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java

@@ -3,4 +3,4 @@
  *
  * TODO 芋艿:后续删除
  */
-package cn.iocoder.yudao.module.iot.plugin;
+package cn.iocoder.yudao.module.iot.api;

+ 5 - 0
yudao-module-iot/yudao-module-iot-plugin/plugin.properties

@@ -0,0 +1,5 @@
+plugin.id=iot-plugin-hppt
+plugin.class=cn.iocoder.yudao.module.iot.plugin.WelcomePlugin
+plugin.version=0.0.1
+plugin.provider=ahh
+plugin.dependencies=

+ 133 - 15
yudao-module-iot/yudao-module-iot-plugin/pom.xml

@@ -1,31 +1,149 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>yudao-module-iot</artifactId>
-        <groupId>cn.iocoder.boot</groupId>
-        <version>${revision}</version>
-    </parent>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
+         http://maven.apache.org/POM/4.0.0
+         http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
+    <groupId>cn.iocoder.boot</groupId>
     <artifactId>yudao-module-iot-plugin</artifactId>
+    <version>0.0.1</version>
     <packaging>jar</packaging>
 
     <name>${project.artifactId}</name>
-    <description>
-        物联网 模块 - 插件
-    </description>
+    <description>物联网 模块 - 插件</description>
+
+    <properties>
+        <!-- 插件相关 -->
+        <plugin.id>iot-plugin-http</plugin.id>
+        <plugin.class>cn.iocoder.yudao.module.iot.plugin.WelcomePlugin</plugin.class>
+        <plugin.version>0.0.1</plugin.version>
+        <plugin.provider>ahh</plugin.provider>
+        <plugin.dependencies/>
+
+        <!-- Maven 相关 -->
+        <java.version>17</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <maven-antrun-plugin.version>1.6</maven-antrun-plugin.version>
+        <maven-assembly-plugin.version>2.3</maven-assembly-plugin.version>
+        <maven-jar-plugin.version>2.4</maven-jar-plugin.version>
+        <pf4j-spring.version>0.9.0</pf4j-spring.version>
+        <!-- 看看咋放到 bom 里 -->
+        <spring.boot.version>3.3.1</spring.boot.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
 
     <dependencies>
+        <!-- 其他依赖项 -->
         <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-common</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <version>${spring.boot.version}</version>
+            <scope>provided</scope>
         </dependency>
-
+        <!-- PF4J Spring 集成 -->
         <dependency>
             <groupId>org.pf4j</groupId>
             <artifactId>pf4j-spring</artifactId>
+            <version>${pf4j-spring.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <!-- 项目依赖 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-iot-plugin-api</artifactId>
+            <version>${project.version}</version>
         </dependency>
     </dependencies>
 
-</project>
+    <build>
+        <plugins>
+            <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>properties-maven-plugin</artifactId>
+                <version>1.0-alpha-2</version>
+                <executions>
+                  <execution>
+                    <phase>initialize</phase>
+                    <goals>
+                      <goal>read-project-properties</goal>
+                    </goals>
+                    <configuration>
+                      <files>
+                        <file>plugin.properties</file>
+                      </files>
+                    </configuration>
+                  </execution>
+                </executions>
+            </plugin>
+            -->
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <version>${maven-antrun-plugin.version}</version>
+                <executions>
+                    <execution>
+                        <id>unzip jar file</id>
+                        <phase>package</phase>
+                        <configuration>
+                            <target>
+                                <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}" dest="target/plugin-classes" />
+                            </target>
+                        </configuration>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>${maven-assembly-plugin.version}</version>
+                <configuration>
+                    <descriptors>
+                        <descriptor>
+                            src/main/assembly/assembly.xml
+                        </descriptor>
+                    </descriptors>
+                    <appendAssemblyId>false</appendAssemblyId>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>attached</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>${maven-jar-plugin.version}</version>
+                <configuration>
+                    <archive>
+                        <manifestEntries>
+                            <Plugin-Id>${plugin.id}</Plugin-Id>
+                            <Plugin-Class>${plugin.class}</Plugin-Class>
+                            <Plugin-Version>${plugin.version}</Plugin-Version>
+                            <Plugin-Provider>${plugin.provider}</Plugin-Provider>
+                            <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 37 - 0
yudao-module-iot/yudao-module-iot-plugin/src/main/assembly/assembly.xml

@@ -0,0 +1,37 @@
+<!--
+ Describes the plugin archive
+
+ @author Decebal Suiu
+ @version 1.0
+-->
+<assembly>
+	<id>plugin</id>
+	<formats>
+		<format>zip</format>
+	</formats>
+	<includeBaseDirectory>false</includeBaseDirectory>
+	<dependencySets>
+		<dependencySet>
+            <useProjectArtifact>false</useProjectArtifact>
+            <scope>runtime</scope>
+			<outputDirectory>lib</outputDirectory>
+			<includes>
+				<include>*:jar:*</include>
+			</includes>
+		</dependencySet>
+	</dependencySets>
+    <!--
+    <fileSets>
+        <fileSet>
+            <directory>target/classes</directory>
+            <outputDirectory>classes</outputDirectory>
+        </fileSet>
+    </fileSets>
+    -->
+	<fileSets>
+		<fileSet>
+			<directory>target/plugin-classes</directory>
+			<outputDirectory>classes</outputDirectory>
+		</fileSet>
+	</fileSets>
+</assembly>

+ 22 - 0
yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/IoTHttpPluginController.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.iot.plugin;
+
+
+import org.pf4j.Extension;
+import org.pf4j.ExtensionPoint;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/iot/plugin-demo")
+@Extension
+public class IoTHttpPluginController implements ExtensionPoint {
+
+    @GetMapping("/greet")
+    public String greet() {
+        return "Hello from MyPlugin!";
+    }
+
+    @PostMapping("/message")
+    public void receiveMessage(@RequestBody String message) {
+        System.out.println("Received message: " + message);
+    }
+}

+ 33 - 0
yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/IoTPlugin.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.iot.plugin;
+
+import org.pf4j.PluginWrapper;
+import org.pf4j.spring.SpringPlugin;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+public class IoTPlugin extends SpringPlugin {
+    public IoTPlugin(PluginWrapper wrapper) {
+        super(wrapper);
+    }
+
+    @Override
+    public void start() {
+        System.out.println("IoTPlugin 启动");
+    }
+
+    @Override
+    public void stop() {
+        System.out.println("IoTPlugin 停止");
+        super.stop(); // to close applicationContext
+    }
+
+    @Override
+    protected ApplicationContext createApplicationContext() {
+        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
+        applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
+        applicationContext.register(IoTHttpPluginController.class); // 注册 IoTPluginConfig
+        applicationContext.refresh();
+        System.out.println("IoTPlugin 加载完成");
+        return applicationContext;
+    }
+}

+ 16 - 0
yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/IotPluginConfig.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.iot.plugin;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.boot.web.server.ConfigurableWebServerFactory;
+import org.springframework.context.annotation.Bean;
+
+@Configuration
+public class IoTPluginConfig {
+    
+    @Bean
+    public IoTHttpPluginController ioTHttpPluginController() {
+        return new IoTHttpPluginController();
+    }
+}

+ 58 - 0
yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/WelcomePlugin.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.iocoder.yudao.module.iot.plugin;
+
+import cn.iocoder.yudao.module.iot.api.Greeting;
+import org.apache.commons.lang.StringUtils;
+import org.pf4j.Extension;
+import org.pf4j.Plugin;
+import org.pf4j.PluginWrapper;
+import org.pf4j.RuntimeMode;
+
+/**
+ * 打招呼 测试用例
+ */
+public class WelcomePlugin extends Plugin {
+
+    public WelcomePlugin(PluginWrapper wrapper) {
+        super(wrapper);
+    }
+
+    @Override
+    public void start() {
+        System.out.println("WelcomePlugin.start()");
+        // for testing the development mode
+        if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
+            System.out.println(StringUtils.upperCase("WelcomePlugin"));
+        }
+    }
+
+    @Override
+    public void stop() {
+        System.out.println("WelcomePlugin.stop()");
+    }
+
+    @Extension
+    public static class WelcomeGreeting implements Greeting {
+
+        @Override
+        public String getGreeting() {
+            return "Welcome to PF4J";
+        }
+
+    }
+
+}