pluginDescriptor = new ArrayList<>();
+ this.process(((properties, map) -> {
+ pluginDescriptor.add(PluginDescriptorUtils.propertiesToStorage(properties));
+ }));
+
+ return pluginDescriptor.get(0);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroup.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroup.java
new file mode 100644
index 0000000..1a54110
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroup.java
@@ -0,0 +1,38 @@
+package cd.casic.plugin.annotation;
+
+
+import cd.casic.plugin.constants.OpsConstants;
+
+import java.lang.annotation.*;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.annotation
+ * @Project:ops
+ * @name:AdminGroup
+ * @Date:2024/03/18 17:06
+ * @Filename:AdminGroup
+ * @description:自定义菜单Group注解
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.ANNOTATION_TYPE})
+@Documented
+public @interface AdminGroup {
+ /** 菜单名称 */
+ String name();
+
+ /** 菜单组id */
+ String groupId();
+
+ /** 菜单图标 */
+ String icon() default "fa-circle-o";
+
+ /** 菜单url */
+ String url() default "";
+
+ /** 菜单角色 */
+ String[] role() default {OpsConstants.ROLE_ADMIN};
+
+ /** 菜单序号 */
+ int seq() default 99;
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroups.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroups.java
new file mode 100644
index 0000000..191c5f4
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/AdminGroups.java
@@ -0,0 +1,20 @@
+package cd.casic.plugin.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.annotation
+ * @Project:ops
+ * @name:AdminGroups
+ * @Date:2024/03/18 17:05
+ * @Filename:AdminGroups
+ * @description:自定义菜单groups注解
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+public @interface AdminGroups {
+ /** 菜单组 */
+ AdminGroup[] groups();
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/InterceptPath.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/InterceptPath.java
new file mode 100644
index 0000000..88b8171
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/InterceptPath.java
@@ -0,0 +1,22 @@
+package cd.casic.plugin.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.annotation
+ * @Project:ops
+ * @name:InterceptPath
+ * @Date:2024/03/18 16:09
+ * @Filename:InterceptPath
+ * @description:插件拦截器
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface InterceptPath {
+ /**
+ * 拦截的路径
+ */
+ String[] value();
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/PluginConfiguration.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/PluginConfiguration.java
new file mode 100644
index 0000000..e6a94ec
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/annotation/PluginConfiguration.java
@@ -0,0 +1,17 @@
+package cd.casic.plugin.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 用于标记插件内部的独立配置类
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PluginConfiguration {
+ String fileName() default ""; // 插件配置文件的文件名
+
+ String deploySuffix() default ""; // 插件配置文件在deployment模式下的后缀
+
+ String devSuffix() default ""; // 插件配置文件在development模式下的后缀
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/config/OpsPluginConfig.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/config/OpsPluginConfig.java
new file mode 100644
index 0000000..28ceb18
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/config/OpsPluginConfig.java
@@ -0,0 +1,29 @@
+package cd.casic.plugin.config;
+
+import cd.casic.plugin.PluginManager;
+import cd.casic.plugin.PluginProperties;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 配置类:用于定义和管理插件系统的Bean
+ */
+@Configuration
+@EnableConfigurationProperties({PluginProperties.class})
+public class OpsPluginConfig {
+ /**
+ * 定义并创建一个PluginManager Bean
+ *
+ * 此Bean负责管理和操作插件系统,仅在没有已定义的PluginManager Bean时创建
+ *
+ * @param pluginProperties 插件系统的属性配置,用于初始化PluginManager
+ * @return 初始化并配置好的PluginManager实例
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public PluginManager pluginManager(PluginProperties pluginProperties) {
+ return new PluginManager(pluginProperties);
+ }
+}
\ No newline at end of file
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/OpsConstants.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/OpsConstants.java
new file mode 100644
index 0000000..a896d40
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/OpsConstants.java
@@ -0,0 +1,53 @@
+package cd.casic.plugin.constants;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.constants
+ * @Project:ops
+ * @name:OpsConstants
+ * @Date:2024/03/18 9:30
+ * @Filename:OpsConstants
+ * @description:Todo
+ */
+public class OpsConstants {
+ /**
+ * 插件目录
+ */
+ public static final String PLUGIN_PATH = "/plugins";
+ /**
+ * 插件路径
+ */
+ public static final String PLUGINS_DIR = "resources/plugins";
+ /**
+ * 插件静态资源目录
+ */
+ public static final String PLUGINS_RESOURCES_DIR = "resources/pluginResources";
+ //public static final String PLUGINS_RESOURCES_DIR = "ops-module-plugins/ops-module-plugins-example-web/src/main/resources/static";
+
+ /**
+ * 角色:管理员
+ */
+ public static final String ROLE_ADMIN = "admin";
+ /**
+ * 角色:用户
+ */
+ public static final String ROLE_USER = "user";
+ /**
+ * 角色:编辑
+ */
+ public static final String ROLE_EDITOR = "editor";
+
+
+ /**
+ * todo 区分插件类型,目前只支持两种,一种为zip ,一种为jar包形式,主要使用zip形式,别面jar包被人轻易破解
+ */
+ public static class Suffix {
+
+ private Suffix() {
+ throw new IllegalStateException();
+ }
+
+ public static final String JAR = "jar";
+ public static final String ZIP = "zip";
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/PluginBuildTypeEnum.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/PluginBuildTypeEnum.java
new file mode 100644
index 0000000..62d34d8
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/constants/PluginBuildTypeEnum.java
@@ -0,0 +1,27 @@
+package cd.casic.plugin.constants;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.constants
+ * @Project:ops
+ * @name:PluginBuildTypeEnum
+ * @Date:2024/03/21 19:53
+ * @Filename:PluginBuildTypeEnum
+ * @description:插件构建方式区分
+ */
+public enum PluginBuildTypeEnum {
+
+ /**
+ * 构建
+ */
+ BUILD,
+ /**
+ * 注册
+ */
+ REGISTER,
+ /**
+ * 卸载
+ */
+ UNREGISTER
+
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppReadyEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppReadyEvent.java
new file mode 100644
index 0000000..9d7a715
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppReadyEvent.java
@@ -0,0 +1,20 @@
+package cd.casic.plugin.event;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.event
+ * @Project:ops
+ * @name:OpsMainAppReadyEvent
+ * @Date:2024/03/22 10:29
+ * @Filename:OpsMainAppReadyEvent
+ * @description:主程序启动完毕,此事件发布到插件应用程序,此时,插件还未启动
+ */
+public class OpsMainAppReadyEvent extends ApplicationEvent {
+
+ public OpsMainAppReadyEvent(ApplicationContext mainApplicationContext) {
+ super(mainApplicationContext);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppStartedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppStartedEvent.java
new file mode 100644
index 0000000..2f798c6
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/OpsMainAppStartedEvent.java
@@ -0,0 +1,20 @@
+package cd.casic.plugin.event;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.event
+ * @Project:ops
+ * @name:OpsMainAppStartedEvent
+ * @Date:2024/03/22 10:31
+ * @Filename:OpsMainAppStartedEvent
+ * @description:主程序启动完毕,此事件发布到插件应用程序,此时,原神启动
+ */
+public class OpsMainAppStartedEvent extends ApplicationEvent {
+
+ public OpsMainAppStartedEvent(ApplicationContext mainApplicationContext) {
+ super(mainApplicationContext);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginDirCheckEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginDirCheckEvent.java
new file mode 100644
index 0000000..7a1972b
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginDirCheckEvent.java
@@ -0,0 +1,13 @@
+package cd.casic.plugin.event;
+
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+@Getter
+public class PluginDirCheckEvent extends ApplicationEvent {
+
+ public PluginDirCheckEvent(Object source) {
+ super(source);
+ }
+
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginLoadedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginLoadedEvent.java
new file mode 100644
index 0000000..96e6a9f
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginLoadedEvent.java
@@ -0,0 +1,30 @@
+package cd.casic.plugin.event;
+
+import cd.casic.plugin.PluginCache;
+import cd.casic.plugin.PluginClassLoaderCache;
+import cd.casic.plugin.PluginInfo;
+import cd.casic.plugin.PluginManager;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+@Getter
+public class PluginLoadedEvent extends ApplicationEvent {
+
+ private final PluginManager pluginManager;
+
+ private final PluginInfo pluginInfo;
+
+ public PluginLoadedEvent(Object source, PluginInfo pluginInfo) {
+ super(source);
+ this.pluginManager = (PluginManager) source;
+ this.pluginInfo = pluginInfo;
+
+ if (!PluginCache.isExist(pluginInfo.getPluginId())) {
+ PluginCache.put(pluginInfo.getPluginId(), pluginInfo);
+ }
+ if (PluginClassLoaderCache.getPlugin(pluginInfo.getPluginId()) == null) {
+ PluginClassLoaderCache.put(pluginInfo.getPluginId(), pluginInfo.getPluginWrapper().getPluginClassLoader());
+ }
+ }
+
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginRestartedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginRestartedEvent.java
new file mode 100644
index 0000000..8933a3f
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginRestartedEvent.java
@@ -0,0 +1,19 @@
+package cd.casic.plugin.event;
+
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.event
+ * @Project:ops
+ * @name:PluginRestartedEvent
+ * @Date:2024/03/22 10:51
+ * @Filename:PluginRestartedEvent
+ * @description:插件重启,在插件管理器中重启插件,和同目录上面的OpsMian****的事件不同
+ */
+public class PluginRestartedEvent extends ApplicationEvent {
+
+ public PluginRestartedEvent(Object source) {
+ super(source);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStartedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStartedEvent.java
new file mode 100644
index 0000000..b8ad5ca
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStartedEvent.java
@@ -0,0 +1,20 @@
+package cd.casic.plugin.event;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.event
+ * @Project:ops
+ * @name:PluginStartedEvent
+ * @Date:2024/03/22 10:57
+ * @Filename:PluginStartedEvent
+ * @description:发布到插件管理器
+ */
+public class PluginStartedEvent extends ApplicationEvent {
+
+ public PluginStartedEvent(ApplicationContext pluginApplicationContext) {
+ super(pluginApplicationContext);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStateChangedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStateChangedEvent.java
new file mode 100644
index 0000000..9f6527c
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStateChangedEvent.java
@@ -0,0 +1,19 @@
+package cd.casic.plugin.event;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.event
+ * @Project:ops
+ * @name:PluginStateChangedEvent
+ * @Date:2024/03/22 11:02
+ * @Filename:PluginStateChangedEvent
+ * @description:插件状态,这个是考虑到插件的更新
+ */
+public class PluginStateChangedEvent extends ApplicationEvent {
+ public PluginStateChangedEvent(ApplicationContext mainApplicationContext) {
+ super(mainApplicationContext);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStoppedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStoppedEvent.java
new file mode 100644
index 0000000..5c76b01
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginStoppedEvent.java
@@ -0,0 +1,19 @@
+package cd.casic.plugin.event;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.event
+ * @Project:ops
+ * @name:PluginStoppedEvent
+ * @Date:2024/03/22 11:05
+ * @Filename:PluginStoppedEvent
+ * @description:插件停止,停止后应该删除一切内容
+ */
+public class PluginStoppedEvent extends ApplicationEvent {
+ public PluginStoppedEvent(ApplicationContext pluginApplicationContext) {
+ super(pluginApplicationContext);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginUnloadedEvent.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginUnloadedEvent.java
new file mode 100644
index 0000000..ab30894
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/event/PluginUnloadedEvent.java
@@ -0,0 +1,26 @@
+package cd.casic.plugin.event;
+
+import cd.casic.plugin.*;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationEvent;
+
+@Getter
+@Slf4j
+public class PluginUnloadedEvent extends ApplicationEvent {
+
+ private final PluginManager pluginManager;
+
+ private final PluginInfo pluginInfo;
+
+ public PluginUnloadedEvent(Object source, PluginInfo pluginInfo) {
+ super(source);
+ this.pluginManager = (PluginManager) source;
+ this.pluginInfo = pluginInfo;
+
+ String startErrorPluginId = this.pluginInfo.getPluginId();
+ PluginCache.remove(startErrorPluginId);
+ PluginClassLoaderCache.remove(startErrorPluginId);
+ PluginApplicationContextManager.removePluginApplicationContext(startErrorPluginId);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/execption/PluginException.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/execption/PluginException.java
new file mode 100644
index 0000000..c665cdf
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/execption/PluginException.java
@@ -0,0 +1,29 @@
+package cd.casic.plugin.execption;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.execption
+ * @Project:ops
+ * @name:PluginException
+ * @Date:2024/03/21 19:54
+ * @Filename:PluginException
+ * @description:毫无疑问,看这个名字就知道是插件异常类
+ */
+public class PluginException extends RuntimeException {
+
+ public PluginException() {
+ super();
+ }
+
+ public PluginException(String s) {
+ super(s);
+ }
+
+ public PluginException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PluginException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsJarPluginRepository.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsJarPluginRepository.java
new file mode 100644
index 0000000..a9cc4dd
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsJarPluginRepository.java
@@ -0,0 +1,55 @@
+package cd.casic.plugin.pf4j;
+
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.JarPluginRepository;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Slf4j
+public class OpsJarPluginRepository extends JarPluginRepository {
+
+ public OpsJarPluginRepository(Path... pluginsRoots) {
+ this(Arrays.asList(pluginsRoots));
+ }
+
+ public OpsJarPluginRepository(List pluginsRoots) {
+ super(pluginsRoots);
+ }
+
+ @Override
+ public List getPluginPaths() {
+ List pluginPaths = new ArrayList<>();
+ for (Path pluginRoot : pluginsRoots){
+ File rootDir = pluginRoot.toFile();
+
+ scanForJars(rootDir.toPath(), pluginPaths);
+
+ }
+ return pluginPaths;
+ }
+
+ private void scanForJars(Path directory, List pluginPaths) {
+ try (DirectoryStream stream = Files.newDirectoryStream(directory)) {
+ for (Path entry : stream) {
+ if (Files.isDirectory(entry)) {
+ // 如果是目录,递归进入下一层
+ scanForJars(entry, pluginPaths);
+ } else if (entry.getFileName().toString().endsWith(".jar")) {
+ // 如果是.jar文件,添加到结果列表
+ pluginPaths.add(entry);
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("Error scanning directory " + directory + ": " + e.getMessage());
+ }
+ }
+
+
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginDescriptor.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginDescriptor.java
new file mode 100644
index 0000000..6fbef82
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginDescriptor.java
@@ -0,0 +1,20 @@
+package cd.casic.plugin.pf4j;
+
+import lombok.Getter;
+import org.pf4j.DefaultPluginDescriptor;
+
+import java.util.List;
+
+@Getter
+public class OpsPluginDescriptor extends DefaultPluginDescriptor {
+ private final String configFileName;
+ private final List configFileActive;
+
+ public OpsPluginDescriptor(String pluginId, String pluginDescription, String pluginClass,
+ String version, String requires, String provider, String license,
+ String configFileName, List configFileActive){
+ super(pluginId, pluginDescription, pluginClass, version, requires, provider, license);
+ this.configFileActive = configFileActive;
+ this.configFileName = configFileName;
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginStatusProvider.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginStatusProvider.java
new file mode 100644
index 0000000..e83b393
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPluginStatusProvider.java
@@ -0,0 +1,50 @@
+package cd.casic.plugin.pf4j;
+
+import cd.casic.plugin.PluginProperties;
+import cn.hutool.core.collection.CollectionUtil;
+import org.pf4j.PluginStatusProvider;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class OpsPluginStatusProvider implements PluginStatusProvider {
+
+ private final Set enabledPlugins = new HashSet<>();
+ private final Set disabledPlugins = new HashSet<>();
+
+ public OpsPluginStatusProvider(PluginProperties pluginProperties) {
+ this.enabledPlugins.addAll(pluginProperties.getEnabledPlugins());
+ this.disabledPlugins.addAll(pluginProperties.getDisabledPlugins());
+ }
+
+ public static boolean isPropertySet(PluginProperties pluginProperties) {
+ return CollectionUtil.isNotEmpty(pluginProperties.getEnabledPlugins())
+ || CollectionUtil.isNotEmpty(pluginProperties.getDisabledPlugins());
+ }
+
+ @Override
+ public boolean isPluginDisabled(String pluginId) {
+ if (disabledPlugins.contains(pluginId)) {
+ return true;
+ }
+ return !enabledPlugins.isEmpty() && !enabledPlugins.contains(pluginId);
+ }
+
+ @Override
+ public void disablePlugin(String pluginId) {
+ if (isPluginDisabled(pluginId)) {
+ return;
+ }
+ disabledPlugins.add(pluginId);
+ enabledPlugins.remove(pluginId);
+ }
+
+ @Override
+ public void enablePlugin(String pluginId) {
+ if (!isPluginDisabled(pluginId)) {
+ return;
+ }
+ enabledPlugins.add(pluginId);
+ disabledPlugins.remove(pluginId);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPropertiesPluginDescriptorFinder.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPropertiesPluginDescriptorFinder.java
new file mode 100644
index 0000000..fe89008
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsPropertiesPluginDescriptorFinder.java
@@ -0,0 +1,60 @@
+package cd.casic.plugin.pf4j;
+
+import cd.casic.plugin.PluginDescriptorCache;
+import cd.casic.plugin.PluginDescriptorStorage;
+import cd.casic.plugin.utils.PluginDescriptorUtils;
+import org.pf4j.DevelopmentPluginClasspath;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginRuntimeException;
+import org.pf4j.PropertiesPluginDescriptorFinder;
+import org.pf4j.util.FileUtils;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+public class OpsPropertiesPluginDescriptorFinder extends PropertiesPluginDescriptorFinder {
+ static final DevelopmentPluginClasspath PLUGIN_CLASSPATH = new DevelopmentPluginClasspath();
+
+ public OpsPropertiesPluginDescriptorFinder(){
+ super();
+ }
+
+ public OpsPropertiesPluginDescriptorFinder(String propertiesFileName){
+ super(propertiesFileName);
+ }
+
+ @Override
+ protected Path getPropertiesPath(Path pluginPath, String propertiesFileName) {
+ if (Files.isDirectory(pluginPath)) {
+ for (String location : PLUGIN_CLASSPATH.getClassesDirectories()) {
+ Path path = pluginPath.resolve(location).resolve(propertiesFileName);
+ Resource propertyResource = new FileSystemResource(path);
+ if (propertyResource.exists()) {
+ return path;
+ }
+ }
+ throw new PluginRuntimeException(
+ "Unable to find plugin descriptor file: " + DEFAULT_PROPERTIES_FILE_NAME);
+ }
+
+ // it's a zip or jar file
+ try {
+ return FileUtils.getPath(pluginPath, propertiesFileName);
+ } catch (IOException e) {
+ throw new PluginRuntimeException(e);
+ }
+ }
+
+ @Override
+ public PluginDescriptor find(Path pluginPath) {
+ Properties properties = readProperties(pluginPath);
+ PluginDescriptorStorage storage = PluginDescriptorUtils.propertiesToStorage(properties);
+ PluginDescriptorCache.put(storage.getPluginId(), storage);
+ return PluginDescriptorUtils.storageToDescriptor(storage);
+ }
+
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsYamlPluginDescriptorFinder.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsYamlPluginDescriptorFinder.java
new file mode 100644
index 0000000..96b5bc8
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/pf4j/OpsYamlPluginDescriptorFinder.java
@@ -0,0 +1,64 @@
+package cd.casic.plugin.pf4j;
+
+import cd.casic.plugin.PluginDescriptorCache;
+import cd.casic.plugin.PluginDescriptorStorage;
+import cd.casic.plugin.PluginYamlProcessor;
+import cd.casic.plugin.utils.PluginDescriptorUtils;
+import cd.casic.plugin.utils.YamlUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginDescriptorFinder;
+import org.pf4j.PluginRuntimeException;
+import org.pf4j.util.FileUtils;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@Slf4j
+public class OpsYamlPluginDescriptorFinder implements PluginDescriptorFinder {
+ public static final String DEFAULT_YAML_FILE_NAME = "plugin.yaml";
+
+ private final String yamlFileName;
+
+ public OpsYamlPluginDescriptorFinder() {
+ this(DEFAULT_YAML_FILE_NAME);
+ }
+
+ public OpsYamlPluginDescriptorFinder(String yamlFileName) {
+ this.yamlFileName = yamlFileName;
+ }
+
+ @Override
+ public boolean isApplicable(Path pluginPath) {
+ return Files.exists(pluginPath)
+ && (Files.isDirectory(pluginPath)
+ || FileUtils.isJarFile(pluginPath));
+ }
+
+ @Override
+ public PluginDescriptor find(Path pluginPath) {
+ Path yamlPath = null;
+ try {
+ yamlPath = YamlUtils.getYamlPath(pluginPath, yamlFileName);
+ if (yamlPath == null) {
+ throw new PluginRuntimeException("Cannot find the plugin manifest path");
+ }
+ log.debug("Lookup plugin descriptor in '{}'", yamlPath);
+ if (Files.notExists(yamlPath)) {
+ throw new PluginRuntimeException("Cannot find '{}' path", yamlPath);
+ }
+
+ Resource yamlResource = new FileSystemResource(yamlPath);
+ PluginYamlProcessor pluginYamlProcessor = new PluginYamlProcessor(yamlResource);
+ PluginDescriptorStorage pluginDescriptor = pluginYamlProcessor.loadYaml();
+ PluginDescriptorCache.put(pluginDescriptor.getPluginId(), pluginDescriptor);
+ OpsPluginDescriptor defaultPluginDescriptor = PluginDescriptorUtils.storageToDescriptor(pluginDescriptor);
+ pluginDescriptor.getDependencies().forEach(defaultPluginDescriptor::addDependency);
+ return defaultPluginDescriptor;
+ } finally {
+ FileUtils.closePath(yamlPath);
+ }
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/AnnotationHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/AnnotationHandler.java
new file mode 100644
index 0000000..f0e53d5
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/AnnotationHandler.java
@@ -0,0 +1,145 @@
+package cd.casic.plugin.register;
+
+import cd.casic.plugin.BasePlugin;
+import cd.casic.plugin.PluginClassLoaderCache;
+import cd.casic.plugin.PluginInfo;
+import cd.casic.plugin.annotation.AdminGroups;
+import cd.casic.plugin.annotation.InterceptPath;
+import cd.casic.plugin.register.group.AnnotationUtils;
+import cd.casic.plugin.register.group.ClassGroupHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.annotations.Mapper;
+import org.pf4j.PluginWrapper;
+import org.pf4j.util.FileUtils;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Controller;
+import org.springframework.stereotype.Repository;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.register
+ * @Project:ops
+ * @name:AnnotationHandler
+ * @Date:2024/03/18 15:59
+ * @Filename:AnnotationHandler
+ * @deprecated 不再使用,改用{@link ClassGroupHandler} 实现扫包后分组存储。
+ */
+@Slf4j
+@Deprecated
+public class AnnotationHandler implements BasePluginHandler {
+ private final static Class>[] REGISTER_ANNO = {
+ Bean.class,
+ Mapper.class,
+ Service.class,
+ Component.class,
+ Repository.class,
+ Controller.class,
+ Configuration.class,
+ RestController.class,
+ InterceptPath.class,
+ };
+
+ @Override
+ public void initialize() throws Exception {
+
+ }
+
+ @Override
+ public void registry(PluginInfo plugin) throws Exception {
+ List> classList = new ArrayList<>();
+ List> adminGroupsClassList = new ArrayList<>();
+ Set classPackageName = scanClassPackageName(plugin.getBasePlugin().scanPackage(), plugin.getBasePlugin().getWrapper());
+ for (String packageName : classPackageName) {
+ ClassLoader loader = PluginClassLoaderCache.getPlugin(plugin.getPluginId());
+ log.info("Load class {} using classloader {} for plugin {}", packageName, PluginClassLoaderCache.getPlugin(plugin.getPluginId()), plugin.getPluginId());
+ Class> clazz = loader.loadClass(packageName);
+// Class> clazz = plugin.getPluginWrapper().getPluginClassLoader().loadClass(packageName);
+// log.info("Load class {} using classloader {} for plugin {}", packageName, plugin.getPluginWrapper().getPluginClassLoader(), plugin.getPluginId());
+ if (!BasePlugin.class.isAssignableFrom(clazz)) {
+ classList.add(clazz);
+ }
+ AdminGroups annotation = clazz.getAnnotation(AdminGroups.class);
+ if (annotation != null) {
+ adminGroupsClassList.add(clazz);
+ }
+ }
+ plugin.setClassList(classList);
+ plugin.setAdminGroupsClassList(adminGroupsClassList);
+ List> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList());
+ if (!pluginClassList.isEmpty()) {
+ List> registryClassList = new ArrayList<>();
+ for (Class> aClass : pluginClassList) {
+ // 原本这里使用Collections.disjoint()实现,是有问题的,结果总是true。
+ // 因为aClass.getAnnotations()获得的是Proxy数组,而REGIS_ANNO是Class数组
+ if(AnnotationUtils.hasAnnotations(aClass, false, Bean.class,
+ Mapper.class,
+ Service.class,
+ Component.class,
+ Repository.class,
+ Controller.class,
+ Configuration.class,
+ RestController.class,
+ InterceptPath.class)){
+ registryClassList.add(aClass);
+ }
+ }
+ if (!registryClassList.isEmpty()) {
+ plugin.getPluginApplicationContext().register(registryClassList.toArray(new Class[0]));
+ // plugin.getBeanCache().putAll(BeanUtils.getTempBeans(registryClassList));
+ }
+ }
+ }
+
+ @Override
+ public void unRegistry(PluginInfo plugin) throws Exception {
+ // 不做操作,直接通过关闭PluginApplicationContext完成注销
+ }
+
+ /**
+ * 扫描jar包中的类。
+ *
+ * @param basePackage 包名
+ * @param pluginWrapper jar的PluginWrapper
+ * @return 类全路径
+ * @throws IOException 扫描异常
+ */
+ public static Set scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException {
+ Path pluginPath = pluginWrapper.getPluginPath();
+ Set classPackageNames = new HashSet<>();
+ File jarFile = null;
+ if(Files.isDirectory(pluginPath)){
+ List jars = FileUtils.getJars(pluginPath);
+ jarFile = jars.get(0);
+ }else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){
+ jarFile = pluginPath.toFile();
+ }else {
+ throw new RuntimeException("不正确的pluginPath");
+ }
+ try (JarFile jar = new JarFile(jarFile)) {
+ Enumeration jarEntries = jar.entries();
+ while (jarEntries.hasMoreElements()) {
+ JarEntry entry = jarEntries.nextElement();
+ String jarEntryName = entry.getName();
+ if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(basePackage)) {
+ String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
+ classPackageNames.add(className);
+ }
+ }
+ }
+ return classPackageNames;
+ }
+
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ApplicationContextPluginHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ApplicationContextPluginHandler.java
new file mode 100644
index 0000000..36b783e
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ApplicationContextPluginHandler.java
@@ -0,0 +1,54 @@
+package cd.casic.plugin.register;
+
+import cd.casic.plugin.PluginInfo;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+
+import java.util.Arrays;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.register
+ * @Project:ops
+ * @name:ApplicationContextPluginHandler
+ * @Date:2024/03/19 14:48
+ * @Filename:ApplicationContextPluginHandler
+ * @description:Todo
+ */
+public class ApplicationContextPluginHandler implements BasePluginHandler {
+ @Override
+ public void initialize() throws Exception {
+
+ }
+
+ @Override
+ public void registry(PluginInfo plugin) throws Exception {
+ if (plugin.getApplicationContextIsRefresh()) {
+ return;
+ }
+ plugin.getPluginApplicationContext().setClassLoader(plugin.getPluginWrapper().getPluginClassLoader());
+ plugin.getPluginApplicationContext().getDefaultListableBeanFactory()
+ .registerSingleton(plugin.getPluginWrapper().getPluginId().trim(),
+ plugin.getPluginWrapper().getPlugin());
+ plugin.getPluginApplicationContext().refresh();
+ plugin.setApplicationContextIsRefresh(true);
+ }
+
+ @Override
+ public void unRegistry(PluginInfo plugin) throws Exception {
+ // 获取插件ApplicationContext的DefaultListableBeanFactory实例
+ DefaultListableBeanFactory beanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory();
+
+ // 根据插件ID获取Bean的名称
+// String beanName = plugin.getPluginWrapper().getPluginId().trim();
+//
+// // 删除已注册的Bean
+// if (beanFactory.containsBeanDefinition(beanName)) {
+// beanFactory.removeBeanDefinition(beanName);
+// }
+
+ String[] beanNames = beanFactory.getBeanNamesForType(plugin.getPluginWrapper().getPlugin().getClass());
+ Arrays.stream(beanNames)
+ .filter(beanName -> beanName.equals(plugin.getPluginWrapper().getPluginId().trim()))
+ .forEach(beanFactory::destroySingleton);
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/BasePluginHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/BasePluginHandler.java
new file mode 100644
index 0000000..be8e9eb
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/BasePluginHandler.java
@@ -0,0 +1,40 @@
+package cd.casic.plugin.register;
+
+
+import cd.casic.plugin.PluginInfo;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.register
+ * @Project:ops
+ * @name:BasePluginHandler
+ * @Date:2024/03/18 15:48
+ * @Filename:BasePluginHandler
+ * @description:基础插件注册类
+ */
+public interface BasePluginHandler {
+
+ /**
+ * 插件组件初始化
+ *
+ * @throws Exception
+ */
+ void initialize() throws Exception;
+
+ /**
+ * 插件组件注册
+ *
+ * @param plugin
+ * @throws Exception
+ */
+ void registry(PluginInfo plugin) throws Exception;
+
+ /**
+ * 插件组件卸载注册
+ *
+ * @param plugin
+ * @throws Exception
+ */
+ void unRegistry(PluginInfo plugin) throws Exception;
+
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ControllerHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ControllerHandler.java
new file mode 100644
index 0000000..a928343
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/ControllerHandler.java
@@ -0,0 +1,169 @@
+package cd.casic.plugin.register;
+
+import cd.casic.plugin.PluginInfo;
+import cd.casic.plugin.PluginProperties;
+import cd.casic.plugin.register.group.filter.impl.ControllerFilter;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.*;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.register
+ * @Project:ops
+ * @name:ControllerHandler
+ * @Date:2024/03/19 14:22
+ * @Filename:ControllerHandler
+ * @description:Todo
+ */
+@Slf4j
+public class ControllerHandler implements BasePluginHandler {
+
+ RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+ Method getMappingForMethod;
+
+ @Override
+ public void initialize() throws Exception {
+
+ // 这里反射获取 getMappingForMethod
+ requestMappingHandlerMapping = SpringUtil.getBean(RequestMappingHandlerMapping.class);
+ getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class);
+ getMappingForMethod.setAccessible(true);
+ }
+
+ @Override
+ public void registry(PluginInfo plugin) throws Exception {
+ ApplicationContext applicationContext = plugin.getMainApplicationContext();
+ PluginProperties pluginProperties = applicationContext.getBean(PluginProperties.class);
+ for (Class> aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) {
+ setPathPrefix(plugin.getPluginId(), aClass, pluginProperties);
+ plugin.addController(aClass);
+ Object bean = plugin.getPluginApplicationContext().getBean(aClass);
+ Method[] methods = aClass.getMethods();
+ for (Method method : methods) {
+ if (method.getAnnotation(RequestMapping.class) != null
+ || method.getAnnotation(GetMapping.class) != null
+ || method.getAnnotation(PostMapping.class) != null
+ || method.getAnnotation(DeleteMapping.class) != null
+ || method.getAnnotation(PutMapping.class) != null
+ || method.getAnnotation(PatchMapping.class) != null) {
+ RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
+ requestMappingHandlerMapping.registerMapping(requestMappingInfo, bean, method);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void unRegistry(PluginInfo plugin) throws Exception {
+ for (RequestMappingInfo requestMappingInfo : getRequestMappingInfo(plugin)) {
+ requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
+ }
+ }
+
+ List getRequestMappingInfo(PluginInfo plugin) throws Exception {
+ List requestMappingInfoList = new ArrayList<>();
+ for (Class> aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) {
+ Method[] methods = aClass.getMethods();
+ for (Method method : methods) {
+ RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
+ requestMappingInfoList.add(requestMappingInfo);
+ }
+ }
+ return requestMappingInfoList;
+ }
+
+
+ /**
+ * 设置请求路径前缀
+ *
+ * @param aClass controller 类
+ */
+ private void setPathPrefix(String pluginId, Class> aClass, PluginProperties pluginProperties) {
+ RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);
+ if (requestMapping == null) {
+ return;
+ }
+ String pathPrefix = pluginProperties.getRestPathPrefix();
+ if (pluginProperties.isEnablePluginIdAsRestPrefix()) {
+ if (StrUtil.isNotEmpty(pathPrefix)) {
+ pathPrefix = joiningPath(pathPrefix, pluginId);
+ } else {
+ pathPrefix = pluginId;
+ }
+ } else {
+ if (StrUtil.isEmpty(pathPrefix)) {
+ // 不启用插件id作为路径前缀, 并且路径前缀为空, 则直接返回。
+ return;
+ }
+ }
+ handleRequestMapping(requestMapping, pathPrefix);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void handleRequestMapping(RequestMapping requestMapping, String pathPrefix) {
+ InvocationHandler invocationHandler = Proxy.getInvocationHandler(requestMapping);
+ Set definePaths = new HashSet<>();
+ definePaths.addAll(Arrays.asList(requestMapping.path()));
+ definePaths.addAll(Arrays.asList(requestMapping.value()));
+ try {
+ Field field = invocationHandler.getClass().getDeclaredField("memberValues");
+ field.setAccessible(true);
+ Map memberValues = (Map) field.get(invocationHandler);
+ String[] newPath = new String[definePaths.size()];
+ int i = 0;
+ for (String definePath : definePaths) {
+ // 解决插件启用、禁用后, 路径前缀重复的问题。
+ if (definePath.contains(pathPrefix)) {
+ newPath[i++] = definePath;
+ } else {
+ newPath[i++] = joiningPath(pathPrefix, definePath);
+ }
+ }
+ if (newPath.length == 0) {
+ newPath = new String[]{pathPrefix};
+ }
+ memberValues.put("path", newPath);
+ memberValues.put("value", new String[]{});
+ } catch (Exception e) {
+ log.error("Define Plugin RestController pathPrefix error : {}", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 拼接路径
+ *
+ * @param path1 路径1
+ * @param path2 路径2
+ * @return 拼接的路径
+ */
+ private String joiningPath(String path1, String path2) {
+ if (path1 != null && path2 != null) {
+ if (path1.endsWith("/") && path2.startsWith("/")) {
+ return path1 + path2.substring(1);
+ } else if (!path1.endsWith("/") && !path2.startsWith("/")) {
+ return path1 + "/" + path2;
+ } else {
+ return path1 + path2;
+ }
+ } else if (path1 != null) {
+ return path1;
+ } else if (path2 != null) {
+ return path2;
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/MybatisHandler.java b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/MybatisHandler.java
new file mode 100644
index 0000000..8dc6f41
--- /dev/null
+++ b/framework/spring-boot-starter-plugin/src/main/java/cd/casic/plugin/register/MybatisHandler.java
@@ -0,0 +1,163 @@
+package cd.casic.plugin.register;
+
+import cd.casic.plugin.PluginInfo;
+import cd.casic.plugin.register.mybatis.mybatisplus.MybatisPlusHandler;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.executor.ErrorContext;
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.defaults.DefaultSqlSession;
+import org.mybatis.spring.mapper.MapperFactoryBean;
+import org.pf4j.util.FileUtils;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.util.ClassUtils;
+
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+
+/**
+ * @Author:mianbin
+ * @Package:cd.casic.plugin.register
+ * @Project:ops
+ * @name:MybatisHandler
+ * @Date:2024/03/19 14:51
+ * @Filename:MybatisHandler
+ * @deprecated 不再使用,采用{@link MybatisPlusHandler} 统一实现
+ */
+@Deprecated
+public class MybatisHandler implements BasePluginHandler {
+
+ @Override
+ public void initialize() throws Exception {
+
+ }
+
+ @Override
+ public void registry(PluginInfo plugin) throws Exception {
+ List> mapperClassList = getMapperList(plugin);
+ if (mapperClassList.isEmpty()) return;
+
+ //注册mapper
+ for (Class> mapperClass : mapperClassList) {
+ GenericBeanDefinition definition = new GenericBeanDefinition();
+ definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass);
+ definition.setBeanClass(MapperFactoryBean.class);
+ definition.getPropertyValues().add("addToConfig", true);
+ definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
+ plugin.getPluginApplicationContext().registerBeanDefinition(mapperClass.getName(), definition);
+ }
+ //注册mapper.xml
+ SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) plugin.getMainApplicationContext().getBean("sqlSessionFactory");
+ Configuration configuration = sqlSessionFactory.getConfiguration();
+ try {
+ Resources.setDefaultClassLoader(plugin.getPluginWrapper().getPluginClassLoader());
+ Path pluginPath = plugin.getPluginWrapper().getPluginPath();
+ String xmlLocationPattern = plugin.getMapperXmlDir();
+ xmlLocationPattern = xmlLocationPattern.replaceAll("\\*\\*", "<>").replaceAll("\\*", "<>")
+ .replaceAll("\\.", "\\.").replaceAll("<>", ".*");
+ File jarFile = null;
+ if(Files.isDirectory(pluginPath)){
+ List jars = FileUtils.getJars(pluginPath);
+ jarFile = jars.get(0);
+ }else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){
+ jarFile = pluginPath.toFile();
+ }else {
+ throw new RuntimeException("不正确的pluginPath");
+ }
+ Enumeration jarEntries = new JarFile(jarFile).entries();
+ while (jarEntries.hasMoreElements()) {
+ JarEntry entry = jarEntries.nextElement();
+ String jarEntryName = entry.getName();
+ if (Pattern.matches(xmlLocationPattern, jarEntryName) && jarEntryName.endsWith(".xml")) {
+ URL url = new URL("jar:file:" + jarFile.getAbsolutePath() + "!/" + jarEntryName);
+ JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
+ InputStream in = jarConnection.getInputStream();
+ try {
+ XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(in,
+ configuration, url.getPath(), configuration.getSqlFragments());
+ xmlMapperBuilder.parse();
+ in.close();
+ } catch (Exception e) {
+ throw new Exception("Failed to parse mapping resource: '" + url.getPath() + "'", e);
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ ErrorContext.instance().reset();
+ JarFile currJarFile = jarConnection.getJarFile();
+ currJarFile.close();
+ }
+ }
+ }
+ } finally {
+ Resources.setDefaultClassLoader(ClassUtils.getDefaultClassLoader());
+ }
+ }
+
+ @Override
+ public void unRegistry(PluginInfo plugin) throws Exception {
+ List> mapperClassList = getMapperList(plugin);
+ if (mapperClassList.isEmpty()) return;
+
+ for (Class> mapperClass : mapperClassList) {
+ String[] beanNames = plugin.getPluginApplicationContext().getBeanNamesForType(mapperClass);
+ Arrays.stream(beanNames).forEach(beanName -> plugin.getPluginApplicationContext().removeBeanDefinition(beanName));
+ }
+ SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) plugin.getMainApplicationContext().getBean("sqlSessionFactory");
+ Configuration configuration = sqlSessionFactory.getConfiguration();
+ clearValues(configuration, "mappedStatements");
+ clearValues(configuration, "caches");
+ clearValues(configuration, "resultMaps");
+ clearValues(configuration, "parameterMaps");
+ clearValues(configuration, "keyGenerators");
+ clearValues(configuration, "sqlFragments");
+ // Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");
+ // loadedResourcesField.setAccessible(true);
+ // ((Set>) loadedResourcesField.get(configuration)).clear();
+ }
+
+ /**
+ * @description 获取所有Mapper接口
+ * @author dolphin
+ * @date 2021/11/13 8:31
+ */
+ private List> getMapperList(PluginInfo plugin){
+ List> mapperClassList = new ArrayList<>();
+
+ for (Class> aClass : plugin.getClassList()) {
+ Mapper annotation = aClass.getAnnotation(Mapper.class);
+ if (annotation != null) {
+ mapperClassList.add(aClass);
+ }
+ }
+ return mapperClassList;
+ }
+
+ private void clearValues(Configuration configuration, String fieldName) throws Exception {
+
+ Field field = configuration.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Map, ?> map = (Map, ?>) field.get(configuration);
+ DefaultSqlSession.StrictMap