在线OJ项目介绍

一.效果展示

                                                              题目列表页

                                                            题目详情页

 二.项目的组成

此在线OJ项目由两个页面组成,主要实现:

题目的列表展示页(题目ID,题目标题,题目描述)

题目详情页展示(题目ID,题目标题,题目描述,题目编辑框,题目提交,编辑运行结果)

三.项目准备

1.创建项目

创建一个Maven项目,并引入相关依赖(servlet ,mysql)

<!-- 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>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.13</version>
</dependency>

在main创建目录webapp,在webapp创建目录WeB-INF,在WEB-INF创建文件web.xml

编写web.xml代码

<!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>

2.操作文件的读写,通过IO流,相关类实现

InputStream 只是一个抽象类,要使用还需要具体的实现类。

字节流(以字节为基本单位,适用于二进制文件)

FileinputStream   读数据:把数据从从硬盘读取到内存中   

FileOutputstream   写数据:把数据写入文件中

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

//把一个文件内容写入到另一个文件中去(文件拷贝)
public class TestFile {
    public static void main(String[] args) throws IOException {
        //先指定初始文件的路径
         //起始文件路径
        String srcpath="d:\\test1.txt";
        //结束文件路径
        String destpath="d:\\test2.txt";
        //先读写文件之前打开文件夹
        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();
    }
}

注意:read方法一次返回一个字节,用int来接收,主要是因为:(1)在java中不存在无符号类型的,byte这样的类型也还是有符号的 (-128~127),按字节读取数据,并不需要将这些数据进行算术运算,所以正负无意义的。(2)read读取完毕之后需要返回-1,也就是EOF,来确定文件已经读取完毕,所以要使用int 来接收 

字符流(以字符为基本单位,适用于文本文件)

FileReader  FileWriter

3.多进程编程

1.进程:(任务管理器)一个跑起来的程序,就是一个进程  ,进程是操作系统资源分配的基本单位

  • 描述:使用结构体(PCB)来进行描述 (pid进程身份标识符,内存指针:进程有哪些资源,文件描述符表)
  • 组织:通过双向链表把PCB串在一起(创建一个进程,本质上就是创建一个PCB这样的结构体对象,把它插入到我们的链表中,任务管理器可以查看这个进程列表,本质上就是遍历这个PCB链表)
  • 进程之间相互独立

2.多进程

  • 并发编程实现的重要方式
  • 操作系统本身就是按照多进程方式进行工作的(并发) 

3.线程:包含在进程中,一个进程默认有一个线程,也可以有多个线程 ,每一个线程都是一个执行流,可以单独在CPU上进行调度。同一个进程中的这些线程公用一份资源(内存+文件),把线程叫做轻量级进程,创建和销毁线程开销比较小。

4.多线程和多进程的优缺点

  • 多线程:充分利用多核CPU,提高效率  ; 只是在创建第一个线程的时候需要申请资源,后续再创建新的线程就不需要; 缺点:互相进行干扰,线程数目也不是越多越好,CPU核心数目有限,可能会影响线程不安全问题
  • 多进程:不进行互相干扰
  • 进程是资源管理的基本单位,线程是调度执行的基本单位

在线OJ有一个服务器进程,运行着Servlet,接收用户请求,返回响应(所有的用户都可以登录做题,用户之间不影响)

用户提交代码,是一个独立的逻辑----------用多进程的方式 ,因为无法控制用户提交代码的正确性,如果使用多线程,之间会互相影响

使用多进程编程

5.操作系统给用户提供了很多和多进程编程相关的接口,用户只需要创建进程和进程等待即可

  • 创建进程(创建出一个新进程来执行一系列任务,被创建出进程叫做子进程,父进程:服务器进程)

Runtime.exec(参数是一个字符串,表示一个可执行程序路径,执行这个方法,就会把指定路径的可执行程序,创建出进程并执行)

  • 进程等待(父子进程是并发执行的,希望父进程等待子进程执行完毕后,在执行后续代码,在线OJ让用户提交代码,编译执行代码之后再把响应返回给用户,父进程执行到waitFore就会等待阻塞,一直阻塞到子进程执行完毕为止) 

6.javac是控制台程序,它的输出是输出到“标准输出和标准错误”这两个特殊符号文件中,要想1看到程序运行效果,就需要获取到标准输出和标准错误内容

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
//程序已经开始运行,只需要标准输出,和标准错误
public class Commandutil {
    public static int run(String cmd,String stdoutFile,String stderrFile){
        try {
            //通过Runtime获取到runtime对象,这是一个单例模式
            Process process=Runtime.getRuntime().exec(cmd);
            //对标准输出进行重定向,文件有内容
            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();
            }
            //对标准错误进行重定向
            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();
            }
            //进程等待
            try {
                int exitCode= process.waitFor();
                return exitCode;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return -1;
    }

    public static void main(String[] args) {
        Commandutil.run("javac","./stdout.txt","./stderr.txt");
    }
}

 4.编译运行设计

1.设计Question

public class Question {
    //用户提交代码
    private String code;
}

2.代码提交后响应

public class Answer {
    //服务器返回响应
    private int error;
    private String reason;
    private String stdout;
    private String stderr;
}

3.对读写文件进一步封装(要进程间通信,就需要很多读写文件操作,一个负责读整个文件内容,返回字符串,一个负责将字符串写入文件中,这些读取文件都是文本文件,所以使用字符流更合适)

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileUtil {
    //一次读取整个文件
    public static String readFile(String filePath) throws IOException {
        StringBuilder stringBuilder=new StringBuilder();
        try (FileInputStream fileInputStream=new FileInputStream(filePath)){
            int ch=fileInputStream.read();
            while (true){
                if (ch==-1){
                    break;
                }
                //一定要把ch转成char在append
                stringBuilder.append((char) ch);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //返回字符串
        return stringBuilder.toString();
    }

    //一次写入整个文件
    public static void writeFile(String filepath,String content)  {
        try(FileOutputStream fileOutputStream=new FileOutputStream(filepath)) {
            fileOutputStream.write(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

String是不可变对象,要修改必须要创建一个新对象,再把内容拷贝过去,所以用Stringbuilder,多个线程同时修改同一个变量,线程安全

4.Task进行编译运行过程展示 (输入:用户提交的代码,输出:程序的编译和运行结果,参数:要编译的java源代码,返回值:编译运行结果,编译需要.java文件,编译出错,javac就会把错误信息写入到stderr中

  • 临时文件
 //0.准备好存放临时文件的目录
    private final String WORK_DIR="./tmp/";
    //编译代码类名
    private final String CLASS="Solution";
    //代码
    private final String CODE=WORK_DIR+"Solution.java";
    //输出错误
    private  final String STDOUT=WORK_DIR+"stdout.txt";
    //标准错误
    private  final String STDERR=WORK_DIR+"stderr.txt";
    //编译错误
    private final String COMPILE_ERROR=WORK_DIR+"compile_error.txt";

为什么需要这么多临时文件:为了进程间通信,javac子进程分开编译和运行代码时,需要知道要编译和要运行的代码是什么。

  • 编译只关注编译错误放在哪个文件(error=0 Ok   =1编译出错     =2运行出错(抛异常)
public Answer compileAndrun(Question question) throws IOException {
        Answer answer=new Answer();
        //0.准备好存放临时文件的目录
        File workDir=new File(WORK_DIR);
        if (!workDir.exists()){
            //创建多级目录
            workDir.mkdirs();
        }
        //1.把question中的code写入.java文件中
        FileUtil.writeFile(CODE,question.getCode());
        //2.创建子进程,调用javac编译,编译需要一个.java文件,如果编译出错,会写入标准错误信息文件,
        //构造编译命令
        String compileCmd=String.format("javac-ending utf8 %s -d %s",CODE,WORK_DIR);
        System.out.println("编译命令: "+compileCmd);
        //只关注标准错误放哪个文件
        Commandutil.run(compileCmd,null,COMPILE_ERROR);
        //如果编译出错,记录这个文件里面
        String compileError=FileUtil.readFile(COMPILE_ERROR);
        if (!compileError.equals("")){
            //有错误信息
            System.out.println("编译出错");
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }
  •  编译运行关注输出错误和标准错误
 //编译正确,继续运行
        //3.创建子进程调用java命令去执行运行程序,关注标准输出,和标准错误
        String runCmd=String.format("java-classpath %s %s",WORK_DIR,CLASS);
        System.out.println("运行命令: "+runCmd);
        Commandutil.run(runCmd,STDOUT,STDERR);
        String runError=FileUtil.readFile(STDERR);
        if (!runError.equals("")){
            System.out.println("运行出错!");
            answer.setError(2);
            answer.setReason(runError);
            return answer;
        }
  •  将运行结构包装到最终Answer对象中,编译执行结果,通过文件获取
 answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
        return answer;

5. 题目管理模块设计

1.创建数据库和数据表

create database oj_databases charset utf8;
use oj_databases;
create table oj_table;
//题目id
id int primary key auto_increment(自增主键)
//标题
title varchar(50)
//题目难度
level varchar(50)
//题目描述
descripation varchar(50)
//测试代码
testCode varchar(50)
//编辑器代码
templateCode varchar(50)

2.封装数据库(建立连接和关闭连接)

package common;

import com.mysql.cj.jdbc.MysqlDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

//数据库相关操作
public class DBUtil {
    //需要封装和数据库之间的连接操作
    //连接数据库名称
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/oj_database?characterEncoding=utf8&useSSL=false";
    //创建用户名
    private static final String USERNAME = "root";
    //创建密码
    private static final String PASSWORD = "20010211";
    private static volatile DataSource dataSource = null;
    //获取实例
    private static DataSource getDataSource(){
        if (dataSource==null){
            synchronized (DBUtil.class){
                if (dataSource==null){
                    MysqlDataSource mysqlDataSource=new MysqlDataSource();
                    mysqlDataSource.setURL(URL);
                    mysqlDataSource.setUser(USERNAME);
                    mysqlDataSource.setPassword(PASSWORD);
                    dataSource=mysqlDataSource;
                }
            }
        }
        return dataSource;
    }
    //获取连接
    public static Connection getConnection() throws SQLException{
        return getDataSource().getConnection();
    }
    //关闭释放连接
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
        if (resultSet!=null){
            try {
                resultSet.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
        if (statement!=null){
            try {
                statement.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
        if (connection!=null){
            try {
                connection.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}

数据库的一些操作,一会细细看一下

 3.题目表的操作(一个实体类对象对应表中一条记录)Problem

//创建一个实体类
public class Problem {
    private int id;
    private String title;
    private String level;
    private String desription;
    private String templateCode;
    private String testCode;

4. 负责题目的管理 

  • 增加题目信息
// 插入题目信息
public void insert(Problem problem) {
    // 1. 获取数据库连接
    Connection connection = DBUtil.getConnection();
    PreparedStatement statement = null;
    String sql = "insert into oj_table values(null, ?, ?, ?, ?, ?)";
    try {
        // 2. 拼装 SQL 语句
        statement = connection.prepareStatement(sql);
        statement.setString(1, problem.getTitle());
        statement.setString(2, problem.getLevel());
        statement.setString(3, problem.getDescription());
        statement.setString(4, problem.getTemplateCode());
        statement.setString(5, problem.getTestCode());
        System.out.println(statement);
        // 3. 执行 SQL 语句
        statement.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.close(connection, statement, null);
    }
}
  • 查找所有信息(题目列表页) 
public List<Problem> selectAll() {
    ArrayList<Problem> problems = new ArrayList<>();
    // 1. 获取数据库连接
    Connection connection = DBUtil.getConnection();
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    String sql = "select id,title,level from oj_table";
    try {
        // 2. 拼装 SQL 语句
        statement = connection.prepareStatement(sql);
        // 3. 执行 SQL 语句
        resultSet = statement.executeQuery();
        // 4. 遍历查询出的结果
        while (resultSet.next()) {
            Problem problem = new Problem();
            problem.setId(resultSet.getInt("id"));
            problem.setTitle(resultSet.getString("title"));
            problem.setLevel(resultSet.getString("level"));
            problems.add(problem);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.close(connection, statement, resultSet);
    }
    return problems;
}
  •  根据题目id,查找详情页信息 
public Problem selectOne(int problemId) {
    // 1. 获取数据库连接
    Connection connection = DBUtil.getConnection();
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    String sql = "select * from oj_table where id = ?";
    try {
        // 2. 拼装 SQL 语句
        statement = connection.prepareStatement(sql);
        statement.setInt(1, problemId);
        // 3. 执行 SQL 语句
        resultSet = statement.executeQuery();
        if (resultSet.next()) {
            Problem problem = new Problem();
            problem.setId(resultSet.getInt("id"));
            problem.setTitle(resultSet.getString("title"));
            problem.setLevel(resultSet.getString("level"));
            problem.setDescription(resultSet.getString("description"));
            problem.setTemplateCode(resultSet.getString("templateCode"));
            problem.setTestCode(resultSet.getString("testCode"));
            return problem;
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.close(connection, statement, resultSet);
    }
    return null;
}
  •  删除题目信息 
//删除
    public void delete(int id){
        Connection connection=null;
        PreparedStatement statement=null;
        try {
            //1.和数据库建立连接
            connection=DBUtil.getConnection();
            //构造sql语句
            String sql="delete from oj_table where id=?";
            statement= connection.prepareStatement(sql);
            //sql动态替换
            statement.setInt(1,id);
            //执行sql
            int ret=statement.executeUpdate();
            if (ret!=1){
                System.out.println("删除题目失败");
            }else {
                System.out.println("删除题目成功");
            }
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,null);
        }

    }
  • 添加测试用例代码 
 private static void testInsert() {
        Problem problem = new Problem();
        problem.setId(1);
        problem.setTitle("两数之和");
        problem.setLevel("简单");
        problem.setDesription("给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。\n" +
                "\n" +
                "你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。\n" +
                "\n" +
                "你可以按任意顺序返回答案。\n" +
                "\n" +
                " \n" +
                "\n" +
                "示例 1:\n" +
                "\n" +
                "输入:nums = [2,7,11,15], target = 9\n" +
                "输出:[0,1]\n" +
                "解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。\n" +
                "示例 2:\n" +
                "\n" +
                "输入:nums = [3,2,4], target = 6\n" +
                "输出:[1,2]\n" +
                "示例 3:\n" +
                "\n" +
                "输入:nums = [3,3], target = 6\n" +
                "输出:[0,1]\n" +
                " \n" +
                "\n" +
                "提示:\n" +
                "\n" +
                "2 <= nums.length <= 103\n" +
                "-109 <= nums[i] <= 109\n" +
                "-109 <= target <= 109\n" +
                "只会存在一个有效答案\n" +
                "\n" +
                "来源:力扣(LeetCode)\n" +
                "链接:https://leetcode-cn.com/problems/two-sum\n" +
                "著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。");
        problem.setTemplateCode("  public int[] twoSum(int[] nums, int target) {\n" +
                "    \n" +
                "    }");
        problem.setTestCode("    public static void main(String[] args) {\n" +
                "        Solution solution = new Solution();\n" +
                "        int[] arr = {2,7,11,15};\n" +
                "        int target = 9;\n" +
                "        int[] result = solution.twoSum(arr, 9);\n" +
                "        if (result.length == 2 && result[0] == 1 && result[1] == 2) {\n" +
                "            System.out.println(\"TestCase OK!\");\n" +
                "        } else {\n" +
                "            System.out.println(\"TestCase Failed! arr: {2, 7, 11, 15}, target: 9\");\n" +
                "        }\n" +
                "\n" +
                "        int[] arr2 = {3,2,4};\n" +
                "        int target2 = 6;\n" +
                "        int[] result2 = solution.twoSum(arr2, target2);\n" +
                "        if (result2.length == 2 && result2[0] == 1 && result2[1] == 2) {\n" +
                "            System.out.println(\"TestCaseOK!\");\n" +
                "        } else {\n" +
                "            System.out.println(\"TestCaseFailed!\");\n" +
                "        }\n" +
                "    }\n");
        ProblemDAO problemDAO = new ProblemDAO();
        problemDAO.insert(problem);
    }

测试用例代码就是一个main函数,在这个方法里面会创建Soulation实例,并且调用里面操作方法,调入方法时传入不同参数,并且针对返回结果进行判定。如果返回结果符合预期,就打印(Tset OK),不符合则打印(Test Filed)

服务器会收到用户提交的Soulation类完整代码,然后从数据库找到相对应的测试用例代码(main方法),就把这个两个代码进行拼接,此时Soulation就有main函数,可以单独1编译和执行。  

6.设计前后端交互的API 

 通过Json格式,引入第三方库来实现前后端交互API

  • 第一个API,向服务器请求,题目列表

 请求:GET/problem

 响应:【

                 {

                        id:1,

                        title:"两数之和“,

                         level:"简单”

                    },

                    

                    {

                        id:2,

                        title:"两数之积“,

                         level:"简单”

                    }

                  ...............

                】

  

  •  第二个API,向服务器请求,获取指定题目详情信息

请求:GET/problem?id=1

响应:{

              id=1,

              title:"两数之和“,

              level:"简单”,

              description:"......................",

               templateCode:"代码模板“

           } 

  • 第三个API,向服务器发送当前的编码,并且获取到结果 (编译)

 GET需要把代码放入到URL中,通过query string来发送请求(需要对代码中字符进行urlencode)          

POST需要把代码放入body中

 请求:POST/compile

            {

               id=1,

               code:"..........",

              }    

服务器要根据id从数据库中拿到1测试用例代码和用户提交代码进行拼接

 响应:

{

   error: 0  (0表示编译运行成功,1表示编译出错,2表示运行出错)

   reason:"出错原因”

   staout:“测试用例输出情况,包含通过几个用例信息”

}

      第一二个API

package api;
import com.fasterxml.jackson.databind.ObjectMapper;
import dao.Problem;
import dao.ProblemDAO;

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;
import java.util.List;

//通过注解建立连接关系
@WebServlet("/problem")
public class ProblemServlet extends HttpServlet {
    private ObjectMapper objectMapper=new  ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //需要放在最上面,否则就有可能不生效
        resp.setStatus(200);
        resp.setContentType("application/json;charset=utf8");


        //尝试获取id参数,能获取到,则是获取题目详情,反之则获取题目列表
        String idString=req.getParameter("id");
        ProblemDAO problemDAO=new ProblemDAO();
        if (idString==null||"".equals(idString)){
            //没有获取到id,是题目列表页
            //查询所有题目列表
            List<Problem> problems= problemDAO.selectAll();
            String respString=objectMapper.writeValueAsString(problems);
            //响应字符串写入,设置http响应中的body部分
            //http协议抱头中就要求Content-length来描述body长度(自动设置的),Content-type来描述body类型(需要手动去设置)
            resp.getWriter().write(respString);
        }else {
            //获取到题目id,查询题目详情
           Problem problem=problemDAO.selectOne(Integer.parseInt(idString));
           String respString=objectMapper.writeValueAsString(problem);
           resp.getWriter().write(respString);
        }
    }
}

   第三个API

package api;

import com.fasterxml.jackson.databind.ObjectMapper;
import common.CodeInValidException;
import common.ProblemNotFoundException;
import compile.Answer;
import compile.Question;
import compile.Task;
import dao.Problem;
import dao.ProblemDAO;

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;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

@WebServlet("/compile")
public class CompileServlet extends HttpServlet {
    //加上static当前内部类实例就不依赖外部类实例,如果不加就需要先创建外部类实例,再加内部类实例
    static class CompileRequest{
        public int id;
        public String code;
    }
    static class CompileResponse{
        //此处约定error为0表示ok,1编译错误,3其他错误 2运行错误
        public int error;
        public String reason;
        public String stdout;
    }
    private ObjectMapper objectMapper=new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        CompileRequest compileRequest=null;
        CompileResponse compileResponse=new CompileResponse();
        try{
            resp.setStatus(200);
            resp.setContentType("application/json;charset=utf8");
            //1.先读取请求正文,并且按照json格式解析
            String body=readBody(req);
            //解析对象
            compileRequest=objectMapper.readValue(body,CompileRequest.class);
            //2.根据id从数据库查找到题目详情,进而得到测试用例代码
            ProblemDAO problemDAO=new ProblemDAO();
            Problem problem=problemDAO.selectOne(compileRequest.id);
            //如果找不到id,处理异常
            if (problem==null){
                //为了统一处理错误,抛出异常
                throw new ProblemNotFoundException();
            }
            //测试用力代码
            String testCode=problem.getTestCode();
            //用户提交代码
            String requestCode=compileRequest.code;
            //3.把用户提交代码和测试用力代码进行拼接
            String finalCode=mergeCode(requestCode,testCode);
            if (finalCode==null){
                //提交代码异常
                throw new CodeInValidException();
            }
            System.out.println(finalCode);
            //4.创建一个Task实例,调用里面的compileAndRun来编译运行
            Task task=new Task();
            Question question=new Question();
            question.setCode(finalCode);
            Answer answer=task.compileAndrun(question);
            //5.根据Task运行结果,包装成一个Http响应
            compileResponse.error=answer.getError();
            compileResponse.reason=answer.getReason();
            compileResponse.stdout=answer.getStdout();
            String respString=objectMapper.writeValueAsString(compileResponse);
            resp.getWriter().write(respString);
        } catch (ProblemNotFoundException e) {
           //处理id题目没有找到异常
            compileResponse.error=3;
            compileResponse.reason="没有找到指定题目! id="+compileRequest.id;
            String respString=objectMapper.writeValueAsString(compileResponse);
            resp.getWriter().write(respString);
        }catch (CodeInValidException e){
            compileResponse.error=3;
            compileResponse.reason="提交代码不符合要求!";
            String respString=objectMapper.writeValueAsString(compileResponse);
            resp.getWriter().write(respString);
        }
    }

    private static  String mergeCode(String requestCode, String testCode) {
            // 1. 先从 requestCode 中找到末尾的 } , 并且截取出前面的代码
            int pos = requestCode.lastIndexOf("}");
            if (pos == -1) {
                return null;
            }
            // 2. 把 testCode 拼接到后面, 并再拼接上一个 } 就好了
            return requestCode.substring(0, pos) + testCode + "}";
        }

    private static String readBody(HttpServletRequest req) throws UnsupportedEncodingException {
        //1、现根据请求头里免得ContentLength来获取body长度
        int contentLength= req.getContentLength();
        //2.按照这个长度来准备好一个byte[]
        byte[] buffer=new byte[contentLength];
        //3.通过req里面的getInputStream方法,获取body流对象
        try (InputStream inputStream=req.getInputStream()){
            //4.基于这个流对象读取内容,将内容放到byte[]数组中
            inputStream.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //5.把byte[]数组内容,构造成一个String,把二进制数据以字节为单位转成文本数据,以字符为单位
        return new String(buffer,"utf8");
    }
}

 测试用例和提价代码拼接(将testCode中的main方法嵌入到RequestCode里面,把testCode放到Soulation最后一个}前面即可)

  • 先在requestCode查找最后一个}前面即可
  • 根据刚才查找的结果进行字符串截取
  • 把刚才截取的结果拼接上测试用例代码+拼接}

 7.进行优化

  • 因为每次有一个请求就需要生成一组临时文件,如果同一时刻有n个请求一起,这些请求的临时文件和目录都是一样的,此时多个请求之间会出现相互干扰的情况(类似于线程安全的问题)

        办法:让每一个请求有自己的目录去生成这些临时文件,使用其他唯一ID来作为目录名字

      每个请求都生成唯一的UUID,进一步创建一个以UUID命名的临时目录,请求生成临时文件放到         临时目录即可。

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 String WORK_DIR = null;
    // 编译代码的类名
    private  String CLASS = null;
    // 编译代码的文件名
    private  String CODE = null;
    //运行时标准输出的文件名
    private  String STDOUT = null;
    //运行时标准错误的文件名
    private  String STDERR = null;
    //编译错误信息文件名
    private  String COMPILE_ERROR = null;
    public Task(){
        WORK_DIR="./tmp/"+ UUID.randomUUID().toString()+"/";
        CLASS="Solution";
        CODE=WORK_DIR + "Solution.java";
        COMPILE_ERROR=WORK_DIR + "compile_error.txt";
        STDOUT=WORK_DIR + "stdout.txt";
        STDERR=WORK_DIR + "stderr.txt";
    }
  • 代码安全性问题(不能保证用户提交代码安全性,所以需要设置黑名单来处理安全问题(docker相当于轻量级虚拟机,每次用户提交代码都会分配一个docker容器,让用户提交的代码在容器里面执行,即使代码有恶意操作,也就是把docker容器损坏,对物理机无影响)
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 String WORK_DIR = null;
    // 编译代码的类名
    private  String CLASS = null;
    // 编译代码的文件名
    private  String CODE = null;
    //运行时标准输出的文件名
    private  String STDOUT = null;
    //运行时标准错误的文件名
    private  String STDERR = null;
    //编译错误信息文件名
    private  String COMPILE_ERROR = null;
    public Task(){
        WORK_DIR="./tmp/"+ UUID.randomUUID().toString()+"/";
        CLASS="Solution";
        CODE=WORK_DIR + "Solution.java";
        COMPILE_ERROR=WORK_DIR + "compile_error.txt";
        STDOUT=WORK_DIR + "stdout.txt";
        STDERR=WORK_DIR + "stderr.txt";
    }
    //这个Task提供核心方法就是编译和运行
    //参数:编译运行的代码
    //返回值:编译运行的结果
    public Answer compileAndrun(Question question) {
        Answer answer = new Answer();
        //先准备好用来存放临时变量的目录
        File workDir = new File(WORK_DIR);
        if (!workDir.exists()) {
            //创建多级目录
            workDir.mkdirs();
        }
        //进行安全性判定
        if (!checkCodeSafe(question.getCode())){
            System.out.println("用户提交不安全代码");
            answer.setError(3);
            answer.setReason("您提交的代码不安全,可能会危害服务器,禁止执行!");
            return answer;

        }
        //1.把question中的code写到一个.java文件中(类名和文件名是一致的,约定都是soulation)
        FileUtil.writeFile(CODE, question.getCode());
//        //2.创建子进程,调用javac编译,编译需要有一个.java文件,如果编译出错,javac就会把错误信息写道stderr里,可以用一个文件compliErro.txt进行保存
//        //先把编译命令构造出来
        String compileCmd=String.format("javac -encoding utf8 %s -d %s",CODE,WORK_DIR);
        System.out.println("编译命令:"+compileCmd);
//        //只关注标准错误放哪个文件
       CommandUtil.run(compileCmd,null,COMPILE_ERROR);
//        //如果编译出错,就会记录到这个文件里面,反之则为空
       String compileError= FileUtil.readFile(COMPILE_ERROR);
       if (!compileError.equals("")){
//            //编译出错,返回Answer,记录出错信息
           System.out.println("编译出错!");
           answer.setError(1);
           answer.setReason(compileError);
          return answer;
       }
        //编译正确,继续执行接下来程序
        //3.创建子进程调用java命令去执行,运行程序时,也会把java子程序的标准输出 stdout.txt和标准错误stderr.txt获取到
        String runCmd=String.format("java -classpath %s %s",WORK_DIR,CLASS);
        System.out.println("运行命令:"+runCmd);
        CommandUtil.run(runCmd,STDOUT,STDERR);
        String runError= FileUtil.readFile(STDERR);
        if (!runError.equals("")){
            System.out.println("运行出错!");
            answer.setError(2);
            answer.setReason(runError);
            return answer;
        }
        //4.父进程获取到编译的结果,打包到Answer对象,编译执行结果,通过文件获取就可以
        answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
       return answer;
    }
    //黑名单可以动态扩充
    private boolean checkCodeSafe(String code){
        List<String> blackList=new ArrayList<>();
        //防止提交的代码运行恶意程序
        blackList.add("Runtime");
        blackList.add("exec");
        //禁止提交的代码读写文件
        blackList.add("java.io");
        //禁止提交代码访问网络
        blackList.add("java.net");
        for (String target:blackList){
            int pos=code.indexOf(target);
            if (pos>=0){
                //找到任意恶意代码特征,不安全
                return false;
            }
        }
        return true;
    }
    public static void main(String[] args) {
        Task task=new Task();
        Question question=new Question();
        question.setCode("public class Solution {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"hello world\");\n" +
                "    }\n" +
                "}");
        Answer answer= task.compileAndrun(question);
        System.out.println(answer);
    }

}

 8.前端页面设计

1.题目列表页(展示当前都有哪些题目,点击题目可以跳转到详情页)

2.题目详情页(展示题目具体信息)

需要让页面通过ajax方式从服务器去获取数据

  •  题目列表页(使用ajax与后端进行交互)
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Charcoal - Free Bootstrap 4 UI Kit</title>
        <meta name="description" content="Charcoal is a free Bootstrap 4 UI kit build by @attacomsian at Wired Dots." />
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <!--Bootstrap 4-->
        <link rel="stylesheet" href="css/bootstrap.min.css">
    </head>
    <body>

        <nav class="navbar navbar-expand-md navbar-dark fixed-top sticky-navigation">
            <a class="navbar-brand font-weight-bold" href="#">小加的OJ系统</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#topMenu" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>

            <div class="collapse navbar-collapse" id="topMenu">
            </div>
        </nav>

        <!--hero section-->
        <section class="bg-hero">
            <div class="container">
                <div class="row vh-100">
                    <div class="col-sm-12 my-auto text-center">
                        <h1>我的OJ系统</h1>
                        <p class="lead text-capitalize my-4">
                            基于 Java Sevlet搭建的在线 OJ 平台
                        </p>
                        <a href="https://gitee.com" class="btn btn-outline-light btn-radius btn-lg">项目链接</a>
                    </div>
                </div>
            </div>
        </section>

        <!--components-->
        <section class="my-5 pt-5">
            <div class="container">

                <!-- Tables  -->
                <div class="row mb-5" id="tables">
                    <div class="col-sm-12">
                        <div class="mt-3 mb-5">
                            <h3>题目列表</h3>
                            <table class="table">
                                <thead class="thead-dark">
                                    <tr>
                                        <th>ID编号</th>
                                        <th>题目标题</th>
                                        <th>难度</th>
                                    </tr>
                                </thead>
                                <tbody id="problemTable">
                                    <!-- <tr>
                                        <td>1</td>
                                        <td>
                                           <a href="#">两数之和</a> 
                                        </td>
                                        <td>简单</td>
                                    </tr> -->
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!--footer-->
        <section class="py-5 bg-dark">
            <div class="container">
                <div class="row">
                    <div class="col-md-6 offset-md-3 col-sm-8 offset-sm-2 col-xs-12 text-center">
                        <!-- <h3>Upgrade to Pro Version</h3>
                        <p class="pt-2">
                            We are working on <b>Charcoal Pro</b> which will be released soon. The pro version 
                            will have a lot more components, sections, icons, plugins and example pages. 
                            Join the waiting list to get notified when we release it (plus discount code).
                        </p>
                        <a class="btn btn-warning" href="https://wireddots.com/newsletter">Join Waiting List</a>
                        <hr class="my-5"/> -->
                        <p class="pt-2 text-muted">
                            &copy; by 加率
                        </p>
                    </div>
                </div>
            </div>
        </section>

        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
        <script src="js/app.js"></script>
        <script>
            //在页面加载时尝试从服务器获取题目列表,用ajax方式
            function getProblems(){
               //1.先通过ajax从服务区获取题目列表
               $.ajax({
                  url:"problem",
                  type:"GET",
                  success:function(data,status){
                    //data是响应的body,status是响应状态码
                    //得到的响应数据构造成HTML片段
                    makeProblemTable(data);
                  }
               })
            }
            function makeProblemTable(data){
                //通过这个函数来吧数据转换成HTML页面片段
                let problemTable=document.querySelector("#problemTable");
                for(let problem of data){
                    let tr=document.createElement("tr");
     
                    let tdId=document.createElement("td");
                    tdId.innerHTML=problem.id;
                    tr.appendChild(tdId);
      
                    let tdTitle=document.createElement("td");
                    let a=document.createElement("a");
                    a.innerHTML=problem.title;

                    a.href='ProblemDetail.html?id='+problem.id;
                    a.target='_blank';
                    tdTitle.appendChild(a);
                    tr.appendChild(tdTitle);
      
                    let tdlevel=document.createElement("td");
                    tdlevel.innerHTML=problem.level;
                    tr.appendChild(tdlevel);

                    problemTable.appendChild(tr);
                }
            }
            getProblems();
        </script>
    </body>
</html>
  •  题目详情页

      先把题目列表页拷贝一份, 修改名字为 problemDetail.html

     调整页面内容. 去掉表格了.

  • 使用一个 jumbotron 表示题目详情
  • 使用一个 textarea 表示代码编辑框
  • 使用 button 表示提交按钮.
  • 再使用一个 jumbotron 表示题目运行结果
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Charcoal - Free Bootstrap 4 UI Kit</title>
        <meta name="description" content="Charcoal is a free Bootstrap 4 UI kit build by @attacomsian at Wired Dots." />
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <!--Bootstrap 4-->
        <link rel="stylesheet" href="css/bootstrap.min.css">
    </head>
    <body>

        <nav class="navbar navbar-expand-md navbar-dark fixed-top sticky-navigation">
            <a class="navbar-brand font-weight-bold" href="#">小加的OJ系统</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#topMenu" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>

            <div class="collapse navbar-collapse" id="topMenu">
            </div>
        </nav>

        <!--hero section-->
        <section class="bg-hero">
            <div class="container">
                <div class="row vh-100">
                    <div class="col-sm-12 my-auto text-center">
                        <h1>我的OJ系统</h1>
                        <p class="lead text-capitalize my-4">
                            基于 Java Sevlet搭建的在线 OJ 平台
                        </p>
                        <a href="https://gitee.com" class="btn btn-outline-light btn-radius btn-lg">项目链接</a>
                    </div>
                </div>
            </div>
        </section>

        <!--components-->
        <section class="my-5 pt-5">
            <div class="container">
                <div class="row mt-4">
                    <div class="col-sm-12 pb-4">
                         <div class="jumbotron jumbotron-fluid">
                            <div class="container" id="problemDesc">
                                <!-- <h1>Container fluid size jumbotron</h1>
                                 <p>Think BIG with a Bootstrap Jumbotron!</p> -->
                            </div>
                        </div>
                    </div>
                </div>


                <div class="row mt-4">
                      <div class="col-sm-12 pb-4">
                          <div class="form-group">
                              <label for="codeEdit">代码编辑框</label>
                              <div id="editor" style="min-height:400px">
                              <textarea class="form-control" id="codeEdit" style="width: 100%; height:400 px;"></textarea>
                            </div>
                    </div>
                </div>
            </div>
                <button type="button" class="btn btn-outline-primary" id="submitButton">提交</button>

                <div class="row mt-4">
                    <div class="col-sm-12 pb-4">
                         <div class="jumbotron jumbotron-fluid">
                            <div class="container" >
                                <pre id="problemResult">

                                </pre>
                                <!-- <h1>Container fluid size jumbotron</h1>
                                 <p>Think BIG with a Bootstrap Jumbotron!</p> -->
                            </div>
                        </div>
                    </div>
                </div>
        </section>

        <!--footer-->
        <section class="py-5 bg-dark">
            <div class="container">
                <div class="row">
                    <div class="col-md-6 offset-md-3 col-sm-8 offset-sm-2 col-xs-12 text-center">
                        <!-- <h3>Upgrade to Pro Version</h3>
                        <p class="pt-2">
                            We are working on <b>Charcoal Pro</b> which will be released soon. The pro version 
                            will have a lot more components, sections, icons, plugins and example pages. 
                            Join the waiting list to get notified when we release it (plus discount code).
                        </p>
                        <a class="btn btn-warning" href="https://wireddots.com/newsletter">Join Waiting List</a>
                        <hr class="my-5"/> -->
                        <p class="pt-2 text-muted">
                            &copy; by 加率
                        </p>
                    </div>
                </div>
            </div>
        </section>

        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
        <script src="js/app.js"></script>
        <script src="https://cdn.bootcss.com/ace/1.2.9/ace.js"></script>
        <script src="https://cdn.bootcss.com/ace/1.2.9/ext-language_tools.js"></script>
        <script>
            console.log(location.search);
    function initAce() {
    // 参数 editor 就对应到刚才在 html 里加的那个 div 的 id
    let editor = ace.edit("editor");
    editor.setOptions({
        enableBasicAutocompletion: true,
        enableSnippets: true,
        enableLiveAutocompletion: true
    });
    editor.setTheme("ace/theme/twilight");
    editor.session.setMode("ace/mode/java");
    editor.resize();
    document.getElementById('editor').style.fontSize = '20px';

    return editor;
}
  let editor = initAce();


           //通过ajax从服务器获取到题目详情
           function getProblem(){
           // 1.通过ajax给服务器发送一个请求
            $.ajax({
                url:"problem"+location.search,
                type:"GET",
                success:function(data,status){
                    makeProblemDetail(data);
                }
             })
           }
           function makeProblemDetail(problem){
              //先获取problemDesc,获取题目详情
              let problemDesc=document.querySelector("#problemDesc");
              let h3=document.createElement("h3");
              h3.innerHTML=problem.id+"."+problem.title+"_"+problem.level
              problemDesc.appendChild(h3);


              let pre=document.createElement("pre");
              let p=document.createElement("p");
              p.innerHTML=problem.desription;
              pre.appendChild(p);
              problemDesc.appendChild(pre);



              //代码模板填写到编辑框
            //   let codeEdit =document.querySelector("#codeEdit");
            //   codeEdit.innerHTML=problem.templateCode;
               editor.setValue(problem.templateCode);


              //给提交按钮注册一个点击事件
              let submitButton=document.querySelector("#submitButton");
              submitButton.onclick=function(){
                //点击这个按钮就需要进行提交
                let body={
                    id:problem.id,
                    //code:codeEdit.value,
                    code:editor.getValue(),
                };
                $.ajax({
                    type:"POST",
                    url:"compile",
                    //手动转成字符串
                    data:JSON.stringify(body),
                    success:function(data,status){
                        let problemResult=document.querySelector("#problemResult");
                        if(data.error==0){
                            //编译运行没有问题
                           problemResult.innerHTML=data.stdout;
                        }else{
                            //编译运行有问题
                            problemResult.innerHTML=data.reason;
                        }
                    }
                });
              }
           }

           getProblem();
        </script>
    </body>
</html>
  •  注意1:数据库的题目要求,换行都用\n来表示,HTML的换行是br标签(让服务器返回数据时,\n都替换成br;给页面标签里面套一层pre标签,pre标签可识别\n)
  • 注意2:编辑框点击提交按钮,不能编译,引入ace.js,它是有前端的编辑器,会重新绘制页面(绘制div #editor,原来那个textarea就没有了,所以需要把代码模板填写到编辑框),还需要初始化编辑框

四.项目测试

1.对在线OJ项目设计相关测试用例 

 2.对在线OJ项目进行单元测试(单元测试:对程序最小单元进行测试)

  • 新增一条数据记录
public void insert(Problem problem) {
        try {
            // 1. 获取数据库连接
            connection = DBUtil.getConnection();
            String sql = "insert into oj_table values(null, ?, ?, ?, ?, ?)";
            // 2. 拼装 SQL 语句
            statement = connection.prepareStatement(sql);
            statement.setString(1, problem.getTitle());
            statement.setString(2, problem.getLevel());
            statement.setString(3, problem.getDesription());
            statement.setString(4, problem.getTemplateCode());
            statement.setString(5, problem.getTestCode());
            System.out.println(statement);
            // 3. 执行 SQL 语句
            int ret=statement.executeUpdate();
            if (ret!=1){
                System.out.println("新增题目失败");
            }else {
                System.out.println("新增题目成功");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(connection, statement, null);
        }
    }


 对新增功能进行验证

 private static void testInsert() {
        Problem problem = new Problem();
        problem.setId(1);
        problem.setTitle("两数之和");
        problem.setLevel("简单");
        problem.setDesription("给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。\n" +
                "\n" +
                "你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。\n" +
                "\n" +
                "你可以按任意顺序返回答案。\n" +
                "\n" +
                " \n" +
                "\n" +
                "示例 1:\n" +
                "\n" +
                "输入:nums = [2,7,11,15], target = 9\n" +
                "输出:[0,1]\n" +
                "解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。\n" +
                "示例 2:\n" +
                "\n" +
                "输入:nums = [3,2,4], target = 6\n" +
                "输出:[1,2]\n" +
                "示例 3:\n" +
                "\n" +
                "输入:nums = [3,3], target = 6\n" +
                "输出:[0,1]\n" +
                " \n" +
                "\n" +
                "提示:\n" +
                "\n" +
                "2 <= nums.length <= 103\n" +
                "-109 <= nums[i] <= 109\n" +
                "-109 <= target <= 109\n" +
                "只会存在一个有效答案\n" +
                "\n" +
                "来源:力扣(LeetCode)\n" +
                "链接:https://leetcode-cn.com/problems/two-sum\n" +
                "著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。");
        problem.setTemplateCode("  public int[] twoSum(int[] nums, int target) {\n" +
                "    \n" +
                "    }");
        problem.setTestCode("    public static void main(String[] args) {\n" +
                "        Solution solution = new Solution();\n" +
                "        int[] arr = {2,7,11,15};\n" +
                "        int target = 9;\n" +
                "        int[] result = solution.twoSum(arr, 9);\n" +
                "        if (result.length == 2 && result[0] == 1 && result[1] == 2) {\n" +
                "            System.out.println(\"TestCase OK!\");\n" +
                "        } else {\n" +
                "            System.out.println(\"TestCase Failed! arr: {2, 7, 11, 15}, target: 9\");\n" +
                "        }\n" +
                "\n" +
                "        int[] arr2 = {3,2,4};\n" +
                "        int target2 = 6;\n" +
                "        int[] result2 = solution.twoSum(arr2, target2);\n" +
                "        if (result2.length == 2 && result2[0] == 1 && result2[1] == 2) {\n" +
                "            System.out.println(\"TestCaseOK!\");\n" +
                "        } else {\n" +
                "            System.out.println(\"TestCaseFailed!\");\n" +
                "        }\n" +
                "    }\n");
        ProblemDAO problemDAO = new ProblemDAO();
        problemDAO.insert(problem);
    }

 验证:符合预期,运行结果显示新增成功,数据库也有相应的修改

  •  删除一条题目信息
 //删除
    public void delete(int id){
        Connection connection=null;
        PreparedStatement statement=null;
        try {
            //1.和数据库建立连接
            connection=DBUtil.getConnection();
            //构造sql语句
            String sql="delete from oj_table where id=?";
            statement= connection.prepareStatement(sql);
            //sql动态替换
            statement.setInt(1,id);
            //执行sql
            int ret=statement.executeUpdate();
            if (ret!=1){
                System.out.println("删除题目失败");
            }else {
                System.out.println("删除题目成功");
            }
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,null);
        }
 
    }

 

  对删除功能进行验证

 private static void testSelectOne(){
        ProblemDAO problemDAO=new ProblemDAO();
        Problem problem=problemDAO.selectOne(1);
        System.out.println(problem);
 
    }


 验证:符合预期,运行结果显示删除成功,数据库也有相应的修改

  • 通过id查找其中一条详细信息 

 

 //根据id查找题目详情
    public Problem selectOne(int id){
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try {
            //1.建立连接
            connection=DBUtil.getConnection();
            //2.构造sql语句
            String sql="select * from oj_table where id=?";
            statement= connection.prepareStatement(sql);
            statement.setInt(1,id);
            //3.执行sql
            resultSet=statement.executeQuery();
            //4.遍历查询,由于id是主键,所以查询结果一定是唯一的,用if进行查找
           if (resultSet.next()){
               Problem problem=new Problem();
               problem.setId(resultSet.getInt("id"));
               problem.setTitle(resultSet.getString("title"));
               problem.setLevel(resultSet.getString("level"));
               problem.setDesription(resultSet.getString("description"));
               problem.setTemplateCode(resultSet.getString("templateCode"));
               problem.setTestCode(resultSet.getString("testCode"));
               return problem;
           }
        }catch (SQLException throwables){
            throwables.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

  

   对通过查找题目详情功能进行验证

 

private static void testSelectOne(){
        ProblemDAO problemDAO=new ProblemDAO();
        Problem problem=problemDAO.selectOne(1);
        System.out.println(problem);
 
    }


 验证:符合预期,运行结果显示题目详情信息,数据库也可以显示

  • 查找所有题目

 

public List<Problem> selectAll(){
        List<Problem> problems=new ArrayList<>();
        Connection connection=null;
        PreparedStatement statement=null;
        ResultSet resultSet=null;
        try {
            //1.和数据库建立连接
            connection=DBUtil.getConnection();
            //2.构造sql语句
            String sql="select id, title, level from oj_table";
            statement= connection.prepareStatement(sql);
            //执行sql,可以获取每一行
           resultSet= statement.executeQuery();
           //遍历resultset
            while (resultSet.next()){
                //每一行都是一个problem对象
                Problem problem=new Problem();
                problem.setId(resultSet.getInt("id"));
                problem.setTitle(resultSet.getString("title"));
                problem.setLevel(resultSet.getString("level"));
                problems.add(problem);
            }
            return problems;
        }catch (SQLException throwables){
            throwables.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
         return null;
    }

 

 验证:符合预期,数据库可以显示所新增的所有题目

  3.对在线OJ项目进行自动化测试

  • 先给pom.xml里面配置,将selenium依赖导入
<dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.0.0</version>
        </dependency>

  • 在test的java里面新建类进行对应操作                             
       对首页和题目列表页进行自动化测试
public class FirstAutotest {
    //题目列表页
    public void dilirebaTest() throws InterruptedException {
        EdgeOptions options = new EdgeOptions();
        options.addArguments("--remote-allow-origins=*");
        EdgeDriver driver = new EdgeDriver(options);
        Thread.sleep(5000);
        //打开网址
        driver.get("http:127.0.0.1:8080/Java-OJ-2023/index.html");
        Thread.sleep(5000);
        //找到页面链接的地方并定位跳转
        driver.findElement(By.xpath("/html/body/section[1]/div/div/div/a")).click();
        Thread.sleep(5000);
        driver.quit();
    }
 
 对题目详情页进行自动化测试
//题目详情页
    public void detail() throws InterruptedException {
        EdgeOptions options = new EdgeOptions();
        options.addArguments("--remote-allow-origins=*");
        EdgeDriver driver = new EdgeDriver(options);
        Thread.sleep(5000);
  //进入到列表页
        driver.get("http:127.0.0.1:8080/Java-OJ-2023/index.html");
        Thread.sleep(5000);
        //找到标题的链接并点击
        driver.findElement(By.xpath("//*[@id=\"problemTable\"]/tr[1]/td[2]/a")).click();
        Thread.sleep(5000);
        //找到题目编辑框
        driver.executeScript("//*[@id=\"editor\"]/div[2]/div");
        Thread.sleep(5000);
        driver.quit();
    }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值