perf : 引擎优化

This commit is contained in:
clay
2024-04-29 17:43:32 +08:00
parent a4286ba65d
commit c2581d2d80
22 changed files with 270 additions and 136 deletions

View File

@@ -13,8 +13,8 @@ import org.springframework.context.annotation.Bean;
public class JavaCodeAutoConfiguration { public class JavaCodeAutoConfiguration {
@Bean @Bean
public JavaCodeEngine javaCodeEngine(JavaCodeProperties javaCodeProperties){ public JavaCodeEngine javaCodeEngine(JavaCodeProperties javaCodeProperties) {
return new JavaCodeEngine(javaCodeProperties); return new JavaCodeEngine(javaCodeProperties.getClassPath());
} }

View File

@@ -5,19 +5,22 @@ import cn.fateverse.common.core.exception.CustomException;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.*;
/** /**
* 控制台输出捕获
*
* @author Clay * @author Clay
* @date 2024/4/22 17:08 * @date 2024/4/22 17:08
*/ */
public class MultiThreadedCapture { public class ConsoleCapture {
private final static ExecutorService executor = Executors.newFixedThreadPool(2);
/**
* 捕获方法
*
* @param task 任務
* @return 返回结果
*/
public static EngineResult capture(Task task) { public static EngineResult capture(Task task) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream oldOut = System.out; PrintStream oldOut = System.out;
@@ -27,7 +30,7 @@ public class MultiThreadedCapture {
try { try {
result = task.execute(); result = task.execute();
} catch (Exception e) { } catch (Exception e) {
if (e instanceof CustomException){ if (e instanceof CustomException) {
throw (CustomException) e; throw (CustomException) e;
} }
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@@ -1,17 +1,16 @@
package cn.fateverse.common.code.engine; package cn.fateverse.common.code.engine;
import cn.fateverse.common.code.config.JavaCodeProperties; import cn.fateverse.common.code.console.ConsoleCapture;
import cn.fateverse.common.code.console.MultiThreadedCapture;
import cn.fateverse.common.code.exception.SandboxClassNotFoundException; import cn.fateverse.common.code.exception.SandboxClassNotFoundException;
import cn.fateverse.common.code.lock.SegmentLock; import cn.fateverse.common.code.lock.SegmentLock;
import cn.fateverse.common.code.model.EngineResult; import cn.fateverse.common.code.model.EngineResult;
import cn.fateverse.common.code.sandbox.SandboxClassLoader; import cn.fateverse.common.code.sandbox.SandboxClassLoader;
import cn.fateverse.common.code.sandbox.SandboxSecurityManager; import cn.fateverse.common.code.sandbox.SandboxSecurityManager;
import cn.fateverse.common.core.exception.CustomException; import cn.fateverse.common.core.exception.CustomException;
import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import javax.tools.JavaCompiler; import javax.tools.JavaCompiler;
@@ -39,9 +38,7 @@ public class JavaCodeEngine {
private final URL url; private final URL url;
private final URLClassLoader classLoader; private final Map<String, CacheWrapper> classCache = new ConcurrentHashMap<>();
private final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
private final SandboxSecurityManager securityManager = new SandboxSecurityManager(classCache); private final SandboxSecurityManager securityManager = new SandboxSecurityManager(classCache);
@@ -50,53 +47,31 @@ public class JavaCodeEngine {
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
public JavaCodeEngine(JavaCodeProperties javaCodeProperties) { public JavaCodeEngine(String classPath) {
try { try {
CLASS_PATH = javaCodeProperties.getClassPath(); CLASS_PATH = classPath;
File file = new File(CLASS_PATH); File file = new File(CLASS_PATH);
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); file.mkdirs();
} }
url = file.toURI().toURL(); url = file.toURI().toURL();
classLoader = new SandboxClassLoader(new URL[]{url});
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
/**
* 执行方法
*
* @param code 代码字符串
* @param className 类名
* @param methodName 方法名
* @param paramClass 参数类型数组
* @param args 参数数组
* @param development 是否为开发环境 开发环境下会将生成的类在执行完成后删除,不是生产环境则会缓存提高运行效率
* @return 执行结果
*/
public EngineResult execute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args, boolean development) {
if (development) {
return developmentExecute(code, className, methodName, paramClass, args);
} else {
return onlineExecute(code, className, methodName, paramClass, args);
}
}
/** /**
* 用于在开发环境中执行代码的私有方法。 * 用于在开发环境中执行代码的私有方法。
* *
* @param code 需要执行的代码字符串 * @param code 需要执行的代码字符串
* @param className 类名 * @param className 类名
* @param methodName 方法名 * @param methodName 方法名
* @param paramClass 参数类型数组
* @param args 参数数组 * @param args 参数数组
* @return 执行结构 * @return 执行结构
*/ */
@SneakyThrows @SneakyThrows
private EngineResult developmentExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) { public EngineResult mockExecute(String code, String className, String methodName, Object[] args) {
Class<?> loadClass = null; Class<?> loadClass = null;
try { try {
// 加锁,确保类只加载一次 // 加锁,确保类只加载一次
@@ -106,12 +81,12 @@ public class JavaCodeEngine {
// 创建一个URLClassLoader用于加载代码字符串 // 创建一个URLClassLoader用于加载代码字符串
tempClassLoader = new URLClassLoader(new URL[]{url}); tempClassLoader = new URLClassLoader(new URL[]{url});
// 编译代码字符串为类 // 编译代码字符串为类
Class<?> tempClass = compilerClass(className, code, tempClassLoader); return compilerClass(className, code, tempClassLoader);
// 将编译好的类放入缓存
classCache.put(className, tempClass);
return tempClass;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
if (e instanceof CustomException) {
throw (CustomException) e;
}
// 异常处理并抛出自定义的SandboxClassNotFoundException异常 // 异常处理并抛出自定义的SandboxClassNotFoundException异常
throw new SandboxClassNotFoundException(e.getMessage()); throw new SandboxClassNotFoundException(e.getMessage());
} finally { } finally {
@@ -121,15 +96,17 @@ public class JavaCodeEngine {
} }
}); });
// 获取需要执行的方法 // 获取需要执行的方法
Method method = loadClass.getMethod(methodName, paramClass); Method method = getMethod(methodName, loadClass);
// 设置安全检查器 // 设置安全检查器
System.setSecurityManager(securityManager); System.setSecurityManager(securityManager);
// 执行方法并返回结果 // 执行方法并返回结果
return MultiThreadedCapture.capture(() -> method.invoke(null, args)); return ConsoleCapture.capture(() -> method.invoke(null, args));
} catch (CustomException e) {
EngineResult result = new EngineResult();
result.setSuccess(Boolean.FALSE);
result.setConsole(e.getMessage());
return result;
} finally { } finally {
// 从缓存中移除编译好的类
classCache.remove(className);
// 清空安全检查器 // 清空安全检查器
System.setSecurityManager(null); System.setSecurityManager(null);
if (loadClass != null) { if (loadClass != null) {
@@ -156,37 +133,53 @@ public class JavaCodeEngine {
* @param code 需要执行的代码字符串 * @param code 需要执行的代码字符串
* @param className 类名 * @param className 类名
* @param methodName 方法名 * @param methodName 方法名
* @param paramClass 参数类型数组
* @param args 参数数组 * @param args 参数数组
* @return 执行结构 * @return 执行结构
*/ */
private EngineResult onlineExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) { public Object execute(String code, String className, String methodName, Object[] args) {
try { try {
Class<?> loadClass = null; Class<?> loadClass = null;
loadClass = classCache.get(className); //从缓存中获取
if (loadClass == null) { CacheWrapper wrapper = classCache.get(className);
loadClass = getLoadClass(code, className); //缓存中不存在
Method method = loadClass.getMethod(methodName, paramClass); if (wrapper == null) {
System.setSecurityManager(securityManager); //加载
Object result = (Object) method.invoke(null, args); wrapper = getLoadClass(code, className);
return new EngineResult(result);
} }
//获取到类信息
loadClass = wrapper.getClazz();
//获取方法
Method method = getMethod(methodName, loadClass);
//开启安全模式
System.setSecurityManager(securityManager);
//执行方法
return method.invoke(null, args);
} catch (Exception e) { } catch (Exception e) {
remove(className);
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
System.setSecurityManager(null); System.setSecurityManager(null);
File javaFile = new File(CLASS_PATH + className + JAVA_SUFFIX);
if (javaFile.exists()) {
javaFile.delete();
}
File classFile = new File(CLASS_PATH + className + CLASS_SUFFIX);
if (classFile.exists()) {
classFile.delete();
}
} }
return null; return null;
} }
/**
* 获取到方法
*
* @param methodName 方法名称
* @param loadClass 类信息
* @return 方法对象
*/
private Method getMethod(String methodName, Class<?> loadClass) {
Method method = null;
for (Method declaredMethod : loadClass.getDeclaredMethods()) {
if (declaredMethod.getName().equals(methodName)) {
method = declaredMethod;
}
}
return method;
}
/** /**
* 获取到编译完成的Class对象 * 获取到编译完成的Class对象
* *
@@ -194,15 +187,18 @@ public class JavaCodeEngine {
* @param className 类名 * @param className 类名
* @return 编译后的Java对象 * @return 编译后的Java对象
*/ */
private Class<?> getLoadClass(String code, String className) { private CacheWrapper getLoadClass(String code, String className) {
//使用分段锁,提高效率,放多并发情况下多次对同一个类进行编译 //使用分段锁,提高效率,放多并发情况下多次对同一个类进行编译
return SegmentLock.lock(className, () -> { return SegmentLock.lock(className, () -> {
try { try {
URLClassLoader classLoader = new SandboxClassLoader(new URL[]{url});
//执行编译 //执行编译
Class<?> tempClass = compilerClass(className, code, classLoader); Class<?> tempClass = compilerClass(className, code, classLoader);
//创建缓存包装对象
CacheWrapper wrapper = new CacheWrapper(tempClass, classLoader);
//将编译之后的类对象放在缓存中,提高线上环境的运行效率 //将编译之后的类对象放在缓存中,提高线上环境的运行效率
classCache.put(className, tempClass); classCache.put(className, wrapper);
return tempClass; return wrapper;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -248,11 +244,19 @@ public class JavaCodeEngine {
*/ */
public Boolean remove(String className) { public Boolean remove(String className) {
return SegmentLock.lock(className, () -> { return SegmentLock.lock(className, () -> {
classCache.remove(className); CacheWrapper wrapper = classCache.get(className);
if (wrapper != null) {
classCache.remove(className);
wrapper.remove();
}
//进行gc 垃圾挥手
System.gc();
//删除Java文件
File javaFile = new File(CLASS_PATH + className + JAVA_SUFFIX); File javaFile = new File(CLASS_PATH + className + JAVA_SUFFIX);
if (javaFile.exists()) { if (javaFile.exists()) {
javaFile.delete(); javaFile.delete();
} }
//删除class文件
File classFile = new File(CLASS_PATH + className + CLASS_SUFFIX); File classFile = new File(CLASS_PATH + className + CLASS_SUFFIX);
if (classFile.exists()) { if (classFile.exists()) {
classFile.delete(); classFile.delete();
@@ -260,4 +264,24 @@ public class JavaCodeEngine {
return true; return true;
}); });
} }
@Getter
public static class CacheWrapper {
private Class<?> clazz;
private URLClassLoader classLoader;
public CacheWrapper(Class<?> clazz, URLClassLoader classLoader) {
this.clazz = clazz;
this.classLoader = classLoader;
}
public void remove(){
clazz = null;
classLoader = null;
}
}
} }

View File

@@ -1,15 +1,13 @@
package cn.fateverse.common.code.engine; package cn.fateverse.common.code.engine;
import cn.fateverse.common.code.console.MultiThreadedCapture; import cn.fateverse.common.code.console.ConsoleCapture;
import cn.fateverse.common.code.lock.SegmentLock; import cn.fateverse.common.code.lock.SegmentLock;
import cn.fateverse.common.code.model.EngineResult; import cn.fateverse.common.code.model.EngineResult;
import cn.fateverse.common.core.exception.CustomException; import cn.fateverse.common.core.exception.CustomException;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value; import org.graalvm.polyglot.Value;
import org.springframework.security.core.parameters.P;
import javax.script.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -42,9 +40,16 @@ public class JavaScriptEngine {
* @param args 参数 * @param args 参数
* @return 返回结构 * @return 返回结构
*/ */
public static EngineResult execute(String script, String functionName, boolean development, Object args) { public static Object execute(String script, String functionName, Object args) {
if (development) { Value executeFunction = getFunction(functionName, script);
return MultiThreadedCapture.capture(() -> { Value result = executeFunction.execute(JSON.toJSONString(args));
return result.as(Object.class);
}
public static EngineResult mockExecute(String script, String functionName, Object args) {
try {
return ConsoleCapture.capture(() -> {
Context context = Context.newBuilder() Context context = Context.newBuilder()
.allowAllAccess(true) .allowAllAccess(true)
// .allowHostClassLoading(true) // .allowHostClassLoading(true)
@@ -63,10 +68,11 @@ public class JavaScriptEngine {
Value result = executeFunction.execute(javaObjectAsValue); Value result = executeFunction.execute(javaObjectAsValue);
return result.as(Object.class); return result.as(Object.class);
}); });
} else { }catch (CustomException e){
Value executeFunction = getFunction(functionName, script); EngineResult result = new EngineResult();
Value result = executeFunction.execute(JSON.toJSONString(args)); result.setSuccess(Boolean.FALSE);
return new EngineResult(result.as(Object.class)); result.setConsole(e.getMessage());
return result;
} }
} }

View File

@@ -6,6 +6,8 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
* 分段锁对象
*
* @author Clay * @author Clay
* @date 2023-10-25 * @date 2023-10-25
*/ */
@@ -15,10 +17,11 @@ public class SegmentLock {
/** /**
* 分段锁 * 分段锁
* @param key 锁名称 *
* @param key 锁名称
* @param supplier 需要执行的函数 * @param supplier 需要执行的函数
* @param <T> 接收泛型
* @return 执行后的结果 * @return 执行后的结果
* @param <T> 接收泛型
*/ */
public static <T> T lock(String key, Supplier<T> supplier) { public static <T> T lock(String key, Supplier<T> supplier) {
ReentrantLock lock = lockMap.get(key); ReentrantLock lock = lockMap.get(key);

View File

@@ -18,8 +18,11 @@ public class EngineResult {
private String console; private String console;
private Boolean success;
public EngineResult(Object result) { public EngineResult(Object result, String console) {
success = true;
this.result = result; this.result = result;
this.console = console;
} }
} }

View File

@@ -1,5 +1,7 @@
package cn.fateverse.common.code.sandbox; package cn.fateverse.common.code.sandbox;
import cn.fateverse.common.code.engine.JavaCodeEngine;
import java.io.FilePermission; import java.io.FilePermission;
import java.lang.reflect.ReflectPermission; import java.lang.reflect.ReflectPermission;
import java.security.Permission; import java.security.Permission;
@@ -9,9 +11,9 @@ import java.util.Set;
public class SandboxSecurityManager extends SecurityManager { public class SandboxSecurityManager extends SecurityManager {
private final Map<String, Class<?>> classCache; private final Map<String, JavaCodeEngine.CacheWrapper> classCache;
public SandboxSecurityManager(Map<String, Class<?>> classCache) { public SandboxSecurityManager(Map<String, JavaCodeEngine.CacheWrapper> classCache) {
this.classCache = classCache; this.classCache = classCache;
} }
@@ -38,7 +40,7 @@ public class SandboxSecurityManager extends SecurityManager {
private boolean isAllowedPermission(Permission permission) { private boolean isAllowedPermission(Permission permission) {
//权限:用于校验文件系统访问权限,包括读取、写入、删除文件,以及目录操作。权限名称可能包括文件路径和操作,如 "read", "write", "delete", "execute" 等。 //权限:用于校验文件系统访问权限,包括读取、写入、删除文件,以及目录操作。权限名称可能包括文件路径和操作,如 "read", "write", "delete", "execute" 等。
if (permission instanceof FilePermission){ if (permission instanceof FilePermission) {
System.out.println("触发文件读写权限"); System.out.println("触发文件读写权限");
return false; return false;
} }

View File

@@ -1,6 +1,5 @@
package cn.fateverse.common.security.filter; package cn.fateverse.common.security.filter;
import cn.fateverse.common.core.utils.ObjectUtils;
import cn.fateverse.common.security.service.TokenService; import cn.fateverse.common.security.service.TokenService;
import cn.fateverse.common.security.entity.LoginUser; import cn.fateverse.common.security.entity.LoginUser;
import cn.fateverse.common.security.utils.SecurityUtils; import cn.fateverse.common.security.utils.SecurityUtils;
@@ -8,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource; import javax.annotation.Resource;

View File

@@ -69,10 +69,11 @@ public class DataAdapter extends BaseEntity {
this.code = "\n" + this.code = "\n" +
"import java.util.*;\n" + "import java.util.*;\n" +
"import java.util.stream.*;\n" + "import java.util.stream.*;\n" +
"import com.alibaba.fastjson2.*;\n" +
"\n" + "\n" +
"public class DataAdapter {\n" + "public class DataAdapter {\n" +
"\n" + "\n" +
" public static Object execute(List<Map<String, Object>> data) {\n" + " public static Object execute(Object data) {\n" +
" return data;\n" + " return data;\n" +
" }\n" + " }\n" +
"}\n"; "}\n";

View File

@@ -29,7 +29,6 @@ public class PortalBo implements Serializable {
private Boolean createDataAdapter; private Boolean createDataAdapter;
private Boolean page; private Boolean page;
private String path; private String path;
private String url;
private Integer state; private Integer state;
private List<PortalMapping> mappings; private List<PortalMapping> mappings;

View File

@@ -29,5 +29,7 @@ public class MockParam {
private String key; private String key;
private Object value; private Object value;
private String type;
} }
} }

View File

@@ -23,7 +23,7 @@ public abstract class AbstractDataAdapterHandler implements DataAdapterHandler {
this.handlerReader = handlerReader; this.handlerReader = handlerReader;
} }
protected Object execute(Long adapterId, Object data, boolean development) { protected Object execute(Long adapterId, Object data) {
if (ObjectUtils.isEmpty(adapterId)) { if (ObjectUtils.isEmpty(adapterId)) {
return data; return data;
} }
@@ -33,15 +33,15 @@ public abstract class AbstractDataAdapterHandler implements DataAdapterHandler {
throw new RuntimeException("dataAdapter is null"); throw new RuntimeException("dataAdapter is null");
} }
handlerReader.preconditioning(dataAdapter); handlerReader.preconditioning(dataAdapter);
EngineResult execute = handlerReader.execute(dataAdapter, data, development); Object result = handlerReader.execute(dataAdapter, data);
if (ObjectUtils.isEmpty(execute)) { if (ObjectUtils.isEmpty(result)) {
throw new RuntimeException("执行结果错误"); throw new RuntimeException("执行结果错误");
} }
return execute.getResult(); return result;
} }
protected EngineResult mockExecute(Long adapterId, String code, Object data, boolean development) { protected EngineResult mockExecute(Long adapterId, String code, Object data) {
//获取当当前接口对应的数据适配器 //获取当当前接口对应的数据适配器
DataAdapter dataAdapter = dataAdapterMapper.selectById(adapterId); DataAdapter dataAdapter = dataAdapterMapper.selectById(adapterId);
if (ObjectUtils.isEmpty(dataAdapter)) { if (ObjectUtils.isEmpty(dataAdapter)) {
@@ -49,7 +49,7 @@ public abstract class AbstractDataAdapterHandler implements DataAdapterHandler {
} }
dataAdapter.setCode(code); dataAdapter.setCode(code);
handlerReader.preconditioning(dataAdapter); handlerReader.preconditioning(dataAdapter);
EngineResult execute = handlerReader.execute(dataAdapter, data, development); EngineResult execute = handlerReader.mockExecute(dataAdapter, data);
if (ObjectUtils.isEmpty(execute)) { if (ObjectUtils.isEmpty(execute)) {
throw new RuntimeException("执行结果错误"); throw new RuntimeException("执行结果错误");
} }

View File

@@ -1,12 +1,15 @@
package cn.fateverse.query.handler.adapter.impl; package cn.fateverse.query.handler.adapter.impl;
import cn.fateverse.common.core.exception.CustomException; import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.query.entity.PortalInterface;
import cn.fateverse.query.entity.PortalMapping;
import cn.fateverse.query.entity.dto.MockParam; import cn.fateverse.query.entity.dto.MockParam;
import cn.fateverse.query.entity.bo.PortalBo; import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.enums.PortalEnum; import cn.fateverse.query.enums.PortalEnum;
import cn.fateverse.query.handler.adapter.AbstractDataAdapterHandler; import cn.fateverse.query.handler.adapter.AbstractDataAdapterHandler;
import cn.fateverse.query.handler.reader.EngineExecuteHandlerReader; import cn.fateverse.query.handler.reader.EngineExecuteHandlerReader;
import cn.fateverse.query.mapper.DataAdapterMapper; import cn.fateverse.query.mapper.DataAdapterMapper;
import cn.fateverse.query.mapper.PortalInterfaceMapper;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@@ -17,6 +20,7 @@ import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
/** /**
* @author Clay * @author Clay
@@ -31,11 +35,15 @@ public class ExternalDataAdapterHandler extends AbstractDataAdapterHandler {
*/ */
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
public ExternalDataAdapterHandler(DataAdapterMapper dataAdapterMapper, private final PortalInterfaceMapper portalInterfaceMapper;
public ExternalDataAdapterHandler(RestTemplate restTemplate,
DataAdapterMapper dataAdapterMapper,
EngineExecuteHandlerReader handlerReader, EngineExecuteHandlerReader handlerReader,
RestTemplate restTemplate) { PortalInterfaceMapper portalInterfaceMapper) {
super(dataAdapterMapper, handlerReader); super(dataAdapterMapper, handlerReader);
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
this.portalInterfaceMapper = portalInterfaceMapper;
} }
@@ -44,18 +52,28 @@ public class ExternalDataAdapterHandler extends AbstractDataAdapterHandler {
if (!PortalEnum.EXTERNAL.equals(portal.getType())) { if (!PortalEnum.EXTERNAL.equals(portal.getType())) {
return null; return null;
} }
PortalInterface portalInterface = portalInterfaceMapper.selectById(portal.getInterfaceId());
if (ObjectUtils.isEmpty(portalInterface)) {
throw new CustomException("接口不存在");
}
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); MediaType type = MediaType.parseMediaType(portalInterface.getContentType());
headers.setContentType(type); headers.setContentType(type);
headers.add("Accept", MediaType.APPLICATION_JSON.toString()); Map<String, Object> requestParams = new HashMap<>();
HashMap<String, Object> map = new HashMap<>(); if (!ObjectUtils.isEmpty(mockParam.getParams())) {
for (MockParam.Param param : mockParam.getParams()) {
if (!ObjectUtils.isEmpty(param.getKey()) && !ObjectUtils.isEmpty(param.getValue())) {
requestParams.put(param.getKey(), param.getValue());
}
}
}
JSONObject response = null; JSONObject response = null;
switch (portal.getRequestMethod()) { switch (portalInterface.getRequestMethod()) {
case "GET": case "GET":
response = restTemplate.getForObject(portal.getUrl(), JSONObject.class, map); response = restTemplate.getForObject(portalInterface.getUrl(), JSONObject.class, requestParams);
break; break;
case "POST": case "POST":
response = restTemplate.postForObject(portal.getUrl(), map, JSONObject.class); response = restTemplate.postForObject(portalInterface.getUrl(), requestParams, JSONObject.class);
break; break;
default: default:
throw new CustomException("请求方式错误"); throw new CustomException("请求方式错误");
@@ -63,12 +81,49 @@ public class ExternalDataAdapterHandler extends AbstractDataAdapterHandler {
if (ObjectUtils.isEmpty(portal.getAdapterId()) || !portal.getCreateDataAdapter()) { if (ObjectUtils.isEmpty(portal.getAdapterId()) || !portal.getCreateDataAdapter()) {
return response; return response;
} else { } else {
return super.mockExecute(portal.getAdapterId(), mockParam.getCode(), response, true); return super.mockExecute(portal.getAdapterId(), mockParam.getCode(), response);
} }
} }
@Override @Override
public Object execute(PortalBo portal, HttpServletRequest request) { public Object execute(PortalBo portal, HttpServletRequest request) {
return null; if (!PortalEnum.EXTERNAL.equals(portal.getType())) {
return null;
}
PortalInterface portalInterface = portalInterfaceMapper.selectById(portal.getInterfaceId());
if (ObjectUtils.isEmpty(portalInterface)) {
throw new CustomException("接口不存在");
}
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType(portalInterface.getContentType());
headers.setContentType(type);
Map<String, Object> requestParams = new HashMap<>();
for (PortalMapping mapping : portal.getMappings()) {
String mappingValue = mapping.getMappingValue();
String mappingKey = mapping.getMappingKey();
if (mapping.getMappingType() == 0) {
requestParams.put(mappingKey, request.getParameter(mappingValue));
} else if (mapping.getMappingType() == 1) {
headers.add(mappingKey, request.getHeader(mappingValue));
} else {
requestParams.put(mappingKey, request.getParameter(mappingValue));
}
}
JSONObject response = null;
switch (portalInterface.getRequestMethod()) {
case "GET":
response = restTemplate.getForObject(portalInterface.getUrl(), JSONObject.class, requestParams);
break;
case "POST":
response = restTemplate.postForObject(portalInterface.getUrl(), requestParams, JSONObject.class);
break;
default:
throw new CustomException("请求方式错误");
}
if (ObjectUtils.isEmpty(portal.getAdapterId()) || !portal.getCreateDataAdapter()) {
return response;
} else {
return super.execute(portal.getAdapterId(), response);
}
} }
} }

View File

@@ -69,7 +69,7 @@ public class LocalDataAdapterHandler extends AbstractDataAdapterHandler {
if (ObjectUtils.isEmpty(portal.getAdapterId()) || !portal.getCreateDataAdapter()) { if (ObjectUtils.isEmpty(portal.getAdapterId()) || !portal.getCreateDataAdapter()) {
return tableDataInfo.getRows(); return tableDataInfo.getRows();
} else { } else {
return super.mockExecute(portal.getAdapterId(), mockParam.getCode(), tableDataInfo.getRows(), true); return super.mockExecute(portal.getAdapterId(), mockParam.getCode(), tableDataInfo.getRows());
} }
} }
@@ -103,7 +103,7 @@ public class LocalDataAdapterHandler extends AbstractDataAdapterHandler {
//根据设置的参数动态调整当前是否需要分页操作 //根据设置的参数动态调整当前是否需要分页操作
TableDataInfo<Map<String, Object>> tableDataInfo = dynamicDataSearchService.searchData(uniConList, query, null, Boolean.TRUE); TableDataInfo<Map<String, Object>> tableDataInfo = dynamicDataSearchService.searchData(uniConList, query, null, Boolean.TRUE);
if (portal.getCreateDataAdapter()) { if (portal.getCreateDataAdapter()) {
return super.execute(portal.getAdapterId(), tableDataInfo.getRows(), false); return super.execute(portal.getAdapterId(), tableDataInfo.getRows());
} else { } else {
return tableDataInfo.getRows(); return tableDataInfo.getRows();
} }

View File

@@ -11,10 +11,18 @@ public interface EngineExecuteHandler {
* *
* @param dataAdapter 数据适配器 * @param dataAdapter 数据适配器
* @param data 数据列表 * @param data 数据列表
* @param development
* @return JSONObject对象 * @return JSONObject对象
*/ */
EngineResult execute(DataAdapter dataAdapter, Object data, boolean development); Object execute(DataAdapter dataAdapter, Object data);
/**
* 模拟执行方法
*
* @param dataAdapter 数据适配器
* @param data 数据列表
* @return JSONObject对象
*/
EngineResult mockExecute(DataAdapter dataAdapter, Object data);
/** /**
* 预处理数据适配器 * 预处理数据适配器
@@ -31,4 +39,5 @@ public interface EngineExecuteHandler {
* @return 删除结果 * @return 删除结果
*/ */
Boolean remove(DataAdapter dataAdapter); Boolean remove(DataAdapter dataAdapter);
} }

View File

@@ -9,7 +9,6 @@ import cn.fateverse.query.handler.engine.EngineExecuteHandler;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -32,12 +31,18 @@ public class JavaEngineExecuteHandler implements EngineExecuteHandler {
@Override @Override
public EngineResult execute(DataAdapter dataAdapter, Object data, boolean development) { public Object execute(DataAdapter dataAdapter, Object data) {
if (!DataAdapterType.JAVA.equals(dataAdapter.getType())) { if (!DataAdapterType.JAVA.equals(dataAdapter.getType())) {
return null; return null;
} }
return javaCodeEngine.execute(dataAdapter.getExecuteCode(), getClassName(dataAdapter), return javaCodeEngine.execute(dataAdapter.getExecuteCode(), getClassName(dataAdapter),
"execute", new Class[]{List.class}, new Object[]{data}, development); "execute", new Object[]{data});
}
@Override
public EngineResult mockExecute(DataAdapter dataAdapter, Object data) {
return javaCodeEngine.mockExecute(dataAdapter.getExecuteCode(), getClassName(dataAdapter),
"execute", new Object[]{data});
} }
private static String getClassName(DataAdapter dataAdapter) { private static String getClassName(DataAdapter dataAdapter) {
@@ -53,7 +58,8 @@ public class JavaEngineExecuteHandler implements EngineExecuteHandler {
String modifiedCode = modifiedCode(dataAdapter.getCode()); String modifiedCode = modifiedCode(dataAdapter.getCode());
String replacedCode = replacedClass(modifiedCode, getClassName(dataAdapter)); String replacedCode = replacedClass(modifiedCode, getClassName(dataAdapter));
String IMPORT_CODE = "import java.util.*;\n" + String IMPORT_CODE = "import java.util.*;\n" +
"import java.util.stream.*;\n"; "import java.util.stream.*;\n" +
"import com.alibaba.fastjson2.*;";
dataAdapter.setExecuteCode(IMPORT_CODE + replacedCode); dataAdapter.setExecuteCode(IMPORT_CODE + replacedCode);
return Boolean.TRUE; return Boolean.TRUE;
} }

View File

@@ -23,11 +23,20 @@ import java.util.regex.Pattern;
public class JavaScriptEngineExecuteHandler implements EngineExecuteHandler { public class JavaScriptEngineExecuteHandler implements EngineExecuteHandler {
@Override @Override
public EngineResult execute(DataAdapter dataAdapter, Object data, boolean development) { public Object execute(DataAdapter dataAdapter, Object data) {
if (!DataAdapterType.JAVA_SCRIPT.equals(dataAdapter.getType())) { if (!DataAdapterType.JAVA_SCRIPT.equals(dataAdapter.getType())) {
return null; return null;
} }
return JavaScriptEngine.execute(dataAdapter.getExecuteCode(), "execute" + dataAdapter.getAdapterId(), development, data); return JavaScriptEngine.execute(dataAdapter.getExecuteCode(), "execute" + dataAdapter.getAdapterId(), data);
}
@Override
public EngineResult mockExecute(DataAdapter dataAdapter, Object data) {
if (!DataAdapterType.JAVA_SCRIPT.equals(dataAdapter.getType())) {
return null;
}
return JavaScriptEngine.mockExecute(dataAdapter.getExecuteCode(), "execute" + dataAdapter.getAdapterId(), data);
} }
@Override @Override

View File

@@ -48,11 +48,11 @@ public class EngineExecuteHandlerReader {
* @param data 数据列表 * @param data 数据列表
* @return 执行结果 * @return 执行结果
*/ */
public EngineResult execute(DataAdapter dataAdapter, Object data, boolean development) { public Object execute(DataAdapter dataAdapter, Object data) {
// 遍历引擎执行处理器列表 // 遍历引擎执行处理器列表
for (EngineExecuteHandler engineExecuteHandler : handlerList) { for (EngineExecuteHandler engineExecuteHandler : handlerList) {
// 执行数据适配器的处理方法 // 执行数据适配器的处理方法
EngineResult result = engineExecuteHandler.execute(dataAdapter, data, development); Object result = engineExecuteHandler.execute(dataAdapter, data);
if (result != null) { if (result != null) {
return result; return result;
} }
@@ -60,6 +60,22 @@ public class EngineExecuteHandlerReader {
// 若未找到匹配的数据适配器处理器则返回null // 若未找到匹配的数据适配器处理器则返回null
return null; return null;
} }
public EngineResult mockExecute(DataAdapter dataAdapter, Object data) {
// 遍历引擎执行处理器列表
for (EngineExecuteHandler engineExecuteHandler : handlerList) {
// 执行数据适配器的处理方法
EngineResult result = engineExecuteHandler.mockExecute(dataAdapter, data);
if (result != null) {
return result;
}
}
// 若未找到匹配的数据适配器处理器则返回null
return null;
}
public Boolean remove(DataAdapter dataAdapter) { public Boolean remove(DataAdapter dataAdapter) {

View File

@@ -3,12 +3,9 @@ package cn.fateverse.query.portal;
import cn.fateverse.common.core.result.Result; import cn.fateverse.common.core.result.Result;
import cn.fateverse.common.security.utils.ResponseRender; import cn.fateverse.common.security.utils.ResponseRender;
import cn.fateverse.query.constant.QueryConstant; import cn.fateverse.query.constant.QueryConstant;
import cn.fateverse.query.entity.DataAdapter;
import cn.fateverse.query.entity.Portal; import cn.fateverse.query.entity.Portal;
import cn.fateverse.query.entity.PortalMapping; import cn.fateverse.query.entity.PortalMapping;
import cn.fateverse.query.entity.bo.PortalBo; import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.entity.dto.SearchInfo;
import cn.fateverse.query.entity.dto.UniConDto;
import cn.fateverse.query.handler.reader.DataAdapterHandlerReader; import cn.fateverse.query.handler.reader.DataAdapterHandlerReader;
import cn.fateverse.query.mapper.DataAdapterMapper; import cn.fateverse.query.mapper.DataAdapterMapper;
import cn.fateverse.query.mapper.PortalMapper; import cn.fateverse.query.mapper.PortalMapper;
@@ -20,7 +17,6 @@ import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**

View File

@@ -77,7 +77,7 @@ public class PortalServiceImpl implements PortalService {
this.handlerReader = handlerReader; this.handlerReader = handlerReader;
taskExecuteExecutor.execute(() -> { taskExecuteExecutor.execute(() -> {
PortalQuery query = new PortalQuery(); PortalQuery query = new PortalQuery();
query.setState(QueryConstant.PORTAL_PUBLISH); // query.setState(QueryConstant.PORTAL_PUBLISH);
List<Portal> portalList = portalMapper.selectList(query); List<Portal> portalList = portalMapper.selectList(query);
if (ObjectUtils.isEmpty(portalList)) { if (ObjectUtils.isEmpty(portalList)) {
log.info("portal is empty!"); log.info("portal is empty!");
@@ -247,9 +247,13 @@ public class PortalServiceImpl implements PortalService {
if (!ObjectUtils.isEmpty(old)) { if (!ObjectUtils.isEmpty(old)) {
throw new CustomException("系统中存在当前请求路径"); throw new CustomException("系统中存在当前请求路径");
} }
UniQuery uniQuery = queryMapper.selectById(portal.getQueryId()); if (PortalEnum.EXTERNAL.equals(portal.getType())) {
if (ObjectUtils.isEmpty(uniQuery)) { createPortalInterface(portalDto, portal);
throw new CustomException("未找到对应的查询接口!"); }else {
UniQuery uniQuery = queryMapper.selectById(portal.getQueryId());
if (ObjectUtils.isEmpty(uniQuery)) {
throw new CustomException("未找到对应的查询接口!");
}
} }
portal.setState(QueryConstant.PORTAL_DEV); portal.setState(QueryConstant.PORTAL_DEV);
// 判断是否需要创建数据适配器 // 判断是否需要创建数据适配器
@@ -261,9 +265,6 @@ public class PortalServiceImpl implements PortalService {
throw new CustomException("未找到对应的数据适配器!"); throw new CustomException("未找到对应的数据适配器!");
} }
} }
if (PortalEnum.EXTERNAL.equals(portal.getType())) {
createPortalInterface(portalDto, portal);
}
portalMapper.insert(portal); portalMapper.insert(portal);
List<PortalMapping> mappings = portalDto.getMappings(); List<PortalMapping> mappings = portalDto.getMappings();
if (!ObjectUtils.isEmpty(mappings)) { if (!ObjectUtils.isEmpty(mappings)) {

View File

@@ -5,19 +5,20 @@
<mapper namespace="cn.fateverse.query.mapper.PortalInterfaceMapper"> <mapper namespace="cn.fateverse.query.mapper.PortalInterfaceMapper">
<select id="selectById" resultType="cn.fateverse.query.entity.PortalInterface"> <select id="selectById" resultType="cn.fateverse.query.entity.PortalInterface">
select interface_id, content_type, request_method select interface_id, url, content_type, request_method
from portal_interface from portal_interface
where interface_id = #{interfaceId} where interface_id = #{interfaceId}
</select> </select>
<insert id="insert" useGeneratedKeys="true" keyColumn="interface_id" keyProperty="interfaceId"> <insert id="insert" useGeneratedKeys="true" keyColumn="interface_id" keyProperty="interfaceId">
insert into portal_interface (interface_id, content_type, request_method) insert into portal_interface (interface_id, url, content_type, request_method)
values (#{interfaceId}, #{contentType}, #{requestMethod}) values (#{interfaceId}, #{url}, #{contentType}, #{requestMethod})
</insert> </insert>
<update id="update"> <update id="update">
update portal_interface update portal_interface
set content_type = #{contentType}, set content_type = #{contentType},
url = #{url},
request_method = #{requestMethod} request_method = #{requestMethod}
where interface_id = #{interfaceId} where interface_id = #{interfaceId}
</update> </update>

View File

@@ -1,7 +1,6 @@
package cn.fateverse.workflow.process; package cn.fateverse.workflow.process;
import cn.fateverse.common.code.engine.JavaScriptEngine; import cn.fateverse.common.code.engine.JavaScriptEngine;
import cn.fateverse.common.code.model.EngineResult;
import cn.fateverse.workflow.constant.ProcessConstant; import cn.fateverse.workflow.constant.ProcessConstant;
import cn.fateverse.workflow.entity.bpmn.*; import cn.fateverse.workflow.entity.bpmn.*;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
@@ -159,14 +158,14 @@ public class TriggerService {
} }
//获取到请求的返回结果 //获取到请求的返回结果
Map<String, Object> result = response.getBody(); Map<String, Object> result = response.getBody();
EngineResult engineResult; ScriptObjectMirror jsResult;
try { try {
//判断请求是否有效 //判断请求是否有效
if (response.getStatusCode() == HttpStatus.OK) { if (response.getStatusCode() == HttpStatus.OK) {
engineResult = JavaScriptEngine.execute(http.getSuccess(), "handlerSuccess", false, result); jsResult = (ScriptObjectMirror) JavaScriptEngine.execute(http.getSuccess(), "handlerSuccess", result);
operation.setState(OperationStateEnums.SUCCESS); operation.setState(OperationStateEnums.SUCCESS);
} else { } else {
engineResult = JavaScriptEngine.execute(http.getFail(), "handlerFail", false, result); jsResult = (ScriptObjectMirror) JavaScriptEngine.execute(http.getFail(), "handlerFail", result);
operation.setState(OperationStateEnums.FAILURE); operation.setState(OperationStateEnums.FAILURE);
} }
} catch (Exception e) { } catch (Exception e) {
@@ -178,7 +177,6 @@ public class TriggerService {
} }
return; return;
} }
ScriptObjectMirror jsResult = (ScriptObjectMirror) engineResult.getResult();
//获取到自定义脚本的状态 //获取到自定义脚本的状态
boolean state = (Boolean) jsResult.get("state"); boolean state = (Boolean) jsResult.get("state");
//获取到js脚本中的内容 //获取到js脚本中的内容