重新写,有点问题,和框架没有结合
This commit is contained in:
parent
296ac900f7
commit
5a5952276a
@ -21,7 +21,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-biz-data-permission</artifactId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -29,6 +29,11 @@
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mongo</artifactId>
|
||||
|
@ -1,29 +0,0 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import org.pf4j.Plugin;
|
||||
import org.pf4j.PluginWrapper;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:BasePlugin
|
||||
* @Date:2024/03/18 9:25
|
||||
* @Filename:BasePlugin
|
||||
* @description:插件基类
|
||||
*/
|
||||
public abstract class BasePlugin extends Plugin {
|
||||
|
||||
public BasePlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据插件主类获取插件包
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String scanPackage() {
|
||||
return this.getClass().getPackage().getName();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginApplicationContextHolder
|
||||
* @Date:2024/03/18 10:04
|
||||
* @Filename:PluginApplicationContextHolder
|
||||
* @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);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginBean
|
||||
* @Date:2024/03/18 14:48
|
||||
* @Filename:PluginBean
|
||||
* @description:Ops 插件的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;
|
||||
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginCache
|
||||
* @Date:2024/03/18 9:34
|
||||
* @Filename:PluginCache
|
||||
* @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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginDetail
|
||||
* @Date:2024/03/18 9:34
|
||||
* @Filename:PluginDetail
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginLifecycle
|
||||
* @Date:2024/06/15 17:31
|
||||
* @Filename:PluginLifecycle
|
||||
* @description:插件生命周期的操作
|
||||
*/
|
||||
public interface PluginLifecycle {
|
||||
|
||||
/**
|
||||
* 插件启动前的操作,比如需要单独对插件进行某些特定操作,
|
||||
*
|
||||
*/
|
||||
public void beforeWork();
|
||||
|
||||
|
||||
/**
|
||||
* 料理后世方的操作
|
||||
*/
|
||||
public void AfterWork();
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginManager
|
||||
* @Date:2024/03/18 15:03
|
||||
* @Filename:PluginManager
|
||||
* @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,存放jar、database、resource
|
||||
│ │ ├─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() {
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginManagerService
|
||||
* @Date:2024/03/18 14:46
|
||||
* @Filename:PluginManagerService
|
||||
* @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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
|
||||
import cd.casic.plugin.constants.OpsConstants;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.annotation
|
||||
* @Project:ops
|
||||
* @name:AdminGroup
|
||||
* @Date:2024/03/18 17:06
|
||||
* @Filename:AdminGroup
|
||||
* @description:自定义菜单Group注解
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.ANNOTATION_TYPE})
|
||||
@Documented
|
||||
public @interface AdminGroup {
|
||||
/** 菜单名称 */
|
||||
String name();
|
||||
|
||||
/** 菜单组id */
|
||||
String groupId();
|
||||
|
||||
/** 菜单图标 */
|
||||
String icon() default "fa-circle-o";
|
||||
|
||||
/** 菜单url */
|
||||
String url() default "";
|
||||
|
||||
/** 菜单角色 */
|
||||
String[] role() default {OpsConstants.ROLE_ADMIN};
|
||||
|
||||
/** 菜单序号 */
|
||||
int seq() default 99;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.annotation
|
||||
* @Project:ops
|
||||
* @name:AdminGroups
|
||||
* @Date:2024/03/18 17:05
|
||||
* @Filename:AdminGroups
|
||||
* @description:自定义菜单groups注解
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Documented
|
||||
public @interface AdminGroups {
|
||||
/** 菜单组 */
|
||||
AdminGroup[] groups();
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.annotation
|
||||
* @Project:ops
|
||||
* @name:InterceptPath
|
||||
* @Date:2024/03/18 16:09
|
||||
* @Filename:InterceptPath
|
||||
* @description:插件拦截器
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
public @interface InterceptPath {
|
||||
/**
|
||||
* 拦截的路径
|
||||
*/
|
||||
String[] value();
|
||||
}
|
@ -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模式下的后缀
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
package cd.casic.plugin.constants;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.constants
|
||||
* @Project:ops
|
||||
* @name:OpsConstants
|
||||
* @Date:2024/03/18 9:30
|
||||
* @Filename:OpsConstants
|
||||
* @description:Todo
|
||||
*/
|
||||
public class OpsConstants {
|
||||
/**
|
||||
* 插件目录
|
||||
*/
|
||||
public static final String PLUGIN_PATH = "/plugins";
|
||||
/**
|
||||
* 插件路径
|
||||
*/
|
||||
public static final String PLUGINS_DIR = "resources/plugins";
|
||||
/**
|
||||
* 插件静态资源目录
|
||||
*/
|
||||
public static final String PLUGINS_RESOURCES_DIR = "resources/pluginResources";
|
||||
//public static final String PLUGINS_RESOURCES_DIR = "ops-module-plugins/ops-module-plugins-example-web/src/main/resources/static";
|
||||
|
||||
/**
|
||||
* 角色:管理员
|
||||
*/
|
||||
public static final String ROLE_ADMIN = "admin";
|
||||
/**
|
||||
* 角色:用户
|
||||
*/
|
||||
public static final String ROLE_USER = "user";
|
||||
/**
|
||||
* 角色:编辑
|
||||
*/
|
||||
public static final String ROLE_EDITOR = "editor";
|
||||
|
||||
|
||||
/**
|
||||
* todo 区分插件类型,目前只支持两种,一种为zip ,一种为jar包形式,主要使用zip形式,别面jar包被人轻易破解
|
||||
*/
|
||||
public static class Suffix {
|
||||
|
||||
private Suffix() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public static final String JAR = "jar";
|
||||
public static final String ZIP = "zip";
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package cd.casic.plugin.constants;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.constants
|
||||
* @Project:ops
|
||||
* @name:PluginBuildTypeEnum
|
||||
* @Date:2024/03/21 19:53
|
||||
* @Filename:PluginBuildTypeEnum
|
||||
* @description:插件构建方式区分
|
||||
*/
|
||||
public enum PluginBuildTypeEnum {
|
||||
|
||||
/**
|
||||
* 构建
|
||||
*/
|
||||
BUILD,
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
REGISTER,
|
||||
/**
|
||||
* 卸载
|
||||
*/
|
||||
UNREGISTER
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:OpsMainAppReadyEvent
|
||||
* @Date:2024/03/22 10:29
|
||||
* @Filename:OpsMainAppReadyEvent
|
||||
* @description:主程序启动完毕,此事件发布到插件应用程序,此时,插件还未启动
|
||||
*/
|
||||
public class OpsMainAppReadyEvent extends ApplicationEvent {
|
||||
|
||||
public OpsMainAppReadyEvent(ApplicationContext mainApplicationContext) {
|
||||
super(mainApplicationContext);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:OpsMainAppStartedEvent
|
||||
* @Date:2024/03/22 10:31
|
||||
* @Filename:OpsMainAppStartedEvent
|
||||
* @description:主程序启动完毕,此事件发布到插件应用程序,此时,原神启动
|
||||
*/
|
||||
public class OpsMainAppStartedEvent extends ApplicationEvent {
|
||||
|
||||
public OpsMainAppStartedEvent(ApplicationContext mainApplicationContext) {
|
||||
super(mainApplicationContext);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginRestartedEvent
|
||||
* @Date:2024/03/22 10:51
|
||||
* @Filename:PluginRestartedEvent
|
||||
* @description:插件重启,在插件管理器中重启插件,和同目录上面的OpsMian****的事件不同
|
||||
*/
|
||||
public class PluginRestartedEvent extends ApplicationEvent {
|
||||
|
||||
public PluginRestartedEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginStartedEvent
|
||||
* @Date:2024/03/22 10:57
|
||||
* @Filename:PluginStartedEvent
|
||||
* @description:发布到插件管理器
|
||||
*/
|
||||
public class PluginStartedEvent extends ApplicationEvent {
|
||||
|
||||
public PluginStartedEvent(ApplicationContext pluginApplicationContext) {
|
||||
super(pluginApplicationContext);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginStateChangedEvent
|
||||
* @Date:2024/03/22 11:02
|
||||
* @Filename:PluginStateChangedEvent
|
||||
* @description:插件状态,这个是考虑到插件的更新
|
||||
*/
|
||||
public class PluginStateChangedEvent extends ApplicationEvent {
|
||||
public PluginStateChangedEvent(ApplicationContext mainApplicationContext) {
|
||||
super(mainApplicationContext);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginStoppedEvent
|
||||
* @Date:2024/03/22 11:05
|
||||
* @Filename:PluginStoppedEvent
|
||||
* @description:插件停止,停止后应该删除一切内容
|
||||
*/
|
||||
public class PluginStoppedEvent extends ApplicationEvent {
|
||||
public PluginStoppedEvent(ApplicationContext pluginApplicationContext) {
|
||||
super(pluginApplicationContext);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package cd.casic.plugin.execption;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.execption
|
||||
* @Project:ops
|
||||
* @name:PluginException
|
||||
* @Date:2024/03/21 19:54
|
||||
* @Filename:PluginException
|
||||
* @description:毫无疑问,看这个名字就知道是插件异常类
|
||||
*/
|
||||
public class PluginException extends RuntimeException {
|
||||
|
||||
public PluginException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PluginException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public PluginException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PluginException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:AnnotationHandler
|
||||
* @Date:2024/03/18 15:59
|
||||
* @Filename:AnnotationHandler
|
||||
* @deprecated 不再使用,改用{@link ClassGroupHandler} 实现扫包后分组存储。
|
||||
*/
|
||||
@Slf4j
|
||||
@Deprecated
|
||||
public class AnnotationHandler implements BasePluginHandler {
|
||||
private final static Class<?>[] REGISTER_ANNO = {
|
||||
Bean.class,
|
||||
Mapper.class,
|
||||
Service.class,
|
||||
Component.class,
|
||||
Repository.class,
|
||||
Controller.class,
|
||||
Configuration.class,
|
||||
RestController.class,
|
||||
InterceptPath.class,
|
||||
};
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
List<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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:ApplicationContextPluginHandler
|
||||
* @Date:2024/03/19 14:48
|
||||
* @Filename:ApplicationContextPluginHandler
|
||||
* @description:Todo
|
||||
*/
|
||||
public class ApplicationContextPluginHandler implements BasePluginHandler {
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
if (plugin.getApplicationContextIsRefresh()) {
|
||||
return;
|
||||
}
|
||||
plugin.getPluginApplicationContext().setClassLoader(plugin.getPluginWrapper().getPluginClassLoader());
|
||||
plugin.getPluginApplicationContext().getDefaultListableBeanFactory()
|
||||
.registerSingleton(plugin.getPluginWrapper().getPluginId().trim(),
|
||||
plugin.getPluginWrapper().getPlugin());
|
||||
plugin.getPluginApplicationContext().refresh();
|
||||
plugin.setApplicationContextIsRefresh(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
// 获取插件ApplicationContext的DefaultListableBeanFactory实例
|
||||
DefaultListableBeanFactory beanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory();
|
||||
|
||||
// 根据插件ID获取Bean的名称
|
||||
// String beanName = plugin.getPluginWrapper().getPluginId().trim();
|
||||
//
|
||||
// // 删除已注册的Bean
|
||||
// if (beanFactory.containsBeanDefinition(beanName)) {
|
||||
// beanFactory.removeBeanDefinition(beanName);
|
||||
// }
|
||||
|
||||
String[] beanNames = beanFactory.getBeanNamesForType(plugin.getPluginWrapper().getPlugin().getClass());
|
||||
Arrays.stream(beanNames)
|
||||
.filter(beanName -> beanName.equals(plugin.getPluginWrapper().getPluginId().trim()))
|
||||
.forEach(beanFactory::destroySingleton);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:BasePluginHandler
|
||||
* @Date:2024/03/18 15:48
|
||||
* @Filename:BasePluginHandler
|
||||
* @description:基础插件注册类
|
||||
*/
|
||||
public interface BasePluginHandler {
|
||||
|
||||
/**
|
||||
* 插件组件初始化
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
void initialize() throws Exception;
|
||||
|
||||
/**
|
||||
* 插件组件注册
|
||||
*
|
||||
* @param plugin
|
||||
* @throws Exception
|
||||
*/
|
||||
void registry(PluginInfo plugin) throws Exception;
|
||||
|
||||
/**
|
||||
* 插件组件卸载注册
|
||||
*
|
||||
* @param plugin
|
||||
* @throws Exception
|
||||
*/
|
||||
void unRegistry(PluginInfo plugin) throws Exception;
|
||||
|
||||
}
|
@ -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.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:ControllerHandler
|
||||
* @Date:2024/03/19 14:22
|
||||
* @Filename:ControllerHandler
|
||||
* @description:Todo
|
||||
*/
|
||||
@Slf4j
|
||||
public class ControllerHandler implements BasePluginHandler {
|
||||
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping;
|
||||
|
||||
Method getMappingForMethod;
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
// 这里反射获取 getMappingForMethod
|
||||
requestMappingHandlerMapping = SpringUtil.getBean(RequestMappingHandlerMapping.class);
|
||||
getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class);
|
||||
getMappingForMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
ApplicationContext applicationContext = plugin.getMainApplicationContext();
|
||||
PluginProperties pluginProperties = applicationContext.getBean(PluginProperties.class);
|
||||
for (Class<?> aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) {
|
||||
setPathPrefix(plugin.getPluginId(), aClass, pluginProperties);
|
||||
plugin.addController(aClass);
|
||||
Object bean = plugin.getPluginApplicationContext().getBean(aClass);
|
||||
Method[] methods = aClass.getMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getAnnotation(RequestMapping.class) != null
|
||||
|| method.getAnnotation(GetMapping.class) != null
|
||||
|| method.getAnnotation(PostMapping.class) != null
|
||||
|| method.getAnnotation(DeleteMapping.class) != null
|
||||
|| method.getAnnotation(PutMapping.class) != null
|
||||
|| method.getAnnotation(PatchMapping.class) != null) {
|
||||
RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
|
||||
requestMappingHandlerMapping.registerMapping(requestMappingInfo, bean, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
for (RequestMappingInfo requestMappingInfo : getRequestMappingInfo(plugin)) {
|
||||
requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
|
||||
}
|
||||
}
|
||||
|
||||
List<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 "";
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:MybatisHandler
|
||||
* @Date:2024/03/19 14:51
|
||||
* @Filename:MybatisHandler
|
||||
* @deprecated 不再使用,采用{@link MybatisPlusHandler} 统一实现
|
||||
*/
|
||||
@Deprecated
|
||||
public class MybatisHandler implements BasePluginHandler {
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
List<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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:ResourcesHandler
|
||||
* @Date:2024/03/19 14:54
|
||||
* @Filename:ResourcesHandler
|
||||
* @description:Todo
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* 插件系统集成SpringDoc。swagger能够根据插件启停自动更新接口文档
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.handler
|
||||
* @Project:ops
|
||||
* @name:LoadPluginHandle
|
||||
* @Date:2024/03/18 15:46
|
||||
* @Filename:LoadPluginHandle
|
||||
* @description:Todo
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<>();
|
||||
|
||||
/**
|
||||
* 活动文件的属性名称,就是dev,prod等
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package cd.casic.plugin.register.group.filter;
|
||||
|
||||
public interface PluginClassFilter{
|
||||
String groupName();
|
||||
|
||||
void initialize();
|
||||
|
||||
boolean filter(Class<?> clazz);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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完成注销
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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){
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package cd.casic.plugin.register.websocket;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
|
||||
public interface BaseServerEndpoint extends DisposableBean {
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.utils
|
||||
* @Project:ops
|
||||
* @name:PluginsUtils
|
||||
* @Date:2024/03/18 10:19
|
||||
* @Filename:PluginsUtils
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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); // 递归查找子目录
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user