重新写,有点问题,和框架没有结合

This commit is contained in:
mianbin 2025-05-08 13:53:15 +08:00
parent 296ac900f7
commit 5a5952276a
86 changed files with 6 additions and 5882 deletions

View File

@ -21,7 +21,7 @@
<dependency> <dependency>
<groupId>cd.casic.boot</groupId> <groupId>cd.casic.boot</groupId>
<artifactId>spring-boot-starter-biz-data-permission</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency> <dependency>
@ -29,6 +29,11 @@
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cd.casic.boot</groupId>
<artifactId>spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency> <dependency>
<groupId>cd.casic.boot</groupId> <groupId>cd.casic.boot</groupId>
<artifactId>spring-boot-starter-mongo</artifactId> <artifactId>spring-boot-starter-mongo</artifactId>

View File

@ -1,29 +0,0 @@
package cd.casic.plugin;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @nameBasePlugin
* @Date2024/03/18 9:25
* @FilenameBasePlugin
* @description插件基类
*/
public abstract class BasePlugin extends Plugin {
public BasePlugin(PluginWrapper wrapper) {
super(wrapper);
}
/**
* 根据插件主类获取插件包
*
* @return String
*/
public String scanPackage() {
return this.getClass().getPackage().getName();
}
}

View File

@ -1,151 +0,0 @@
package cd.casic.plugin;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.server.ServerWebInputException;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
@Component
public class BufferedPluginBundleResource implements DisposableBean {
private final AtomicReference<FileSystemResource> jsBundle = new AtomicReference<>();
private final AtomicReference<FileSystemResource> cssBundle = new AtomicReference<>();
private final ReadWriteLock jsLock = new ReentrantReadWriteLock();
private final ReadWriteLock cssLock = new ReentrantReadWriteLock();
private Path tempDir;
public FileSystemResource getJsBundle(String version, List<DataBuffer> dataBuffers) {
String fileName = tempFileName(version, ".js");
jsLock.readLock().lock();
try {
FileSystemResource jsBundleResource = jsBundle.get();
if (getResourceIfNotChange(fileName, jsBundleResource) != null) {
return jsBundleResource;
}
} finally {
jsLock.readLock().unlock();
}
jsLock.writeLock().lock();
try {
FileSystemResource oldJsBundle = jsBundle.get();
FileSystemResource newJsBundle = writeBundle(fileName, dataBuffers);
jsBundle.compareAndSet(oldJsBundle, newJsBundle);
return newJsBundle;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
jsLock.writeLock().unlock();
}
}
public FileSystemResource getCssBundle(String version, List<DataBuffer> dataBuffers) {
String fileName = tempFileName(version, ".css");
try {
cssLock.readLock().lock();
FileSystemResource cssBundleResource = cssBundle.get();
if (getResourceIfNotChange(fileName, cssBundleResource) != null) {
return cssBundleResource;
}
} finally {
cssLock.readLock().unlock();
}
cssLock.writeLock().lock();
try {
FileSystemResource oldCssBundle = cssBundle.get();
FileSystemResource newCssBundle = writeBundle(fileName, dataBuffers);
cssBundle.compareAndSet(oldCssBundle, newCssBundle);
return newCssBundle;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
cssLock.writeLock().unlock();
}
}
@Nullable
private Resource getResourceIfNotChange(String fileName, Resource resource) {
if (resource != null && resource.exists() && fileName.equals(resource.getFilename())) {
return resource;
}
return null;
}
private FileSystemResource writeBundle(String fileName, List<DataBuffer> dataBuffers) throws IOException {
Path filePath = createTempFileToStore(fileName);
// 打开文件输出流
try (FileOutputStream fileOutputStream = new FileOutputStream(filePath.toFile());
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)) {
// 遍历 DataBuffer 列表并写入文件
for (DataBuffer dataBuffer : dataBuffers) {
// DataBuffer 转换为字节数组
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
// 写入文件
bufferedOutputStream.write(bytes);
// 释放 DataBuffer
DataBufferUtils.release(dataBuffer);
}
}
return new FileSystemResource(filePath);
}
private Path createTempFileToStore(String fileName) {
try {
if (tempDir == null || !Files.exists(tempDir)) {
this.tempDir = Files.createTempDirectory("ops-plugin-bundle");
}
Path path = tempDir.resolve(fileName);
log.info("Create temp file: {}", path);
Files.deleteIfExists(path);
return Files.createFile(path);
} catch (IOException e) {
throw new ServerWebInputException("Failed to create temp file.", null, e);
}
}
/**
* 生成临时文件名
*
* @param v
* @param suffix
* @return
*/
private String tempFileName(String v, String suffix) {
Assert.notNull(v, "Version must not be null");
Assert.notNull(suffix, "Suffix must not be null");
return v + suffix;
}
@Override
public void destroy() throws Exception {
if (tempDir != null && Files.exists(tempDir)) {
FileSystemUtils.deleteRecursively(tempDir);
}
this.jsBundle.set(null);
this.cssBundle.set(null);
}
}

View File

@ -1,106 +0,0 @@
package cd.casic.plugin;
import cd.casic.plugin.utils.FrontResourceUtils;
import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* 前端资源处理器将各个插件的css和js资源合并
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class FrontResourceProcessor {
@Autowired
private PluginManager pluginManager;
public List<DataBuffer> uglifyJsBundle() {
List<DataBuffer> dataBuffers = new ArrayList<>();
List<PluginWrapper> startedPlugins = pluginManager.getStartedPlugins();
String plugins = String.format("this.enabledPluginNames = [%s];",
startedPlugins.stream()
.map(PluginWrapper::getPluginId)
.collect(Collectors.joining("','", "'", "'")));
for (PluginWrapper pluginWrapper : startedPlugins) {
String pluginName = pluginWrapper.getPluginId();
Resource jsBundleResource = FrontResourceUtils.getJsBundleResource(pluginManager, pluginName,
FrontResourceUtils.JS_BUNDLE);
if (jsBundleResource != null) {
try (InputStream inputStream = jsBundleResource.getInputStream()) {
byte[] bytes = IoUtil.readBytes(inputStream);
DataBuffer dataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
dataBuffers.add(dataBuffer);
// Add a new line after each plugin bundle to avoid syntax error
byte[] newLineBytes = "\n".getBytes(StandardCharsets.UTF_8);
dataBuffers.add(DefaultDataBufferFactory.sharedInstance.wrap(newLineBytes));
} catch (IOException e) {
log.error("Failed to read plugin bundle resource", e);
}
}
}
// Add the plugins JavaScript object at the end
byte[] pluginsBytes = plugins.getBytes(StandardCharsets.UTF_8);
DataBuffer pluginsDataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(pluginsBytes);
dataBuffers.add(pluginsDataBuffer);
return dataBuffers;
}
public List<DataBuffer> uglifyCssBundle() {
List<DataBuffer> cssDataBuffers = new ArrayList<>();
for (PluginWrapper pluginWrapper : pluginManager.getStartedPlugins()) {
String pluginName = pluginWrapper.getPluginId();
Resource resource = FrontResourceUtils.getJsBundleResource(pluginManager, pluginName, FrontResourceUtils.CSS_BUNDLE);
if (resource != null) {
try (InputStream inputStream = resource.getInputStream()) {
byte[] cssBytes = IoUtil.readBytes(inputStream); // Assuming you have Apache Commons IO for this
DataBuffer dataBuffer = new DefaultDataBufferFactory().wrap(cssBytes);
cssDataBuffers.add(dataBuffer);
} catch (IOException e) {
log.error("Failed to read plugin css bundle resource for plugin: {}", pluginName, e);
}
}
}
return cssDataBuffers;
}
public String generateJsBundleVersion() {
if (RuntimeMode.DEVELOPMENT.equals(pluginManager.getRuntimeMode())) {
return String.valueOf(System.currentTimeMillis());
}
var compactVersion = pluginManager.getStartedPlugins()
.stream()
.sorted(Comparator.comparing(PluginWrapper::getPluginId))
.map(pluginWrapper -> pluginWrapper.getPluginId() + ":"
+ pluginWrapper.getDescriptor().getVersion()
)
.collect(Collectors.joining());
return DigestUtil.sha256Hex(compactVersion);
}
}

View File

@ -1,61 +0,0 @@
package cd.casic.plugin;
import org.pf4j.RuntimeMode;
import java.util.Set;
/**
* 插件系统属性接口定义了插件系统的一些核心配置和操作
*/
public interface IPluginSystemProperties {
/**
* 检查插件系统是否启用
*
* @return 如果插件系统启用则返回true否则返回false
*/
Boolean enable();
/**
* 获取REST API路径前缀
*
* @return REST API路径前缀字符串
*/
String restPathPrefix();
/**
* 检查插件ID是否作为REST路径前缀启用
*
* @return 如果启用插件ID作为REST路径前缀则返回true否则返回false
*/
Boolean enablePluginIdAsRestPrefix();
/**
* 获取运行时模式
*
* @return 当前运行时模式
*/
RuntimeMode runtimeMode();
/**
* 获取插件根目录路径
*
* @return 插件根目录路径字符串
*/
String pluginsRoot();
/**
* 获取已启用插件的ID集合
*
* @return 包含已启用插件ID的集合
*/
Set<String> enabledPlugins();
/**
* 获取已禁用插件的ID集合
*
* @return 包含已禁用插件ID的集合
*/
Set<String> disabledPlugins();
}

View File

@ -1,32 +0,0 @@
package cd.casic.plugin;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @namePluginApplicationContextHolder
* @Date2024/03/18 10:04
* @FilenamePluginApplicationContextHolder
* @description插件的上下文管理器
*/
public class PluginApplicationContextManager {
private final static Map<String, AnnotationConfigApplicationContext> pluginApplicationMap = new ConcurrentHashMap<>();
public static void addPluginApplicationContext(String pluginId, AnnotationConfigApplicationContext applicationContext) {
pluginApplicationMap.put(pluginId, applicationContext);
}
public static void removePluginApplicationContext(String pluginId) {
pluginApplicationMap.remove(pluginId);
}
public static AnnotationConfigApplicationContext getApplicationContext(String pluginId) {
return pluginApplicationMap.get(pluginId);
}
}

View File

@ -1,47 +0,0 @@
package cd.casic.plugin;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @namePluginBean
* @Date2024/03/18 14:48
* @FilenamePluginBean
* @descriptionOps 插件的bean
*/
@Data
public class PluginBean implements Serializable {
private static final long serialVersionUID = 7817277417501722377L;
// @ApiModelProperty(value="插件ID",name="id")
private Long id;
// @ApiModelProperty(value="插件名",name="name")
private String name;
// @ApiModelProperty(value="插件路径",name="path")
private String path;
// @ApiModelProperty(value="插件描述",name="desc")
private String desc;
// @ApiModelProperty(value="插件版本",name="version")
private String version;
// @ApiModelProperty(value="插件作者",name="author")
private String author;
// @ApiModelProperty(value="创建时间",name="createTime")
private Date createTime;
// @ApiModelProperty(value="更新时间",name="updateTime")
private Date updateTime;
// @ApiModelProperty(value="插件状态",name="status")
private Integer status = 0;
}

View File

@ -1,38 +0,0 @@
package cd.casic.plugin;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @namePluginCache
* @Date2024/03/18 9:34
* @FilenamePluginCache
* @description已安装插件的存储
*/
public class PluginCache {
private static final Map<String, PluginInfo> pluginMaps = Collections.synchronizedMap(new HashMap<>());
public static void put(String pluginId, PluginInfo plugin) {
pluginMaps.putIfAbsent(pluginId, plugin);
}
public static PluginInfo getPlugin(String pluginId) {
return pluginMaps.get(pluginId);
}
public static void remove(String pluginId) {
pluginMaps.remove(pluginId);
}
public static Map<String, PluginInfo> getAllPlugin() {
return pluginMaps;
}
public static Boolean isExist(String pluginId) {
return pluginMaps.containsKey(pluginId);
}
}

View File

@ -1,24 +0,0 @@
package cd.casic.plugin;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class PluginClassLoaderCache {
private static final Map<String, ClassLoader> loaderMap = Collections.synchronizedMap(new HashMap<>());
public static void put(String pluginId, ClassLoader loader) {
loaderMap.putIfAbsent(pluginId, loader);
}
public static ClassLoader getPlugin(String pluginId) {
return loaderMap.get(pluginId);
}
public static void remove(String pluginId) {
loaderMap.remove(pluginId);
}
public static Map<String, ClassLoader> getAllPlugin() {
return loaderMap;
}
}

View File

@ -1,44 +0,0 @@
package cd.casic.plugin;
import lombok.Data;
import java.util.Objects;
@Data
public class PluginConfigWrapper {
/**
* 插件中的配置文件名称
*/
private final String fileName;
/**
* 配置文件实现类的Class定义
*/
private final Class<?> configClass;
public PluginConfigWrapper(String fileName, Class<?> configClass) {
this.fileName = fileName;
this.configClass = configClass;
}
@Override
public boolean equals(Object o) {
if (this == o){
return true;
}
if (!(o instanceof PluginConfigWrapper)){
return false;
}
PluginConfigWrapper that = (PluginConfigWrapper) o;
return getFileName().equals(that.getFileName()) &&
getConfigClass().equals(that.getConfigClass());
}
@Override
public int hashCode() {
return Objects.hash(getFileName(), getConfigClass());
}
}

View File

@ -1,25 +0,0 @@
package cd.casic.plugin;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class PluginDescriptorCache {
private static final Map<String, PluginDescriptorStorage> pluginMaps = Collections.synchronizedMap(new HashMap<>());
public static void put(String pluginId, PluginDescriptorStorage pluginDescriptorStorage) {
pluginMaps.putIfAbsent(pluginId, pluginDescriptorStorage);
}
public static PluginDescriptorStorage getPlugin(String pluginId) {
return pluginMaps.get(pluginId);
}
public static void remove(String pluginId) {
pluginMaps.remove(pluginId);
}
public static Map<String, PluginDescriptorStorage> getAllPlugin() {
return pluginMaps;
}
}

View File

@ -1,70 +0,0 @@
package cd.casic.plugin;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import lombok.experimental.Accessors;
import org.pf4j.Plugin;
import org.pf4j.PluginDependency;
import java.util.List;
/**
* PF4J要用到的DefaultDescriptorStorage里一堆protected方法所以另外弄了个类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("sys_plugins")
@Accessors(chain = true)
public class PluginDescriptorStorage {
@TableId
private String pluginId;
private String pluginDescription;
private String pluginClass = Plugin.class.getName();
private String version;
private String requires = "*"; // SemVer format
private String provider;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<PluginDependency> dependencies;
private String license;
private String mapperXmlDir;
private String staticDir;
private Integer enable;
private String path;
@TableField(exist = false)
private String configFileName;
@TableField(exist = false)
private List<String> configFileActive;
private String pluginDirPath;
@Getter
public enum EnableStatus {
/**
* 启用
*/
ENABLE(1, "启用"),
/**
* 禁用
*/
DISABLE(0, "禁用");
private final Integer code;
private final String value;
EnableStatus(Integer code, String value) {
this.value = value;
this.code = code;
}
}
}

View File

@ -1,155 +0,0 @@
package cd.casic.plugin;
import cd.casic.plugin.utils.PluginsUtils;
import cn.hutool.setting.dialect.Props;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.pf4j.PluginWrapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @namePluginDetail
* @Date2024/03/18 9:34
* @FilenamePluginDetail
* @description插件信息类
*/
@Slf4j
@Data
public class PluginInfo {
// jar或zip的list集合
private List<Class<?>> classList;
private ApplicationContext mainApplicationContext;
private Boolean applicationContextIsRefresh = false;
private AnnotationConfigApplicationContext pluginApplicationContext;
private PluginWrapper pluginWrapper;
private List<Class<?>> adminGroupsClassList = new ArrayList<>();
private List<String> websocketPaths = new ArrayList<>();
private String pluginId;
private String mapperXmlDir;
private final BasePlugin basePlugin;
private List<HandlerInterceptor> handlerInterceptorList = new ArrayList<>();
private Set<String> staticClassPathLocations = new HashSet<>();
private Set<String> staticFileLocations = new HashSet<>();
private List<Class<?>> controllers = new ArrayList<>();
private Set<Object> pluginConfigObjects = new HashSet<>();
private Map<String, String> webSocketPathMap = new ConcurrentHashMap<>();
// private ConcurrentHashMap<Class<?>, Object> beanCache = new ConcurrentHashMap<>();
// TODO 这个map用于替代前面的ClassList
private Map<String, List<Class<?>>> classGroups = new ConcurrentHashMap<>();
public PluginInfo(PluginWrapper pluginWrapper, ApplicationContext applicationContext) {
this.classList = new ArrayList<>();
this.pluginWrapper = pluginWrapper;
this.pluginId = pluginWrapper.getPluginId();
this.pluginApplicationContext = getContext();
this.mainApplicationContext = applicationContext;
this.basePlugin = (BasePlugin) pluginWrapper.getPlugin();
this.pluginApplicationContext.setParent(mainApplicationContext);
Props setting = PluginsUtils.getSetting(pluginWrapper.getPluginId());
if (!setting.isEmpty()) {
this.mapperXmlDir = setting.getStr("mybatis.mapper.location", null);
String locations = setting.getStr("static.locations", null);
if (StringUtils.isNotBlank(locations)) {
loadResources(locations);
}
}
}
public AnnotationConfigApplicationContext getContext() {
AnnotationConfigApplicationContext pluginApplicationContext =
PluginApplicationContextManager.getApplicationContext(pluginWrapper.getPluginId());
if (pluginApplicationContext == null)
pluginApplicationContext = new AnnotationConfigApplicationContext();
pluginApplicationContext.setClassLoader(pluginWrapper.getPluginClassLoader());
PluginApplicationContextManager.addPluginApplicationContext(pluginWrapper.getPluginId(), pluginApplicationContext);
return PluginApplicationContextManager.getApplicationContext(pluginWrapper.getPluginId());
}
private void loadResources(String locations) {
String[] staticLocations = locations.split(",");
for (String staticLocation : staticLocations) {
if (staticLocation.contains("classpath:")) {
staticLocation = staticLocation.replace("classpath:", "");
if (StringUtils.isNotBlank(staticLocation) && staticLocation.startsWith("/")) {
staticLocation = staticLocation.substring(1);
}
this.staticClassPathLocations.add(staticLocation);
} else {
this.staticFileLocations.add(staticLocation);
}
}
}
public String getMapperXmlDir() {
if (StringUtils.isNotBlank(mapperXmlDir) && mapperXmlDir.startsWith("classpath:")) {
mapperXmlDir = mapperXmlDir.replace("classpath:", "");
}
if (StringUtils.isNotBlank(mapperXmlDir) && mapperXmlDir.startsWith("/")) {
mapperXmlDir = mapperXmlDir.substring(1);
}
return mapperXmlDir;
}
/**
* 清理ApplicationContext
*/
public void clearApplicationContext() {
Optional.of(this.getPluginId())
.ifPresent(var -> PluginApplicationContextManager.removePluginApplicationContext(var.trim()));
Optional.of(this.getPluginApplicationContext())
.ifPresent(var -> {
var.getDefaultListableBeanFactory().destroySingletons();
var.close();
});
this.applicationContextIsRefresh = false;
this.pluginApplicationContext = null;
}
/**
* @param c class
* @return java.util.List<java.lang.Class < ?>>
* @description 获取插件内实现指定类的bean
* @author dolphin
*/
public <T> T getPluginBean(Class<T> c) {
try {
return pluginApplicationContext.getBean(c);
} catch (Exception e) {
log.error("要删除的bean{}不存在" , c.getName());
return null;
}
}
public void addController(Class<?> controller) {
this.controllers.add(controller);
}
public void addPluginConfigObject(Object config) {
this.pluginConfigObjects.add(config);
}
public void addGroupClass(String groupName, Class<?> clazz) {
classGroups.computeIfAbsent(groupName, k -> new ArrayList<>()).add(clazz);
}
public List<Class<?>> getGroupClass(String groupName) {
List<Class<?>> result = new ArrayList<>();
List<Class<?>> classes = classGroups.get(groupName);
if (classes != null) {
result.addAll(classes);
}
return result;
}
}

View File

@ -1,27 +0,0 @@
package cd.casic.plugin;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @namePluginLifecycle
* @Date2024/06/15 17:31
* @FilenamePluginLifecycle
* @description插件生命周期的操作
*/
public interface PluginLifecycle {
/**
* 插件启动前的操作比如需要单独对插件进行某些特定操作,
*
*/
public void beforeWork();
/**
* 料理后世方的操作
*/
public void AfterWork();
}

View File

@ -1,392 +0,0 @@
package cd.casic.plugin;
import cd.casic.plugin.event.PluginLoadedEvent;
import cd.casic.plugin.event.PluginUnloadedEvent;
import cd.casic.plugin.pf4j.OpsJarPluginRepository;
import cd.casic.plugin.pf4j.OpsPluginStatusProvider;
import cd.casic.plugin.pf4j.OpsPropertiesPluginDescriptorFinder;
import cd.casic.plugin.pf4j.OpsYamlPluginDescriptorFinder;
import cd.casic.plugin.register.StartPluginManagerHandler;
import cd.casic.plugin.utils.PluginsUtils;
import cd.casic.plugin.utils.SM4EncryptUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.pf4j.*;
import org.pf4j.util.FileUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @namePluginManager
* @Date2024/03/18 15:03
* @FilenamePluginManager
* @description插件核心管理类
*/
@Slf4j
@Component
public class PluginManager extends DefaultPluginManager implements PluginManagerService, ApplicationContextAware, PluginLifecycle {
@Getter
ApplicationContext applicationContext;
@Getter
private final PluginProperties pluginProperties;
@Autowired
private StartPluginManagerHandler startPluginManagerHandler;
@Autowired
private BufferedPluginBundleResource bufferedPluginBundleResource;
@Autowired
private FrontResourceProcessor frontResourceProcessor;
public PluginManager(@Autowired PluginProperties pluginProperties) {
this.pluginsRoots.add(Paths.get(pluginProperties.getPluginsRoot()));
this.pluginProperties = pluginProperties;
this.setRuntimeMode(pluginProperties.getRuntimeMode());
super.initialize();
}
@Override
protected void initialize() {
// 这里不需要任何操作只是覆盖父类的initialize方法
// 如果不重写的话在调用构造函数的时候会调用父类的无参构造函数导致父类initialize方法被调用
// 父类initialize方法中会调用createPluginStatusProvider方法该方法需要使用到OpsPluginSystemProperties而此时还未被注入
}
@Override
public PluginInfo install(Path path) throws Exception {
// TODO 在install的时候生成插件目录结构
/*
工作目录
plugins
example-mybatis-plugin@0.1 插件目录id@version存放jardatabaseresource
ops-module-plugin-example-mybatis-plugin.jar 插件jar包jar包命名不影响
database 存放sqlite数据库文件
resource 存放静态资源
example-redis-plugin@1.1.4
ops-module-plugin-example-redis-plugin.jar
database
resource
*/
String pluginId = null;
try {
this.beforeWork();
pluginId = this.loadPlugin(path);
super.startPlugin(pluginId);
log.info("install plugin [{}] success", pluginId);
return PluginCache.getPlugin(pluginId);
} catch (Exception e) {
log.error("安装插件出现异常:{}", e.getMessage());
if (StringUtils.isNotBlank(pluginId)) {
PluginCache.remove(pluginId);
}
throw new Exception(e.getMessage());
}
}
@Override
public void installAfter(String pluginId) {
try {
super.stopPlugin(pluginId);
} catch (Exception e) {
e.printStackTrace();
log.error("install plugin after error:{}", e.getMessage());
}
}
@Override
public void unInstall(String pluginId, boolean isUpdate) throws Exception {
// TODO unInstall的时候除掉插件资源
log.info("准备卸载插件:" + pluginId);
PluginInfo plugin = PluginCache.getPlugin(pluginId);
plugin.clearApplicationContext();
PluginCache.remove(pluginId);
//删除插件
PluginsUtils.forceDelete(plugin.getPluginWrapper().getPluginPath().toFile());
}
@Override
public void initPlugins(List<PluginBean> plugins) throws Exception {
// 初始化插件处理器
startPluginManagerHandler.initialize();
for (PluginBean plugin : plugins) {
File file = new File(pluginsRoots.get(0) + File.separator + plugin.getPath());
if (!file.exists()) {
continue;
}
String pluginId = this.loadPlugin(file.toPath().toAbsolutePath());
PluginInfo pluginInfo = PluginCache.getPlugin(pluginId);
if (plugin.getStatus() == 1) {
this.startPlugin(pluginInfo.getPluginId());
}
}
}
@Override
public List<PluginWrapper> getInstallPlugins() {
return getPlugins();
}
/**
* 加载插件
*
* @param pluginPath 插件路径
* @return String
*/
@Override
public String loadPlugin(Path pluginPath) {
Optional.ofNullable(pluginPath)
.filter(path -> Files.exists(path))
.orElseThrow(() -> new IllegalArgumentException(String.format("plugin %s 不存在", pluginPath)));
log.debug("Loading plugin from '{}'", pluginPath);
PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath);
PluginInfo pluginInfo = new PluginInfo(pluginWrapper, applicationContext);
try {
resolvePlugins();
sendPluginEvenet(new PluginLoadedEvent(this, pluginInfo));
} catch (Exception e) {
log.error("加载插件出现异常:{}", e.getMessage());
sendPluginEvenet(new PluginUnloadedEvent(this, pluginInfo));
}
return pluginWrapper.getDescriptor().getPluginId();
}
@Override
public PluginWrapper loadPluginFromPath(Path pluginPath) {
try {
pluginPath = FileUtils.expandIfZip(pluginPath);
} catch (Exception var3) {
log.info(MessageFormat.format("路径:{0}的插件包解压缩失败,失败原因{1} ", pluginPath, var3));
return null;
}
return super.loadPluginFromPath(pluginPath);
}
@Override
public void loadPlugins() {
log.debug("Lookup plugins in '{}'", pluginsRoots);
// check for plugins roots
if (pluginsRoots.isEmpty()) {
log.warn("No plugins roots configured");
return;
}
pluginsRoots.forEach(path -> {
if (Files.notExists(path) || !Files.isDirectory(path)) {
log.warn("No '{}' root", path);
}
});
// get all plugin paths from repository
List<Path> pluginPaths = pluginRepository.getPluginPaths();
// check for no plugins
if (pluginPaths.isEmpty()) {
log.info("No plugins");
return;
}
log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
// load plugins from plugin paths
for (Path pluginPath : pluginPaths) {
try {
PluginWrapper pluginWrapper = this.loadPluginFromPath(pluginPath);
if (ObjectUtils.isNotEmpty(pluginWrapper)) {
String license = pluginWrapper.getDescriptor().getLicense();
if (SM4EncryptUtil.checkLicense(pluginWrapper.getDescriptor().getLicense(), pluginWrapper.getPluginId())) {
PluginInfo plugin = new PluginInfo(pluginWrapper, applicationContext);
// startPluginManagerHandler.registry(plugin);
sendPluginEvenet(new PluginLoadedEvent(this, plugin));
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
// resolve plugins
try {
resolvePlugins();
} catch (PluginRuntimeException e) {
log.error(e.getMessage(), e);
}
}
@Override
public boolean unloadPlugin(String pluginId) {
if (pluginId == null) {
throw new IllegalArgumentException(String.format("plugin %s 不存在", pluginId));
}
log.info("unloading plugin {}", pluginId);
try {
super.unloadPlugin(pluginId, true);
} catch (Exception e) {
log.info("卸载插件出现异常,{}", e.getMessage());
return false;
} finally {
sendPluginEvenet(new PluginUnloadedEvent(this, PluginCache.getPlugin(pluginId)));
System.gc();
}
return true;
}
@Override
public void unloadPlugins() {
for (PluginWrapper pluginWrapper : new ArrayList<>(resolvedPlugins)) {
this.unloadPlugin(pluginWrapper.getPluginId());
}
}
@Override
public PluginState startPlugin(String pluginId) {
PluginState pluginState = null;
try {
PluginInfo plugin = PluginCache.getPlugin(pluginId);
if (plugin.getPluginWrapper().getPluginState().equals(PluginState.DISABLED)) return PluginState.STOPPED;
if (ObjectUtils.isEmpty((plugin.getPluginApplicationContext()))) {
plugin.setPluginApplicationContext(plugin.getContext());
plugin.getPluginApplicationContext().setParent(applicationContext);
}
startPluginManagerHandler.registry(plugin);
pluginState = super.startPlugin(pluginId);
// todo 这里有uglify存在循环遍历的问题后面改
bufferedPluginBundleResource.getJsBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyJsBundle());
bufferedPluginBundleResource.getCssBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyCssBundle());
// bufferedPluginBundleResource.getHtmlBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyHtmlBundle());
} catch (Exception e) {
e.printStackTrace();
log.error("plugin start error : {}", e.getMessage());
}
return pluginState;
}
protected PluginState stopPlugin(String pluginId, boolean stopDependents) {
checkPluginId(pluginId);
PluginWrapper pluginWrapper = getPlugin(pluginId);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
PluginState pluginState = pluginWrapper.getPluginState();
if (PluginState.STOPPED == pluginState) {
log.debug("Already stopped plugin '{}'", getPluginLabel(pluginDescriptor));
return PluginState.STOPPED;
}
// test for disabled plugin
if (PluginState.DISABLED == pluginState) {
// do nothing
return pluginState;
}
if (stopDependents) {
List<String> dependents = dependencyResolver.getDependents(pluginId);
while (!dependents.isEmpty()) {
String dependent = dependents.remove(0);
stopPlugin(dependent, false);
dependents.addAll(0, dependencyResolver.getDependents(dependent));
}
}
log.info("Stop plugin '{}'", getPluginLabel(pluginDescriptor));
pluginWrapper.getPlugin().stop();
pluginWrapper.setPluginState(PluginState.STOPPED);
startedPlugins.remove(pluginWrapper);
try {
// todo 没得到plugin
PluginInfo plugin = PluginCache.getPlugin(pluginId);
// todo 停止事件
startPluginManagerHandler.unRegistry(plugin);
plugin.clearApplicationContext();
} catch (Exception e) {
log.error("plugin stop error : {}", e.getMessage());
}
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
this.AfterWork();
return pluginWrapper.getPluginState();
}
public void setRuntimeMode(RuntimeMode runtimeMode) {
this.runtimeMode = runtimeMode;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
protected PluginDescriptorFinder createPluginDescriptorFinder() {
return new CompoundPluginDescriptorFinder()
.add(new OpsPropertiesPluginDescriptorFinder())
.add(new OpsYamlPluginDescriptorFinder());
}
@Override
protected PluginRepository createPluginRepository() {
// return new CompoundPluginRepository()
// .add(new DevelopmentPluginRepository(getPluginsRoots()), this::isDevelopment)
// .add(new OpsJarPluginRepository(getPluginsRoots()), this::isNotDevelopment);
// .add(new DefaultPluginRepository(getPluginsRoots()), this::isNotDevelopment);
return new OpsJarPluginRepository(getPluginsRoots());
}
@Override
protected PluginLoader createPluginLoader() {
return new CompoundPluginLoader()
.add(new JarPluginLoader(this))
.add(new DefaultPluginLoader(this));
}
@Override
public boolean isDevelopment() {
return this.getRuntimeMode().equals(RuntimeMode.DEVELOPMENT);
}
@Override
protected PluginStatusProvider createPluginStatusProvider() {
if (OpsPluginStatusProvider.isPropertySet(pluginProperties)) {
return new OpsPluginStatusProvider(pluginProperties);
}
return super.createPluginStatusProvider();
}
@Override
public void beforeWork() {
}
@Override
public void AfterWork() {
}
}

View File

@ -1,69 +0,0 @@
package cd.casic.plugin;
import cd.casic.framework.commons.util.spring.SpringUtils;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.springframework.context.ApplicationEvent;
import java.nio.file.Path;
import java.util.List;
/**
* @Authormianbin
* @Packagecd.casic.plugin
* @Projectops
* @namePluginManagerService
* @Date2024/03/18 14:46
* @FilenamePluginManagerService
* @description插件基础安装类
*/
public interface PluginManagerService {
/**
* @param path 插件路径
* @description 安装插件
*/
PluginInfo install(Path path) throws Exception;
/**
* 安装插件后置操作
*
* @param pluginId pluginId
*/
void installAfter(String pluginId);
/**
* @description 卸载插件
*/
void unInstall(String pluginId, boolean isUpdate) throws Exception;
/**
* 启动插件
*
* @param pluginId
* @return
*/
PluginState startPlugin(String pluginId);
/**
* 停止插件
*
* @param pluginId pluginId
* @return PluginState
*/
PluginState stopPlugin(String pluginId);
/**
* @description 插件初始化
*/
void initPlugins(List<PluginBean> plugins) throws Exception;
/**
* @description 获取所有插件
*/
List<PluginWrapper> getInstallPlugins();
default void sendPluginEvenet(ApplicationEvent applicationEvent) {
SpringUtils.publishEvent(applicationEvent);
}
}

View File

@ -1,53 +0,0 @@
package cd.casic.plugin;
import cn.hutool.core.io.FileUtil;
import lombok.Data;
import org.pf4j.RuntimeMode;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
@Data
@ConfigurationProperties(prefix = "cd.casic.ops.plugin")
public class PluginProperties implements InitializingBean {
/**
* 是否启用插件系统
*/
private Boolean enable;
/**
* 插件根目录
*/
private String pluginsRoot;
/**
* 运行模式可以是 development 或者 deployment
*/
private RuntimeMode runtimeMode;
/**
* 插件接口RESTful API的路径前缀
*/
private String restPathPrefix;
/**
* 是否在RESTful API前缀中包含插件ID默认为true
*/
private boolean enablePluginIdAsRestPrefix = true;
/**
* 已启用的插件集合
*/
private Set<String> enabledPlugins = new HashSet<>();
/**
* 已禁用的插件集合
*/
private Set<String> disabledPlugins = new HashSet<>();
@Override
public void afterPropertiesSet() {
Path pluginsRootPath = Paths.get(this.pluginsRoot);
if (!pluginsRootPath.toFile().exists()) {
FileUtil.mkdir(pluginsRootPath.toFile());
}
}
}

View File

@ -1,72 +0,0 @@
package cd.casic.plugin;
import cd.casic.plugin.utils.YamlUtils;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import java.io.InputStream;
import java.nio.file.Path;
public class PluginYamlConfigurationParser {
private final ObjectMapper objectMapper;
private final YAMLFactory yamlFactory;
public PluginYamlConfigurationParser(){
this.objectMapper = new ObjectMapper();
this.yamlFactory = new YAMLFactory();
}
public Object parse(PluginInfo pluginInfo, PluginConfigWrapper pluginConfigWrapper) throws Exception {
Class<?> configClass = pluginConfigWrapper.getConfigClass();
if(configClass == null){
throw new IllegalArgumentException("pluginConfigDefinition : " + pluginConfigWrapper + " " +
"configClass can not be null");
}
String yamlFileName = pluginConfigWrapper.getFileName();
if(StrUtil.isEmpty(yamlFileName)){
throw new IllegalArgumentException("pluginConfigDefinition : " + pluginConfigWrapper + " " +
"fileName can not be empty");
}
Path yamlPath = YamlUtils.getYamlPath(pluginInfo.getPluginWrapper().getPluginPath(), yamlFileName);
Resource resource = new FileSystemResource(yamlPath);
Object o = convert(resource, configClass);
if(o == null){
return configClass.getConstructor().newInstance();
}
return o;
}
private Object convert(Resource resource, Class<?> configClass) throws Exception {
InputStream inputStream = null;
YAMLParser yamlParser = null;
TreeTraversingParser treeTraversingParser = null;
try {
inputStream = resource.getInputStream();
yamlParser = yamlFactory.createParser(inputStream);
final JsonNode node = objectMapper.readTree(yamlParser);
if(node == null){
return configClass.getConstructor().newInstance();
}
treeTraversingParser = new TreeTraversingParser(node);
return objectMapper.readValue(treeTraversingParser, configClass);
} finally {
if(treeTraversingParser != null){
treeTraversingParser.close();
}
if(yamlParser != null){
yamlParser.close();
}
if(inputStream != null){
inputStream.close();
}
}
}
}

View File

@ -1,43 +0,0 @@
package cd.casic.plugin;
import cd.casic.plugin.utils.PluginDescriptorUtils;
import org.springframework.beans.factory.config.YamlProcessor;
import org.springframework.core.io.Resource;
import java.util.ArrayList;
import java.util.List;
import static cd.casic.plugin.utils.PluginDescriptorUtils.*;
public class PluginYamlProcessor extends YamlProcessor {
/**
* 检查这些必须有的属性是否存在
*/
private static final DocumentMatcher DEFAULT_DOCUMENT_MATCHER = properties -> {
if (properties.containsKey(PLUGIN_ID)
&& properties.containsKey(PLUGIN_VERSION)
&& properties.containsKey(PLUGIN_CLASS)){
return MatchStatus.FOUND;
}
return MatchStatus.NOT_FOUND;
};
public PluginYamlProcessor(Resource... resources) {
setResources(resources);
setDocumentMatchers(DEFAULT_DOCUMENT_MATCHER);
}
/**
* 读取yaml文件
* @return PluginDescriptorStorage 插件的相关配置信息以及后续用于插件信息持久化存储的持久化对象
*/
public PluginDescriptorStorage loadYaml() {
List<PluginDescriptorStorage> pluginDescriptor = new ArrayList<>();
this.process(((properties, map) -> {
pluginDescriptor.add(PluginDescriptorUtils.propertiesToStorage(properties));
}));
return pluginDescriptor.get(0);
}
}

View File

@ -1,38 +0,0 @@
package cd.casic.plugin.annotation;
import cd.casic.plugin.constants.OpsConstants;
import java.lang.annotation.*;
/**
* @Authormianbin
* @Packagecd.casic.plugin.annotation
* @Projectops
* @nameAdminGroup
* @Date2024/03/18 17:06
* @FilenameAdminGroup
* @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;
}

View File

@ -1,20 +0,0 @@
package cd.casic.plugin.annotation;
import java.lang.annotation.*;
/**
* @Authormianbin
* @Packagecd.casic.plugin.annotation
* @Projectops
* @nameAdminGroups
* @Date2024/03/18 17:05
* @FilenameAdminGroups
* @description自定义菜单groups注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface AdminGroups {
/** 菜单组 */
AdminGroup[] groups();
}

View File

@ -1,22 +0,0 @@
package cd.casic.plugin.annotation;
import java.lang.annotation.*;
/**
* @Authormianbin
* @Packagecd.casic.plugin.annotation
* @Projectops
* @nameInterceptPath
* @Date2024/03/18 16:09
* @FilenameInterceptPath
* @description插件拦截器
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface InterceptPath {
/**
* 拦截的路径
*/
String[] value();
}

View File

@ -1,17 +0,0 @@
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模式下的后缀
}

View File

@ -1,29 +0,0 @@
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
* <p>
* 此Bean负责管理和操作插件系统仅在没有已定义的PluginManager Bean时创建
*
* @param pluginProperties 插件系统的属性配置用于初始化PluginManager
* @return 初始化并配置好的PluginManager实例
*/
@Bean
@ConditionalOnMissingBean
public PluginManager pluginManager(PluginProperties pluginProperties) {
return new PluginManager(pluginProperties);
}
}

View File

@ -1,53 +0,0 @@
package cd.casic.plugin.constants;
/**
* @Authormianbin
* @Packagecd.casic.plugin.constants
* @Projectops
* @nameOpsConstants
* @Date2024/03/18 9:30
* @FilenameOpsConstants
* @descriptionTodo
*/
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";
}
}

View File

@ -1,27 +0,0 @@
package cd.casic.plugin.constants;
/**
* @Authormianbin
* @Packagecd.casic.plugin.constants
* @Projectops
* @namePluginBuildTypeEnum
* @Date2024/03/21 19:53
* @FilenamePluginBuildTypeEnum
* @description插件构建方式区分
*/
public enum PluginBuildTypeEnum {
/**
* 构建
*/
BUILD,
/**
* 注册
*/
REGISTER,
/**
* 卸载
*/
UNREGISTER
}

View File

@ -1,20 +0,0 @@
package cd.casic.plugin.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
/**
* @Authormianbin
* @Packagecd.casic.plugin.event
* @Projectops
* @nameOpsMainAppReadyEvent
* @Date2024/03/22 10:29
* @FilenameOpsMainAppReadyEvent
* @description主程序启动完毕此事件发布到插件应用程序,此时插件还未启动
*/
public class OpsMainAppReadyEvent extends ApplicationEvent {
public OpsMainAppReadyEvent(ApplicationContext mainApplicationContext) {
super(mainApplicationContext);
}
}

View File

@ -1,20 +0,0 @@
package cd.casic.plugin.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
/**
* @Authormianbin
* @Packagecd.casic.plugin.event
* @Projectops
* @nameOpsMainAppStartedEvent
* @Date2024/03/22 10:31
* @FilenameOpsMainAppStartedEvent
* @description主程序启动完毕此事件发布到插件应用程序,此时原神启动
*/
public class OpsMainAppStartedEvent extends ApplicationEvent {
public OpsMainAppStartedEvent(ApplicationContext mainApplicationContext) {
super(mainApplicationContext);
}
}

View File

@ -1,13 +0,0 @@
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);
}
}

View File

@ -1,30 +0,0 @@
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());
}
}
}

View File

@ -1,19 +0,0 @@
package cd.casic.plugin.event;
import org.springframework.context.ApplicationEvent;
/**
* @Authormianbin
* @Packagecd.casic.plugin.event
* @Projectops
* @namePluginRestartedEvent
* @Date2024/03/22 10:51
* @FilenamePluginRestartedEvent
* @description插件重启在插件管理器中重启插件和同目录上面的OpsMian****的事件不同
*/
public class PluginRestartedEvent extends ApplicationEvent {
public PluginRestartedEvent(Object source) {
super(source);
}
}

View File

@ -1,20 +0,0 @@
package cd.casic.plugin.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
/**
* @Authormianbin
* @Packagecd.casic.plugin.event
* @Projectops
* @namePluginStartedEvent
* @Date2024/03/22 10:57
* @FilenamePluginStartedEvent
* @description发布到插件管理器
*/
public class PluginStartedEvent extends ApplicationEvent {
public PluginStartedEvent(ApplicationContext pluginApplicationContext) {
super(pluginApplicationContext);
}
}

View File

@ -1,19 +0,0 @@
package cd.casic.plugin.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
/**
* @Authormianbin
* @Packagecd.casic.plugin.event
* @Projectops
* @namePluginStateChangedEvent
* @Date2024/03/22 11:02
* @FilenamePluginStateChangedEvent
* @description插件状态这个是考虑到插件的更新
*/
public class PluginStateChangedEvent extends ApplicationEvent {
public PluginStateChangedEvent(ApplicationContext mainApplicationContext) {
super(mainApplicationContext);
}
}

View File

@ -1,19 +0,0 @@
package cd.casic.plugin.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
/**
* @Authormianbin
* @Packagecd.casic.plugin.event
* @Projectops
* @namePluginStoppedEvent
* @Date2024/03/22 11:05
* @FilenamePluginStoppedEvent
* @description插件停止停止后应该删除一切内容
*/
public class PluginStoppedEvent extends ApplicationEvent {
public PluginStoppedEvent(ApplicationContext pluginApplicationContext) {
super(pluginApplicationContext);
}
}

View File

@ -1,26 +0,0 @@
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);
}
}

View File

@ -1,29 +0,0 @@
package cd.casic.plugin.execption;
/**
* @Authormianbin
* @Packagecd.casic.plugin.execption
* @Projectops
* @namePluginException
* @Date2024/03/21 19:54
* @FilenamePluginException
* @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);
}
}

View File

@ -1,55 +0,0 @@
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<Path> pluginsRoots) {
super(pluginsRoots);
}
@Override
public List<Path> getPluginPaths() {
List<Path> pluginPaths = new ArrayList<>();
for (Path pluginRoot : pluginsRoots){
File rootDir = pluginRoot.toFile();
scanForJars(rootDir.toPath(), pluginPaths);
}
return pluginPaths;
}
private void scanForJars(Path directory, List<Path> pluginPaths) {
try (DirectoryStream<Path> 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());
}
}
}

View File

@ -1,20 +0,0 @@
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<String> configFileActive;
public OpsPluginDescriptor(String pluginId, String pluginDescription, String pluginClass,
String version, String requires, String provider, String license,
String configFileName, List<String> configFileActive){
super(pluginId, pluginDescription, pluginClass, version, requires, provider, license);
this.configFileActive = configFileActive;
this.configFileName = configFileName;
}
}

View File

@ -1,50 +0,0 @@
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<String> enabledPlugins = new HashSet<>();
private final Set<String> 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);
}
}

View File

@ -1,60 +0,0 @@
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);
}
}

View File

@ -1,64 +0,0 @@
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);
}
}
}

View File

@ -1,145 +0,0 @@
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;
/**
* @Authormianbin
* @Packagecd.casic.plugin.register
* @Projectops
* @nameAnnotationHandler
* @Date2024/03/18 15:59
* @FilenameAnnotationHandler
* @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<Class<?>> classList = new ArrayList<>();
List<Class<?>> adminGroupsClassList = new ArrayList<>();
Set<String> 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<Class<?>> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList());
if (!pluginClassList.isEmpty()) {
List<Class<?>> registryClassList = new ArrayList<>();
for (Class<?> aClass : pluginClassList) {
// 原本这里使用Collections.disjoint()实现是有问题的结果总是true
// 因为aClass.getAnnotations()获得的是Proxy数组而REGIS_ANNO是Class<Annotation>数组
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<String> scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException {
Path pluginPath = pluginWrapper.getPluginPath();
Set<String> classPackageNames = new HashSet<>();
File jarFile = null;
if(Files.isDirectory(pluginPath)){
List<File> 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<JarEntry> 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;
}
}

View File

@ -1,54 +0,0 @@
package cd.casic.plugin.register;
import cd.casic.plugin.PluginInfo;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import java.util.Arrays;
/**
* @Authormianbin
* @Packagecd.casic.plugin.register
* @Projectops
* @nameApplicationContextPluginHandler
* @Date2024/03/19 14:48
* @FilenameApplicationContextPluginHandler
* @descriptionTodo
*/
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);
}
}

View File

@ -1,40 +0,0 @@
package cd.casic.plugin.register;
import cd.casic.plugin.PluginInfo;
/**
* @Authormianbin
* @Packagecd.casic.plugin.register
* @Projectops
* @nameBasePluginHandler
* @Date2024/03/18 15:48
* @FilenameBasePluginHandler
* @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;
}

View File

@ -1,169 +0,0 @@
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.*;
/**
* @Authormianbin
* @Packagecd.casic.plugin.register
* @Projectops
* @nameControllerHandler
* @Date2024/03/19 14:22
* @FilenameControllerHandler
* @descriptionTodo
*/
@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<RequestMappingInfo> getRequestMappingInfo(PluginInfo plugin) throws Exception {
List<RequestMappingInfo> 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<String> 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<String, Object> memberValues = (Map<String, Object>) 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 "";
}
}
}

View File

@ -1,163 +0,0 @@
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;
/**
* @Authormianbin
* @Packagecd.casic.plugin.register
* @Projectops
* @nameMybatisHandler
* @Date2024/03/19 14:51
* @FilenameMybatisHandler
* @deprecated 不再使用采用{@link MybatisPlusHandler} 统一实现
*/
@Deprecated
public class MybatisHandler implements BasePluginHandler {
@Override
public void initialize() throws Exception {
}
@Override
public void registry(PluginInfo plugin) throws Exception {
List<Class<?>> 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<File> 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<JarEntry> 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<Class<?>> 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<Class<?>> getMapperList(PluginInfo plugin){
List<Class<?>> 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<Object> newMap = new DefaultSqlSession.StrictMap<Object>();
for (Object key : map.keySet()) {
try {
newMap.put((String) key, map.get(key));
} catch (IllegalArgumentException ex) {
newMap.put((String) key, ex.getMessage());
}
}
field.set(configuration, newMap);
}
}

View File

@ -1,90 +0,0 @@
package cd.casic.plugin.register;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.constants.OpsConstants;
import cn.hutool.core.io.FileUtil;
import org.pf4j.util.FileUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @Authormianbin
* @Packagecd.casic.plugin.register
* @Projectops
* @nameResourcesHandler
* @Date2024/03/19 14:54
* @FilenameResourcesHandler
* @descriptionTodo
*/
public class ResourcesHandler implements BasePluginHandler {
@Override
public void initialize() throws Exception {
}
@Override
public void registry(PluginInfo plugin) throws Exception {
Path pluginPath = plugin.getPluginWrapper().getPluginPath();
Set<String> staticClassPathLocations = plugin.getStaticClassPathLocations();
File jarFile = null;
if(Files.isDirectory(pluginPath)){
List<File> 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<JarEntry> jarEntries = new JarFile(jarFile).entries();
File file = new File(OpsConstants.PLUGINS_RESOURCES_DIR + File.separator + plugin.getPluginId());
if (!file.exists()) {
FileUtil.mkdir(file);
}
FileUtil.clean(file);
while (jarEntries.hasMoreElements()) {
JarEntry entry = jarEntries.nextElement();
String jarEntryName = entry.getName();
for (String staticClassPathLocation : staticClassPathLocations) { //staticClassPathLocation里读取到所有静态资源的位置 然后将以插件为单位 打包到web的static目录下 即可访问
if (!staticClassPathLocation.equals(jarEntryName) && jarEntryName.startsWith(staticClassPathLocation)
&& !jarEntryName.endsWith(".class") && !entry.isDirectory()) {
URL url = new URL("jar:file:" + jarFile.getAbsolutePath() + "!/" + jarEntryName);
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
InputStream in = jarConnection.getInputStream();
File file1 = new File(file.getAbsolutePath() + File.separator + jarEntryName);
FileUtil.touch(file1.getAbsolutePath());
int index;
byte[] bytes = new byte[1024];
FileOutputStream downloadFile = new FileOutputStream(file.getAbsolutePath() + File.separator + jarEntryName);
while ((index = in.read(bytes)) != -1) {
downloadFile.write(bytes, 0, index);
downloadFile.flush();
}
downloadFile.close();
in.close();
JarFile currJarFile = jarConnection.getJarFile();
currJarFile.close();
}
}
}
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
File file = new File(OpsConstants.PLUGINS_RESOURCES_DIR + File.separator + plugin.getPluginId());
if (file.exists()) {
FileUtil.del(file.getAbsolutePath());
}
}
}

View File

@ -1,42 +0,0 @@
package cd.casic.plugin.register;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.register.group.filter.impl.BasicBeanFilter;
import cd.casic.plugin.register.group.filter.impl.ControllerFilter;
import cd.casic.plugin.register.group.filter.impl.MapperFilter;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import java.util.ArrayList;
import java.util.List;
public class SpringBeanRegisterHandler implements BasePluginHandler {
@Override
public void initialize() throws Exception {
}
@Override
public void registry(PluginInfo plugin) throws Exception {
List<Class<?>> basicBeanClassList = new ArrayList<>();
basicBeanClassList.addAll(plugin.getGroupClass(BasicBeanFilter.GROUP_NAME));
basicBeanClassList.addAll(plugin.getGroupClass(MapperFilter.GROUP_NAME));
basicBeanClassList.addAll(plugin.getGroupClass(ControllerFilter.GROUP_NAME));
// if(!basicBeanClassList.isEmpty()){
// plugin.getPluginApplicationContext().register(basicBeanClassList.toArray(new Class[0]));
// }
basicBeanClassList.forEach(clazz -> {
AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(clazz);
beanDefinition.setBeanClass(clazz);
BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, plugin.getPluginApplicationContext());
plugin.getPluginApplicationContext().registerBeanDefinition(beanName, beanDefinition);
});
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
}
}

View File

@ -1,69 +0,0 @@
package cd.casic.plugin.register;
import cd.casic.plugin.PluginInfo;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.api.AbstractOpenApiResource;
import org.springdoc.core.service.OpenAPIService;
import org.springframework.context.ApplicationContext;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.Set;
/**
* 插件系统集成SpringDocswagger能够根据插件启停自动更新接口文档
*/
@Slf4j
public class SpringDocHandler implements BasePluginHandler {
private Set<Class<?>> restControllers;
private OpenAPIService openAPIService;
@SuppressWarnings("unchecked")
@Override
public void initialize() {
ApplicationContext applicationContext = SpringUtil.getApplicationContext();
AbstractOpenApiResource openApiResource = applicationContext.getBean(AbstractOpenApiResource.class);
try {
// 获取OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段
Field additionalRestControllers = ReflectUtil.getField(openApiResource.getClass(), "ADDITIONAL_REST_CONTROLLERS");
additionalRestControllers.setAccessible(true);
restControllers = (Set<Class<?>>) additionalRestControllers.get(openApiResource);
} catch (IllegalAccessException e) {
restControllers = null;
}
openAPIService = applicationContext.getBean(OpenAPIService.class);
}
@Override
public void registry(PluginInfo plugin) throws Exception {
if(restControllers != null){
// 将插件中的Controller类添加到OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段
restControllers.addAll(plugin.getControllers());
refresh();
}
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
if(restControllers != null && !restControllers.isEmpty()){
for (Class<?> clazz : plugin.getControllers()) {
// 从OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段中移除插件中的Controller类
restControllers.remove(clazz);
}
refresh();
plugin.getControllers().clear();
}
}
private void refresh(){
if(openAPIService != null){
// 手动刷新OpenApiResource
openAPIService.setCachedOpenAPI(null, Locale.getDefault());
// openAPIService.resetCalculatedOpenAPI();
}
}
}

View File

@ -1,74 +0,0 @@
package cd.casic.plugin.register;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.register.config.ConfigurationFileHandler;
import cd.casic.plugin.register.config.SpringAutoConfigurationFileHandler;
import cd.casic.plugin.register.database.DatabaseHandler;
import cd.casic.plugin.register.group.ClassGroupHandler;
import cd.casic.plugin.register.mongoplus.MongoHandler;
import cd.casic.plugin.register.mybatis.mybatisplus.MybatisPlusHandler;
import cd.casic.plugin.register.websocket.WebSocketHandler;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @Authormianbin
* @Packagecd.casic.plugin.handler
* @Projectops
* @nameLoadPluginHandle
* @Date2024/03/18 15:46
* @FilenameLoadPluginHandle
* @descriptionTodo
*/
@Component
@Slf4j
public class StartPluginManagerHandler implements BasePluginHandler {
List<BasePluginHandler> pluginRegisterList = Collections.synchronizedList(new ArrayList<>());
@PostConstruct
@Override
public void initialize() throws Exception {
pluginRegisterList.clear();
pluginRegisterList.add(new ClassGroupHandler());
// pluginRegisterList.add(new AnnotationHandler());
pluginRegisterList.add(new ConfigurationFileHandler());
pluginRegisterList.add(new SpringAutoConfigurationFileHandler());
pluginRegisterList.add(new SpringBeanRegisterHandler());
pluginRegisterList.add(new MybatisPlusHandler());
// pluginRegisterList.add(new MybatisHandler());
pluginRegisterList.add(new ResourcesHandler());
pluginRegisterList.add(new ApplicationContextPluginHandler());
pluginRegisterList.add(new DatabaseHandler());
pluginRegisterList.add(new ControllerHandler());
pluginRegisterList.add(new WebSocketHandler());
pluginRegisterList.add(new SpringDocHandler());
pluginRegisterList.add(new MongoHandler());
for (BasePluginHandler pluginHandle : pluginRegisterList) {
pluginHandle.initialize();
}
}
@Override
public void registry(PluginInfo plugin) throws Exception {
for (BasePluginHandler pluginHandler : pluginRegisterList) {
pluginHandler.registry(plugin);
}
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
for (BasePluginHandler pluginHandler : pluginRegisterList) {
pluginHandler.unRegistry(plugin);
}
// for (int i = pluginRegisterList.size() - 1; i >= 0; i--){
// pluginRegisterList.get(i).unRegistry(plugin);
// }
plugin.getClassList().clear();
plugin.setClassList(null);
}
}

View File

@ -1,35 +0,0 @@
package cd.casic.plugin.register.config;
import org.pf4j.util.StringUtils;
public class ConfigFileUtils {
/**
* 拼接插件独立配置文件的文件名和环境后缀
* @param fileName 插件独立配置文件文件名
* @param suffix 配置文件环境后缀PluginConfiguration中定义
* @return 拼接后的文件名
*/
public static String joinConfigFileName(String fileName, String suffix){
if(StringUtils.isNullOrEmpty(fileName)){
return null;
}
String fileNamePrefix;
String fileNamePrefixSuffix;
if(fileName.lastIndexOf(".") == -1) {
fileNamePrefix = fileName;
fileNamePrefixSuffix = "";
} else {
int index = fileName.lastIndexOf(".");
fileNamePrefix = fileName.substring(0, index);
fileNamePrefixSuffix = fileName.substring(index);
}
if(suffix == null){
suffix = "";
}
if(StringUtils.isNotNullOrEmpty(suffix) && !suffix.startsWith("-")){
suffix = "-" + suffix;
}
return fileNamePrefix + suffix + fileNamePrefixSuffix;
}
}

View File

@ -1,126 +0,0 @@
package cd.casic.plugin.register.config;
import cd.casic.plugin.PluginConfigWrapper;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.PluginProperties;
import cd.casic.plugin.PluginYamlConfigurationParser;
import cd.casic.plugin.annotation.PluginConfiguration;
import cd.casic.plugin.register.BasePluginHandler;
import org.pf4j.RuntimeMode;
import org.pf4j.util.StringUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import java.util.ArrayList;
import java.util.List;
/**
* 配置文件处理器用于处理插件独立的配置文件
*/
public class ConfigurationFileHandler implements BasePluginHandler {
private final PluginYamlConfigurationParser configurationParser;
public ConfigurationFileHandler(){
this.configurationParser = new PluginYamlConfigurationParser();
}
@Override
public void initialize() throws Exception {
}
@Override
public void registry(PluginInfo plugin) throws Exception {
PluginProperties pluginProperties = plugin.getMainApplicationContext().getBean(PluginProperties.class);
RuntimeMode runtimeMode = pluginProperties.getRuntimeMode();
for(Class<?> aClass : plugin.getClassList()){
PluginConfiguration configDefinition = aClass.getAnnotation(PluginConfiguration.class);
if(configDefinition == null){
continue;
}
String fileName = getConfigFileName(configDefinition, runtimeMode);
Object parseObject;
if(!StringUtils.isNullOrEmpty(fileName)){
PluginConfigWrapper pluginConfigWrapper =
new PluginConfigWrapper(fileName, aClass);
parseObject = configurationParser.parse(plugin, pluginConfigWrapper);
} else {
try {
parseObject = aClass.getDeclaredConstructor().newInstance();
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Failed to instantiate class " + aClass.getName(), e);
}
}
String name = aClass.getSimpleName();
DefaultListableBeanFactory listableBeanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory();
if(!listableBeanFactory.containsSingleton(name)) {
listableBeanFactory.registerSingleton(name, parseObject);
}
plugin.addPluginConfigObject(parseObject);
}
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
/*上面的写法没有考虑到资源泄露这里把clear关闭资源*/
List<Object> objectsToClear = new ArrayList<>(plugin.getPluginConfigObjects());
plugin.getPluginConfigObjects().clear();
for (Object obj : objectsToClear) {
if (obj instanceof AutoCloseable) {
((AutoCloseable) obj).close();
}
}
}
/**
* 获取插件独立配置文件的文件名
* @param pluginConfiguration 插件配置类上的自定义注解
* @param runtimeMode 插件系统运行模式
* @return 插件独立配置文件的文件名
*/
private String getConfigFileName(PluginConfiguration pluginConfiguration, RuntimeMode runtimeMode){
String fileName = pluginConfiguration.fileName();
if(StringUtils.isNullOrEmpty(fileName)){
return null;
}
String suffix = "";
if(runtimeMode == RuntimeMode.DEPLOYMENT){
// deployment模式
suffix = pluginConfiguration.deploySuffix();
} else if(runtimeMode == RuntimeMode.DEVELOPMENT){
// development模式
suffix = pluginConfiguration.devSuffix();
}
return joinConfigFileName(fileName, suffix);
}
/**
* 拼接插件独立配置文件的文件名和环境后缀
* @param fileName 插件独立配置文件文件名
* @param suffix 配置文件环境后缀PluginConfiguration中定义
* @return 拼接后的文件名
*/
private String joinConfigFileName(String fileName, String suffix){
if(StringUtils.isNullOrEmpty(fileName)){
return null;
}
String fileNamePrefix;
String fileNamePrefixSuffix;
if(fileName.lastIndexOf(".") == -1) {
fileNamePrefix = fileName;
fileNamePrefixSuffix = "";
} else {
int index = fileName.lastIndexOf(".");
fileNamePrefix = fileName.substring(0, index);
fileNamePrefixSuffix = fileName.substring(index);
}
if(suffix == null){
suffix = "";
}
if(StringUtils.isNotNullOrEmpty(suffix) && !suffix.startsWith("-")){
suffix = "-" + suffix;
}
return fileNamePrefix + suffix + fileNamePrefixSuffix;
}
}

View File

@ -1,286 +0,0 @@
package cd.casic.plugin.register.config;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.pf4j.OpsPluginDescriptor;
import cd.casic.plugin.register.BasePluginHandler;
import cd.casic.plugin.utils.YamlUtils;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginDescriptor;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class SpringAutoConfigurationFileHandler implements BasePluginHandler {
List<PropertySourceLoader> propertySourceLoaders = new ArrayList<>();
/**
* 活动文件的属性名称就是devprod等
*/
private static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
/**
* 配置文件的属性名称
*/
private static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
@Override
public void initialize() throws Exception {
propertySourceLoaders.add(new YamlPropertySourceLoader());
propertySourceLoaders.add(new PropertiesPropertySourceLoader());
}
@Override
public void registry(PluginInfo plugin) throws Exception {
PluginDescriptor pluginDescriptor = plugin.getPluginWrapper().getDescriptor();
ConfigurableEnvironment environment = plugin.getPluginApplicationContext().getEnvironment();
if(!(pluginDescriptor instanceof OpsPluginDescriptor)){
return;
}
List<Resource> resourcesFromDescriptor = getConfigResourceFromDescriptor(plugin);
List<PropertySource<?>> propProfiles = getPropProfiles(resourcesFromDescriptor);
if(ObjectUtils.isEmpty(propProfiles)){
return;
}
for (PropertySource<?> propertySource : propProfiles) {
environment.getPropertySources().addLast(propertySource);
}
// 发现原始文件中配置的 profiles
List<Profile> profiles = getProfiles(environment);
if(!ObjectUtils.isEmpty(profiles)){
loadProfilesConfig(plugin.getPluginWrapper().getPluginPath(), (OpsPluginDescriptor) pluginDescriptor, environment, profiles);
}
ConfigurationPropertiesBindingPostProcessor.register(plugin.getPluginApplicationContext());
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
// 无需处理后续关闭pluginApplicationContext即可
}
private List<Resource> getConfigResourceFromDescriptor(PluginInfo plugin){
log.info("插件 {} 尝试从插件描述中读取配置文件名", plugin.getPluginId());
PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor();
if(!(descriptor instanceof OpsPluginDescriptor)){
return Collections.emptyList();
}
OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor;
String configFileName = opsPluginDescriptor.getConfigFileName();
if(StrUtil.isEmpty(configFileName)){
log.info("插件 {} 的插件描述中未设置配置文件名", plugin.getPluginId());
return Collections.emptyList();
}
List<String> configFileActiveNames = getConfigFileActiveNames(configFileName, opsPluginDescriptor.getConfigFileActive());
log.info("插件{} 的配置文件为 {} {}", plugin.getPluginId(), configFileName, configFileActiveNames);
Path pluginPath = plugin.getPluginWrapper().getPluginPath();
List<Resource> configFileResources = configFileActiveNames.stream()
.map(configFileActiveName -> YamlUtils.getYamlPath(pluginPath, configFileActiveName))
.filter(Objects::nonNull)
.map(FileSystemResource::new)
.collect(Collectors.toList());
configFileResources.add(new FileSystemResource(YamlUtils.getYamlPath(pluginPath, configFileName)));
return configFileResources;
}
private List<Resource> getConfigResourceFromAnnotation(){
return Collections.emptyList();
}
private List<String> getConfigFileActiveNames(String configFileName, List<String> configFileActives){
return configFileActives.stream()
.map(configFileActive -> ConfigFileUtils.joinConfigFileName(configFileName, configFileActive))
.collect(Collectors.toList());
}
/**
* Resource 中解析出 PropertySource
* @param resources resources
* @return List
* @throws IOException 加载文件 IOException 异常
*/
private List<PropertySource<?>> getPropProfiles(List<Resource> resources) throws IOException {
List<PropertySource<?>> propProfiles = new ArrayList<>();
if(resources == null || resources.isEmpty()){
return propProfiles;
}
for (Resource resource : resources) {
if(resource == null || !resource.exists()){
continue;
}
String filename = resource.getFilename();
if(ObjectUtils.isEmpty(filename)){
log.error("File name is empty!");
return null;
}
for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
if(!canLoadFileExtension(propertySourceLoader, filename)){
continue;
}
log.info("正在从 {} 读取插件配置", filename);
List<PropertySource<?>> propertySources = propertySourceLoader.load(filename, resource);
if(ObjectUtils.isEmpty(propertySources)){
continue;
}
propProfiles.addAll(propertySources);
}
}
return propProfiles;
}
/**
* 根据文件后缀判断是否可解析
* @param loader PropertySourceLoader
* @param name 文件名称
* @return boolean
*/
private boolean canLoadFileExtension(PropertySourceLoader loader, String name) {
return Arrays.stream(loader.getFileExtensions())
.anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(name,
fileExtension));
}
/**
* 加载 spring.profiles.active/spring.profiles.include 定义的配置
* @param environment ConfigurableEnvironment
* @param profiles 主配置文件中定义的值
* @throws Exception Exception
*/
private void loadProfilesConfig(Path pluginPath, OpsPluginDescriptor opsPluginDescriptor,
ConfigurableEnvironment environment, List<Profile> profiles) throws Exception {
// 解析当前文件名称
for (Profile profile : profiles) {
String name = profile.getName();
String fileName = ConfigFileUtils.joinConfigFileName(opsPluginDescriptor.getConfigFileName(), name);
Path configFilePath = YamlUtils.getYamlPath(pluginPath, fileName);
FileSystemResource configFileResource = new FileSystemResource(configFilePath);
if(ObjectUtils.isEmpty(configFileResource)){
continue;
}
List<PropertySource<?>> propProfiles = getPropProfiles(Collections.singletonList(configFileResource));
if(ObjectUtils.isEmpty(propProfiles)){
return;
}
for (PropertySource<?> propertySource : propProfiles) {
environment.getPropertySources().addLast(propertySource);
}
}
// 重新设置 ActiveProfiles
String[] names = profiles.stream()
.filter((profile) -> profile != null && !profile.isDefaultProfile())
.map(Profile::getName)
.toArray(String[]::new);
environment.setActiveProfiles(names);
}
private List<Profile> getProfiles(Environment environment) {
List<Profile> profiles = new ArrayList<>();
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty(environment);
profiles.addAll(getOtherActiveProfiles(environment, activatedViaProperty));
profiles.addAll(activatedViaProperty);
profiles.removeIf(
(profile) -> (profile != null && profile.isDefaultProfile()));
return profiles;
}
private Set<Profile> getProfilesActivatedViaProperty(Environment environment) {
if (!environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
&& !environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
return Collections.emptySet();
}
Binder binder = Binder.get(environment);
Set<Profile> activeProfiles = new LinkedHashSet<>();
activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
return activeProfiles;
}
private List<Profile> getOtherActiveProfiles(Environment environment, Set<Profile> activatedViaProperty) {
return Arrays.stream(environment.getActiveProfiles()).map(Profile::new)
.filter((profile) -> !activatedViaProperty.contains(profile))
.collect(Collectors.toList());
}
private Set<Profile> getProfiles(Binder binder, String name) {
return binder.bind(name, String[].class).map(this::asProfileSet)
.orElse(Collections.emptySet());
}
private Set<Profile> asProfileSet(String[] profileNames) {
List<Profile> profiles = new ArrayList<>();
for (String profileName : profileNames) {
profiles.add(new Profile(profileName));
}
return new LinkedHashSet<>(profiles);
}
@Getter
private static class Profile {
private final String name;
private final boolean defaultProfile;
Profile(String name) {
this(name, false);
}
Profile(String name, boolean defaultProfile) {
Assert.notNull(name, "Name must not be null");
this.name = name;
this.defaultProfile = defaultProfile;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
return ((Profile) obj).name.equals(this.name);
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
}
}

View File

@ -1,12 +0,0 @@
package cd.casic.plugin.register.database;
import cd.casic.plugin.utils.dBUtils.DBEnums;
public interface DataBaseProperty {
DBEnums type();
String driver();
String url();
String username();
String password();
}

View File

@ -1,84 +0,0 @@
package cd.casic.plugin.register.database;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.pf4j.OpsPluginDescriptor;
import cd.casic.plugin.register.BasePluginHandler;
import cd.casic.plugin.register.group.filter.impl.DataBaseEntityFilter;
import cd.casic.plugin.utils.dBUtils.SQLGenerator;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginDescriptor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
@Slf4j
public class DatabaseHandler implements BasePluginHandler {
@Override
public void initialize() throws Exception {
}
@Override
public void registry(PluginInfo plugin) throws Exception {
DataBaseProperty dataBaseProperty;
SQLGenerator sqlGenerator;
PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor();
if(!(descriptor instanceof OpsPluginDescriptor)){
return;
}
OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor;
if(StrUtil.isNotEmpty(opsPluginDescriptor.getConfigFileName())){
// 使用插件独立数据源
AnnotationConfigApplicationContext pluginApplicationContext = plugin.getPluginApplicationContext();
try{
dataBaseProperty = pluginApplicationContext.getBean(DataBaseProperty.class);
}catch (Exception e){
dataBaseProperty = null;
log.info("插件未配置独立数据源");
}
} else {
dataBaseProperty = null;
}
try {
sqlGenerator = plugin.getMainApplicationContext().getBean(SQLGenerator.class);
}catch (Exception e){
log.error("无法获取SqlGenerator类型的Bean对象");
return;
}
List<Class<?>> entities = plugin.getGroupClass(DataBaseEntityFilter.GROUP_NAME);
for(Class<?> entity : entities){
sqlGenerator.sqlDeleteGenerator(entity, dataBaseProperty, plugin.getPluginId());
sqlGenerator.sqlGenerator(entity, dataBaseProperty, plugin.getPluginId());
}
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
DataBaseProperty dataBaseProperty;
SQLGenerator sqlGenerator;
PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor();
if(!(descriptor instanceof OpsPluginDescriptor)){
return;
}
OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor;
if(StrUtil.isNotEmpty(opsPluginDescriptor.getConfigFileName())){
// 使用插件独立数据源
AnnotationConfigApplicationContext pluginApplicationContext = plugin.getPluginApplicationContext();
try{
dataBaseProperty = pluginApplicationContext.getBean(DataBaseProperty.class);
}catch (Exception e){
log.info("插件未配置独立数据源");
return;
}
} else {
return;
}
try {
sqlGenerator = plugin.getMainApplicationContext().getBean(SQLGenerator.class);
}catch (Exception e){
log.error("无法获取SqlGenerator类型的Bean对象");
return;
}
sqlGenerator.destroyDataSource(dataBaseProperty, plugin.getPluginId());
}
}

View File

@ -1,20 +0,0 @@
package cd.casic.plugin.register.group;
import java.lang.annotation.Annotation;
public class AnnotationUtils {
@SafeVarargs
public static boolean hasAnnotations(Class<?> clazz, boolean allRequired, Class<? extends Annotation>... annotations){
if(clazz == null) return false;
if(annotations == null) return false;
for(Class<? extends Annotation> annotation : annotations){
if(clazz.isAnnotationPresent(annotation)){
if(!allRequired) return true;
}else{
if(allRequired) return false;
}
}
return allRequired;
}
}

View File

@ -1,111 +0,0 @@
package cd.casic.plugin.register.group;
import cd.casic.plugin.BasePlugin;
import cd.casic.plugin.PluginClassLoaderCache;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.register.BasePluginHandler;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
import cd.casic.plugin.register.group.filter.impl.*;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper;
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;
/**
* 加载插件中所有的类,并且分组存放到pluginInfo的map中,便于后面的handler进行处理
*/
@Slf4j
public class ClassGroupHandler implements BasePluginHandler {
private final List<PluginClassFilter> classFilters = new ArrayList<>();
@Override
public void initialize() throws Exception {
classFilters.add(new BasicBeanFilter());
classFilters.add(new ControllerFilter());
classFilters.add(new DataBaseEntityFilter());
classFilters.add(new MapperFilter());
classFilters.add(new PluginConfigurationFilter());
classFilters.add(new WebSocketFilter());
}
/**
* 将类分组存到PluginInfo的map中,方便后面其他Handler进行处理
* @param plugin PluginInfo
*/
@Override
public void registry(PluginInfo plugin) throws Exception {
List<Class<?>> classList = new ArrayList<>();
Set<String> classPackageName = scanClassPackageName(plugin.getBasePlugin().scanPackage(), plugin.getBasePlugin().getWrapper());
for (String packageName : classPackageName) {
ClassLoader loader = PluginClassLoaderCache.getPlugin(plugin.getPluginId());
log.warn("Load class {} using classloader {} for plugin {}", packageName, PluginClassLoaderCache.getPlugin(plugin.getPluginId()), plugin.getPluginId());
Class<?> clazz = loader.loadClass(packageName);
if (!BasePlugin.class.isAssignableFrom(clazz)) {
classList.add(clazz);
}
}
plugin.setClassList(classList);
/*这里分组其实只是为了分组而分组,目前没有特别明确的用途*/
List<Class<?>> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList());
if (!pluginClassList.isEmpty()) {
for (Class<?> clazz : pluginClassList) {
boolean grouped = false;
for(PluginClassFilter filter : classFilters){
if(filter.filter(clazz)){
log.info("将类 {} 添加到 {} 分组", clazz, filter.groupName());
plugin.addGroupClass(filter.groupName(), clazz);
grouped = true;
}
}
if(!grouped){
log.info("类 {} 不属于任何已知分组", clazz);
plugin.addGroupClass("unknown", clazz);
}
}
}
}
/**
* 卸载插件时,将插件的类列表清空
* @param plugin PluginInfo
*/
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
plugin.getClassList().clear();
}
/**
* 扫描插件所在包,获取插件中所有的类名
* @param basePackage 插件所在包名
* @param pluginWrapper 插件对象包装类
* @return 所有的类名集合
*/
private Set<String> scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException {
Path pluginPath = pluginWrapper.getPluginPath();
if(pluginPath == null || !Files.exists(pluginPath)){
throw new RuntimeException("错误的插件路径");
}
File pluginFile = pluginPath.toFile();
Set<String> classPackageNames = new HashSet<>();
try (JarFile jar = new JarFile(pluginFile)) {
Enumeration<JarEntry> 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;
}
}

View File

@ -1,9 +0,0 @@
package cd.casic.plugin.register.group.filter;
public interface PluginClassFilter{
String groupName();
void initialize();
boolean filter(Class<?> clazz);
}

View File

@ -1,30 +0,0 @@
package cd.casic.plugin.register.group.filter.impl;
import cd.casic.plugin.register.group.AnnotationUtils;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
public class BasicBeanFilter implements PluginClassFilter {
public static final String GROUP_NAME = "basic_bean";
@Override
public String groupName() {
return GROUP_NAME;
}
@Override
public void initialize() {
}
@Override
public boolean filter(Class<?> clazz) {
return AnnotationUtils.hasAnnotations(clazz, false, Bean.class,
Service.class,
Component.class,
Configuration.class);
}
}

View File

@ -1,28 +0,0 @@
package cd.casic.plugin.register.group.filter.impl;
import cd.casic.plugin.register.group.AnnotationUtils;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
/**
* Controller 过滤器筛选带有Controller或者RestController注解的类
*/
public class ControllerFilter implements PluginClassFilter {
public static final String GROUP_NAME = "spring_controller";
@Override
public String groupName() {
return GROUP_NAME;
}
@Override
public void initialize() {
}
@Override
public boolean filter(Class<?> clazz) {
return AnnotationUtils.hasAnnotations(clazz, false, Controller.class, RestController.class);
}
}

View File

@ -1,24 +0,0 @@
package cd.casic.plugin.register.group.filter.impl;
import cd.casic.plugin.register.group.AnnotationUtils;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
import cd.casic.plugin.utils.dBUtils.annotation.Entity;
public class DataBaseEntityFilter implements PluginClassFilter {
public static final String GROUP_NAME = "database_entity";
@Override
public String groupName() {
return GROUP_NAME;
}
@Override
public void initialize() {
}
@Override
public boolean filter(Class<?> clazz) {
return AnnotationUtils.hasAnnotations(clazz, false, Entity.class);
}
}

View File

@ -1,24 +0,0 @@
package cd.casic.plugin.register.group.filter.impl;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
public class MapperFilter implements PluginClassFilter {
public static final String GROUP_NAME = "mapper";
@Override
public String groupName() {
return GROUP_NAME;
}
@Override
public void initialize() {
}
@Override
public boolean filter(Class<?> clazz) {
return clazz.isAnnotationPresent(Mapper.class) || clazz.isAnnotationPresent(Repository.class);
}
}

View File

@ -1,29 +0,0 @@
package cd.casic.plugin.register.group.filter.impl;
import cd.casic.plugin.BasePlugin;
import cd.casic.plugin.annotation.PluginConfiguration;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
public class PluginConfigurationFilter implements PluginClassFilter {
public static final String GROUP_NAME = "plugin_config";
@Override
public String groupName() {
return GROUP_NAME;
}
@Override
public void initialize() {
}
@Override
public boolean filter(Class<?> clazz) {
if(BasePlugin.class.isAssignableFrom(clazz)){
return false;
}
return clazz.isAnnotationPresent(PluginConfiguration.class);
}
}

View File

@ -1,25 +0,0 @@
package cd.casic.plugin.register.group.filter.impl;
import cd.casic.plugin.register.group.filter.PluginClassFilter;
import jakarta.websocket.server.ServerEndpoint;
public class WebSocketFilter implements PluginClassFilter {
public static final String GROUP_NAME = "websocket";
@Override
public String groupName() {
return GROUP_NAME;
}
@Override
public void initialize() {
}
@Override
public boolean filter(Class<?> clazz) {
return clazz.isAnnotationPresent(ServerEndpoint.class);
}
}

View File

@ -1,112 +0,0 @@
package cd.casic.plugin.register.mongoplus;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.register.BasePluginHandler;
import com.anwen.mongo.annotation.collection.CollectionName;
import com.anwen.mongo.conn.CollectionManager;
import com.anwen.mongo.conn.ConnectMongoDB;
import com.anwen.mongo.convert.CollectionNameConvert;
import com.anwen.mongo.manager.MongoPlusClient;
import com.anwen.mongo.mapper.BaseMapper;
import com.anwen.mongo.service.IService;
import com.anwen.mongo.service.impl.ServiceImpl;
import com.mongodb.MongoException;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.beans.BeansException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
public class MongoHandler implements BasePluginHandler {
private AnnotationConfigApplicationContext applicationContext;
@Override
public void initialize() throws Exception {
log.info("Start to hand the Mongo Class for plugin");
}
@Override
public void registry(PluginInfo plugin) throws Exception {
try {
applicationContext = plugin.getPluginApplicationContext();
// 注册AutoConfigurationPackages, 用于插件可自动配置
// AutoConfigurationPackages.register(applicationContext.getDefaultListableBeanFactory(),
// plugin.getBasePlugin().scanPackage());
//
// Set.of(MongoPlusConfiguration.class, MongoPlusAutoConfiguration.class, OverrideMongoConfiguration.class,
// MongoSpringProperty.class, MongoDBFieldProperty.class, MongoTransactionManagerAutoConfiguration.class,
// MongoPropertyConfiguration.class, )
// ClassLoader pluginClassLoader = pluginRegistryInfo.getPluginClassLoader();
// for (String autoConfigClassPackage : installAutoConfigClassString) {
// Class<?> aClass = Class.forName(autoConfigClassPackage, false, pluginClassLoader);
// autoConfigurationClassSet.add(aClass);
// }
// for (Class<?> autoConfigurationClass : autoConfigurationClassSet) {
// pluginApplicationContext.registerBean(autoConfigurationClass);
// }
applicationContext.getBeansOfType(IService.class)
.values()
.stream()
.filter(s -> s instanceof ServiceImpl)
.forEach(s -> {
Class<?> clazz = s.getGenericityClass();
ServiceImpl<?> serviceImpl = (ServiceImpl<?>) s;
serviceImpl.setClazz(clazz);
String database = initFactory(clazz);
// 这里需要将MongoPlusClient给工厂
serviceImpl.setDatabase(database);
serviceImpl.setBaseMapper(applicationContext.getBean("baseMapper", BaseMapper.class));
});
}catch(BeansException e){
log.info(e.getMessage());
}
}
private String initFactory(Class<?> clazz) {
String collectionName = clazz.getSimpleName().toLowerCase();
final String[] dataBaseName = {""};
if (clazz.isAnnotationPresent(CollectionName.class)) {
CollectionName annotation = clazz.getAnnotation(CollectionName.class);
collectionName = annotation.value();
dataBaseName[0] = annotation.database();
}
try {
String finalCollectionName = collectionName;
final String[] finalDataBaseName = {dataBaseName[0]};
List<MongoDatabase> mongoDatabaseList = new ArrayList<>();
MongoPlusClient mongoPlusClient = applicationContext.getBean("mongoPlusClient", MongoPlusClient.class);
String database = mongoPlusClient.getBaseProperty().getDatabase();
Arrays.stream(database.split(",")).collect(Collectors.toList()).forEach(db -> {
CollectionNameConvert collectionNameConvert = applicationContext.getBean("collectionNameConvert", CollectionNameConvert.class);
CollectionManager collectionManager = new CollectionManager(mongoPlusClient.getMongoClient(), collectionNameConvert, db);
ConnectMongoDB connectMongodb = new ConnectMongoDB(mongoPlusClient.getMongoClient(), db, finalCollectionName);
MongoDatabase mongoDatabase = mongoPlusClient.getMongoClient().getDatabase(db);
mongoDatabaseList.add(mongoDatabase);
if (Objects.equals(db, finalDataBaseName[0])) {
MongoCollection<Document> collection = connectMongodb.open(mongoDatabase);
collectionManager.setCollectionMap(finalCollectionName, collection);
}
mongoPlusClient.getCollectionManager().put(db, collectionManager);
});
mongoPlusClient.setMongoDatabase(mongoDatabaseList);
} catch (MongoException e) {
log.error("Failed to connect to MongoDB: {}", e.getMessage(), e);
}
return dataBaseName[0];
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
// 不做操作直接通过关闭PluginApplicationContext完成注销
}
}

View File

@ -1,101 +0,0 @@
package cd.casic.plugin.register.mybatis;
import cd.casic.plugin.PluginInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.*;
import java.util.ArrayList;
import java.util.List;
public class MapperHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MapperHandler.class);
private static final String MAPPER_INTERFACE_NAMES = "MybatisMapperInterfaceNames";
private final ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
public MapperHandler() {
}
/**
* 处理插件中的Mapper
* @param pluginInfo 插件信息
* @param processMapper Mapper的具体处理者
*/
public void processMapper(PluginInfo pluginInfo,
ProcessMapper processMapper){
AnnotationConfigApplicationContext applicationContext = pluginInfo.getPluginApplicationContext();
// TODO 这里可以把类进行分组就不用每次都扫mapper
List<Class<?>> mapperClassList = new ArrayList<>();
for (Class<?> aClass : pluginInfo.getClassList()) {
Mapper annotation = aClass.getAnnotation(Mapper.class);
if (annotation != null) {
mapperClassList.add(aClass);
}
}
String pluginId = pluginInfo.getPluginId();
for (Class<?> mapperClass : mapperClassList) {
if (mapperClass == null) {
continue;
}
// BeanNameGenerator beanNameGenerator = new PluginAnnotationBeanNameGenerator(pluginId);
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(mapperClass);
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
// String beanName = beanNameGenerator.generateBeanName(abd, applicationContext);
String beanName = abd.getBeanClassName();
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, applicationContext);
try {
processMapper.process(definitionHolder, mapperClass);
} catch (Exception e) {
LOGGER.error("process mapper '{}' error. {}", mapperClass.getName(), e.getMessage(), e);
}
}
}
/**
* 公共注册生成代理Mapper接口
* @param holder ignore
* @param mapperClass ignore
* @param sqlSessionFactory ignore
* @param sqlSessionTemplate ignore
*/
public void commonProcessMapper(BeanDefinitionHolder holder,
Class<?> mapperClass,
SqlSessionFactory sqlSessionFactory,
SqlSessionTemplate sqlSessionTemplate) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass);
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", true);
definition.getPropertyValues().add("sqlSessionFactory", sqlSessionFactory);
definition.getPropertyValues().add("sqlSessionTemplate", sqlSessionTemplate);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
@FunctionalInterface
public interface ProcessMapper{
void process(BeanDefinitionHolder holder, Class<?> mapperClass) throws Exception;
}
}

View File

@ -1,38 +0,0 @@
package cd.casic.plugin.register.mybatis;
import java.util.Set;
/**
* Mybatis基础配置
*/
public interface MybatisCommonConfig {
/**
* 数据库表对应的实体类的包名集合可配置多个
* @return Set
*/
Set<String> entityPackage();
/**
* mybatis xml mapper 匹配规则 <br>
* ? 匹配一个字符 <br>
* * 匹配零个或多个字符 <br>
* ** 匹配路径中的零或多个目录 <br>
* 例如: <br>
* 文件路径配置为 <p>file:D://xml/*PluginMapper.xml<p> <br>
* resources路径配置为 <p>classpath:xml/mapper/*PluginMapper.xml<p> <br>
* 包路径配置为 <p>package:com.plugin.xml.mapper.*PluginMapper.xml<p> <br>
* @return Set
*/
Set<String> xmlLocationsMatch();
/**
* 插件是否自主启用配置. 默认进行禁用, 使用主程序的配置
* @return 返回true, 表示进行插件自主进行Mybatis相关配置
*/
default boolean enableOneselfConfig(){
return false;
}
}

View File

@ -1,123 +0,0 @@
package cd.casic.plugin.register.mybatis;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.LanguageDriverRegistry;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils;
import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 插件跟随主程序时, 获取主程序的Mybatis定义的一些配置
*/
public class PluginFollowCoreConfig {
private final ApplicationContext mainApplicationContext;
public PluginFollowCoreConfig(ApplicationContext mainApplicationContext) {
this.mainApplicationContext = mainApplicationContext;
}
public DataSource getDataSource(){
return mainApplicationContext.getBean(DataSource.class);
}
public MybatisConfiguration getMybatisPlusConfiguration(){
MybatisConfiguration configuration = new MybatisConfiguration();
try {
Map<String, com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer> customizerMap =
mainApplicationContext.getBeansOfType(com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer.class);
if(!customizerMap.isEmpty()){
for (com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer customizer : customizerMap.values()) {
customizer.customize(configuration);
}
}
} catch (Exception e){
// ignore
}
return configuration;
}
public Interceptor[] getInterceptor(){
Map<Class<? extends Interceptor>, Interceptor> interceptorMap = new HashMap<>();
try {
SqlSessionFactory sqlSessionFactory = mainApplicationContext.getBean(SqlSessionFactory.class);
// 先从 SqlSessionFactory 工厂中获取拦截器
List<Interceptor> interceptors = sqlSessionFactory.getConfiguration().getInterceptors();
if(interceptors != null){
for (Interceptor interceptor : interceptors) {
if(interceptor == null){
continue;
}
interceptorMap.put(interceptor.getClass(), interceptor);
}
}
} catch (Exception e){
// ignore
}
// 再从定义Bean中获取拦截器
Map<String, Interceptor> beanInterceptorMap = mainApplicationContext.getBeansOfType(Interceptor.class);
if(!beanInterceptorMap.isEmpty()){
beanInterceptorMap.forEach((k, v)->{
// 如果Class一致, 则会覆盖
interceptorMap.put(v.getClass(), v);
});
}
if(interceptorMap.isEmpty()) {
return null;
} else {
return interceptorMap.values().toArray(new Interceptor[0]);
}
}
public DatabaseIdProvider getDatabaseIdProvider(){
String[] beanNamesForType = mainApplicationContext.getBeanNamesForType(DatabaseIdProvider.class, false, false);
if(beanNamesForType.length > 0){
return mainApplicationContext.getBean(DatabaseIdProvider.class);
}
return null;
}
@SuppressWarnings("unchecked")
public LanguageDriver[] getLanguageDriver(){
Map<Class<? extends LanguageDriver>, LanguageDriver> languageDriverMap = new HashMap<>();
try {
SqlSessionFactory sqlSessionFactory = mainApplicationContext.getBean(SqlSessionFactory.class);
LanguageDriverRegistry languageRegistry = sqlSessionFactory.getConfiguration()
.getLanguageRegistry();
// 先从 SqlSessionFactory 工厂中获取LanguageDriver
Field proxyTypesField = ReflectionUtils.findField(languageRegistry.getClass(), "LANGUAGE_DRIVER_MAP");
Map<Class<? extends LanguageDriver>, LanguageDriver> driverMap = null;
if(proxyTypesField != null){
proxyTypesField.setAccessible(true);
driverMap = (Map<Class<? extends LanguageDriver>, LanguageDriver>) proxyTypesField.get(languageRegistry);
}
if(driverMap != null){
languageDriverMap.putAll(driverMap);
}
} catch (Exception e){
// ignore
}
Map<String, LanguageDriver> beansLanguageDriver = mainApplicationContext.getBeansOfType(LanguageDriver.class);
if(!beansLanguageDriver.isEmpty()){
beansLanguageDriver.forEach((k, v)->{
// 如果Class一致, 则会覆盖
languageDriverMap.put(v.getClass(), v);
});
}
if(languageDriverMap.isEmpty()){
return null;
}
return languageDriverMap.values().toArray(new LanguageDriver[0]);
}
}

View File

@ -1,121 +0,0 @@
package cd.casic.plugin.register.mybatis;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.utils.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.util.*;
/**
* 插件资源发现者
* @author starBlues
* @version 2.4.0
*/
public class PluginResourceFinder {
private static final Logger LOGGER = LoggerFactory.getLogger(PluginResourceFinder.class);
private final ClassLoader classLoader;
private final ResourcePatternResolver resourcePatternResolver;
private final MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
public PluginResourceFinder(PluginInfo pluginInfo) {
this.classLoader = pluginInfo.getPluginWrapper().getPluginClassLoader();
this.resourcePatternResolver = new PathMatchingResourcePatternResolver(classLoader);
}
/**
* 获取插件中xml资源
* @param xmlLocationsMatchSet xml资源匹配集合
* @return xml Resource 数组
* @throws IOException 获取xml资源异常
*/
public Resource[] getXmlResource(Set<String> xmlLocationsMatchSet) throws IOException {
if(xmlLocationsMatchSet == null || xmlLocationsMatchSet.isEmpty()){
return null;
}
List<Resource> resources = new ArrayList<>();
for (String xmlLocationsMatch : xmlLocationsMatchSet) {
if(xmlLocationsMatchSet.isEmpty()){
continue;
}
List<Resource> loadResources = getXmlResources(xmlLocationsMatch);
if(loadResources != null && !loadResources.isEmpty()){
resources.addAll(loadResources);
}
}
if(resources.isEmpty()){
return null;
}
return resources.toArray(new Resource[0]);
}
/**
* 获取插件的实体类及其别名
* @param packagePatterns 实体类包名
* @return class 数组
* @throws IOException 获取医院异常
*/
public Class<?>[] getAliasesClasses(Set<String> packagePatterns) throws IOException {
if(packagePatterns == null || packagePatterns.isEmpty()){
return null;
}
Set<Class<?>> aliasesClasses = new HashSet<>();
for (String packagePattern : packagePatterns) {
Resource[] resources = resourcePatternResolver.getResources(
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
for (Resource resource : resources) {
try {
ClassMetadata classMetadata = metadataReaderFactory.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = classLoader.loadClass(classMetadata.getClassName());
aliasesClasses.add(clazz);
} catch (Throwable e) {
LOGGER.warn("Cannot load the '{}'. Cause by {}", resource, e.toString());
}
}
}
return aliasesClasses.toArray(new Class<?>[0]);
}
/**
* 获取Xml资源
* @param mybatisMapperXmlLocationMatch mybatis xml 批量规则
* @return 匹配到的xml资源
* @throws IOException IO 异常
*/
private List<Resource> getXmlResources(String mybatisMapperXmlLocationMatch) throws IOException {
String matchLocation = ResourceUtils.getMatchLocation(mybatisMapperXmlLocationMatch);
if(matchLocation == null){
LOGGER.error("mybatisMapperXmlLocation {} illegal", mybatisMapperXmlLocationMatch);
return null;
}
try {
Resource[] resources = resourcePatternResolver.getResources(matchLocation);
if(resources.length > 0){
return Arrays.asList(resources);
} else {
return null;
}
} catch (IOException e) {
LOGGER.error("mybatis xml resource '{}' match error : {}", mybatisMapperXmlLocationMatch,
e.getMessage(), e);
throw e;
}
}
}

View File

@ -1,27 +0,0 @@
package cd.casic.plugin.register.mybatis.mybatisplus;
import cd.casic.plugin.register.mybatis.MybatisCommonConfig;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
public interface MybatisPlusConfig extends MybatisCommonConfig {
/**
* 插件自主配置Mybatis-Plus的MybatisSqlSessionFactoryBean
* MybatisSqlSessionFactoryBean 具体配置说明参考 Mybatis-plus 官网
* @param sqlSessionFactoryBean MybatisSqlSessionFactoryBean
*/
default void oneselfConfig(MybatisSqlSessionFactoryBean sqlSessionFactoryBean){
}
/**
* 重写设置配置
* 只有 enableOneselfConfig 返回 false, 实现该方法才生效
* @param configuration 当前 MybatisConfiguration
* @param globalConfig 当前全局配置GlobalConfig
*/
default void reSetMainConfig(MybatisConfiguration configuration, GlobalConfig globalConfig){
}
}

View File

@ -1,133 +0,0 @@
package cd.casic.plugin.register.mybatis.mybatisplus;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.register.BasePluginHandler;
import cd.casic.plugin.register.mybatis.MapperHandler;
import cd.casic.plugin.register.mybatis.PluginFollowCoreConfig;
import cd.casic.plugin.register.mybatis.PluginResourceFinder;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.util.Set;
public class MybatisPlusHandler implements BasePluginHandler {
@Override
public void initialize() throws Exception {
}
@Override
public void registry(PluginInfo plugin) throws IOException {
MybatisPlusConfig config = getObjectByInterfaceClass(plugin.getPluginConfigObjects(), MybatisPlusConfig.class);
if(config == null) return;
final MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
if(config.enableOneselfConfig()){
config.oneselfConfig(factory);
} else {
PluginFollowCoreConfig followCoreConfig = new PluginFollowCoreConfig(
plugin.getMainApplicationContext()
);
MybatisConfiguration mybatisPlusConfiguration = followCoreConfig.getMybatisPlusConfiguration();
factory.setDataSource(followCoreConfig.getDataSource());
factory.setConfiguration(mybatisPlusConfiguration);
Interceptor[] interceptor = followCoreConfig.getInterceptor();
if(interceptor != null && interceptor.length > 0){
factory.setPlugins(interceptor);
}
DatabaseIdProvider databaseIdProvider = followCoreConfig.getDatabaseIdProvider();
if(databaseIdProvider != null){
factory.setDatabaseIdProvider(databaseIdProvider);
}
LanguageDriver[] languageDriver = followCoreConfig.getLanguageDriver();
if(languageDriver != null){
factory.setScriptingLanguageDrivers(languageDriver);
}
// 配置mybatis-plus私有的配置
GlobalConfig globalConfig = mybatisPlusFollowCoreConfig(factory, plugin.getMainApplicationContext());
config.reSetMainConfig(mybatisPlusConfiguration, globalConfig);
}
PluginResourceFinder pluginResourceFinder = new PluginResourceFinder(plugin);
Class<?>[] aliasesClasses = pluginResourceFinder.getAliasesClasses(config.entityPackage());
if(aliasesClasses != null && aliasesClasses.length > 0){
factory.setTypeAliases(aliasesClasses);
}
Resource[] xmlResource = pluginResourceFinder.getXmlResource(config.xmlLocationsMatch());
if(xmlResource != null && xmlResource.length > 0){
factory.setMapperLocations(xmlResource);
}
ClassLoader defaultClassLoader = Resources.getDefaultClassLoader();
try {
Resources.setDefaultClassLoader(plugin.getPluginWrapper().getPluginClassLoader());
SqlSessionFactory sqlSessionFactory = factory.getObject();
if(sqlSessionFactory == null){
throw new Exception("Get mybatis-plus sqlSessionFactory is null");
}
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
MapperHandler mapperHandler = new MapperHandler();
mapperHandler.processMapper(plugin, (holder, mapperClass) ->
mapperHandler.commonProcessMapper(holder, mapperClass, sqlSessionFactory, sqlSessionTemplate));
DefaultListableBeanFactory beanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory();
beanFactory.registerSingleton("sqlSessionFactory", sqlSessionFactory);
beanFactory.registerSingleton("sqlSession", sqlSessionTemplate);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
Resources.setDefaultClassLoader(defaultClassLoader);
}
}
@Override
public void unRegistry(PluginInfo plugin) throws Exception {
// 不做操作直接通过关闭PluginApplicationContext完成注销
}
private GlobalConfig mybatisPlusFollowCoreConfig(MybatisSqlSessionFactoryBean factory,
ApplicationContext mainApplicationContext){
MybatisPlusProperties plusProperties = mainApplicationContext.getBean(MybatisPlusProperties.class);
GlobalConfig currentGlobalConfig = new GlobalConfig();
currentGlobalConfig.setBanner(false);
GlobalConfig globalConfig = plusProperties.getGlobalConfig();
if(globalConfig != null){
currentGlobalConfig.setDbConfig(globalConfig.getDbConfig());
currentGlobalConfig.setIdentifierGenerator(globalConfig.getIdentifierGenerator());
currentGlobalConfig.setMetaObjectHandler(globalConfig.getMetaObjectHandler());
currentGlobalConfig.setSqlInjector(globalConfig.getSqlInjector());
}
factory.setGlobalConfig(currentGlobalConfig);
return currentGlobalConfig;
}
// TODO 临时放这里先跑通mybatis-plus集成测试
public static <T> T getObjectByInterfaceClass(Set<Object> sourceObject, Class<T> interfaceClass){
if(sourceObject == null || sourceObject.isEmpty()){
return null;
}
for (Object configSingletonObject : sourceObject) {
Set<Class<?>> allInterfacesForClassAsSet = ClassUtils
.getAllInterfacesAsSet(configSingletonObject);
if(allInterfacesForClassAsSet.contains(interfaceClass)){
return (T) configSingletonObject;
}
}
return null;
}
}

View File

@ -1,7 +0,0 @@
package cd.casic.plugin.register.websocket;
import org.springframework.beans.factory.DisposableBean;
public interface BaseServerEndpoint extends DisposableBean {
}

View File

@ -1,235 +0,0 @@
package cd.casic.plugin.register.websocket;
import cd.casic.plugin.PluginInfo;
import cd.casic.plugin.register.BasePluginHandler;
import cd.casic.plugin.register.group.filter.impl.WebSocketFilter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.ServletContext;
import jakarta.websocket.DeploymentException;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerContainer;
import jakarta.websocket.server.ServerEndpoint;
import jakarta.websocket.server.ServerEndpointConfig;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.util.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
@Slf4j
@SuppressWarnings("unchecked")
public class WebSocketHandler implements BasePluginHandler {
private ApplicationContext mainApplicationContext;
@Override
public void initialize() throws Exception {
this.mainApplicationContext = SpringUtil.getApplicationContext();
}
@Override
public void registry(PluginInfo plugin) throws Exception {
ServerContainer serverContainer = getServerContainer();
if (serverContainer == null) return;
List<Class<?>> webSocketClassList = plugin.getGroupClass(WebSocketFilter.GROUP_NAME);
if(webSocketClassList.isEmpty()) return;
String pluginId = plugin.getPluginId();
webSocketClassList.forEach(websocketClass -> {
ServerEndpoint serverEndpoint = websocketClass.getDeclaredAnnotation(ServerEndpoint.class);
if (serverEndpoint == null) {
log.warn("WebSocket class {} doesn't has annotation {}", websocketClass.getName(), ServerEndpoint.class.getName());
return;
}
String sourcePath = serverEndpoint.value();
if (StringUtils.isNullOrEmpty(sourcePath)) {
return;
}
String processPath = sourcePath;
if(!processPath.startsWith("/")){
processPath = "/".concat(processPath);
}
UriTemplate uriTemplate;
try {
uriTemplate = new UriTemplate(processPath);
} catch (DeploymentException e) {
log.error("Websocket path validate failed.", e);
return;
}
String newWebsocketPath = "/".concat(pluginId).concat(processPath);
String newWebsocketTemplatePath = "/".concat(pluginId).concat(uriTemplate.getPath());
Map<String, Object> annotationsUpdater;
try {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(serverEndpoint);
Field field = invocationHandler.getClass().getDeclaredField("memberValues");
field.setAccessible(true);
annotationsUpdater = (Map<String, Object>) field.get(invocationHandler);
} catch (Exception e) {
log.error("Process and update websocket path '{}' annotation exception.", sourcePath, e);
return;
}
try {
annotationsUpdater.put("value", newWebsocketPath);
serverContainer.addEndpoint(websocketClass);
plugin.getWebSocketPathMap().put(newWebsocketPath, newWebsocketTemplatePath);
log.info("Succeed to create websocket service for path {}", newWebsocketPath);
} catch (Exception e) {
log.error("Create websocket service for websocket class " + websocketClass.getName() + " failed.", e);
} finally {
annotationsUpdater.put("value", sourcePath);
}
});
}
@Override
public void unRegistry(PluginInfo plugin){
ServerContainer serverContainer = getServerContainer();
if (serverContainer == null) {
log.warn("Not found ServerContainer, So websocket can't used!");
return;
}
Class<? extends ServerContainer> containerClass = serverContainer.getClass();
try {
Field configExactMatchMapField = containerClass.getDeclaredField("configExactMatchMap");
configExactMatchMapField.setAccessible(true);
Map<String, Object> configExactMatchMap = (Map<String, Object>) configExactMatchMapField.get(serverContainer);
Field configTemplateMatchMapField = containerClass.getDeclaredField("configTemplateMatchMap");
configTemplateMatchMapField.setAccessible(true);
Map<Integer, ConcurrentSkipListMap<String, Object>> configTemplateMatchMap =
(Map<Integer, ConcurrentSkipListMap<String, Object>>) configTemplateMatchMapField.get(serverContainer);
Map<String, String> webSocketPathMap = plugin.getWebSocketPathMap();
webSocketPathMap.forEach((webSocketPath, newWebsocketTemplatePath)->{
configExactMatchMap.remove(webSocketPath);
log.debug("Removed websocket config for path {}", webSocketPath);
configTemplateMatchMap.forEach((key, value) -> value.remove(newWebsocketTemplatePath));
log.debug("Removed websocket session for path {}", webSocketPath);
log.info("Remove websocket for path {} success.", webSocketPath);
});
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 得到 Tomcat ServerContainer
* @return ServerContainer
*/
private ServerContainer getServerContainer() {
try {
mainApplicationContext.getBean(ServerEndpointExporter.class);
} catch (BeansException e) {
log.debug("The required bean of {} not found, if you want to use plugin websocket, please create it.", ServerEndpointExporter.class.getName());
return null;
}
if (!(mainApplicationContext instanceof WebApplicationContext)) {
return null;
}
WebApplicationContext webApplicationContext = (WebApplicationContext) mainApplicationContext;
ServletContext servletContext = webApplicationContext.getServletContext();
if (servletContext == null) {
log.warn("Servlet context is null.");
return null;
}
Object obj = servletContext.getAttribute("javax.websocket.server.ServerContainer");
if (!(obj instanceof ServerContainer)) {
return null;
}
return (ServerContainer) obj;
}
/**
* 关闭session
* @param session session
* @param websocketPath websocketPath 路径
* @return 如果需要关闭并且关闭成功, 则返回true 否则返回false
* @throws Exception 关闭异常
*/
@Deprecated
private boolean closeSession(Session session, String websocketPath) throws Exception{
EndpointConfig endpointConfig = (EndpointConfig) session.getClass().getDeclaredField("endpointConfig").get(session);
ServerEndpointConfig perEndpointConfig = (ServerEndpointConfig) endpointConfig.getClass().getDeclaredField("perEndpointConfig").get(endpointConfig);
String path = perEndpointConfig.getPath();
if (path.equals(websocketPath)) {
session.close();
log.info("Closed websocket session {} for path {}", session.getId(), websocketPath);
return true;
}
return false;
}
/**
* websocket路径解析类主要用于处理参数
*/
@Getter
private static class UriTemplate {
private final Map<String, Integer> paramMap = new ConcurrentHashMap<>();
private final String path;
private UriTemplate(String path) throws DeploymentException {
if (StrUtil.isEmpty(path) || !path.startsWith("/") || path.contains("/../") || path.contains("/./") || path.contains("//")) {
throw new DeploymentException(String.format("The path [%s] is not valid.", path));
}
StringBuilder normalized = new StringBuilder(path.length());
Set<String> paramNames = new HashSet<>();
// Include empty segments.
String[] segments = path.split("/", -1);
int paramCount = 0;
for (int i = 0; i < segments.length; i++) {
String segment = segments[i];
if (segment.isEmpty()) {
if (i == 0 || (i == segments.length - 1 && paramCount == 0)) {
// Ignore the first empty segment as the path must always
// start with '/'
// Ending with a '/' is also OK for instances used for
// matches but not for parameterised templates.
continue;
} else {
// As per EG discussion, all other empty segments are
// invalid
throw new DeploymentException(String.format("The path [%s] contains one or more empty segments which is not permitted", path));
}
}
normalized.append('/');
if (segment.startsWith("{") && segment.endsWith("}")) {
segment = segment.substring(1, segment.length() - 1);
normalized.append('{');
normalized.append(paramCount++);
normalized.append('}');
if (!paramNames.add(segment)) {
throw new DeploymentException(String.format("The parameter [%s] appears more than once in the path which is not permitted", segment));
}
paramMap.put(segment, paramCount - 1);
} else {
if (segment.contains("{") || segment.contains("}")) {
throw new DeploymentException(String.format("The segment [%s] is not valid in the provided path [%s]", segment, path));
}
normalized.append(segment);
}
}
this.path = normalized.toString();
}
}
}

View File

@ -1,226 +0,0 @@
package cd.casic.plugin.utils;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.lang.NonNull;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@NoArgsConstructor
public class BeanUtils {
public static <T> T getFieldValue(@NonNull Object target, @NonNull String path) {
String[] fieldPath = path.split("\\.");
Object obj = target;
int i = 0;
while (i < fieldPath.length) {
if (obj == null) break;
if ("*".equals(fieldPath[i])) {
// merge map
if (obj instanceof Map) {
obj = ((Map<?, ?>) obj).values();
} else if (obj instanceof Collection) {
obj = obj;
} else {
// non-support object fields
return null;
}
} else {
if (obj instanceof Collection) {
List<Object> values = new ArrayList<>();
for (Object item : (Collection<?>) obj) {
Object value = getFieldValue(item, item.getClass(), fieldPath[i]);
values.add(value);
}
obj = values;
} else {
obj = getFieldValue(obj, obj.getClass(), fieldPath[i]);
}
}
i++;
}
return (T) obj;
}
public static Class<?> getFieldClass(@NonNull Object target, @NonNull String fieldName) {
try {
return target.getClass().getDeclaredField(fieldName).getType();
} catch (Exception e) {
return null;
}
}
private static Object getFieldValue(@NonNull Object target, @NonNull Class<?> clazz, @NonNull String fieldName) {
if (Map.class.isAssignableFrom(clazz)) {
return ((Map<?, ?>) target).get(fieldName);
}
try {
Field field = target instanceof Class
? ((Class<?>) target).getDeclaredField(fieldName)
: clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(target);
} catch (NoSuchFieldException nsfe) {
if (clazz.getSuperclass() != null) {
return target instanceof Class
? getFieldValue(((Class<?>) target).getSuperclass(), clazz, fieldName)
: getFieldValue(target, clazz.getSuperclass(), fieldName);
} else {
return null;
}
} catch (Exception e) {
return null;
}
}
public static void setFieldValue(@NonNull Object target, @NonNull String fieldName, Object value) {
setFieldValue(target, target.getClass(), fieldName, value);
}
private static void setFieldValue(@NonNull Object target, @NonNull Class clazz, @NonNull String fieldName, Object value) {
try {
Field field = target instanceof Class
? ((Class<?>) target).getDeclaredField(fieldName)
: clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
} catch (NoSuchFieldException nsfe) {
if (clazz.getSuperclass() != null) {
setFieldValue(target, clazz.getSuperclass(), fieldName, value);
} else {
throw new RuntimeException("Set field " + fieldName + " failed.", nsfe);
}
} catch (Exception e) {
throw new RuntimeException("Set field " + fieldName + " failed.", e);
}
}
public static <T extends Serializable> T deepClone(@NonNull T o) {
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(o);
out.flush();
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
return (T) o.getClass().cast(in.readObject());
} catch (Exception e) {
throw new RuntimeException("Failed to copy Object " + o.getClass().getName(), e);
}
}
public static Method getDeclaredMethod(@NonNull Class<?> clazz, @NonNull String methodName, Class<?>... parameterTypes) {
Method method = null;
Class<?> clz = clazz;
while (clz != Object.class) {
try {
method = parameterTypes.length > 0
? clz.getDeclaredMethod(methodName, parameterTypes)
: clz.getDeclaredMethod(methodName);
break;
} catch (NoSuchMethodException e) {
clz = clz.getSuperclass();
}
}
if (method != null) method.setAccessible(true);
return method;
}
public static Method getMethod(@NonNull Class<?> clazz, @NonNull String methodName, Class<?>... parameterTypes) {
Method method;
try {
method = parameterTypes.length > 0
? clazz.getMethod(methodName, parameterTypes)
: clazz.getMethod(methodName);
} catch (NoSuchMethodException e) {
return null;
}
if (method != null) method.setAccessible(true);
return method;
}
public static <R, O> R callMethod(O object, @NonNull String methodName, Object... parameters) {
Class<O> clazz = (Class<O>) object.getClass();
return callMethod(clazz, object, methodName, parameters);
}
/**
* This method doesn't always function as expected. Be 100% sure
* and tested when you use it.
* <p>
* As known, this method is not worked for following case:
* * parameter type is primitive number, e.g. int.class
* * parameter type is general type, e.g. Object.class
*/
public static <R> R callMethod(Class<?> clazz, Object object, @NonNull String methodName, Object... parameters) {
if (object == null) return null;
Method method;
// try get method from `getMethod`
if (parameters.length == 0) {
method = getMethod(clazz, methodName);
} else {
method = getMethod(clazz, methodName,
Arrays.stream(parameters).map(Object::getClass).toArray(Class[]::new));
}
// try get method from `getDeclaredMethod`
if (method == null) {
if (parameters.length == 0) {
method = getDeclaredMethod(clazz, methodName);
} else {
method = getDeclaredMethod(clazz, methodName,
Arrays.stream(parameters).map(Object::getClass).toArray(Class[]::new));
}
}
if (method == null) return null;
try {
return (R) method.invoke(object, parameters);
} catch (IllegalAccessException | InvocationTargetException ignore) {
}
return null;
}
public static String getBeanName(BeanFactory beanFactory, Object bean) {
String beanName = null;
Map<String, Object> beans = BeanUtils.getFieldValue(beanFactory, "disposableBeans");
if (beans != null) {
beanName = beans.entrySet().stream()
.filter(entry -> entry.getValue() == bean).findAny()
.map(Map.Entry::getKey).orElse(null);
}
if (beanName == null) {
beans = BeanUtils.getFieldValue(beanFactory, "singletonObjects");
if (beans != null) {
beanName = beans.entrySet().stream()
.filter(entry -> entry.getValue() == bean).findAny()
.map(Map.Entry::getKey).orElse(null);
}
}
return beanName;
}
public static Map<Class<?>, ?> getTempBeans(List<Class<?>> classList){
// 创建临时上下文
AnnotationConfigApplicationContext tempContext = new AnnotationConfigApplicationContext();
// 注册需要即时使用的类
classList.forEach(tempContext::register);
// 刷新临时上下文
tempContext.refresh();
// 获取bean
Map<Class<?>, ?> tempBean = classList.stream().collect(Collectors.toMap(Function.identity(), tempContext::getBean));
// 关闭临时上下文
tempContext.close();
return tempBean;
}
}

View File

@ -1,152 +0,0 @@
package cd.casic.plugin.utils;
import cd.casic.plugin.PluginProperties;
import cn.hutool.core.util.StrUtil;
import org.pf4j.util.StringUtils;
import java.io.File;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
/**
* 通用工具
*/
public class CommonUtils {
private CommonUtils() {
}
/**
* list按照int排序. 数字越大, 越排在前面
*
* @param list list集合
* @param orderImpl 排序实现
* @param <T> T
* @return List
*/
public static <T> List<T> order(List<T> list, Function<T, Integer> orderImpl) {
if (list == null) {
return null;
}
list.sort(Comparator.comparing(orderImpl, Comparator.nullsLast(Comparator.reverseOrder())));
return list;
}
/**
* 得到插件接口前缀
*
* @param configuration 配置
* @param pluginId 插件id
* @return 接口前缀
*/
public static String getPluginRestPrefix(PluginProperties pluginProperties, String pluginId) {
String pathPrefix = pluginProperties.getRestPathPrefix();
if (pluginProperties.isEnablePluginIdAsRestPrefix()) {
if (StrUtil.isNotEmpty(pathPrefix)) {
pathPrefix = restJoiningPath(pathPrefix, pluginId);
} else {
pathPrefix = pluginId;
}
return pathPrefix;
} else {
if (StrUtil.isEmpty(pathPrefix)) {
// 不启用插件id作为路径前缀, 并且路径前缀为空, 则直接返回
return null;
}
}
return pathPrefix;
}
/**
* rest接口拼接路径
*
* @param path1 路径1
* @param path2 路径2
* @return 拼接的路径
*/
public static String restJoiningPath(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 "";
}
}
/**
* 拼接url路径
*
* @param paths 拼接的路径
* @return 拼接的路径
*/
public static String joiningPath(String... paths) {
if (paths == null || paths.length == 0) {
return "";
}
StringBuilder stringBuilder = new StringBuilder();
int length = paths.length;
for (int i = 0; i < length; i++) {
String path = paths[i];
if (StringUtils.isNullOrEmpty(path)) {
continue;
}
if ((i < length - 1) && path.endsWith("/")) {
path = path.substring(path.lastIndexOf("/"));
}
if (path.startsWith("/")) {
stringBuilder.append(path);
} else {
stringBuilder.append("/").append(path);
}
}
return stringBuilder.toString();
}
/**
* 拼接file路径
*
* @param paths 拼接的路径
* @return 拼接的路径
*/
public static String joiningFilePath(String... paths) {
if (paths == null || paths.length == 0) {
return "";
}
StringBuilder stringBuilder = new StringBuilder();
int length = paths.length;
for (int i = 0; i < length; i++) {
String path = paths[i];
if (StringUtils.isNullOrEmpty(path)) {
continue;
}
if (i > 0) {
if (path.startsWith(File.separator) || path.startsWith("/") ||
path.startsWith("\\") || path.startsWith("//")) {
stringBuilder.append(path);
} else {
stringBuilder.append(File.separator).append(path);
}
} else {
stringBuilder.append(path);
}
}
return stringBuilder.toString();
}
}

View File

@ -1,47 +0,0 @@
package cd.casic.plugin.utils;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginManager;
import org.pf4j.PluginWrapper;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Slf4j
public abstract class FrontResourceUtils {
private static final String FRONT_BUNDLE_LOCATION = "/static/ui";
public static final String JS_BUNDLE = "/assets/main.js";
public static final String CSS_BUNDLE = "/assets/style.css";
@Nullable
public static Resource getJsBundleResource(PluginManager pluginManager, String pluginName,
String bundleName) {
Assert.hasText(pluginName, "The pluginName must not be blank");
Assert.hasText(bundleName, "Bundle name must not be blank");
DefaultResourceLoader resourceLoader = getResourceLoader(pluginManager, pluginName);
if (resourceLoader == null) {
return null;
}
String path = FRONT_BUNDLE_LOCATION + bundleName;
String simplifyPath = StringUtils.cleanPath(path);
Resource resource = resourceLoader.getResource(simplifyPath);
log.info("Get {} for plugin {} in the {} using {}", bundleName, pluginName, simplifyPath, resourceLoader);
return resource.exists() ? resource : null;
}
@Nullable
public static DefaultResourceLoader getResourceLoader(PluginManager pluginManager,
String pluginName) {
Assert.notNull(pluginManager, "Plugin manager must not be null");
PluginWrapper plugin = pluginManager.getPlugin(pluginName);
if (plugin == null) {
return null;
}
return new DefaultResourceLoader(plugin.getPluginClassLoader());
}
}

View File

@ -1,118 +0,0 @@
package cd.casic.plugin.utils;
import cd.casic.plugin.PluginDescriptorStorage;
import cd.casic.plugin.pf4j.OpsPluginDescriptor;
import cn.hutool.core.util.StrUtil;
import org.pf4j.PluginDependency;
import java.util.*;
import java.util.stream.Collectors;
public class PluginDescriptorUtils {
public static final String PLUGIN_ID = "plugin.id";
public static final String PLUGIN_DESCRIPTION = "plugin.description";
public static final String PLUGIN_CLASS = "plugin.class";
public static final String PLUGIN_VERSION = "plugin.version";
public static final String PLUGIN_PROVIDER = "plugin.provider";
public static final String PLUGIN_DEPENDENCIES = "plugin.dependencies";
public static final String PLUGIN_REQUIRES = "plugin.requires";
public static final String PLUGIN_LICENSE = "plugin.license";
public static final String MYBATIS_MAPPER_LOCATION = "mybatis.mapper.location";
public static final String STATIC_LOCATIONS = "static.locations";
public static final String CONFIG_FILE_NAME = "plugin.config.file";
public static final String CONFIG_FILE_ACTIVE = "plugin.config.active";
public static PluginDescriptorStorage propertiesToStorage(Properties properties){
PluginDescriptorStorage pluginDescriptor = new PluginDescriptorStorage();
String id = properties.getProperty(PLUGIN_ID);
pluginDescriptor.setPluginId(id);
String description = properties.getProperty(PLUGIN_DESCRIPTION);
if (StrUtil.isEmptyIfStr(description)) {
pluginDescriptor.setPluginDescription("");
} else {
pluginDescriptor.setPluginDescription(description);
}
String clazz = properties.getProperty(PLUGIN_CLASS);
if (!StrUtil.isEmptyIfStr(clazz)) {
pluginDescriptor.setPluginClass(clazz);
}
String version = properties.getProperty(PLUGIN_VERSION);
if (!StrUtil.isEmptyIfStr(version)) {
pluginDescriptor.setVersion(version);
}
String provider = properties.getProperty(PLUGIN_PROVIDER);
pluginDescriptor.setProvider(provider);
String requires = properties.getProperty(PLUGIN_REQUIRES);
if (StrUtil.isEmptyIfStr(requires)) {
pluginDescriptor.setRequires("0.0.0");
}else{
pluginDescriptor.setRequires(requires);
}
String mapperXmlDir = properties.getProperty(MYBATIS_MAPPER_LOCATION);
pluginDescriptor.setMapperXmlDir(mapperXmlDir);
String staticDir = properties.getProperty(STATIC_LOCATIONS);
pluginDescriptor.setStaticDir(staticDir);
String license = properties.getProperty(PLUGIN_LICENSE);
pluginDescriptor.setLicense(license);
String dependencies = properties.getProperty(PLUGIN_DEPENDENCIES);
if(!StrUtil.isEmptyIfStr(dependencies)){
List<PluginDependency> dependencyList = Arrays.stream(dependencies.trim().split(","))
.map(dependency->new PluginDependency(dependency.trim()))
.collect(Collectors.toList());
pluginDescriptor.setDependencies(dependencyList);
}else {
pluginDescriptor.setDependencies(Collections.emptyList());
}
String configFileName = properties.getProperty(CONFIG_FILE_NAME);
if(!StrUtil.isEmptyIfStr(configFileName)){
pluginDescriptor.setConfigFileName(configFileName);
}
List<String> configFileActives = new ArrayList<>();
for (String key : properties.stringPropertyNames()) {
if (key.startsWith(CONFIG_FILE_ACTIVE)) {
configFileActives.add(properties.getProperty(key));
}
}
if(!configFileActives.isEmpty()){
pluginDescriptor.setConfigFileActive(configFileActives);
}else{
pluginDescriptor.setConfigFileActive(Collections.emptyList());
}
return pluginDescriptor;
}
// public static DefaultPluginDescriptor storageToDescriptor(PluginDescriptorStorage pluginDescriptor){
// return new DefaultPluginDescriptor(pluginDescriptor.getPluginId(),
// pluginDescriptor.getPluginDescription(),
// pluginDescriptor.getPluginClass(),
// pluginDescriptor.getVersion(),
// pluginDescriptor.getRequires(),
// pluginDescriptor.getProvider(),
// pluginDescriptor.getLicense());
// }
public static OpsPluginDescriptor storageToDescriptor(PluginDescriptorStorage pluginDescriptor){
return new OpsPluginDescriptor(pluginDescriptor.getPluginId(),
pluginDescriptor.getPluginDescription(),
pluginDescriptor.getPluginClass(),
pluginDescriptor.getVersion(),
pluginDescriptor.getRequires(),
pluginDescriptor.getProvider(),
pluginDescriptor.getLicense(),
pluginDescriptor.getConfigFileName(),
pluginDescriptor.getConfigFileActive());
}
}

View File

@ -1,121 +0,0 @@
package cd.casic.plugin.utils;
import cd.casic.plugin.PluginCache;
import cd.casic.plugin.PluginDescriptorCache;
import cd.casic.plugin.PluginDescriptorStorage;
import cd.casic.plugin.PluginInfo;
import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.dialect.Props;
import org.pf4j.PluginState;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* @Authormianbin
* @Packagecd.casic.plugin.utils
* @Projectops
* @namePluginsUtils
* @Date2024/03/18 10:19
* @FilenamePluginsUtils
* @description插件工具类
*/
public class PluginsUtils {
/**
* param jarFile jarFile
* @description 加载插件配置文件
* @author dolphin
*/
public static Props getSetting(String pluginId){
// TODO 这个地方如果按照原来的写法既要区分file是jar还是目录又要区分是properties还是yaml而且还要重复读取
// TODO 目前暂时是弄了个PluginDescriptorCache在两个DescriptorFinder中读取配置时就写Cache
// TODO 上面的做法有待完善
// Enumeration<JarEntry> entries = new JarFile(jarFile).entries();
// while (entries.hasMoreElements()) {
// JarEntry jarEntry = entries.nextElement();
// String entryName = jarEntry.getName();
// if (entryName.equals("plugin.properties")) {
// URL url = new URL("jar:file:" + jarFile.getPath() + "!/" + entryName);
// JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
// InputStream in = jarConnection.getInputStream();
// String read = IoUtil.read(in, CharsetUtil.UTF_8);
// in.close();
// JarFile currJarFile = jarConnection.getJarFile();
// currJarFile.close();
// File file = new File("resources/temp");
// File tempFile = new File(file.getAbsolutePath() + "/plugin.properties");
// FileWriter writer = new FileWriter(tempFile);
// writer.write(read);
// return new Props(tempFile, CharsetUtil.CHARSET_UTF_8);
// }else if(entryName.equals("plugin.yaml")){
// Path path = FileUtils.getPath(Paths.get(jarFile.getPath()), "plugin.yaml");
// FileSystemResource resource = new FileSystemResource(path);
// PluginDescriptorStorage pluginDescriptorStorage = new PluginYamlProcessor(resource).loadYaml();
// Props props = new Props();
// props.put("mybatis.mapper.location", pluginDescriptorStorage.getMapperXmlDir());
// props.put("static.locations", pluginDescriptorStorage.getStaticDir());
// return props;
//
// }
// }
PluginDescriptorStorage descriptor = PluginDescriptorCache.getPlugin(pluginId);
if(descriptor == null){
// throw new Exception("加载插件:读取配置文件失败");
}else{
Props props = new Props();
if(!StrUtil.isEmptyIfStr(descriptor.getMapperXmlDir())){
props.put("mybatis.mapper.location", descriptor.getMapperXmlDir());
}
if(!StrUtil.isEmptyIfStr(descriptor.getStaticDir())){
props.put("static.locations", descriptor.getStaticDir());
}
return props;
}
return null;
}
/**
* @description 强制删除
* @author dolphin
*/
public static void forceDelete(File file) {
if (!file.exists()) {
return;
}
boolean result = file.delete();
if (result) {
return;
}
int tryCount = 0;
while (!result && tryCount++ < 5) {
System.gc();
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = file.delete();
}
}
public static <T> List<T> getAllPluginProxyClass(Class<T> clazz) {
List<T> result = new ArrayList<>();
Map<String, PluginInfo> allPlugin = PluginCache.getAllPlugin();
for (String key : allPlugin.keySet()) {
PluginInfo pluginInfo = allPlugin.get(key);
if (!pluginInfo.getPluginWrapper().getPluginState().equals(PluginState.STARTED)) {
continue;
}
T pluginBean = pluginInfo.getPluginApplicationContext().getBean(clazz);
if (pluginBean != null) {
result.add(pluginBean);
}
Optional.of(pluginBean).ifPresent(result::add);
}
return result;
}
}

View File

@ -1,92 +0,0 @@
package cd.casic.plugin.utils;
import cd.casic.plugin.PluginInfo;
import org.pf4j.PluginWrapper;
import org.pf4j.RuntimeMode;
import org.pf4j.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 对资源解析的工具类
*/
public class ResourceUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtils.class);
public final static String TYPE_FILE = "file";
public final static String TYPE_CLASSPATH = "classpath";
public final static String TYPE_PACKAGE = "package";
public static final String ROOT_PLUGIN_SIGN = "~";
public final static String TYPE_SPLIT = ":";
/**
* 获取匹配路绝
* @param locationMatch 原始匹配路径规则为: file:xxx, classpath:xxx , package:xxx
* @return 整合出完整的匹配路绝
*/
public static String getMatchLocation(String locationMatch){
if(StringUtils.isNullOrEmpty(locationMatch)){
return null;
}
String classPathType = TYPE_CLASSPATH + TYPE_SPLIT;
if(isClasspath(locationMatch)){
return locationMatch.replaceFirst(classPathType, "");
}
String fileType = TYPE_FILE + TYPE_SPLIT;
if(isFile(locationMatch)){
return locationMatch.replaceFirst(fileType, "");
}
String packageType = TYPE_PACKAGE + TYPE_SPLIT;
if(isPackage(locationMatch)){
String location = locationMatch.replaceFirst(packageType, "");
return location.replace(".", "/");
}
LOGGER.error("locationMatch {} illegal", locationMatch);
return null;
}
public static boolean isClasspath(String locationMatch){
return locationMatch.startsWith(TYPE_CLASSPATH + TYPE_SPLIT);
}
public static boolean isFile(String locationMatch){
return locationMatch.startsWith(TYPE_FILE + TYPE_SPLIT);
}
public static boolean isPackage(String locationMatch){
return locationMatch.startsWith(TYPE_PACKAGE + TYPE_SPLIT);
}
/**
* 根据 ~ 标记获取, 得到绝对路径
* @param pluginRegistryInfo pluginRegistryInfo
* @param rootDir 根目录
* @return java.lang.String
**/
public static String getAbsolutePath(PluginInfo pluginRegistryInfo, String rootDir){
if(StringUtils.isNullOrEmpty(rootDir)){
return rootDir;
}
String home = null;
if(rootDir.startsWith(ResourceUtils.ROOT_PLUGIN_SIGN)){
String pluginRootDir;
PluginWrapper pluginWrapper = pluginRegistryInfo.getPluginWrapper();
RuntimeMode runtimeMode = pluginWrapper.getRuntimeMode();
if(runtimeMode == RuntimeMode.DEVELOPMENT){
pluginRootDir = pluginWrapper.getPluginPath().toString();
} else {
pluginRootDir = System.getProperty("user.dir");
}
// 如果root路径中开始存在ROOT_PLUGIN_SIGN,则说明进行插件根路替换
home = rootDir.replaceFirst("\\" + ResourceUtils.ROOT_PLUGIN_SIGN, "");
home = CommonUtils.joiningFilePath(pluginRootDir, home);
} else {
home = rootDir;
}
return home;
}
}

View File

@ -1,47 +0,0 @@
package cd.casic.plugin.utils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
public class SM4EncryptUtil {
private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// @Value("${encryption-key}")
private static String key = "abcdabcdabcdabcd";
public static String encrypt(String str) {
SM4 sm4 = SmUtil.sm4(key.getBytes());
String s = sm4.encryptHex(str.getBytes());
return s;
}
public static String decrypt(String str) {
SM4 sm4 = SmUtil.sm4(key.getBytes());
String s = sm4.decryptStr(str);
return s;
}
public static void main(String[] args) {
String encrypt = encrypt(LocalDateTime.now().plusMonths(1).format(dateTimeFormatter));
System.out.println(encrypt);
System.out.println(decrypt(encrypt));
}
public static boolean checkLicense(String license, String pluginId) {
// 逻辑是 不存在证书直接放行 存在证书需要判断证书是否过期
if (ObjectUtil.isNotEmpty(license)) {
String decrypt = SM4EncryptUtil.decrypt(license);
LocalDateTime overDueTime = LocalDateTime.parse(decrypt, dateTimeFormatter);
LocalDateTime now = LocalDateTime.now();
if (now.isAfter(overDueTime)) {
log.warn("plugin:[{}] license expired!", pluginId);
return false;
}
}
return true;
}
}

View File

@ -1,35 +0,0 @@
package cd.casic.plugin.utils;
import org.pf4j.DevelopmentPluginClasspath;
import org.pf4j.PluginRuntimeException;
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;
public class YamlUtils {
static final DevelopmentPluginClasspath PLUGIN_CLASSPATH = new DevelopmentPluginClasspath();
public static Path getYamlPath(Path pluginPath, String yamlFileName) {
if (Files.isDirectory(pluginPath)) {
for (String location : PLUGIN_CLASSPATH.getClassesDirectories()) {
Path path = pluginPath.resolve(location).resolve(yamlFileName);
Resource propertyResource = new FileSystemResource(path);
if (propertyResource.exists()) {
return path;
}
}
throw new PluginRuntimeException(
"Unable to find plugin yaml file: " + yamlFileName);
} else {
try {
return FileUtils.getPath(pluginPath, yamlFileName);
} catch (IOException e) {
throw new PluginRuntimeException(e);
}
}
}
}

View File

@ -1,77 +0,0 @@
package cd.casic.plugin.utils.dBUtils;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class AnnotationScanner {
public static List<Class<?>> scanPackageForAnnotatedClasses(String packageName, Class<? extends Annotation> annotationClass,ClassLoader classLoader) throws IOException, ClassNotFoundException {
List<Class<?>> annotatedClasses = new ArrayList<>();
// 获取包所在的URL
Enumeration<URL> urls = classLoader.getResources(packageName.replace('.', '/'));
//
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
// 处理文件系统中的包
File packageDir = new File(url.getFile());
scanDirectoryForAnnotatedClasses(packageDir, packageName, annotationClass, annotatedClasses);
} else if ("jar".equals(protocol)) {
// 处理JAR文件中的包
JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
scanJarFileForAnnotatedClasses(jarFile, packageName, annotationClass, annotatedClasses, classLoader);
}
}
return annotatedClasses;
}
private static void scanDirectoryForAnnotatedClasses(File directory, String packageName, Class<? extends Annotation> annotationClass, List<Class<?>> annotatedClasses) throws ClassNotFoundException {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
scanDirectoryForAnnotatedClasses(file, packageName + "." + file.getName(), annotationClass, annotatedClasses);
} else if (file.isFile() && file.getName().endsWith(".class")) {
String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(annotationClass)) {
annotatedClasses.add(clazz);
}
}
}
}
}
private static void scanJarFileForAnnotatedClasses(JarFile jarFile, String packageName, Class<? extends Annotation> annotationClass, List<Class<?>> annotatedClasses,ClassLoader classLoader) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.endsWith(".class") && entryName.startsWith(packageName.replace('.', '/')) && !entryName.contains("$")) { // 忽略内部类
String className = entryName.substring(0, entryName.length() - 6).replace('/', '.');
try {
// Class<?> clazz = Class.forName(className);
Class<?> clazz = classLoader.loadClass(className);
if (clazz.isAnnotationPresent(annotationClass)) {
annotatedClasses.add(clazz);
}
} catch (Exception e) {
// ignore
System.err.println("Class not found: " + className);;
}
}
}
}
}

View File

@ -1,26 +0,0 @@
package cd.casic.plugin.utils.dBUtils;
import lombok.Getter;
@Getter
public enum DBEnums {
MYSQL("mysql"),
SQLITE("sqlite");
final String DBType;
DBEnums(String DBType) {
this.DBType = DBType;
}
public static DBEnums getEnumByString(String type){
switch (type){
case "mysql":
return MYSQL;
case "sqlite":
return SQLITE;
default:
return null;
}
}
}

View File

@ -1,222 +0,0 @@
package cd.casic.plugin.utils.dBUtils;
import cd.casic.plugin.register.database.DataBaseProperty;
import cd.casic.plugin.utils.dBUtils.annotation.Entity;
import cd.casic.plugin.utils.dBUtils.annotation.SQL;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.mysql.cj.jdbc.MysqlDataSource;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.stereotype.Component;
import org.sqlite.SQLiteDataSource;
import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@Data
@Slf4j
@Component
public class SQLGenerator {
@Resource
private DynamicRoutingDataSource dynamicRoutingDataSource;
private String mapToSqlType(Class<?> fieldType) {
// 简化的类型映射针对MySQL数据库
if (fieldType == int.class || fieldType == Integer.class) {
return "INTEGER";
} else if (fieldType == long.class || fieldType == Long.class) {
return "BIGINT";
} else if (fieldType == float.class || fieldType == Float.class) {
return "FLOAT";
} else if (fieldType == double.class || fieldType == Double.class) {
return "DOUBLE";
} else if (fieldType == boolean.class || fieldType == Boolean.class) {
return "BOOLEAN"; // 或者 "TINYINT(1)"
} else if (fieldType == String.class) {
return "VARCHAR(255)";
} else if (fieldType == java.util.Date.class || fieldType == java.sql.Date.class) {
return "DATETIME"; // 或者 "TIMESTAMP"
} else if (fieldType == java.sql.Timestamp.class) {
return "TIMESTAMP";
} else if (fieldType == byte[].class) {
return "BLOB";
} else if (fieldType.isEnum()) {
return "ENUM"; // 需要进一步处理如列出所有枚举值
} else if (fieldType.isArray() || Collection.class.isAssignableFrom(fieldType)) {
throw new IllegalArgumentException("Arrays and Collections are not directly supported. Consider using a join table or serialization.");
} else {
throw new IllegalArgumentException("Unsupported field type: " + fieldType.getName());
}
}
public void sqlDeleteGenerator(Class<?> c, DataBaseProperty dataBaseProperty, String pluginId) {
StringBuilder sql = new StringBuilder("\n");
String tableName = "plugin_" + (c.toString().substring(c.toString().lastIndexOf(".") + 1)).toLowerCase();
sql.append("drop table if exists ").append(tableName);
String dbPath = "ops-module-plugins/plugins/" + pluginId + "/datasource/database.db";
this.executeSql(sql.toString(), dataBaseProperty, pluginId);
log.info(sql.toString());
}
public void sqlGenerator(Class<?> c, DataBaseProperty dataBaseProperty, String pluginId) {
String dbPath = "ops-module-plugins/plugins/" + pluginId + "/datasource/database.db";
// log.info(pluginRoot);
Entity entityAnnotation = c.getAnnotation(Entity.class);
if (ObjectUtils.isEmpty(entityAnnotation)) {
return;
}
StringBuilder sql = new StringBuilder("\n");
String tableName = "plugin_" + (c.toString().substring(c.toString().lastIndexOf(".") + 1)).toLowerCase();
sql.append("create table ").append(tableName).append("(").append("\n");
int count = c.getDeclaredFields().length;
int index = 0;
boolean pkFlag = false;
for (Field field : c.getDeclaredFields()) {
SQL annotation = field.getAnnotation(SQL.class);
index += 1;
sql.append(field.getName()).append(" ").append(mapToSqlType(field.getType())).append(" ");
if (ObjectUtils.isNotEmpty(annotation) && ObjectUtils.isNotEmpty(annotation.defaultValue())) {
sql.append(" default " + "'").append(annotation.defaultValue()).append("'");
}
if (ObjectUtils.isNotEmpty(annotation) && annotation.notNull()) {
sql.append(" not null ");
}
if (ObjectUtils.isNotEmpty(annotation) && annotation.pK()) {
// TODO 这是如果dataBaseProperty是null即使用主程序的数据源时数据库类型先写死
DBEnums type = dataBaseProperty == null ? DBEnums.MYSQL : dataBaseProperty.type();
if (pkFlag) {
log.error("Duplicate Primary Key");
throw new RuntimeException("Duplicate Primary Key");
}
this.genPrimaryKey(sql, type, tableName);
pkFlag = true;
if (annotation.autoIncrement()) {
this.genAutoIncrement(sql, type);
}
}
if (index != count) {
sql.append("," + "\n");
} else {
sql.append("\n");
}
}
sql.append(")");
log.info(sql.toString());
this.executeSql(sql.toString(), dataBaseProperty, pluginId);
}
public void destroyDataSource(DataBaseProperty dataBaseProperty, String pluginId){
if(dataBaseProperty == null) return;
String dataSourceId = pluginId + "-" + dataBaseProperty.type().getDBType();
if(dynamicRoutingDataSource.getDataSources().containsKey(dataSourceId)){
dynamicRoutingDataSource.removeDataSource(dataSourceId);
}
}
private void executeSql(String sql, DataBaseProperty dataBaseProperty, String pluginId){
try{
Connection connection;
if(Objects.isNull(dataBaseProperty)){
connection = dynamicRoutingDataSource.getConnection();
}else{
String dataSourceId = pluginId + "-" + dataBaseProperty.type().getDBType();
if(!dynamicRoutingDataSource.getDataSources().containsKey(dataSourceId)){
DataSource dataSource;
if(dataBaseProperty.type().equals(DBEnums.SQLITE)){
dataSource = DataSourceBuilder.create().type(SQLiteDataSource.class)
.driverClassName(dataBaseProperty.driver())
.url(dataBaseProperty.url()).build();
}else {
dataSource = DataSourceBuilder.create()
.type(MysqlDataSource.class)
.driverClassName(dataBaseProperty.driver())
.username(dataBaseProperty.username()).password(dataBaseProperty.password())
.url(dataBaseProperty.url()).build();
}
dynamicRoutingDataSource.addDataSource(dataSourceId, dataSource);
}
connection = dynamicRoutingDataSource.getDataSource(dataSourceId).getConnection();
}
connection.createStatement().execute(sql);
}catch(Exception e){
log.error(e.getMessage());
}
}
private void genPrimaryKey(StringBuilder sql, DBEnums dbEnums, String tableName) {
switch (dbEnums) {
case MYSQL:
sql.append("primary key ");
break;
case SQLITE:
sql.append("constraint ").append(tableName).append("_pk primary key ");
break;
default:
log.error("Unsupported Database");
throw new RuntimeException("Unsupported Database");
}
}
private void genAutoIncrement(StringBuilder sql, DBEnums dbEnums) {
switch (dbEnums) {
case MYSQL:
sql.append(" AUTO_INCREMENT");
break;
case SQLITE:
sql.append(" AUTOINCREMENT");
break;
default:
log.error("Unsupported Database");
throw new RuntimeException("Unsupported Database");
}
}
private String getSQLiteDBLocation(String pluginRoot, ClassLoader classLoader, String pluginId) {
List<String> pluginFolders = new ArrayList<>();
try {
Path rootPath = Paths.get(pluginRoot);
if (!Files.isDirectory(rootPath)) {
System.out.println("提供的路径不是一个有效的目录:" + pluginRoot);
}
this.findPluginFoldersRecursively(rootPath, pluginFolders);
} catch (IOException e) {
throw new RuntimeException(e);
}
return "s";
}
private void findPluginFoldersRecursively(Path folder,List<String> pluginFolders) throws IOException
{
try (DirectoryStream<Path> stream = Files.newDirectoryStream(folder)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
String folderName = entry.getFileName().toString();
if (folderName.contains("datasource")) {
pluginFolders.add(entry.toString());
}
findPluginFoldersRecursively(entry, pluginFolders); // 递归查找子目录
}
}
}
}
}

View File

@ -1,10 +0,0 @@
package cd.casic.plugin.utils.dBUtils.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Entity {
}

View File

@ -1,14 +0,0 @@
package cd.casic.plugin.utils.dBUtils.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SQL {
boolean pK() default false;
boolean notNull() default false;
String defaultValue() default "";
boolean autoIncrement() default false;
}