① 题目列表页 (展示当前的所有题目)
② 题目详情页 (展示当前的题目详情)
③ 题目代码编辑功能 (详情页里,能够编辑代码)
④ 题目提交功能 (详情页里,编辑完成后,可以提交代码的功能)
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello");
}
}
本项目 需要从一个文件中读取信息 也需要给文件写入信息.就需要用到文件的IO操作.
具体看博客: 文件的IO操作
import java.io.*;
public class TestIO {
public static final String srcPath = "./tmp/text1.txt";
public static final String destPath = "./tmp/text2.txt";
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream(srcPath);
FileOutputStream fileOutputStream = new FileOutputStream(destPath);
while(true){
int ch = fileInputStream.read();
if(ch == -1){
break;
}
fileOutputStream.write(ch);
}
fileInputStream.close();
fileOutputStream.close();
}
}
运行之后
当前的项目是一个在线OJ的平台, 虽然线程相比于进程更轻量, 多个线程之间共用着同一个进程的地址空间, 某个线程挂了, 就可能把整个进程也搞挂了.
如果是多进程, 某个进程挂了, 就不会影响其他的进程.
用户提交的代码, 可能出现很多问题. 所以这里要采用多进程的方法来执行.
Java 中 就可以使用Runtime.exec
方法来解决这个问题.
这个方法的参数是一个字符串, 表示一个可执行的路径. 执行这个方法就, 就会把指定的可执行程序, 创建出进程并执行.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class TestExec {
public static void main(String[] args) throws IOException {
// Runtime 在 JVM 中是一个单例
Runtime runtime = Runtime.getRuntime();
// 1. 进程的创建
Process process = runtime.exec("javac");
// 获取到子进程的标准输出和标准错误, 把这里的内容写入两个文件.
// a. 标准输出
InputStream stdoutFrom = process.getInputStream();
FileOutputStream stdoutTo = new FileOutputStream("stdout.txt");
while(true){
int ch = stdoutFrom.read();
if(ch == -1) break;
stdoutTo.write(ch);
}
stdoutFrom.close();
stdoutTo.close();
// b. 标准错误
InputStream stderrFrom = process.getErrorStream();
FileOutputStream stderrTo = new FileOutputStream("stderr.txt");
while(true) {
int ch = stderrFrom.read();
if (ch == -1) break;
stderrTo.write(ch);
}
stderrFrom.close();
stderrTo.close();
}
}
想要把用户的代码, 编译执行之后,再把响应返回给用户. 就需要把进程执行的顺序进行调整.
这段代码添加到上面代码后面就ok了
// 2. 进程等待
// 执行到这里就会阻塞等待, 直到子进程执行完毕
int exitCode = process.waitFor();
// 会输出错误码
System.out.println(exitCode);
创建一个 包compile
用来放编译功能的代码
这个类是用来对命令行进行调用的.
通过执行cmd命令. 将标准输出或标准错误写入到对应的文件.并返回状态码.
具体实现:
package compile;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class CommandUtil {
/**
* 1. 通过 Runtime 类得到 Runtime 实例, 执行 exec 方法
* 2. 获取到标准输出, 并写入到指定文件中
* 3. 获取到标准错误, 并写入到指定文件中
* 4. 等待子进程结束, 拿到子进程的状态码
* @param cmd cmd 中的命令
* @param stdoutFile 标准输出文件地址
* @param stderrFile 标准错误文件地址
* @return 返回状态码
*/
public static int run(String cmd, String stdoutFile, String stderrFile) {
try {
// 1. 通过 Runtime 类得到 Runtime 实例, 执行 exec 方法
Process process = Runtime.getRuntime().exec(cmd);
// 2. 获取到标准输出, 并写入到指定文件中
if (stdoutFile != null) {
InputStream stdoutFrom = process.getInputStream();
FileOutputStream stdoutTo = new FileOutputStream(stdoutFile);
while (true) {
int ch = stdoutFrom.read();
if (ch == -1) {
break;
}
stdoutTo.write(ch);
}
stdoutFrom.close();
stdoutTo.close();
}
// 3. 获取到标准错误, 并写入到指定文件中
if (stderrFile != null) {
InputStream stderrFrom = process.getErrorStream();
FileOutputStream stderrTo = new FileOutputStream(stderrFile);
while (true) {
int ch = stderrFrom.read();
if (ch == -1) {
break;
}
stderrTo.write(ch);
}
stderrFrom.close();
stderrTo.close();
}
// 4. 等待子进程结束, 拿到子进程的状态码
int exitCode = process.waitFor();
return exitCode;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return 1;
}
}
测试这个类:
public static void main(String[] args) {
CommandUtil.run("javac", "stdout.txt","stderr.txt");
}
这个类是放的要编译运行的代码.
/**
* 这是包含了要编译的代码
*/
public class Question {
private String code;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
这个类是放的运行后的结果.
首先有一个状态码, 表示当前的运行的状态.
有一个reason. 表示出错的信息.
有一个stdout 表示程序得到的标准输出的结果
有一个stderr, 表示待续得到的标准错误的结果
public class Answer {
// error 为状态码.
// 0 编译通过
// 1 表示编译出错
// 2 表示运行出错
// 3 表示其他错误
private int error;
// reason 为出错的提示信息.
// error=1, reason 就是错误信息
// error=2, reason 就是异常信息
private String reason;
// 运行程序得到的标准输出的结果
private String stdout;
// 运行程序得到的标准错误的结果
private String stderr;
//...一堆getter和setter 省略
}
这个类放到 common 包里, 这个类封装了对文件的读写操作
package common;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 读写文件的操作
*/
public class FileUtil {
/**
* 读文件
* @param filePath 读取的文件
* @return 返回读取的内容
*/
public static String readFile(String filePath) {
StringBuilder result = new StringBuilder();
try(FileReader fileReader = new FileReader(filePath)){
while (true) {
int ch = fileReader.read();
if (ch == -1){
break;
}
result.append((char)ch);
}
} catch (IOException e) {
e.printStackTrace();
}
return result.toString();
}
/**
* 写文件
* @param filePath 要写入的文件
* @param content 写入的内容
*/
public static void writeFile(String filePath, String content) {
try(FileWriter fileWriter = new FileWriter(filePath)){
fileWriter.write(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个类表示一次运行编译的结果
传入一个要编译的代码 question, 返回编译运行后的结果 answer
首先需要约定一系列临时文件的名字
// 约定临时文件所在的目录
private final String WORK_DIR = "./tmp/";
// 约定代码的类名
private final String CLASS = "Solution";
// 约定要编译的代码文件名
private final String CODE = WORK_DIR + "Solution.java";
// 约定存放编译错误信息的文件名
private final String COMPILE_ERROR = WORK_DIR + "compile_error.txt";
// 约定存放运行时的标准输出的文件名
private final String STDOUT = WORK_DIR + "stdout.txt";
// 约定存放运行时的标准错误的文件名
private final String STDERR = WORK_DIR + "stderr.txt";
实现 compileAndRun方法
package compile;
import common.FileUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Task 运行的结果
*/
public class Task {
// 约定临时文件所在的目录
private final String WORK_DIR = "./tmp/";
// 约定代码的类名
private final String CLASS = "Solution";
// 约定要编译的代码文件名
private final String CODE = WORK_DIR + "Solution.java";
// 约定存放编译错误信息的文件名
private final String COMPILE_ERROR = WORK_DIR + "compile_error.txt";
// 约定存放运行时的标准输出的文件名
private final String STDOUT = WORK_DIR + "stdout.txt";
// 约定存放运行时的标准错误的文件名
private final String STDERR = WORK_DIR + "stderr.txt";
/**
* 编译 + 运行
* @param question 要编译运行的 java 源代码
* @return 编译运行的结果
*/
public Answer compileAndRun(Question question) {
Answer answer = new Answer();
// 创建临时文件的目录
File workDir = new File(WORK_DIR);
if(!workDir.exists()){
System.out.println("创建成功!");
workDir.mkdirs();
}
// 1. 把 question 中的 code 写入到一个 Solution.java 文件中
FileUtil.writeFile(CODE,question.getCode());
// 2. 创建子进程, 调用 javac 进行编译. (这里需要 .java 文件)
// 如果编译出错, 放入到 compileError.txt
String compileCmd = String.format("javac -encoding utf8 %s -d %s",CODE,WORK_DIR);
// 对于 javac 进程来说, 不关心他的标准输出.
CommandUtil.run(compileCmd,null,COMPILE_ERROR);
// 读取编译错误的信息.
String compileError = FileUtil.readFile(COMPILE_ERROR);
if (!"".equals(compileError)){
// 编译错误
// 返回 Answer 让 Answer中记录编译错误的信息.
System.out.println("编译出错");
answer.setError(1);
answer.setReason(compileError);
return answer;
}
// 3. 创建子进程, 调用 java 命令并执行
// 运行程序时候, 获取 java 子进程的标准输出 和 标准错误
String runCmd = String.format("java -classpath %s %s",WORK_DIR,CLASS);
CommandUtil.run(runCmd,STDOUT,STDERR);
String runError = FileUtil.readFile(STDERR);
if (!"".equals(runError)) {
System.out.println("运行出错!");
answer.setError(2);
answer.setReason(runError);
return answer;
}
// 4. 父进程获取到刚才的编译执行的结果, 并打包成 Answer对象
answer.setError(0);
answer.setStdout(FileUtil.readFile(STDOUT));
return answer;
// 编译执行的结果, 就通过刚刚约定的这几个文件来获取即可
}
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://wangzhi430.blog.csdn.net/article/details/124920206
内容来源于网络,如有侵权,请联系作者删除!