feat(plugin): support for new plugin manager

This commit is contained in:
qianmoQ 2024-11-20 17:24:35 +08:00
parent a08400d0cc
commit c28f20dc1c
34 changed files with 1908 additions and 743 deletions

View File

@ -425,6 +425,15 @@ common.reset=Reset
common.cropper=Crop
common.tip.pageNotNetwork=Oops! Unable to connect to the network. Please check your connection!
common.store=Store
common.author=Author
common.releasedTime=Released Time
common.install=Install
common.uninstall=Uninstall
common.plugin.version=Plugin Version
common.plugin.systemVersion=Current system version:
common.plugin.list.name=Plugin List
common.plugin.list.description=List of available plugins
common.plugin.list.supportVersion=Support Version
## User i18n
user.common.username=Username

View File

@ -425,6 +425,15 @@ common.reset=重置
common.cropper=裁剪
common.tip.pageNotNetwork=哎呀!无法连接到网络,请检查网络是否正常!
common.store=商店
common.author=作者
common.releasedTime=发布时间
common.install=安装
common.uninstall=卸载
common.plugin.version=插件版本
common.plugin.systemVersion=当前系统版本:
common.plugin.list.name=插件列表
common.plugin.list.description=可用的插件列表
common.plugin.list.supportVersion=支持的版本
## User i18n
user.common.username=用户名

31
configure/metadata.json Normal file
View File

@ -0,0 +1,31 @@
[
{
"key": "plugin",
"label": "common.plugin.list.name",
"description": "common.plugin.list.description",
"i18nFormat": true,
"children": [
{
"key": "MySQL",
"label": "MySQL",
"description": "Integrate MySQL data sources",
"i18nFormat": true,
"type": "JDBC",
"version": "2024.4.0",
"author": "datacap-community",
"logo": "https://www.vectorlogo.zone/logos/mysql/mysql-icon.svg",
"released": "2024-01-01",
"supportVersion": [
"8.0",
"5.7"
]
}
]
},
{
"key": "connector",
"label": "common.connector.list.name",
"description": "common.connector.list.description",
"i18nFormat": true
}
]

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap</artifactId>
<version>2024.4.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>datacap-plugin</artifactId>
<description>DataCap - Plugin Core</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>3.9.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 支持获取当前插件的版本 -->
<!-- Support getting the current plugin version -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,7 @@
package io.edurt.datacap.plugin;
public interface Plugin
{
String name();
String version();
}

View File

@ -0,0 +1,27 @@
package io.edurt.datacap.plugin;
import lombok.Builder;
import lombok.Data;
import java.nio.file.Path;
import java.nio.file.Paths;
@Data
@Builder
public class PluginConfig
{
private Path pluginsDir;
private boolean autoReload;
private long scanInterval;
private String pluginConfigFile;
public static PluginConfig defaultConfig()
{
return PluginConfig.builder()
.pluginsDir(Paths.get("plugins"))
.autoReload(false)
.scanInterval(5000)
.pluginConfigFile("plugin.properties")
.build();
}
}

View File

@ -0,0 +1,21 @@
package io.edurt.datacap.plugin;
import lombok.Builder;
import lombok.Data;
import java.nio.file.Path;
import java.util.Set;
@Data
@Builder
public class PluginInfo
{
private String name;
private String version;
private Path location;
private PluginState state;
private ClassLoader classLoader;
private Object instance;
private Set<String> dependencies;
private long loadTime;
}

View File

@ -0,0 +1,210 @@
package io.edurt.datacap.plugin;
import com.google.common.collect.Maps;
import com.google.inject.Guice;
import com.google.inject.Injector;
import io.edurt.datacap.plugin.loader.CompiledPomPluginLoader;
import io.edurt.datacap.plugin.loader.DirectoryPluginLoader;
import io.edurt.datacap.plugin.loader.PluginLoader;
import io.edurt.datacap.plugin.loader.PluginLoaderFactory;
import io.edurt.datacap.plugin.loader.PomPluginLoader;
import io.edurt.datacap.plugin.loader.SPIPluginLoader;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
@Slf4j
public class PluginManager
{
// 插件配置
// Plugin configuration
private final PluginConfig config;
// 插件存储映射
// Plugin storage mapping
private final Map<String, PluginInfo> plugins;
// Guice注入器
// Guice injector
private final Injector injector;
// 插件加载器列表
// Plugin loaders list
private final List<PluginLoader> loaders;
// 运行状态标志
// Running state flag
private volatile boolean running;
public PluginManager(PluginConfig config)
{
this.config = config;
this.plugins = Maps.newConcurrentMap();
this.injector = Guice.createInjector(new PluginModule());
// 注册插件加载器
// Register plugin loaders
this.loaders = List.of(
new SPIPluginLoader(),
new PomPluginLoader(),
new DirectoryPluginLoader(),
new CompiledPomPluginLoader()
);
}
// 启动插件管理器
// Start plugin manager
public void start()
{
running = true;
createPluginsDirectoryIfNotExists();
loadPlugins();
if (config.isAutoReload()) {
startPluginWatcher();
}
}
// 停止插件管理器
// Stop plugin manager
public void stop()
{
running = false;
plugins.values().forEach(this::closePluginClassLoader);
plugins.clear();
}
// 创建插件目录如果不存在
// Create plugins directory if not exists
private void createPluginsDirectoryIfNotExists()
{
try {
Files.createDirectories(config.getPluginsDir());
}
catch (IOException e) {
log.warn("Failed to create plugins directory", e);
}
}
// 加载所有插件
// Load all plugins
private void loadPlugins()
{
try (Stream<Path> paths = Files.walk(config.getPluginsDir(), 1)) {
paths.filter(Files::isDirectory)
.peek(path -> log.info("Scanning plugin directory: {}", path))
.filter(path -> !path.equals(config.getPluginsDir()))
.forEach(this::loadPluginFromDirectory);
}
catch (IOException e) {
log.error("Failed to scan plugins directory", e);
}
}
// 从目录加载插件
// Load plugin from directory
private void loadPluginFromDirectory(Path pluginDir)
{
try {
List<PluginModule> modules = PluginLoaderFactory.loadPlugins(pluginDir);
for (PluginModule module : modules) {
String pluginName = module.getName();
log.info("Found plugin module: [ {} ] type [ {} ]", pluginName, module.getType());
PluginInfo pluginInfo = PluginInfo.builder()
.name(pluginName)
.version(module.getVersion())
.location(pluginDir)
.state(PluginState.CREATED)
.classLoader(module.getClass().getClassLoader())
.instance(module)
.loadTime(System.currentTimeMillis())
.build();
// 移除旧版本插件
// Remove old version plugin
PluginInfo oldPlugin = plugins.remove(pluginName);
if (oldPlugin != null) {
closePluginClassLoader(oldPlugin);
}
plugins.put(pluginName, pluginInfo);
log.info("Successfully loaded plugin: [ {} ] from directory [ {} ]",
pluginName, pluginDir);
}
}
catch (Exception e) {
log.error("Failed to load plugin from directory: {}", pluginDir, e);
}
}
// 关闭插件类加载器
// Close plugin class loader
private void closePluginClassLoader(PluginInfo pluginInfo)
{
try {
if (pluginInfo.getClassLoader() instanceof URLClassLoader) {
((URLClassLoader) pluginInfo.getClassLoader()).close();
}
}
catch (IOException e) {
log.error("Failed to close plugin classloader: {}", pluginInfo.getName(), e);
}
}
// 启动插件监视器线程
// Start plugin watcher thread
private void startPluginWatcher()
{
Thread watchThread = new Thread(() -> {
while (running) {
try {
Thread.sleep(config.getScanInterval());
loadPlugins();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
watchThread.setDaemon(true);
watchThread.start();
}
// 获取指定名称的插件
// Get plugin by name
public Optional<PluginModule> getPlugin(String name)
{
return Optional.ofNullable(plugins.get(name))
.map(info -> (PluginModule) info.getInstance());
}
// 获取所有插件信息
// Get all plugin information
public List<PluginInfo> getPluginInfos()
{
return new ArrayList<>(plugins.values());
}
// 卸载指定名称的插件
// Unload plugin by name
public boolean unloadPlugin(String name)
{
PluginInfo pluginInfo = plugins.remove(name);
if (pluginInfo != null) {
closePluginClassLoader(pluginInfo);
return true;
}
return false;
}
}

View File

@ -0,0 +1,26 @@
package io.edurt.datacap.plugin;
import com.google.inject.AbstractModule;
public class PluginModule
extends AbstractModule
{
String getName()
{
return this.getClass().getSimpleName()
.replace("PluginModule", "")
.replace("Module", "");
}
String getVersion()
{
return this.getClass()
.getPackage()
.getImplementationVersion();
}
String getType()
{
return "Plugin";
}
}

View File

@ -0,0 +1,10 @@
package io.edurt.datacap.plugin;
public enum PluginState
{
CREATED,
INITIALIZED,
STARTED,
STOPPED,
FAILED
}

View File

@ -0,0 +1,165 @@
package io.edurt.datacap.plugin.loader;
import io.edurt.datacap.plugin.PluginModule;
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@Slf4j
public class CompiledPomPluginLoader
implements PluginLoader
{
@Override
public String getType()
{
return "CompiledPom";
}
@Override
public List<PluginModule> load(Path path)
{
try {
// 处理传入的是pom.xml文件的情况
// Handle the case when input is pom.xml file
Path pomFile;
if (path.toString().endsWith("pom.xml")) {
pomFile = path;
path = path.getParent();
}
else {
pomFile = path.resolve("pom.xml");
}
if (!Files.exists(pomFile)) {
log.debug("No pom.xml found in {}", path);
return List.of();
}
// 读取POM文件
// Read POM file
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileReader(pomFile.toFile()));
// 获取编译后的类路径
// Get compiled classpath
Path targetClasses = path.resolve("target/classes");
Path targetDependencies = path.resolve("target/dependency");
// 如果已编译的类不存在直接返回空列表
// If compiled classes don't exist, return empty list
if (!Files.exists(targetClasses)) {
log.debug("Target classes directory not found: {}", targetClasses);
return List.of();
}
// 创建类加载器
// Create class loader
URLClassLoader classLoader = createProjectClassLoader(targetClasses, targetDependencies);
// 查找并加载插件类
// Find and load plugin classes
return findAndLoadPlugins(classLoader, targetClasses);
}
catch (Exception e) {
log.error("Failed to load compiled plugin from: {}", path, e);
return List.of();
}
}
// 创建项目类加载器
// Create project class loader
private URLClassLoader createProjectClassLoader(Path targetClasses, Path targetDependencies)
throws Exception
{
List<URL> urls = new ArrayList<>();
// 添加编译后的类路径
// Add compiled classes path
log.debug("Adding classes directory to classpath: {}", targetClasses);
urls.add(targetClasses.toUri().toURL());
// 添加所有依赖jar如果存在
// Add all dependency jars (if exist)
if (Files.exists(targetDependencies)) {
log.debug("Adding dependencies from: {}", targetDependencies);
try (Stream<Path> paths = Files.walk(targetDependencies)) {
paths.filter(path -> path.toString().endsWith(".jar"))
.forEach(path -> {
try {
log.debug("Adding dependency to classpath: {}", path);
urls.add(path.toUri().toURL());
}
catch (Exception e) {
log.error("Failed to add dependency jar to classpath: {}", path, e);
}
});
}
}
else {
log.debug("Dependencies directory not found: {}", targetDependencies);
}
return new URLClassLoader(
urls.toArray(new URL[0]),
getClass().getClassLoader()
);
}
// 查找并加载插件类
// Find and load plugin classes
private List<PluginModule> findAndLoadPlugins(URLClassLoader classLoader, Path targetClasses)
{
List<PluginModule> plugins = new ArrayList<>();
try {
// 扫描编译后的类文件
// Scan compiled class files
try (Stream<Path> paths = Files.walk(targetClasses)) {
paths.filter(path -> path.toString().endsWith(".class"))
.filter(path -> !path.toString().contains("$"))
.forEach(path -> {
try {
String className = getClassName(targetClasses, path);
Class<?> cls = classLoader.loadClass(className);
// 检查是否是具体的插件类
// Check if it's a concrete plugin class
if (PluginModule.class.isAssignableFrom(cls) &&
!cls.isInterface() &&
!Modifier.isAbstract(cls.getModifiers())) {
PluginModule plugin = (PluginModule) cls.getDeclaredConstructor().newInstance();
plugins.add(plugin);
log.info("Loaded plugin class: {}", className);
}
}
catch (Exception e) {
log.debug("Failed to load class: {}", path, e);
}
});
}
}
catch (Exception e) {
log.error("Failed to scan for plugin classes", e);
}
return plugins;
}
// 获取类名
// Get class name
private String getClassName(Path baseDir, Path classFile)
{
String relativePath = baseDir.relativize(classFile).toString();
return relativePath.replace(File.separatorChar, '.')
.replace(".class", "");
}
}

View File

@ -0,0 +1,66 @@
package io.edurt.datacap.plugin.loader;
import io.edurt.datacap.plugin.PluginModule;
import io.edurt.datacap.plugin.utils.PluginClassLoaderUtils;
import lombok.extern.slf4j.Slf4j;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
public class DirectoryPluginLoader
implements PluginLoader
{
@Override
public String getType()
{
return "Directory";
}
@Override
public List<PluginModule> load(Path path)
{
try {
URLClassLoader classLoader = PluginClassLoaderUtils.createClassLoader(path);
return Files.walk(path)
.filter(p -> p.toString().endsWith(".class"))
.filter(p -> !p.toString().contains("$"))
.map(p -> loadClass(classLoader, p))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
catch (Exception e) {
log.error("Failed to load plugins from directory: {}", path, e);
return List.of();
}
}
private Optional<PluginModule> loadClass(URLClassLoader classLoader, Path classFile)
{
try {
String className = getClassName(classFile);
Class<?> cls = classLoader.loadClass(className);
if (PluginModule.class.isAssignableFrom(cls)) {
return Optional.of((PluginModule) cls.getDeclaredConstructor().newInstance());
}
}
catch (Exception e) {
log.debug("Failed to load class: {}", classFile, e);
}
return Optional.empty();
}
private String getClassName(Path classFile)
{
String path = classFile.toString();
return path.substring(path.indexOf("io/edurt"))
.replace("/", ".")
.replace(".class", "");
}
}

View File

@ -0,0 +1,17 @@
package io.edurt.datacap.plugin.loader;
import io.edurt.datacap.plugin.PluginModule;
import java.nio.file.Path;
import java.util.List;
public interface PluginLoader
{
// 获取加载器类型
// Get loader type
String getType();
// 加载插件
// Load plugins
List<PluginModule> load(Path path);
}

View File

@ -0,0 +1,122 @@
package io.edurt.datacap.plugin.loader;
import io.edurt.datacap.plugin.PluginModule;
import lombok.extern.slf4j.Slf4j;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class PluginLoaderFactory
{
// 用于缓存已注册的加载器
// Cache for registered loaders
private static final Map<String, PluginLoader> loaderRegistry = new ConcurrentHashMap<>();
// 注册默认的加载器
// Register default loaders
static {
registerLoader(new SPIPluginLoader());
registerLoader(new PomPluginLoader());
registerLoader(new DirectoryPluginLoader());
registerLoader(new CompiledPomPluginLoader());
}
/**
* 注册一个新的插件加载器
* Register a new plugin loader
*
* @param loader 要注册的加载器
* @param loader the loader to register
*/
public static void registerLoader(PluginLoader loader)
{
String type = loader.getType();
if (type == null || type.trim().isEmpty()) {
log.warn("Attempted to register loader with null or empty type: {}", loader.getClass().getName());
return;
}
PluginLoader existing = loaderRegistry.putIfAbsent(type.toLowerCase(), loader);
if (existing != null) {
log.warn("Loader type '{}' is already registered, skipping registration of {}",
type, loader.getClass().getName());
}
else {
log.info("Registered plugin loader: {} for type: {}", loader.getClass().getName(), type);
}
}
/**
* 根据类型获取加载器
* Get loader by type
*
* @param type 加载器类型
* @param type loader type
* @return 对应的加载器实例如果未找到则返回null
* @return corresponding loader instance, or null if not found
*/
public static PluginLoader getLoader(String type)
{
if (type == null) {
return null;
}
return loaderRegistry.get(type.toLowerCase());
}
/**
* 使用所有已注册的加载器尝试加载插件
* Attempt to load plugins using all registered loaders
*
* @param pluginDir 插件目录
* @param pluginDir plugin directory
* @return 加载的插件模块列表
* @return list of loaded plugin modules
*/
public static List<PluginModule> loadPlugins(Path pluginDir)
{
if (pluginDir == null) {
log.warn("Plugin directory is null");
return Collections.emptyList();
}
// 遍历所有注册的加载器尝试加载
// Iterate through all registered loaders to attempt loading
for (Map.Entry<String, PluginLoader> entry : loaderRegistry.entrySet()) {
String type = entry.getKey();
PluginLoader loader = entry.getValue();
try {
List<PluginModule> modules = loader.load(pluginDir);
if (modules != null && !modules.isEmpty()) {
log.info("Successfully loaded {} plugin(s) using loader type: {}", modules.size(), type);
return modules;
}
}
catch (Exception e) {
log.debug("Failed to load plugins using loader type '{}': {}", type, e.getMessage());
}
}
log.warn("No plugins could be loaded from directory: {}", pluginDir);
return Collections.emptyList();
}
/**
* 获取所有已注册的加载器类型
* Get all registered loader types
*
* @return 加载器类型列表
* @return list of loader types
*/
public static List<String> getRegisteredTypes()
{
return Collections.unmodifiableList(
new ArrayList<>(loaderRegistry.keySet())
);
}
}

View File

@ -0,0 +1,58 @@
package io.edurt.datacap.plugin.loader;
import io.edurt.datacap.plugin.PluginModule;
import io.edurt.datacap.plugin.utils.PluginClassLoaderUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import java.io.FileReader;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@Slf4j
public class PomPluginLoader
implements PluginLoader
{
@Override
public String getType()
{
return "Pom";
}
@Override
public List<PluginModule> load(Path path)
{
try {
Path pomFile = path.resolve("pom.xml");
if (!Files.exists(pomFile)) {
return List.of();
}
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileReader(pomFile.toFile()));
String mainClass = model.getProperties().getProperty("plugin.class");
if (mainClass == null) {
return List.of();
}
URLClassLoader classLoader = PluginClassLoaderUtils.createClassLoader(path);
Class<?> pluginClass = classLoader.loadClass(mainClass);
if (!PluginModule.class.isAssignableFrom(pluginClass)) {
log.error("Class {} does not implement PluginModule", mainClass);
return List.of();
}
PluginModule plugin = (PluginModule) pluginClass.getDeclaredConstructor().newInstance();
return List.of(plugin);
}
catch (Exception e) {
log.error("Failed to load plugins using POM from: {}", path, e);
return List.of();
}
}
}

View File

@ -0,0 +1,38 @@
package io.edurt.datacap.plugin.loader;
import io.edurt.datacap.plugin.PluginModule;
import io.edurt.datacap.plugin.utils.PluginClassLoaderUtils;
import lombok.extern.slf4j.Slf4j;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@Slf4j
public class SPIPluginLoader
implements PluginLoader
{
@Override
public String getType()
{
return "SPI";
}
@Override
public List<PluginModule> load(Path path)
{
try {
URLClassLoader classLoader = PluginClassLoaderUtils.createClassLoader(path);
ServiceLoader<PluginModule> serviceLoader = ServiceLoader.load(PluginModule.class, classLoader);
return StreamSupport.stream(serviceLoader.spliterator(), false)
.collect(Collectors.toList());
}
catch (Exception e) {
log.error("Failed to load plugins using SPI from: {}", path, e);
return List.of();
}
}
}

View File

@ -0,0 +1,36 @@
package io.edurt.datacap.plugin.utils;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class PluginClassLoaderUtils
{
public static URLClassLoader createClassLoader(Path directory)
throws Exception
{
List<URL> urls = new ArrayList<>();
// Add all JARs in the directory
if (Files.isDirectory(directory)) {
Files.walk(directory)
.filter(path -> path.toString().endsWith(".jar"))
.forEach(path -> {
try {
urls.add(path.toUri().toURL());
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
}
return new URLClassLoader(
urls.toArray(new URL[0]),
PluginClassLoaderUtils.class.getClassLoader()
);
}
}

View File

@ -0,0 +1,253 @@
package io.edurt.datacap.plugin.utils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@Slf4j
public class PluginPathUtils
{
// 项目根目录标志文件/目录
// Project root markers
private static final List<String> PROJECT_ROOT_MARKERS = Arrays.asList(
"pom.xml", // Maven项目标志
"build.gradle", // Gradle项目标志
".git", // Git仓库标志
".gitignore", // Git配置标志
".idea", // IDEA项目标志
"mvnw", // Maven包装器标志
"gradlew" // Gradle包装器标志
);
// 模块目录标志
// Module directory markers
private static final List<String> MODULE_MARKERS = Arrays.asList(
"src/main/java",
"src/main/resources",
"src/test/java",
"target/classes",
"build/classes"
);
/**
* 查找项目根目录
* Find project root directory
*
* @return 项目根目录的Path对象
* @return Path object of project root directory
*/
public static Path findProjectRoot()
{
Path rootPath = null;
// 1. 首先尝试从类加载路径查找
// First try to find from class loading path
try {
String className = PluginPathUtils.class.getName().replace('.', '/') + ".class";
URL classUrl = PluginPathUtils.class.getClassLoader().getResource(className);
if (classUrl != null) {
String classPath = classUrl.getPath();
// 处理JAR文件路径
// Handle JAR file path
if (classPath.contains(".jar!")) {
classPath = classPath.substring(0, classPath.indexOf(".jar!") + 4);
}
Path path = Paths.get(new File(classPath).toURI());
log.debug("Starting search from class path: {}", path);
rootPath = findRootFromPath(path);
if (rootPath != null) {
log.info("Found project root from class path: {}", rootPath);
return rootPath;
}
}
}
catch (Exception e) {
log.debug("Failed to find root from class path", e);
}
// 2. 尝试从当前工作目录查找
// Try to find from current working directory
try {
Path currentPath = Paths.get("").toAbsolutePath();
log.debug("Starting search from current directory: {}", currentPath);
rootPath = findRootFromPath(currentPath);
if (rootPath != null) {
log.info("Found project root from current directory: {}", rootPath);
return rootPath;
}
}
catch (Exception e) {
log.debug("Failed to find root from current directory", e);
}
// 3. 尝试从系统属性user.dir查找
// Try to find from system property user.dir
try {
String userDir = System.getProperty("user.dir");
if (userDir != null) {
Path userPath = Paths.get(userDir);
log.debug("Starting search from user.dir: {}", userPath);
rootPath = findRootFromPath(userPath);
if (rootPath != null) {
log.info("Found project root from user.dir: {}", rootPath);
return rootPath;
}
}
}
catch (Exception e) {
log.debug("Failed to find root from user.dir", e);
}
// 4. 如果都找不到向上遍历所有父目录
// If not found, traverse all parent directories
try {
Path currentPath = Paths.get("").toAbsolutePath();
while (currentPath != null && currentPath.getParent() != null) {
if (isProjectRoot(currentPath)) {
log.info("Found project root from parent traversal: {}", currentPath);
return currentPath;
}
currentPath = currentPath.getParent();
}
}
catch (Exception e) {
log.debug("Failed to find root from parent traversal", e);
}
// 5. 最后返回当前目录作为后备方案
// Finally return current directory as fallback
Path fallback = Paths.get("").toAbsolutePath();
log.warn("Could not find project root, using fallback: {}", fallback);
return fallback;
}
/**
* 从指定路径向上查找项目根目录
* Find project root directory from specified path
*
* @param startPath 开始搜索的路径
* @param startPath path to start search from
* @return 项目根目录路径如果未找到返回null
* @return project root path, null if not found
*/
private static Path findRootFromPath(Path startPath)
{
try {
Path currentPath = startPath;
while (currentPath != null && currentPath.getParent() != null) {
// 如果当前目录是模块目录继续向上查找
// If current directory is a module directory, continue searching up
if (isModuleDirectory(currentPath)) {
currentPath = currentPath.getParent();
continue;
}
// 检查是否是项目根目录
// Check if it's project root directory
if (isProjectRoot(currentPath)) {
return currentPath;
}
currentPath = currentPath.getParent();
}
}
catch (Exception e) {
log.debug("Error while searching for project root from path: {}", startPath, e);
}
return null;
}
/**
* 检查给定路径是否为项目根目录
* Check if given path is project root directory
*/
private static boolean isProjectRoot(Path path)
{
try {
// 检查是否存在任何根目录标志
// Check if any root marker exists
boolean hasRootMarker = PROJECT_ROOT_MARKERS.stream()
.anyMatch(marker -> Files.exists(path.resolve(marker)));
if (!hasRootMarker) {
return false;
}
// 额外检查是否有src目录或其他项目结构
// Additional check for src directory or other project structure
return Files.exists(path.resolve("src")) ||
Files.exists(path.resolve("pom.xml")) ||
Files.exists(path.resolve("build.gradle"));
}
catch (Exception e) {
return false;
}
}
/**
* 检查给定路径是否为模块目录
* Check if given path is module directory
*/
private static boolean isModuleDirectory(Path path)
{
return MODULE_MARKERS.stream()
.anyMatch(marker -> Files.exists(path.resolve(marker)));
}
/**
* 解析插件路径
* Resolve plugin path
*/
public static Path resolvePluginPath(Path path)
{
if (path == null) {
throw new IllegalArgumentException("Path cannot be null");
}
// 如果是绝对路径直接返回
// If absolute path, return directly
if (path.isAbsolute()) {
log.debug("Using absolute path: {}", path);
return path;
}
// 解析相对于项目根目录的路径
// Resolve path relative to project root
Path projectRoot = findProjectRoot();
Path resolvedPath = projectRoot.resolve(path).normalize();
log.debug("Resolved plugin path {} -> {}", path, resolvedPath);
return resolvedPath;
}
/**
* 从指定路径中查找编译输出目录
* Find compilation output directory from specified path
*/
public static Optional<Path> findOutputDirectory(Path projectPath)
{
try (Stream<Path> paths = Files.walk(projectPath, 3)) {
return paths.filter(path -> path.endsWith("classes") ||
path.endsWith("resources"))
.filter(Files::exists)
.findFirst();
}
catch (IOException e) {
log.debug("Failed to find output directory for path: {}", projectPath, e);
return Optional.empty();
}
}
}

View File

@ -0,0 +1,23 @@
package io.edurt.datacap.plugin;
import io.edurt.datacap.plugin.utils.PluginPathUtils;
import org.junit.Test;
import java.nio.file.Path;
public class PluginManagerTest
{
@Test
public void testLoadPlugin()
{
Path projectRoot = PluginPathUtils.findProjectRoot();
PluginConfig config = PluginConfig.builder()
.pluginsDir(projectRoot.resolve("test/datacap-test-plugin"))
.build();
PluginManager pluginManager = new PluginManager(config);
pluginManager.start();
System.out.println(pluginManager.getPluginInfos());
}
}

View File

@ -93,463 +93,463 @@
<artifactId>datacap-security</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-mysql</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-clickhouse</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-presto</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-redis</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-trino</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-postgresql</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-elasticsearch</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-druid</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-kyuubi</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-hive</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-kylin</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-ignite</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-db2</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-mongo</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-dremio</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-monetdb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-phoenix</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-h2</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-sqlserver</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-oracle</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-cratedb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-dm</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-http-cratedb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-http-clickhouse</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-tdengine</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-impala</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-oceanbase</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-native-redis</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-neo4j</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-iotdb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-snowflake</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-ydb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-native-zookeeper</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-duckdb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-native-alioss</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-native-kafka</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-native-h2</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-http-ceresdb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-http-greptime</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-http-questdb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-doris</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-starrocks</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-jdbc-hologres</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-native-hdfs</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-pinot</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-mongo-community</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-cassandra</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-matrixone</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-scylladb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-paradedb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-timescale</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-solr</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin-influxdb</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-captcha</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Fs -->
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-fs-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-fs-local</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-fs-qiniu</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-fs-alioss</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-fs-tencent-cos</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-fs-amazon-s3</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-fs-minio</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-mysql</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-clickhouse</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-presto</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-redis</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-trino</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-postgresql</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-elasticsearch</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-druid</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-kyuubi</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-hive</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-kylin</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-ignite</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-db2</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; <dependency>&ndash;&gt;-->
<!-- &lt;!&ndash; <groupId>io.edurt.datacap</groupId>&ndash;&gt;-->
<!-- &lt;!&ndash; <artifactId>datacap-jdbc-mongo</artifactId>&ndash;&gt;-->
<!-- &lt;!&ndash; <version>${project.version}</version>&ndash;&gt;-->
<!-- &lt;!&ndash; </dependency>&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-dremio</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-monetdb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-phoenix</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-h2</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-sqlserver</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-oracle</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-cratedb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-dm</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-http-cratedb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-http-clickhouse</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-tdengine</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-impala</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-oceanbase</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-native-redis</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-neo4j</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-iotdb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-snowflake</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-ydb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-native-zookeeper</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-duckdb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-native-alioss</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-native-kafka</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-native-h2</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-http-ceresdb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-http-greptime</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-http-questdb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-doris</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-starrocks</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-jdbc-hologres</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-native-hdfs</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-pinot</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-mongo-community</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-cassandra</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-matrixone</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-scylladb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-paradedb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-timescale</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-solr</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-plugin-influxdb</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-captcha</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; Fs &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-fs-spi</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-fs-local</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-fs-qiniu</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-fs-alioss</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-fs-tencent-cos</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-fs-amazon-s3</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-fs-minio</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- Notify -->
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-notify-spi</artifactId>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-notify-dingtalk</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-notify-dingtalk</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- Parser -->
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-parser-spi</artifactId>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-parser-mysql</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-parser-trino</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-parser-mysql</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-parser-trino</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- Executor -->
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-executor-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-executor-local</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-executor-seatunnel</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-executor-local</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-executor-seatunnel</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- Scheduler -->
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-scheduler-spi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-scheduler-local</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-scheduler-local</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- Convert -->
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-convert-spi</artifactId>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-convert-csv</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-convert-json</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-convert-xml</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-convert-txt</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-convert-none</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-convert-csv</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-convert-json</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-convert-xml</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-convert-txt</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>io.edurt.datacap</groupId>-->
<!-- <artifactId>datacap-convert-none</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
</dependencies>
<build>

View File

@ -7,8 +7,10 @@ import com.google.inject.TypeLiteral;
import io.edurt.datacap.common.response.CommonResponse;
import io.edurt.datacap.executor.Executor;
import io.edurt.datacap.scheduler.Scheduler;
import io.edurt.datacap.spi.Plugin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@ -43,4 +45,15 @@ public class PluginController
plugins.put("scheduler", schedulers);
return CommonResponse.success(plugins);
}
@GetMapping(value = {"filter"})
public CommonResponse getPluginByType(@RequestParam String type)
{
if (type.equalsIgnoreCase("plugin")) {
return CommonResponse.success(injector.getInstance(Key.get(new TypeLiteral<Set<Plugin>>() {})));
}
else {
return CommonResponse.failure("Unknown type " + type);
}
}
}

View File

@ -37,24 +37,24 @@ public class DatasetSchedulerInitializer
throws Exception
{
log.info("Start dataset scheduler initializer");
this.repository.findAllBySyncMode(SyncMode.TIMING)
.forEach(item -> {
log.info("Dataset [ {} ] will be scheduled", item.getName());
SpiUtils.findSchedule(this.injector, item.getScheduler())
.ifPresent(scheduler -> {
SchedulerRequest request = new SchedulerRequest();
request.setName(item.getId().toString());
request.setGroup("datacap");
request.setExpression(item.getExpression());
request.setJobId(String.valueOf(item.getId()));
request.setCreateBeforeDelete(true);
if (scheduler.name().equals("Default")) {
request.setJob(new DatasetJob());
request.setScheduler(this.scheduler);
}
scheduler.initialize(request);
});
});
// this.repository.findAllBySyncMode(SyncMode.TIMING)
// .forEach(item -> {
// log.info("Dataset [ {} ] will be scheduled", item.getName());
// SpiUtils.findSchedule(this.injector, item.getScheduler())
// .ifPresent(scheduler -> {
// SchedulerRequest request = new SchedulerRequest();
// request.setName(item.getId().toString());
// request.setGroup("datacap");
// request.setExpression(item.getExpression());
// request.setJobId(String.valueOf(item.getId()));
// request.setCreateBeforeDelete(true);
// if (scheduler.name().equals("Default")) {
// request.setJob(new DatasetJob());
// request.setScheduler(this.scheduler);
// }
// scheduler.initialize(request);
// });
// });
log.info("End dataset scheduler initializer");
}
}

View File

@ -25,6 +25,28 @@ const createDefaultRouter = (router: any) => {
]
}
router.addRoute(indexRouter)
const storeRouter = {
path: '/',
meta: {
title: 'common.store',
isRoot: true
},
component: LayoutContainer,
redirect: '/store',
children: [
{
name: 'store',
path: 'store',
meta: {
title: 'common.store',
isRoot: false
},
component: () => import('@/views/pages/store/StoreHome.vue')
}
]
}
router.addRoute(storeRouter)
}
createSystemRouter(router)

View File

@ -7,7 +7,12 @@ class PluginService
{
getPlugins(): Promise<ResponseModel>
{
return new HttpUtils().get(`${DEFAULT_PATH}`)
return new HttpUtils().get(`${ DEFAULT_PATH }`)
}
filterByType(type: string): Promise<ResponseModel>
{
return new HttpUtils().get(`${ DEFAULT_PATH }/filter`, { type })
}
}

View File

@ -1,53 +0,0 @@
# App.vue
<template>
<div class="flex h-screen bg-gray-100">
<!-- 左侧组件面板 -->
<BigScreenPanel />
<!-- 中间编辑区域 -->
<BigScreenEditor
ref="editorRef"
:grid-size="20"
:selected-id="selectedId"
@select="handleSelect"
@update:components="handleComponentsUpdate"
/>
<!-- 右侧配置面板 -->
<BigScreenConfigure
:selected-component="selectedComponent"
@update="handleConfigUpdate"
/>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import BigScreenPanel from './components/BigScreenPanel.vue'
import BigScreenEditor from './components/BigScreenEditor.vue'
import BigScreenConfigure from './components/BigScreenConfigure.vue'
const editorRef = ref(null)
const components = ref([])
const selectedId = ref(null)
//
const selectedComponent = computed(() =>
components.value.find(item => item.id === selectedId.value)
)
//
const handleSelect = (component) => {
selectedId.value = component.id
}
//
const handleComponentsUpdate = (newComponents) => {
components.value = newComponents
}
//
const handleConfigUpdate = (updatedComponent) => {
editorRef.value?.updateComponent(updatedComponent)
}
</script>

View File

@ -1,104 +0,0 @@
# BigScreenConfigure.vue
<template>
<div class="w-64 bg-white border-l border-gray-200 p-4">
<div class="text-lg font-medium mb-4">配置面板</div>
<template v-if="selectedComponent">
<div class="space-y-4">
<!-- 位置配置 -->
<div class="space-y-2">
<div class="text-sm font-medium text-gray-600">位置</div>
<div class="grid grid-cols-2 gap-2">
<div>
<div class="text-xs text-gray-500 mb-1">X 坐标</div>
<input
type="number"
v-model="componentConfig.x"
@input="handleUpdate"
class="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:outline-none focus:border-blue-500"
>
</div>
<div>
<div class="text-xs text-gray-500 mb-1">Y 坐标</div>
<input
type="number"
v-model="componentConfig.y"
@input="handleUpdate"
class="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:outline-none focus:border-blue-500"
>
</div>
</div>
</div>
<!-- 大小配置 -->
<div class="space-y-2">
<div class="text-sm font-medium text-gray-600">大小</div>
<div class="grid grid-cols-2 gap-2">
<div>
<div class="text-xs text-gray-500 mb-1">宽度</div>
<input
type="number"
v-model="componentConfig.width"
@input="handleUpdate"
class="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:outline-none focus:border-blue-500"
>
</div>
<div>
<div class="text-xs text-gray-500 mb-1">高度</div>
<input
type="number"
v-model="componentConfig.height"
@input="handleUpdate"
class="w-full px-2 py-1 border border-gray-200 rounded text-sm focus:outline-none focus:border-blue-500"
>
</div>
</div>
</div>
</div>
</template>
<div v-else class="text-gray-400 text-center py-4">
请选择组件进行配置
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
selectedComponent: {
type: Object,
default: null
}
})
const emit = defineEmits(['update'])
//
const componentConfig = ref({
x: 0,
y: 0,
width: 0,
height: 0
})
//
watch(() => props.selectedComponent, (newVal) => {
if (newVal) {
componentConfig.value = {
x: newVal.x,
y: newVal.y,
width: newVal.width,
height: newVal.height
}
}
}, { deep: true })
//
const handleUpdate = () => {
if (!props.selectedComponent) return
emit('update', {
...props.selectedComponent,
...componentConfig.value
})
}
</script>

View File

@ -1,108 +0,0 @@
# BigScreenEditor.vue
<template>
<div
class="flex-1 relative overflow-auto"
@dragover.prevent
@drop="handleDrop"
:style="gridStyle"
>
<!-- 已添加的组件 -->
<div
v-for="item in components"
:key="item.id"
class="absolute bg-white border-2 flex items-center justify-center cursor-move transition-all"
:class="[
selectedId === item.id
? 'border-blue-500 shadow-lg'
: 'border-gray-200 hover:border-gray-300'
]"
:style="getPosition(item)"
@click.stop="handleSelect(item)"
>
{{ item.label }}
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const props = defineProps({
gridSize: {
type: Number,
default: 20
},
selectedId: {
type: [Number, null],
default: null
}
})
const emit = defineEmits(['update:components', 'select'])
//
const gridStyle = {
backgroundSize: `${props.gridSize}px ${props.gridSize}px`,
backgroundImage: 'linear-gradient(#f0f0f0 1px, transparent 1px), linear-gradient(90deg, #f0f0f0 1px, transparent 1px)'
}
//
const components = ref([])
//
const handleDrop = (e) => {
const type = e.dataTransfer.getData('componentType')
const label = e.dataTransfer.getData('componentLabel')
//
const rect = e.target.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
//
const alignedX = Math.round(x / props.gridSize) * props.gridSize
const alignedY = Math.round(y / props.gridSize) * props.gridSize
//
const newComponents = [...components.value, {
id: Date.now(),
type,
label,
x: alignedX,
y: alignedY,
width: props.gridSize * 5,
height: props.gridSize * 3
}]
components.value = newComponents
emit('update:components', newComponents)
}
//
const handleSelect = (component) => {
emit('select', component)
}
//
const getPosition = (component) => {
return {
left: component.x + 'px',
top: component.y + 'px',
width: component.width + 'px',
height: component.height + 'px'
}
}
//
defineExpose({
updateComponent: (updatedComponent) => {
const index = components.value.findIndex(item => item.id === updatedComponent.id)
if (index > -1) {
const newComponents = [...components.value]
newComponents[index] = updatedComponent
components.value = newComponents
emit('update:components', newComponents)
}
}
})
</script>

View File

@ -1,29 +0,0 @@
<template>
<div class="w-64 bg-white border-r border-gray-200 p-4">
<div class="text-lg font-medium mb-4">组件列表</div>
<div class="space-y-2">
<div
v-for="item in componentList"
:key="item.type"
class="p-3 bg-gray-50 border border-gray-200 rounded cursor-move text-center hover:bg-gray-100 transition-colors"
draggable="true"
@dragstart="handleDragStart($event, item)"
>
{{ item.label }}
</div>
</div>
</div>
</template>
<script setup>
const componentList = [
{type: 'text', label: '文本'},
{type: 'image', label: '图片'},
{type: 'chart', label: '图表'},
]
const handleDragStart = (e, component) => {
e.dataTransfer.setData('componentType', component.type)
e.dataTransfer.setData('componentLabel', component.label)
}
</script>

View File

@ -0,0 +1,185 @@
<template>
<div class="relative min-h-screen">
<ShadcnSpin v-if="loading" fixed/>
<ShadcnAlert type="warning">
{{ $t('common.plugin.systemVersion') }}
<ShadcnTag class="text-red-400">{{ version }}</ShadcnTag>
</ShadcnAlert>
<ShadcnTab v-model="activeTab" v-show="!loading" @on-change="onChange">
<ShadcnTabItem v-for="item in metadata" :label="item.i18nFormat ? $t(item.label) : item.label" :value="item.key">
<div class="relative">
<ShadcnSpin v-model="item.loading" fixed/>
<ShadcnSpace wrap size="15">
<ShadcnAlert v-if="item.description">
{{ item.i18nFormat ? $t(item.description) : item.description }}
</ShadcnAlert>
<ShadcnCard v-for="child in item.children" class="w-full">
<div class="p-3 px-6">
<div class="flex items-center justify-between">
<!-- Plugin -->
<div class="flex items-center space-x-4 justify-between">
<!-- Logo and Name -->
<div class="flex flex-col items-center space-y-2 justify-between">
<ShadcnAvatar class="bg-transparent border p-1.5" :src="child.logo" :alt="child.i18nFormat ? $t(child.label) : child.label"/>
<ShadcnText type="h6">
{{ child.i18nFormat ? $t(child.label) : child.label }}
</ShadcnText>
</div>
<ShadcnSpace wrap :size="[20, 40]">
<!-- Description -->
<div class="flex flex-col space-y-2">
<ShadcnText class="text-sm text-gray-500" type="small">
{{ child.i18nFormat ? $t(child.description) : child.description }}
</ShadcnText>
<!-- Support Version -->
<div class="flex space-x-2 text-sm text-gray-500">
<div class="flex items-center space-x-2">
{{ $t('common.plugin.list.supportVersion') }} :
</div>
<ShadcnTag v-for="version in child.supportVersion" type="success" :key="version">
{{ version }}
</ShadcnTag>
</div>
<!-- Version -->
<div class="flex space-x-2 text-sm text-gray-500">
<div class="flex items-center space-x-2">
{{ $t('common.plugin.version') }} :
</div>
<ShadcnTag>{{ child.version }}</ShadcnTag>
</div>
<!-- Other -->
<div class="flex space-x-2 text-sm text-gray-500">
<div class="space-x-1">
{{ $t('common.author') }} {{ child.author }}
</div>
<ShadcnDivider type="vertical"/>
<div class="space-x-1">
{{ $t('common.type') }}
<ShadcnTag>{{ child.type }}</ShadcnTag>
</div>
<ShadcnDivider type="vertical"/>
<div class="space-x-1">
{{ $t('common.releasedTime') }}
<ShadcnTag type="warning">{{ child.released }}</ShadcnTag>
</div>
</div>
</div>
</ShadcnSpace>
</div>
<!-- Action -->
<div class="flex items-center space-x-2">
<ShadcnButton>
{{ $t('common.install') }}
</ShadcnButton>
</div>
</div>
</div>
</ShadcnCard>
</ShadcnSpace>
</div>
</ShadcnTabItem>
</ShadcnTab>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
import { useI18nHandler } from '@/i18n/I18n'
import { PackageUtils } from '@/utils/package.ts'
import PluginService from '@/services/plugin.ts'
interface MetadataItem
{
key: string
label: string
description: string
logo: string
type: string
released: string
i18nFormat: boolean
version: string
supportVersion: string[]
author: string
}
interface Metadata
{
key: string
label: string
description: string
i18nFormat: boolean
children: MetadataItem[]
}
const metadataUrl = ref('https://cdn.north.devlive.org/applications/datacap/metadata.json')
const { proxy } = getCurrentInstance()!
const loading = ref(false)
const metadata = ref<Metadata[]>([])
const installPlugins = ref([])
const activeTab = ref('plugin')
// @ts-ignore
const { loadingState } = useI18nHandler()
const version = ref(PackageUtils.get('version'))
watch(loadingState, async (newVal) => {
if (!newVal && !metadata.value.length) {
await loadMetadata()
}
})
const loadMetadata = async () => {
loading.value = true
try {
const response = await fetch(metadataUrl.value)
if (!response.ok) {
throw new Error(`HTTP error! status: ${ response.status }`)
}
const data = await response.json()
metadata.value = data
activeTab.value = 'plugin'
}
catch (error) {
if (error instanceof TypeError) {
proxy?.$Message.error({ content: proxy?.$t('common.tip.pageNotNetwork'), showIcon: true })
}
else {
proxy?.$Message.error({ content: `${ proxy?.$t('common.pageNotFoundTip') }: ${ error.message }`, showIcon: true })
}
}
finally {
loading.value = false
}
}
const onChange = (value: string) => {
PluginService.filterByType(value)
.then(response => {
if (response.status) {
installPlugins.value = response.data
}
})
}
onBeforeMount(() => {
if (!loadingState.value) {
loadMetadata()
}
})
</script>

View File

@ -16,6 +16,7 @@
<module>core/datacap-security</module>
<module>core/datacap-captcha</module>
<module>core/datacap-sql</module>
<module>core/datacap-plugin</module>
<module>lib/datacap-http</module>
<module>lib/datacap-logger</module>
<module>lib/datacap-shell</module>
@ -100,6 +101,7 @@
<module>convert/datacap-convert-none</module>
<module>convert/datacap-convert-csv</module>
<module>convert/datacap-convert-xml</module>
<module>test/datacap-test-plugin</module>
</modules>
<name>datacap</name>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap</artifactId>
<version>2024.4.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>datacap-test-plugin</artifactId>
<description>DataCap - Test plugin</description>
<dependencies>
<dependency>
<groupId>io.edurt.datacap</groupId>
<artifactId>datacap-plugin</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
package io.edurt.datacap.test;
import io.edurt.datacap.plugin.PluginModule;
public class LocalModule
extends PluginModule
{
}

View File

@ -0,0 +1,19 @@
package io.edurt.datacap.test;
import io.edurt.datacap.plugin.Plugin;
public class LocalPlugin
implements Plugin
{
@Override
public String name()
{
return "Local";
}
@Override
public String version()
{
return "1.0.0";
}
}

View File

@ -0,0 +1 @@
io.edurt.datacap.test.LocalModule