创建一个代码沙箱的实现类
JavaNativeCodeSandboxImpl
实现了CodeSandbox的接口
核心流程的事项
用程序代替人工 用程序来操作命令行 去编译执行代码
我们要用到Java进程执行管理类 Process
1.把用户的代码保存为文件
2.编译代码 得到字节码文件
3.执行代码
4.搜集整理输出结果
5.文件清理
6.错误处理 提升程序健壮性
新建目录
把用户代码保存为一个文件
我们试一试能不能在这个目录里
保存我们的代码
在项目中引用一下我们的hutool工具类
使用这个工具类我们可以直接进行文件的读写操作
因为Java原生的文件读写操作还是很复杂的
第三方工具类 可以简化第三方开发
<!--hutool 工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
我们把大体的框架搭建出来哦
public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
List<String> inputList = executeCodeRequest.getInputList();
String code = executeCodeRequest.getCode();
String language = executeCodeRequest.getLanguage();
String userDir =System.getProperty("user.dir");
String globalCodePathName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;
// 判断全局代码目录是否存在 没有则新建
if(!FileUtil.exist(globalCodePathName)){
FileUtil.mkdir(globalCodePathName);
}
// 把用户代码隔离存放
String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;
File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
return null;
}
搭建出来后 我们创建一个主类
模拟一下启动流程
public static void main(String[] args) {
JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox();
// 搭建请求体
ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
executeCodeRequest.setInputList(Arrays.asList("1 2","1 3"));
String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
executeCodeRequest.setCode(code);
executeCodeRequest.setLanguage("java");
// 把请求体反馈给接口 得到响应
ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);
// 输出查看
System.out.println(executeCodeRequest);
}
自动生成文件
这样就是成功的 代表我们已经拿到了代码
接下来我们把打印输出注释掉 我们要编译java代码了
java执行程序
java获取控制台的输出
接下来运行程序 获得状态码 根据状态码输出信息
// 编译代码 得到class文件
String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
try {
Process compileProcess= Runtime.getRuntime().exec(compileCmd);
// 等待程序执行 获取错误码
int exitValue = compileProcess.waitFor();
if (exitValue == 0){
// 正常退出
System.out.println("编译成功");
}else {
// 异常退出
System.out.println("编译失败"+ exitValue );
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
控制台输出
查看生成的文件
怎么拿到编译后 控制台输出的信息呢
会有一点麻烦 从进程中获取流 以成块分批的方式去读取
像这样只打印状态码的 我们也不知道到底内部是什么 这样就形成了一个程序黑盒...
改写代码
用stream流
// 编译代码 得到class文件
String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
try {
Process compileProcess= Runtime.getRuntime().exec(compileCmd);
// 等待程序执行 获取错误码
int exitValue = compileProcess.waitFor();
if (exitValue == 0){
// 正常退出
System.out.println("编译成功");
// 分批获取进程的正常输出
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()));
// ACM模式 逐行读取
String compileOutputLine ;
while ( (compileOutputLine = bufferedReader.readLine()) !=null){
System.out.println(compileOutputLine);
}
}else {
// 异常退出
System.out.println("编译失败 错误码: "+ exitValue );
// 分批获取进程的异常输出
BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getErrorStream()));
// ACM模式 逐行读取
String errorCompileOutputLine ;
while ( (errorCompileOutputLine = errorBufferedReader.readLine()) !=null){
System.out.println(errorCompileOutputLine);
}
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
用acm模式读取流 拼接字符串 然后输出
整体逻辑是通过exitValue判断程序是否正常返回 从inputStream和errorStream获取控制台输出
package work.bigdata1421.dduojcodesandbox;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import work.bigdata1421.dduojcodesandbox.model.ExecuteCodeRequest;
import work.bigdata1421.dduojcodesandbox.model.ExecuteCodeResponse;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class JavaNativeCodeSandbox implements CodeSandbox{
private static final String GLOBAL_CODE_DIR_NAME = "tmpCode";
private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java";
public static void main(String[] args) {
JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox();
// 搭建请求体
ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
executeCodeRequest.setInputList(Arrays.asList("1 2","1 3"));
String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
executeCodeRequest.setCode(code);
executeCodeRequest.setLanguage("java");
// 把请求体反馈给接口 得到响应
ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);
// 输出查看 是否拿到了代码
System.out.println(executeCodeRequest);
}
@Override
public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
List<String> inputList = executeCodeRequest.getInputList();
String code = executeCodeRequest.getCode();
String language = executeCodeRequest.getLanguage();
String userDir =System.getProperty("user.dir");
String globalCodePathName = userDir + File.separator + GLOBAL_CODE_DIR_NAME;
// 判断全局代码目录是否存在 没有则新建
if(!FileUtil.exist(globalCodePathName)){
FileUtil.mkdir(globalCodePathName);
}
// 把用户代码隔离存放
String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
String userCodePath = userCodeParentPath + File.separator + GLOBAL_JAVA_CLASS_NAME;
File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
// 编译代码 得到class文件
String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath());
try {
Process compileProcess= Runtime.getRuntime().exec(compileCmd);
// 等待程序执行 获取错误码
int exitValue = compileProcess.waitFor();
if (exitValue == 0){
// 正常退出
System.out.println("编译成功");
// 分批获取进程的正常输出
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getInputStream()));
StringBuilder compileOutputStringBuilder = new StringBuilder();
// ACM模式 逐行读取
String compileOutputLine ;
while ( (compileOutputLine = bufferedReader.readLine()) !=null){
compileOutputStringBuilder.append(compileOutputLine);
}
System.out.println(compileOutputStringBuilder);
}else {
// 异常退出
System.out.println("编译失败 错误码: "+ exitValue );
// 分批获取进程的异常输出
BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(compileProcess.getErrorStream()));
StringBuilder errorCompileOutputStringBuilder =new StringBuilder();
// ACM模式 逐行读取
String errorCompileOutputLine ;
while ( (errorCompileOutputLine = errorBufferedReader.readLine()) !=null){
errorCompileOutputStringBuilder.append(errorCompileOutputLine);
}
System.out.println(errorCompileOutputStringBuilder);
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
return null;
}
}