init
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package cn.fateverse.common.code;
|
||||
|
||||
import cn.fateverse.common.code.config.JavaCodeProperties;
|
||||
import cn.fateverse.common.code.engine.JavaCodeEngine;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-30 23:09
|
||||
*/
|
||||
@EnableConfigurationProperties({JavaCodeProperties.class})
|
||||
public class JavaCodeAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public JavaCodeEngine javaCodeEngine(JavaCodeProperties javaCodeProperties){
|
||||
return new JavaCodeEngine(javaCodeProperties);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.fateverse.common.code.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-30 23:09
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "code.java")
|
||||
public class JavaCodeProperties {
|
||||
|
||||
private String classPath;
|
||||
|
||||
public String getClassPath() {
|
||||
return classPath;
|
||||
}
|
||||
|
||||
public void setClassPath(String classPath) {
|
||||
this.classPath = classPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package cn.fateverse.common.code.engine;
|
||||
|
||||
|
||||
import cn.fateverse.common.code.config.JavaCodeProperties;
|
||||
import cn.fateverse.common.code.exception.SandboxClassNotFoundException;
|
||||
import cn.fateverse.common.code.lock.SegmentLock;
|
||||
import cn.fateverse.common.code.sandbox.SandboxClassLoader;
|
||||
import cn.fateverse.common.code.sandbox.SandboxSecurityManager;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.ToolProvider;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-24
|
||||
*/
|
||||
@Slf4j
|
||||
public class JavaCodeEngine {
|
||||
|
||||
private final String JAVA_SUFFIX = ".java";
|
||||
|
||||
private final String CLASS_SUFFIX = ".class";
|
||||
|
||||
private final String CLASS_PATH;
|
||||
|
||||
private final URL url;
|
||||
|
||||
private final URLClassLoader classLoader;
|
||||
|
||||
private final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
private final SandboxSecurityManager securityManager = new SandboxSecurityManager(classCache);
|
||||
|
||||
// 获取Java编译器
|
||||
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
|
||||
public JavaCodeEngine(JavaCodeProperties javaCodeProperties) {
|
||||
try {
|
||||
CLASS_PATH = javaCodeProperties.getClassPath();
|
||||
File file = new File(CLASS_PATH);
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
url = file.toURI().toURL();
|
||||
classLoader = new SandboxClassLoader(new URL[]{url});
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行方法
|
||||
*
|
||||
* @param code 代码字符串
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param paramClass 参数类型数组
|
||||
* @param args 参数数组
|
||||
* @param development 是否为开发环境 开发环境下会将生成的类在执行完成后删除,不是生产环境则会缓存提高运行效率
|
||||
* @return 执行结果
|
||||
*/
|
||||
public <T> T 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 className 类名
|
||||
* @param methodName 方法名
|
||||
* @param paramClass 参数类型数组
|
||||
* @param args 参数数组
|
||||
* @param <T> 接收泛型
|
||||
* @return 执行结构
|
||||
*/
|
||||
@SneakyThrows
|
||||
private <T> T developmentExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
|
||||
Class<?> loadClass = null;
|
||||
try {
|
||||
// 加锁,确保类只加载一次
|
||||
loadClass = SegmentLock.lock(className, () -> {
|
||||
URLClassLoader tempClassLoader = null;
|
||||
try {
|
||||
// 创建一个URLClassLoader,用于加载代码字符串
|
||||
tempClassLoader = new URLClassLoader(new URL[]{url});
|
||||
// 编译代码字符串为类
|
||||
Class<?> tempClass = compilerClass(className, code, tempClassLoader);
|
||||
// 将编译好的类放入缓存
|
||||
classCache.put(className, tempClass);
|
||||
return tempClass;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// 异常处理,并抛出自定义的SandboxClassNotFoundException异常
|
||||
throw new SandboxClassNotFoundException(e.getMessage());
|
||||
} finally {
|
||||
if (tempClassLoader != null) {
|
||||
tempClassLoader = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 获取需要执行的方法
|
||||
Method method = loadClass.getMethod(methodName, paramClass);
|
||||
// 设置安全检查器
|
||||
System.setSecurityManager(securityManager);
|
||||
// 执行方法并返回结果
|
||||
return (T) method.invoke(null, args);
|
||||
} finally {
|
||||
// 从缓存中移除编译好的类
|
||||
classCache.remove(className);
|
||||
// 清空安全检查器
|
||||
System.setSecurityManager(null);
|
||||
if (loadClass != null) {
|
||||
loadClass = null;
|
||||
}
|
||||
// 删除生成的java文件
|
||||
File javaFile = new File(CLASS_PATH + className + JAVA_SUFFIX);
|
||||
if (javaFile.exists()) {
|
||||
javaFile.delete();
|
||||
}
|
||||
// 删除生成的class文件
|
||||
File classFile = new File(CLASS_PATH + className + CLASS_SUFFIX);
|
||||
if (classFile.exists()) {
|
||||
classFile.delete();
|
||||
}
|
||||
// 执行垃圾回收
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 线上环境执行
|
||||
*
|
||||
* @param code 需要执行的代码字符串
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param paramClass 参数类型数组
|
||||
* @param args 参数数组
|
||||
* @param <T> 接收泛型
|
||||
* @return 执行结构
|
||||
*/
|
||||
private <T> T onlineExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
|
||||
try {
|
||||
Class<?> loadClass = null;
|
||||
loadClass = classCache.get(className);
|
||||
if (loadClass == null) {
|
||||
loadClass = getLoadClass(code, className);
|
||||
Method method = loadClass.getMethod(methodName, paramClass);
|
||||
System.setSecurityManager(securityManager);
|
||||
return (T) method.invoke(null, args);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到编译完成的Class对象
|
||||
*
|
||||
* @param code 需要编译的代码
|
||||
* @param className 类名
|
||||
* @return 编译后的Java对象
|
||||
*/
|
||||
private Class<?> getLoadClass(String code, String className) {
|
||||
//使用分段锁,提高效率,放多并发情况下多次对同一个类进行编译
|
||||
return SegmentLock.lock(className, () -> {
|
||||
try {
|
||||
//执行编译
|
||||
Class<?> tempClass = compilerClass(className, code, classLoader);
|
||||
//将编译之后的类对象放在缓存中,提高线上环境的运行效率
|
||||
classCache.put(className, tempClass);
|
||||
return tempClass;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译Java代码
|
||||
*
|
||||
* @param className 类名
|
||||
* @param code Java代码
|
||||
* @param classLoader 类加载器
|
||||
* @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();
|
||||
// 编译.java文件
|
||||
compiler.run(null, null, null, tempFile.getPath());
|
||||
return classLoader.loadClass(className);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.fateverse.common.code.engine;
|
||||
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
/**
|
||||
* js 工具类
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-03-24
|
||||
*/
|
||||
public class JavaScriptEngine {
|
||||
|
||||
/**
|
||||
* 执行js代码
|
||||
* @param script js脚本
|
||||
* @param function 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.fateverse.common.code.exception;
|
||||
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-25
|
||||
*/
|
||||
public class SandboxClassNotFoundException extends CustomException {
|
||||
|
||||
public SandboxClassNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.fateverse.common.code.lock;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-25
|
||||
*/
|
||||
public class SegmentLock {
|
||||
|
||||
private static final Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 分段锁
|
||||
* @param key 锁名称
|
||||
* @param supplier 需要执行的函数
|
||||
* @return 执行后的结果
|
||||
* @param <T> 接收泛型
|
||||
*/
|
||||
public static <T> T lock(String key, Supplier<T> supplier) {
|
||||
ReentrantLock lock = lockMap.get(key);
|
||||
if (lock == null) {
|
||||
lock = lockMap.get(key);
|
||||
if (lock == null) {
|
||||
synchronized (lockMap) {
|
||||
lock = new ReentrantLock();
|
||||
lockMap.put(key, lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
lock.lock();
|
||||
try {
|
||||
return supplier.get();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.fateverse.common.code.sandbox;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
public class SandboxClassLoader extends URLClassLoader {
|
||||
|
||||
|
||||
public SandboxClassLoader(final URL[] urls) {
|
||||
super(urls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package cn.fateverse.common.code.sandbox;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.lang.reflect.ReflectPermission;
|
||||
import java.security.Permission;
|
||||
import java.util.Map;
|
||||
import java.util.PropertyPermission;
|
||||
import java.util.Set;
|
||||
|
||||
public class SandboxSecurityManager extends SecurityManager {
|
||||
|
||||
private final Map<String, Class<?>> classCache;
|
||||
|
||||
public SandboxSecurityManager(Map<String, Class<?>> classCache) {
|
||||
this.classCache = classCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkPermission(Permission perm) {
|
||||
if (isSandboxCode(perm)) {
|
||||
if (!isAllowedPermission(perm)) {
|
||||
throw new SecurityException("Permission denied " + perm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isSandboxCode(Permission perm) {
|
||||
Set<String> classKeySet = classCache.keySet();
|
||||
for (String key : classKeySet) {
|
||||
if (perm.getName().contains(key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private boolean isAllowedPermission(Permission permission) {
|
||||
//权限:用于校验文件系统访问权限,包括读取、写入、删除文件,以及目录操作。权限名称可能包括文件路径和操作,如 "read", "write", "delete", "execute" 等。
|
||||
if (permission instanceof FilePermission){
|
||||
System.out.println("触发文件读写权限");
|
||||
return false;
|
||||
}
|
||||
//权限:用于校验运行时权限,如程序启动、关闭虚拟机等。您可以根据名称进行控制,如 "exitVM"、"setSecurityManager" 等。
|
||||
if (permission instanceof RuntimePermission) {
|
||||
System.out.println("用于校验运行时权限");
|
||||
return false;
|
||||
}
|
||||
//权限:用于校验Java反射操作的权限,如 `suppressAccessChecks`、`newProxyInPackage` 等。
|
||||
if (permission instanceof ReflectPermission) {
|
||||
System.out.println("用于校验Java反射操作的权限");
|
||||
return false;
|
||||
}
|
||||
//权限:用于校验系统属性的权限,包括读取和设置系统属性。权限名称通常以属性名称和操作(如 "read" 或 "write")表示。
|
||||
if (permission instanceof PropertyPermission) {
|
||||
System.out.println("用于校验系统属性的权限");
|
||||
return false;
|
||||
}
|
||||
// 权限:用于校验数据库访问权限,包括连接数据库、执行SQL语句等。权限名称通常与数据库URL和操作相关。
|
||||
// if (permission instanceof SQLPermission) {
|
||||
// return false;
|
||||
//
|
||||
// }
|
||||
//权限:用于校验网络套接字的权限,包括连接到特定主机和端口。权限名称通常以主机名和端口号的形式表示,如 "www.example.com:80".
|
||||
// if (permission instanceof SocketPermission) {
|
||||
// return false;
|
||||
//
|
||||
// }
|
||||
//权限:用于校验安全管理器操作的权限,如 `createAccessControlContext`、`setPolicy` 等。
|
||||
// if (permission instanceof SecurityPermission) {
|
||||
// return false;
|
||||
//
|
||||
// }
|
||||
// //序列化
|
||||
// if (permission instanceof SerializablePermission) {
|
||||
// return false;
|
||||
// }
|
||||
// System.out.println(permission);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.fateverse.common.code.JavaCodeAutoConfiguration
|
||||
Reference in New Issue
Block a user