feat: java执行引擎和js执行引擎调试完成

This commit is contained in:
clay
2024-04-23 11:32:55 +08:00
parent f29e4fca14
commit 450ed63e60
50 changed files with 1353 additions and 235 deletions

View File

@@ -0,0 +1,45 @@
package cn.fateverse.common.code.console;
import cn.fateverse.common.code.model.EngineResult;
import cn.fateverse.common.core.exception.CustomException;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.*;
/**
* @author Clay
* @date 2024/4/22 17:08
*/
public class MultiThreadedCapture {
private final static ExecutorService executor = Executors.newFixedThreadPool(2);
public static EngineResult capture(Task task) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream oldOut = System.out;
System.setOut(new PrintStream(baos));
Object result;
String capturedOutput;
try {
result = task.execute();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
System.setOut(oldOut);
// 从捕获的字节数组输出流中获取打印的文本
capturedOutput = baos.toString();
}
return new EngineResult(result, capturedOutput);
}
public interface Task {
Object execute() throws Exception;
}
}

View File

@@ -2,18 +2,21 @@ package cn.fateverse.common.code.engine;
import cn.fateverse.common.code.config.JavaCodeProperties;
import cn.fateverse.common.code.console.MultiThreadedCapture;
import cn.fateverse.common.code.exception.SandboxClassNotFoundException;
import cn.fateverse.common.code.lock.SegmentLock;
import cn.fateverse.common.code.model.EngineResult;
import cn.fateverse.common.code.sandbox.SandboxClassLoader;
import cn.fateverse.common.code.sandbox.SandboxSecurityManager;
import cn.fateverse.common.core.exception.CustomException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.util.ObjectUtils;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
@@ -73,7 +76,7 @@ public class JavaCodeEngine {
* @param development 是否为开发环境 开发环境下会将生成的类在执行完成后删除,不是生产环境则会缓存提高运行效率
* @return 执行结果
*/
public <T> T execute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args, boolean development) {
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 {
@@ -90,11 +93,10 @@ public class JavaCodeEngine {
* @param methodName 方法名
* @param paramClass 参数类型数组
* @param args 参数数组
* @param <T> 接收泛型
* @return 执行结构
*/
@SneakyThrows
private <T> T developmentExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
private EngineResult developmentExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
Class<?> loadClass = null;
try {
// 加锁,确保类只加载一次
@@ -123,7 +125,8 @@ public class JavaCodeEngine {
// 设置安全检查器
System.setSecurityManager(securityManager);
// 执行方法并返回结果
return (T) method.invoke(null, args);
return MultiThreadedCapture.capture(() -> method.invoke(null, args));
} finally {
// 从缓存中移除编译好的类
classCache.remove(className);
@@ -155,10 +158,9 @@ public class JavaCodeEngine {
* @param methodName 方法名
* @param paramClass 参数类型数组
* @param args 参数数组
* @param <T> 接收泛型
* @return 执行结构
*/
private <T> T onlineExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
private EngineResult onlineExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
try {
Class<?> loadClass = null;
loadClass = classCache.get(className);
@@ -166,7 +168,8 @@ public class JavaCodeEngine {
loadClass = getLoadClass(code, className);
Method method = loadClass.getMethod(methodName, paramClass);
System.setSecurityManager(securityManager);
return (T) method.invoke(null, args);
Object result = (Object) method.invoke(null, args);
return new EngineResult(result);
}
} catch (Exception e) {
e.printStackTrace();
@@ -215,22 +218,31 @@ public class JavaCodeEngine {
* @return 编译完成的类对象
*/
private Class<?> compilerClass(String className, String code, URLClassLoader classLoader) {
log.info(code);
File tempFile = new File(CLASS_PATH + className + JAVA_SUFFIX);
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write(code);
writer.close();
ByteArrayOutputStream errorStream = new ByteArrayOutputStream(10240);
// 编译.java文件
compiler.run(null, null, null, tempFile.getPath());
compiler.run(null, null, errorStream, tempFile.getPath());
String trace = errorStream.toString();//存放控制台输出的字符串
if (!ObjectUtils.isEmpty(trace)) {
trace = trace.replace(CLASS_PATH + className + ".", "");
throw new CustomException("编译错误: " + trace);
}
return classLoader.loadClass(className);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
if (e instanceof CustomException) {
throw (CustomException) e;
}
throw new CustomException("执行或者编辑错误!");
}
}
/**
* 删除类
*
* @param className 删除类
* @return 删除结果
*/

View File

@@ -1,9 +1,17 @@
package cn.fateverse.common.code.engine;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import cn.fateverse.common.code.console.MultiThreadedCapture;
import cn.fateverse.common.code.lock.SegmentLock;
import cn.fateverse.common.code.model.EngineResult;
import cn.fateverse.common.core.exception.CustomException;
import com.alibaba.fastjson2.JSON;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.springframework.security.core.parameters.P;
import javax.script.*;
import java.util.HashMap;
import java.util.Map;
/**
* js 工具类
@@ -13,25 +21,60 @@ import javax.script.ScriptException;
*/
public class JavaScriptEngine {
// 创建 GraalVM 上下文
private static final Context context = Context.newBuilder()
.allowAllAccess(true)
// .allowHostClassLoading(true)
// .allowIO(true)
// .allowNativeAccess(true)
.build();
private static final Map<String, Value> functionMap = new HashMap<>();
/**
* 执行js代码
* @param script js脚本
* @param function js函数名
* @param args 参数
*
* @param script js脚本
* @param functionName js函数名
* @param args 参数
* @return 返回结构
* @param <T> 泛型类型
*/
public static <T> T executeScript(String script, String function, Object... args) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
engine.eval(script);
Invocable inv = (Invocable) engine;
return (T) inv.invokeFunction(function, args);
} catch (ScriptException | NoSuchMethodException e) {
throw new RuntimeException(e);
public static EngineResult execute(String script, String functionName, boolean development, Object args) {
if (development) {
return MultiThreadedCapture.capture(() -> {
Context context = Context.newBuilder()
.allowAllAccess(true)
.allowHostClassLoading(true)
.allowIO(true)
.allowNativeAccess(true).build();
context.eval("js", script);
Value executeFunction = context.getBindings("js").getMember(functionName);
Value javaObjectAsValue = Value.asValue(args);
Value result = executeFunction.execute(javaObjectAsValue);
return result.as(Object.class);
});
} else {
Value executeFunction = getFunction(functionName, script);
Value result = executeFunction.execute(JSON.toJSONString(args));
return new EngineResult(result.as(Object.class));
}
}
private static Value getFunction(String functionName, String script) {
return SegmentLock.lock(functionName, () -> {
if (functionMap.containsKey(functionName)) {
return functionMap.get(functionName);
}
context.eval("js", script);
Value executeFunction = context.getBindings("js").getMember(functionName);
functionMap.put(functionName, executeFunction);
return executeFunction;
});
}
}

View File

@@ -0,0 +1,25 @@
package cn.fateverse.common.code.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Clay
* @date 2024/4/22 17:10
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EngineResult {
private Object result;
private String console;
public EngineResult(Object result) {
this.result = result;
}
}