diff --git a/dbapi-assembly/bin/dbapi-daemon.sh b/dbapi-assembly/bin/dbapi-daemon.sh index c020052804d4cd3f977cc3ca9d00de7b571a7bbb..8bac53429e315ac63d1843e7d2223a982e90e201 100644 --- a/dbapi-assembly/bin/dbapi-daemon.sh +++ b/dbapi-assembly/bin/dbapi-daemon.sh @@ -69,7 +69,7 @@ if [ "$command" = "standalone" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.DBApiStandalone HEAP_OPTS="-Xms4g -Xmx4g -Xmn2g" - PROFILES="-Dspring.profiles.active=standalone" + PROFILES="-Dspring.profiles.active=standalone $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" elif [ "$command" = "manager" ]; then @@ -77,7 +77,7 @@ elif [ "$command" = "manager" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.manager.DBApiManager HEAP_OPTS="-Xms1g -Xmx1g -Xmn512m" - PROFILES="-Dspring.profiles.active=manager" + PROFILES="-Dspring.profiles.active=manager $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" elif [ "$command" = "apiServer" ]; then @@ -85,7 +85,7 @@ elif [ "$command" = "apiServer" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.apiserver.DBApiApiServer HEAP_OPTS="-Xms4g -Xmx4g -Xmn2g" - PROFILES="-Dspring.profiles.active=apiServer" + PROFILES="-Dspring.profiles.active=apiServer $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" elif [ "$command" = "gateway" ]; then @@ -93,7 +93,7 @@ elif [ "$command" = "gateway" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.gateway.DBApiGateWay HEAP_OPTS="-Xms4g -Xmx4g -Xmn2g" - PROFILES="-Dspring.profiles.active=gateway -Dreactor.netty.http.server.accessLogEnabled=true " + PROFILES="-Dspring.profiles.active=gateway -Dreactor.netty.http.server.accessLogEnabled=true $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" else diff --git a/dbapi-assembly/bin/dbapi.sh b/dbapi-assembly/bin/dbapi.sh index 8c0b4a9670d8056f469b9c0b823b41bbd6c32e90..14f429dde906cf14e92de32c3d714a9fbbe80a94 100644 --- a/dbapi-assembly/bin/dbapi.sh +++ b/dbapi-assembly/bin/dbapi.sh @@ -69,7 +69,7 @@ if [ "$command" = "standalone" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.DBApiStandalone HEAP_OPTS="-Xms4g -Xmx4g -Xmn2g" - PROFILES="-Dspring.profiles.active=standalone" + PROFILES="-Dspring.profiles.active=standalone $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" elif [ "$command" = "manager" ]; then @@ -77,7 +77,7 @@ elif [ "$command" = "manager" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.manager.DBApiManager HEAP_OPTS="-Xms1g -Xmx1g -Xmn512m" - PROFILES="-Dspring.profiles.active=manager" + PROFILES="-Dspring.profiles.active=manager $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" elif [ "$command" = "apiServer" ]; then @@ -85,7 +85,7 @@ elif [ "$command" = "apiServer" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.apiserver.DBApiApiServer HEAP_OPTS="-Xms4g -Xmx4g -Xmn2g" - PROFILES="-Dspring.profiles.active=apiServer" + PROFILES="-Dspring.profiles.active=apiServer $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" elif [ "$command" = "gateway" ]; then @@ -93,7 +93,7 @@ elif [ "$command" = "gateway" ]; then generate_classpath CLASS=com.gitee.freakchicken.dbapi.gateway.DBApiGateWay HEAP_OPTS="-Xms4g -Xmx4g -Xmn2g" - PROFILES="-Dspring.profiles.active=gateway -Dreactor.netty.http.server.accessLogEnabled=true " + PROFILES="-Dspring.profiles.active=gateway -Dreactor.netty.http.server.accessLogEnabled=true $PROFILES" export DBAPI_OPTS="$HEAP_OPTS $DBAPI_OPTS" else @@ -107,7 +107,7 @@ case $startStop in echo start $command in docker export DBAPI_OPTS="$DBAPI_OPTS -XX:-UseContainerSupport" exec_command="$PROFILES -classpath $cp $CLASS" -# echo $exec_command + # echo $exec_command java $exec_command else [ -w "$DBAPI_PID_DIR" ] || mkdir -p "$DBAPI_PID_DIR" @@ -121,7 +121,8 @@ case $startStop in echo starting $command exec_command="$PROFILES -classpath $cp $CLASS" - # echo "nohup java $exec_command > $log 2>&1 &" + # echo "nohup java $exec_command > $log 2>&1 &" + # echo "java $exec_command" java $exec_command fi diff --git a/dbapi-assembly/docs/plugin development.md b/dbapi-assembly/docs/plugin development.md index 24c4ebe8c1f7d19379b63bfb2ff9a692f0e09fcc..b67482d1e07437792b766df34cb336b5da1b0199 100644 --- a/dbapi-assembly/docs/plugin development.md +++ b/dbapi-assembly/docs/plugin development.md @@ -30,13 +30,29 @@ ### 2.2 缓存插件开发 - 新建java类实现`com.gitee.freakchicken.dbapi.plugin.CachePlugin` + ```java import com.gitee.freakchicken.dbapi.common.ApiConfig; import com.gitee.freakchicken.dbapi.plugin.CachePlugin; +import com.gitee.freakchicken.dbapi.plugin.PluginManager; import java.util.Map; public class Cdemo extends CachePlugin { + + /** + * 在静态代码中使用`PluginManager.registCachePlugin`注册插件 + */ + static { + PluginManager.registCachePlugin(new Cdemo()); + } + + /** + * 必须提供无参构造方法 + */ + public Cdemo() { + } + /** * 插件初始化方法,实例化插件的时候执行,永远只会执行一次, * 一般是用来创建连接池 @@ -96,26 +112,54 @@ public class Cdemo extends CachePlugin { ```java import com.alibaba.fastjson.JSONObject; import com.gitee.freakchicken.dbapi.common.ApiConfig; +import com.gitee.freakchicken.dbapi.plugin.PluginManager; import com.gitee.freakchicken.dbapi.plugin.TransformPlugin; import java.util.List; public class Tdemo extends TransformPlugin { - /** - * 插件初始化方法,实例化插件的时候执行,永远只会执行一次, - */ - public abstract void init(){} + /** + * 插件初始化方法,实例化插件的时候执行,永远只会执行一次, + */ + public abstract void init(){} - /** - * 数据转换逻辑 - * @param data sql查询结果 - * @param params 缓存插件局部参数 - * @return - */ - public abstract Object transform(List data, String params){ - return null; - } + /** + * 在静态代码中使用`PluginManager.registTransformPlugin`注册插件 + */ + static { + PluginManager.registTransformPlugin(new Tdemo()); + } + + /** + * 必须提供无参构造方法 + */ + public Tdemo() {} + + /** + * 插件初始化方法,实例化插件的时候执行,永远只会执行一次, + */ + public abstract void init(); + + /** + * 数据转换逻辑 + * @param data sql查询结果 + * @param params 缓存插件局部参数 + * @return + */ + public abstract Object transform(List data, String params){ + return null; + } + + /** + * 数据转换逻辑 + * @param data sql查询结果 + * @param params 缓存插件局部参数 + * @return + */ + public abstract Object transform(List data, String params) { + return null; + } } ``` - 数据转换逻辑写在`transform`方法里,第一个参数就是sql查询结果,第二个参数是插件局部参数 @@ -164,7 +208,44 @@ public abstract Object transform(List data, String params){ ``` ![](https://freakchicken.gitee.io/images/dbApi/20220313/api_add_high.png) -### 2.6 插件使用 +### 2.6 插件注册 +提供两种插件注册方式 +- SPI注册 + - 在工程的resources中新建`META-INF/service`目录 + - 插件注册 + - 如果注册缓存插件,在`META-INF/service`目录下 + 新建一个名为`com.gitee.freakchicken.dbapi.plugin.CachePlugin`的**文件**,然后在文件中写入要注册插件的`类全限定名`,例如: + ```txt + com.gitee.freakchicken.demo.plugin.RedisCachePlugin + ``` + 如果有多个插件,换行填入 + + - 如果注册格式转化插件,在`META-INF/service`目录下 + 新建一个名为`com.gitee.freakchicken.dbapi.plugin.TransformPlugin`的**文件**,然后在文件中写入要注册插件的`类全限定名`,例如: + ```txt + com.gitee.freakchicken.demo.plugin.EncryptTransformerPlugin + ``` + 如果有多个插件,换行填入 + - 打包,确保jar包目录结构如下所示 + ![jar包目录结构](https://fuzi1996.pages.dev/assets/pictbed/dbapi-plugin-demo.png) + +- 环境变量注册 + - 在程序启动时,使用环境变量注入插件的`类全限定名` + - 插件注册 + - 如果注册缓存插件 + ```bash + export PROFILES="-Dcom.gitee.freakchicken.dbapi.plugin.CachePlugin=com.gitee.freakchicken.demo.plugin.RedisCachePlugin" + ``` + 如果是多个值,中间用":"隔开 + + - 如果注册格式转化插件 + ```bash + export PROFILES="-Dcom.gitee.freakchicken.dbapi.plugin.TransformPlugin=com.gitee.freakchicken.demo.plugin.EncryptTransformerPlugin" + ``` + 如果是多个值,中间用":"隔开 + + +### 2.7 插件使用 - 用户开发完插件后,请打包,将最后生成的jar包和插件依赖的jar包拷贝进DBApi的`lib`目录下, 再重启DBApi服务(**如果是集群模式,每个节点都需要拷贝jar包并重启集群**),就可以使用插件了 - 如果插件中使用了全局参数,还需要在`conf/plugin.properties`文件添加相应配置并重启生效(**如果是集群模式,每个节点都需要添加相应配置并重启生效**) diff --git a/dbapi-controller/src/main/java/com/gitee/freakchicken/dbapi/basic/controller/DataSourceController.java b/dbapi-controller/src/main/java/com/gitee/freakchicken/dbapi/basic/controller/DataSourceController.java index 30f3aeb4199f9bb87a02bb95836e226821afff52..60da11e67844465aa68199b9ae59bebe888746b0 100644 --- a/dbapi-controller/src/main/java/com/gitee/freakchicken/dbapi/basic/controller/DataSourceController.java +++ b/dbapi-controller/src/main/java/com/gitee/freakchicken/dbapi/basic/controller/DataSourceController.java @@ -22,12 +22,7 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.List; -/** - * @program: dbApi - * @description: - * @author: jiangqiang - * @create: 2021-01-20 10:43 - **/ + @Slf4j @RestController @RequestMapping("/datasource") @@ -97,8 +92,9 @@ public class DataSourceController { e.printStackTrace(); } finally { try { - if (os != null) + if (os != null) { os.close(); + } } catch (IOException e) { e.printStackTrace(); } diff --git a/dbapi-controller/src/main/java/com/gitee/freakchicken/dbapi/basic/controller/PluginController.java b/dbapi-controller/src/main/java/com/gitee/freakchicken/dbapi/basic/controller/PluginController.java new file mode 100644 index 0000000000000000000000000000000000000000..5734b6a41e5d4bfa937551d8c0b6d056698b1f2e --- /dev/null +++ b/dbapi-controller/src/main/java/com/gitee/freakchicken/dbapi/basic/controller/PluginController.java @@ -0,0 +1,32 @@ +package com.gitee.freakchicken.dbapi.basic.controller; + +import com.gitee.freakchicken.dbapi.basic.domain.DataSource; +import com.gitee.freakchicken.dbapi.basic.service.PluginService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +/** + * @program: dbApi + * @description: 插件接口 + * @author: kensan + * @create: 2022-03-09 21:12 + */ +@Slf4j +@RestController +@RequestMapping("/plugin") +public class PluginController { + + @Autowired + private PluginService pluginService; + + @RequestMapping("/listAll") + public Map> listAllPlugin() { + return pluginService.getAllPlugin(); + } +} diff --git a/dbapi-plugin/src/main/java/com/gitee/freakchicken/dbapi/plugin/PluginManager.java b/dbapi-plugin/src/main/java/com/gitee/freakchicken/dbapi/plugin/PluginManager.java index bdcd55be18d87a79f2a32eb58a5eb2309ab963bf..f720898265d02c60d4a8b855dc84b4777250f5a7 100644 --- a/dbapi-plugin/src/main/java/com/gitee/freakchicken/dbapi/plugin/PluginManager.java +++ b/dbapi-plugin/src/main/java/com/gitee/freakchicken/dbapi/plugin/PluginManager.java @@ -2,8 +2,11 @@ package com.gitee.freakchicken.dbapi.plugin; import lombok.extern.slf4j.Slf4j; -import java.util.Map; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @Slf4j public class PluginManager { @@ -11,6 +14,88 @@ public class PluginManager { private static Map cachePlugins = new ConcurrentHashMap<>(); private static Map transformPlugins = new ConcurrentHashMap<>(); + public static List getAllCachePlugin(){ + return new ArrayList<>(cachePlugins.keySet()); + } + + public static List getAllTransformPlugin(){ + return new ArrayList<>(transformPlugins.keySet()); + } + + /** + * 使用spi自动加载插件 + */ + public static void loadPlugins(){ + loadInitialPlugin("com.gitee.freakchicken.dbapi.plugin.CachePlugin",CachePlugin.class); + loadInitialPlugin("com.gitee.freakchicken.dbapi.plugin.TransformPlugin",TransformPlugin.class); + } + + private static void loadInitialPlugin(String property,Class clazz) { + String plugins; + try { + plugins = AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty(property)); + log.info("property {} get value {}",property,plugins); + } catch (Exception ex) { + plugins = null; + } + // If the driver is packaged as a Service Provider, load it. + // Get all the drivers through the classloader + // exposed as a Plugin.class service. + // ServiceLoader.load() replaces the sun.misc.Providers() + + AccessController.doPrivileged((PrivilegedAction) () -> { + + ServiceLoader loadedPlugins = ServiceLoader.load(clazz); + Iterator pluginsIterator = loadedPlugins.iterator(); + + /* Load these drivers, so that they can be instantiated. + * It may be the case that the driver class may not be there + * i.e. there may be a packaged driver with the service class + * as implementation of Plugin but the actual class + * may be missing. In that case a java.util.ServiceConfigurationError + * will be thrown at runtime by the VM trying to locate + * and load the service. + * + * Adding a try catch block to catch those runtime errors + * if driver not available in classpath but it's + * packaged as service and that service is there in classpath. + */ + try{ + while(pluginsIterator.hasNext()) { + pluginsIterator.next(); + } + } catch(Throwable t) { + // Do nothing + } + return null; + }); + + log.info("{} initialize: {}",clazz.getCanonicalName(),plugins); + + if (plugins == null || plugins.equals("")) { + return; + } + String[] pluginsList = plugins.split(":"); + log.info("number of plugins: {}",pluginsList.length); + for (String aPlugin : pluginsList) { + try { + log.info("{} Initialize: {} loading ",clazz.getCanonicalName(),aPlugin); + Class.forName(aPlugin, true, + ClassLoader.getSystemClassLoader()); + } catch (Exception ex) { + log.warn("{} Initialize: {} load failed",clazz.getCanonicalName(),aPlugin,ex); + } + } + } + + public static void registCachePlugin(CachePlugin cachePlugin){ + if(null == cachePlugin){ + throw new IllegalArgumentException("can't regist a null transform plugin"); + } + Class aClass = cachePlugin.getClass(); + cachePlugins.put(aClass.getCanonicalName(),cachePlugin); + } + public static CachePlugin getCachePlugin(String className) { if (cachePlugins.containsKey(className)) { return cachePlugins.get(className); @@ -28,6 +113,14 @@ public class PluginManager { } } + public static void registTransformPlugin(TransformPlugin transformPlugin){ + if(null == transformPlugin){ + throw new IllegalArgumentException("can't regist a null transform plugin"); + } + Class aClass = transformPlugin.getClass(); + transformPlugins.put(aClass.getCanonicalName(),transformPlugin); + } + public static TransformPlugin getTransformPlugin(String className) { if (transformPlugins.containsKey(className)) { return transformPlugins.get(className); diff --git a/dbapi-service/src/main/java/com/gitee/freakchicken/dbapi/basic/event/LoadPluginOnSpringReady.java b/dbapi-service/src/main/java/com/gitee/freakchicken/dbapi/basic/event/LoadPluginOnSpringReady.java new file mode 100644 index 0000000000000000000000000000000000000000..9d9370303b55cfa348969ad778f20e3e42d9f933 --- /dev/null +++ b/dbapi-service/src/main/java/com/gitee/freakchicken/dbapi/basic/event/LoadPluginOnSpringReady.java @@ -0,0 +1,14 @@ +package com.gitee.freakchicken.dbapi.basic.event; + +import com.gitee.freakchicken.dbapi.plugin.PluginManager; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class LoadPluginOnSpringReady { + @EventListener + public void loadPlugins(ApplicationReadyEvent event){ + PluginManager.loadPlugins(); + } +} diff --git a/dbapi-service/src/main/java/com/gitee/freakchicken/dbapi/basic/service/PluginService.java b/dbapi-service/src/main/java/com/gitee/freakchicken/dbapi/basic/service/PluginService.java new file mode 100644 index 0000000000000000000000000000000000000000..50560d280f57e64530c37e015a1c1f2252b286a2 --- /dev/null +++ b/dbapi-service/src/main/java/com/gitee/freakchicken/dbapi/basic/service/PluginService.java @@ -0,0 +1,29 @@ +package com.gitee.freakchicken.dbapi.basic.service; + +import com.gitee.freakchicken.dbapi.plugin.PluginManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @description: + * @program: dbApi + * @author: kensan + * @create: 2022-03-09 21:19 + */ +@Slf4j +@Service +public class PluginService { + + public Map> getAllPlugin(){ + Map> allPlugin = new HashMap<>(2); + allPlugin.put("cachePlugin",PluginManager.getAllCachePlugin()); + allPlugin.put("transformPlugin",PluginManager.getAllTransformPlugin()); + return allPlugin; + } + +} diff --git a/dbapi-ui/src/components/api/common.vue b/dbapi-ui/src/components/api/common.vue index cec869e0bd37fbb1175c6cad781b2dade2d1c84b..a838842165de0ba13ba86cbc65ef202e200357f0 100644 --- a/dbapi-ui/src/components/api/common.vue +++ b/dbapi-ui/src/components/api/common.vue @@ -64,14 +64,13 @@ -
- sql-{{ item.label }} : - +
+ sql-{{ item.id }} : +
- 填写“插件类名”表示对sql执行结果开启数据转换功能,不填写表示不转换。 如果有多条sql,每个sql对应一个数据转换插件 @@ -83,8 +82,8 @@ - + 填写“插件类名”表示对结果数据开启缓存,不填写表示不开启缓存 @@ -148,12 +147,29 @@ export default { isFullScreen: false, mode: 'mini', contentType:'application/x-www-form-urlencoded', - types:['application/x-www-form-urlencoded','application/json'] + types:['application/x-www-form-urlencoded','application/json'], + cachePlugin:[], + transformPlugin:[] } }, props: ["id"], + computed:{ + cachePluginOptions(){ + return this.getPluginOptions(this.cachePlugin) + }, + transformPluginOptions(){ + return this.getPluginOptions(this.transformPlugin) + } + }, methods: { - + getPluginOptions(list){ + return list.map(plugin => { + return { + name: plugin, + value: plugin + } + }) + }, addRow() { this.detail.params.push({name: null, type: null}) }, @@ -207,16 +223,27 @@ export default { }) }) }, - getAllGroups() { this.axios.post("/group/getAll/").then((response) => { this.groups = response.data }).catch((error) => { }) + }, + getAllPlugin(){ + this.axios.get("/plugin/listAll").then((response)=>{ + const {cachePlugin,transformPlugin} = response.data + if(Array.isArray(cachePlugin)){ + this.cachePlugin = cachePlugin + } + if(Array.isArray(transformPlugin)){ + this.transformPlugin = transformPlugin + } + }).catch((error)=>{}) } }, mounted() { this.getAddress() + this.getAllPlugin() //编辑页面 if (this.id != undefined) { this.getDetail(this.id) diff --git a/dbapi-ui/src/components/common/MySelect.vue b/dbapi-ui/src/components/common/MySelect.vue index 8d8ebe13152bcdd7bcc2375de60421b86ac13766..18724d078f21579beef59a72311d7c20b5472e40 100644 --- a/dbapi-ui/src/components/common/MySelect.vue +++ b/dbapi-ui/src/components/common/MySelect.vue @@ -3,10 +3,9 @@ {{ (nullable ? '' : '*') + label }}
-
+
{{ item[option_label] }}
@@ -163,6 +162,8 @@ export default { padding: 0px 10px; line-height: 30px; cursor: pointer; + word-break: break-word; + white-space: pre-line; &:hover { //color: rgb(61, 192, 21);