【项目】在线OJ项目总结

1.项目简介

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

2.核心功能

  • 题目列表页面,表示当前系统中的所有题目

【项目】在线OJ项目总结_第1张图片

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

【项目】在线OJ项目总结_第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类:借助这个类来描述一次编译运行的过程,定义了一些临时文件的名称,这些临时文件记录了编译运行过程中的中间结果,为了方便调试;

得到的临时文件的目录:

【项目】在线OJ项目总结_第3张图片

在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

设计的数据库为:

【项目】在线OJ项目总结_第4张图片

在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依赖:


    com.google.code.gson
    gson
    2.8.2
  • 创建方式:
// 方式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 前端代码逻辑

需要写一些网页,通过这些网页把后端的逻辑串到一起,组织成一个功能相对完整的程序;

系统主要有两个页面:

  1. 题目列表页:主要有一个表格
  2. 题目详情页:主要能显示题目的详细信息,以及需要提供一个代码编辑框

借助JQuery这个库,完成从网页访问访问服务器的操作:

借助Vue这个框架,实现把数据渲染到页面上:



实现题目列表页和题目详情页和数据之间的关联,同时实现两个页面之间的切换;

6.项目的缺点

题目的录入是要每次在后台录入,不能在线录入,比较麻烦;

没有用户管理,不能清楚的统计每个人做的题;

7.项目源码

https://github.com/yaoguo00/java-oj

 

你可能感兴趣的:(Java)