feat: java执行引擎和js执行引擎调试完成
This commit is contained in:
@@ -21,6 +21,16 @@
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.graalvm.js</groupId>
|
||||
<artifactId>js-scriptengine</artifactId>
|
||||
<version>22.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.graalvm.js</groupId>
|
||||
<artifactId>js</artifactId>
|
||||
<version>22.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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 删除结果
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -12,4 +12,23 @@ import java.lang.annotation.Target;
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Encrypt {
|
||||
|
||||
Position value() default Position.ALL;
|
||||
|
||||
|
||||
EncryptType type() default EncryptType.SM4;
|
||||
|
||||
|
||||
enum EncryptType {
|
||||
|
||||
SM4,
|
||||
|
||||
}
|
||||
|
||||
enum Position {
|
||||
ALL,
|
||||
OUT,
|
||||
IN
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,4 +11,12 @@ import java.lang.annotation.Target;
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EncryptField {
|
||||
|
||||
Position value() default Position.ALL;
|
||||
|
||||
enum Position {
|
||||
ALL,
|
||||
OUT,
|
||||
IN
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package cn.fateverse.common.decrypt.aspect;
|
||||
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
import cn.fateverse.common.core.result.Result;
|
||||
import cn.fateverse.common.decrypt.annotation.Encrypt;
|
||||
import cn.fateverse.common.decrypt.annotation.EncryptField;
|
||||
import cn.fateverse.common.decrypt.service.EncryptService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -11,12 +12,10 @@ import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@@ -41,36 +40,35 @@ public class EncryptAspect {
|
||||
@Around("@annotation(cn.fateverse.common.decrypt.annotation.Encrypt)")
|
||||
public Object decryptField(ProceedingJoinPoint point) throws Throwable {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
//获取请求参数
|
||||
Object[] args = point.getArgs();
|
||||
//获取方法
|
||||
Method method = signature.getMethod();
|
||||
//获取方法参数 Parameter对象集 参数修饰符、参数名、注解及注解类型
|
||||
Parameter[] parameters = method.getParameters();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
//获取参数注解
|
||||
EncryptField encryptField = parameter.getAnnotation(EncryptField.class);
|
||||
Object arg = args[i];
|
||||
if (null != encryptField) {
|
||||
if (arg instanceof String) {
|
||||
String decrypt = encryptService.decrypt((String) arg);
|
||||
args[i] = decrypt;
|
||||
} else if (arg instanceof List) {
|
||||
try {
|
||||
List<String> list = (List<String>) arg;
|
||||
list.replaceAll(encryptService::decrypt);
|
||||
args[i] = list;
|
||||
} catch (Exception e) {
|
||||
throw new CustomException("接受参数类型错误,请使用String类型的泛型参数");
|
||||
}
|
||||
}
|
||||
} else if (parameter.getType().getName().startsWith(BASE_PACKAGE)) { //返回一个类对象,该类对象标识此参数对象表示的参数的声明类型
|
||||
decrypt(arg);
|
||||
}
|
||||
Encrypt encrypt = method.getAnnotation(Encrypt.class);
|
||||
if (encrypt == null) {
|
||||
return point.proceed();
|
||||
}
|
||||
//获取请求参数
|
||||
Object[] args = point.getArgs();
|
||||
if (Encrypt.Position.ALL.equals(encrypt.value()) || Encrypt.Position.IN.equals(encrypt.value())) {
|
||||
decryptParams(args, method);
|
||||
}
|
||||
//正常执行业务,最后返回的返回值为Result
|
||||
Object proceed = point.proceed(args);
|
||||
if (Encrypt.Position.ALL.equals(encrypt.value()) || Encrypt.Position.OUT.equals(encrypt.value())) {
|
||||
Result<Object> error = encryptResult(proceed);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return proceed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密返回值
|
||||
*
|
||||
* @param proceed 返回执
|
||||
* @return 加密结果
|
||||
*/
|
||||
private Result<Object> encryptResult(Object proceed) {
|
||||
if (proceed instanceof Result) {
|
||||
Result<Object> result = (Result<Object>) proceed;
|
||||
Object data = result.getData();
|
||||
@@ -91,29 +89,71 @@ public class EncryptAspect {
|
||||
return Result.error("加密异常!");
|
||||
}
|
||||
}
|
||||
return proceed;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密参数
|
||||
*
|
||||
* @param args 参数
|
||||
* @param method 方法
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
private void decryptParams(Object[] args, Method method) throws Exception {
|
||||
//获取方法参数 Parameter对象集 参数修饰符、参数名、注解及注解类型
|
||||
Parameter[] parameters = method.getParameters();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
//获取参数注解
|
||||
EncryptField encryptField = parameter.getAnnotation(EncryptField.class);
|
||||
Object arg = args[i];
|
||||
if (null != encryptField) {
|
||||
if (arg instanceof String) {
|
||||
String decrypt = encryptService.decrypt((String) arg);
|
||||
args[i] = decrypt;
|
||||
} else if (arg instanceof List) {
|
||||
try {
|
||||
List<String> list = (List<String>) arg;
|
||||
list.replaceAll(encryptService::decrypt);
|
||||
args[i] = list;
|
||||
} catch (Exception e) {
|
||||
throw new CustomException("接受参数类型错误,请使用String类型的泛型参数");
|
||||
}
|
||||
}
|
||||
} else if (parameter.getType().getName().startsWith(BASE_PACKAGE)) {
|
||||
//返回一个类对象,该类对象标识此参数对象表示的参数的声明类型
|
||||
decrypt(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void encrypt(Object data) throws Exception {
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param data 数据
|
||||
*/
|
||||
private void encrypt(Object data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
Class<?> argClass = data.getClass();
|
||||
List<Field> fieldList = new ArrayList<>();
|
||||
if (argClass.getTypeName().startsWith(BASE_PACKAGE)) {
|
||||
Field[] fields = argClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
getFields(argClass, fieldList);
|
||||
for (Field field : fieldList) {
|
||||
EncryptField encryptField = field.getAnnotation(EncryptField.class);
|
||||
field.setAccessible(true);
|
||||
Object value = ReflectionUtils.getField(field, data);
|
||||
if (null == value) {
|
||||
continue;
|
||||
}
|
||||
if (null != encryptField && value instanceof String) {
|
||||
if (null != encryptField && value instanceof String
|
||||
&& (EncryptField.Position.ALL.equals(encryptField.value())
|
||||
|| EncryptField.Position.IN.equals(encryptField.value()))) {
|
||||
String decrypt = encryptService.encrypt((String) value);
|
||||
ReflectionUtils.setField(field, data, decrypt);
|
||||
} else if (field.getType().getName().startsWith(BASE_PACKAGE)) {
|
||||
if (!value.getClass().isEnum()){
|
||||
if (!value.getClass().isEnum()) {
|
||||
encrypt(value);
|
||||
}
|
||||
} else if (value instanceof Collection) {
|
||||
@@ -128,29 +168,49 @@ public class EncryptAspect {
|
||||
for (Object item : collection) {
|
||||
encrypt(item);
|
||||
}
|
||||
} else if (data instanceof Map) {
|
||||
Map<Object, Object> map = (Map<Object, Object>) data;
|
||||
for (Object key : map.keySet()) {
|
||||
Object value = map.get(key);
|
||||
encrypt(key);
|
||||
encrypt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void decrypt(Object arg) throws Exception {
|
||||
Class<?> argClass = arg.getClass();
|
||||
Field[] fields = argClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
List<Field> fieldList = new ArrayList<>();
|
||||
getFields(argClass, fieldList);
|
||||
for (Field field : fieldList) {
|
||||
EncryptField encryptField = field.getAnnotation(EncryptField.class);
|
||||
field.setAccessible(true);
|
||||
Object value = ReflectionUtils.getField(field, arg);
|
||||
if (null == value) {
|
||||
continue;
|
||||
}
|
||||
if (null != encryptField && value instanceof String) {
|
||||
if (null != encryptField && value instanceof String
|
||||
&& (EncryptField.Position.ALL.equals(encryptField.value())
|
||||
|| EncryptField.Position.OUT.equals(encryptField.value()))) {
|
||||
String decrypt = encryptService.decrypt((String) value);
|
||||
ReflectionUtils.setField(field, arg, decrypt);
|
||||
} else if (field.getType().getName().startsWith(BASE_PACKAGE)) {
|
||||
if (!value.getClass().isEnum()){
|
||||
if (!value.getClass().isEnum()) {
|
||||
decrypt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void getFields(Class<?> argClass, List<Field> fieldList) {
|
||||
if (argClass.getTypeName().startsWith(BASE_PACKAGE)) {
|
||||
Field[] fields = argClass.getDeclaredFields();
|
||||
fieldList.addAll(Arrays.asList(fields));
|
||||
getFields(argClass.getSuperclass(), fieldList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user