1.项目简介
仿照leetcode来写一个在线判题系统的项目,用户可以实现通过url在浏览器访问试题列表、选中相关题目并编写代码、提交到服务器编译运行、将结果反馈给浏览器展示在页面等相关操作。
2.核心功能
- 题目列表页面,表示当前系统中的所有题目

- 题目详情页面,显示题目的具体要求和代码模板,提供一个编辑框供用户来编辑代码,提交并显示出运行结果;

- 代码的编译、运行、测试功能,能够针对用户提交的代码,进行编译运行,并自动执行测试用例,返回测试结果;
3.开发环境
- maven :使用Maven来管理依赖,打包项目;
- MySQL:使用MySQL数据库作为业务数据的存储;
- Servlet:每个页面调用后台接口都需要使用哪些Servlet来完成业务;
- IDEA:进行Java代码的编写
- Linux:进行环境的部署
4.项目使用技术
- JDBC:用来实现数据库的操作;
- Runtime多进程:通过代码控制jdk进行编译和运行;
- Java:利用了Java的基础语法,继承、单例模式、异常等;
- Gson:用来在Java对象和JSON数据之间的转换;
5.项目模块介绍
5.1 编译模块
目标:给定以Java代码文件(这里只考虑单个文件的情况,不考虑多文件复杂工程的情况),能够通过代码并并控制jdk进行编译和运行
第一步:借助一个类:CommandUtil,让Java代码能够去执行一个具体的指令
Runtime类和Process类:这里我们需要在当前程序中调用另一个现成的系统命令,这样就利用利用Runtime类和Process类,可以创建一个子进程,并且让子进程来执行一个相关的命令(编译:javac,运行:java)
//1.获取Runtime对象,Runtime对象是一个单例的
Runtime runtime=Runtime.getRuntime();
//2.通过Runtime对象中的exec方法来执行一个指令
//相当于在命令行中输入cmd命令并执行
Process process=runtime.exec(cmd);重定向:使用重定向,是为了把进程输出的内容写到指定的文件中去;
实现重定向,需要先获取到创建出的新的进程对象:process;然后我们只对标准重定向,不一定能看到最终结果,一个命令的输出内容也有可能是通过标准错误来打印的;所以,我们定义两个文件标准输出、标准错误;
//3.针对标准输出进行重定向
if(stdoutFile!=null){
//进程的标准输出中的结果就可以通过这个InputStream获取到
InputStream stdoutFrom=process.getInputStream();
OutputStream stdoutTo=new FileOutputStream(stdoutFile);
int ch=-1;
while((ch=stdoutFrom.read())!= -1){
stdoutTo.write(ch);
}
stdoutFrom.close();
stdoutTo.close();
}
//4.针对标准错误也进行重定向
if(stderrFile!=null){
InputStream stderrFrom =process.getErrorStream();
OutputStream stderrTo=new FileOutputStream(stderrFile);
int ch=-1;
while((ch=stderrFrom.read())!=-1){
stderrTo.write(ch);
}
stderrFrom.close();
stderrTo.close();
}进程等待:多进程和多线程一样,都是并发式执行的,为了能够确保子进程先执行完,就先让父进程进行等待;
//5.为了确保子进程先执行完,需要加上进程等待
// 父进程会在waitfor阻塞等待,直到子进程执行结束,再继续往下执行
int exitCode= process.waitFor();
return exitCode;第二步:借助上面的类,把整个Java程序的编译和运行过程组合在一起;
- Question类:描述了一次编译运行过程中都依赖哪些数据;
- Answer类:描述了一次编译运行过程中都产生了哪些数据;
- FileUtil:一个读写文件操作的类,让读写文件更加的方便;
- Task类:借助这个类来描述一次编译运行的过程,定义了一些临时文件的名称,这些临时文件记录了编译运行过程中的中间结果,为了方便调试;
得到的临时文件的目录:

在Task类里面定义方法compileAndRun来描述编译运行的过程;
主要过程为:
- 1.创建存放临时文件的目录
- 2.根据Question对象,构建临时文件
- 3.构建编译命令:形如javac -encoding utf8 ./tmp/Solution.java -d ./tmp/(-d表示在编译命令中,生成的.class文件在哪个目录中)
- 判断编译是否出错,如果出错,不需要再去执行了,即判断编译错误对应的文件是否为空;
- 4.构造运行命令并执行:形如java -classpath ./tmp/ Solution(-classpath来指定加载路径)
- 判断运行是否出错(是否存在异常),如果不错,不需要去执行,即判断标准错误对应的文件是否为空;
- 5.将最终的运行结果包装到Answer中返回;
5.2 题目管理模块
第一步:数据库设计
这个操作主要在Linux上进行,提前要在Linux上安装好JDK、MySQL、tomcat、Maven
设计的数据库为:

在Linux上访问利用的方法是把代码打包放到云服务器上进行执行;
进行数据库的操作:
进行数据库的连接操作时,采用的是DataSource数据库连接池,这里,为了提高效率,采用的是线程安全版本的单例模式,核心操作为:synchronized加锁、双重if判断、volatile关键字:
private static volatile DataSource dataSource = null;
public static DataSource getDataSource(){
if(dataSource == null){
synchronized (DBUtil.class) {
if(dataSource == null){
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL(URL);
((MysqlDataSource)dataSource).setUser(USERNAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
}
}
return dataSource;
}此项目需要数据库的操作还有:获取所有题目信息、获取指定id的题目、新增一个题目到数据库、删除指定信息;
第二步:设计前后端交互的API
设计过程是和需求有关的,需求如下:
- 1.需要有一个主页,包含了OJ题目列表,所以要有一个API能查到所有的题目信息(可能需要考虑分页,只需要知道id,标题,难度)
- 2.点击主页中的某个题目列表,会进入到详情页,详情页中包含了题目的进一步信息,所以要有一个API能查到题目的详细信息(标题,id,难度,描述,代码模板)
- 3.点击提交按钮,会把当前编辑框中的代码提交到服务器上并编译运行,所以要有一个API能提供编译运行功能;
Gson介绍:
- Gson是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库。可以将一个Json字符转成一个Java对象,或者将一个Java转换为Json字符串;
- 特点:快速、高效;代码量少、简洁;面向对象;数据传递和解析方面;
- Gson的pom依赖:
<!--JSON库,gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>- 创建方式:
// 方式1
Gson gson = new gson();
// 方式2
Gson gson = new GsonBuilder().create();
- 使用方法:
// 将Java对象转换为JSON字符串(problems是创建的对象)
String jsonString = gson.toJson(problems);
// 将JSON字符串转换为Java对象(body为请求到的前端数据,将他转换为CompileRequest对象)
CompileRequest compileRequest = gson.fromJson(body,CompileRequest.class);
ProblemServlet类作用:
1.利用get获取到所有的题目列表:获取到题目列表只需要知道题目的id,title,level;那么可以通过ProblemDAO类里面的selectAll来获取数据库里面的类信息,最后将它转换为Json字符串,最后将他返回到前端;
2.利用get获取到题目的详细信息:获取到题目的详细信息要根据具体的题目id来确定,方法和上一个类似;
CompileServlet类作用:把编译框中的代码进行编译运行并给出结果
1.创建两个类,用来完成请求解析和相应的构建
2.在doPost()方法中处理过程:
步骤为:先读取请求中的所有数据——>解析json数据,得到CompileRequest对象——>根据CompileRequest对象得到id,按照id从数据库中读取到对应的测试用例代码,再根据CompileRequest对象得到用户输入的代码——>把用户输入的代码和测试用例进行组装,成一个完整的可执行编译的代码——>创建Task对象对刚才的代码进行编译运行——>把运行结果构造成响应数据,并写会给客户端;
5.3 前端代码逻辑
需要写一些网页,通过这些网页把后端的逻辑串到一起,组织成一个功能相对完整的程序;
系统主要有两个页面:
- 题目列表页:主要有一个表格
- 题目详情页:主要能显示题目的详细信息,以及需要提供一个代码编辑框
借助JQuery这个库,完成从网页访问访问服务器的操作:
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>借助Vue这个框架,实现把数据渲染到页面上:
<!--Vue的引入-->
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>实现题目列表页和题目详情页和数据之间的关联,同时实现两个页面之间的切换;
6.项目的缺点
题目的录入是要每次在后台录入,不能在线录入,比较麻烦;
没有用户管理,不能清楚的统计每个人做的题;
7.项目源码
https://github.com/yaoguo00/java-oj