欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于Java开发的在线OJ项目

程序员文章站 2022-04-15 18:56:53
文章目录一、项目说明1.1 项目目标1.2 项目平台与技术栈1.3 项目功能二、项目演示2.1 进入题目列表页2.2 进入题目详情页2.3 编写代码2.4 提交运行三、系统流程一、项目说明1.1 项目目标巩固JDBC、Http、Servlet和Java基础等内容实现一个在线做题、判题系统1.2 项目平台与技术栈技术栈:Servlet、Mysql、Runtime平台与环境:Windows、IDEA、Maven、Tomcat1.3 项目功能进入题目列表页,展示当前系统中的所有题目进...

一、项目说明

1.1 项目目标

  • 巩固JDBC、Http、Servlet和Java基础等内容
  • 实现一个在线做题、判题系统

1.2 项目平台与技术栈

  • 技术栈:Servlet、Mysql、Runtime
  • 平台与环境:Windows、IDEA、Maven、Tomcat

1.3 项目功能

  • 进入题目列表页,展示当前系统中的所有题目
  • 进入题目详情页,展示题目的具体要求和代码模块,提供一个编辑框来供用户编辑
  • 对Java代码的编译/运行/测试功能,针对用户提交的代码进行编译运行,并自动执行测试用例、返回测试结果

二、项目演示

2.1 进入题目列表页

基于Java开发的在线OJ项目

2.2 进入题目详情页

基于Java开发的在线OJ项目

2.3 编写代码

基于Java开发的在线OJ项目

2.4 提交运行

点击提交之后,服务器最终会将结果会返给客户端。

基于Java开发的在线OJ项目

三、系统流程(核心)

起初,我们需要在后台录入题目和测试用例。
基于Java开发的在线OJ项目

四、系统设计

4.1 执行指令设计

通过Runtime对象下的exec()方法能让Java代码去执行一个具体的指令,并且将标准输出和标准错误进行重定向。

4.2 编译和运行设计

  • 一次编译过程中依赖要编译的代码,和标准输入中的内容
  • 一次编译运行过程中会产生:状态码、出错原因、标准输出对应的内容、标准错误对应的内容
  • 编译过程中依赖临时文件:要编译运行的文件、编译错误对应的文件、标准输出对应的文件、标准错误对应的文件

4.3 读写文件的设计

  • 将指定文件中的内容读到String中
  • 将String中的内容写到指定文件中

4.4 数据库设计

需要保存题目id、标题、难易程度、描述信息、模板代码和测试用例

4.5 JDBC工具类

  • 获取数据库连接
  • 释放资源

4.6 题目增删查的设计

4.6.1 查

用sql语句直接去数据库中查询

  • 获取题目列表
    • 获取所有题目id、标题、难易程度
  • 获取一道题目详细信息
    • 获取指定id题目的id、标题、难易程度、描述信息、模板代码、测试用例;测试用例不提供给客户端。

4.6.2 增

插入题目的id、标题、难易程度、描述信息、模板代码、测试用例信息到数据库中

4.6.3 删

删除数据库中指定id的行

4.7 题目信息API

4.7.1 获取题目列表页

系统流程橘色部分对应模块

4.7.2 获取题目详情页

系统流程黄色部分对应模块

4.8 提交运行API

系统流程绿色部分对应模块

4.9 前端设计

五、开发步骤

5.1 创建maven项目

在pom.xml文件中引入依赖包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>haozhang</groupId>
    <artifactId>online_oj</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <!-- <name>blogdemo Maven Webapp</name> -->
    <!-- FIXME change it to the project's website -->
    <!-- <url>http://www.example.com</url> -->

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <!--    servlet和tomcat版本有对应关系        -->
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--这个意思是我们只在开发阶段使用servlet,部署到tomcat上的时候就不需要了-->
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>online_oj</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

5.2 编译模块设计

5.2.1 设计类让Java代码可以执行一条指令

package compile;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 
 * 借助这个类让Java代码能够去执行一个具体的命令
 * 如 javac Test.java
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class CommandUtil {

    /**
     *
     * @param cmd 要执行的命令
     * @param stdoutFile 表示标准输出结果重定向到那个文件中  如果为null表示不需要重定向
     * @param stderrFile 表示标准错误结果重定向到哪个文件中
     * @throws IOException
     */
    public static int run(String cmd, String stdoutFile, String stderrFile) throws IOException, InterruptedException {
        //1.获取Runtime对象,Runtime对象是一个单例的
        Runtime runtime = Runtime.getRuntime();

        //2.通过Runtime中的 exec 方法来执行一个指令 相当于在命令行中执行cmd命令
        Process process = runtime.exec(cmd);

        //3.针对标准输出进行重定向
        if (stdoutFile != null) {
            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.为了确保子进程先执行完  就需要加上进程等待的逻辑
        //父进程会在这里阻塞,直到子进程执行结束,才继续往下执行
        int exitCode = process.waitFor();
        return exitCode;

    }

    public static void main(String[] args) throws IOException, InterruptedException {
        run("javac", "d:/oj/stdout.txt", "d:/oj/stderr.txt");
    }
}

5.2.2 一次编译过程中的依赖

package compile;

/**
 * 
 * 一次编译运行过程中都依赖哪写数据
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class Question {
    /**
     * 要编译和执行的代码
     */
    private String code;

    /**
     * 执行时标准输入要输入的内容
     *    我们实际上没用
     */
    private String stdin;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getStdin() {
        return stdin;
    }

    public void setStdin(String stdin) {
        this.stdin = stdin;
    }
}

5.2.3 一次编译运行过程中产生的数据

package compile;

/**
 * 一次编译运行过程中都产生了哪写数据
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class Answer {

    /**
     * 通过error来表示当前的错误类型
     * 0 表示没错
     * 1 表示编译出错
     * 2 表示运行出错
     */
    private int error;

    /**
     * 表示具体的出错原因。可能是编译错误,也可能是运行错误(异常信息)
     */
    private String reason;

    /**
     * 执行时标准输出对应的内容
     */
    private String stdout;

    /**
     * 执行时标准错误对应的内容
     */
    private String stderr;

    public int getError() {
        return error;
    }

    public void setError(int error) {
        this.error = error;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public String getStdout() {
        return stdout;
    }

    public void setStdout(String stdout) {
        this.stdout = stdout;
    }

    public String getStderr() {
        return stderr;
    }

    public void setStderr(String stderr) {
        this.stderr = stderr;
    }

    @Override
    public String toString() {
        return "Answer{" +
                "error=" + error +
                ", reason='" + reason + '\'' +
                ", stdout='" + stdout + '\'' +
                ", stderr='" + stderr + '\'' +
                '}';
    }
}

5.2.4 描述一次编译运行的过程

package compile;

import common.FileUtil;

import java.io.File;
import java.io.IOException;

/**
 * 借助这个类来描述一次编译运行的过程
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class Task {

    /**
     * 编译过程中依赖了一些临时文件,需要约定临时文件名称
     *
     * 这些临时文件就是为了把编译执行过程中涉及到的各种中间结果记录下来
     */

    /**
     * 所有临时文件都放在 tmp 中
     */
    private static final String WORK_DIR = "./tmp/";

    /**
     * 要编译的代码的类名
     */
    private static final String CLASS = "Solution";

    /**
     * 要编译的代码对应的文件名   要和类名一致
     */
    private static final String CODE = WORK_DIR + "Solution.java";

    /**
     * 标准输入对应的文件(其实也没用到)
     */
    private static final String STDIN = WORK_DIR + "stdin.txt";

    /**
     *标准输出对应的文件(编译执行的代码结果放到这个文件中)
     */
    private static final String STDOUT = WORK_DIR + "stdout.txt";

    /**
     * 标准错误对应的文件(编译执行的代码结果放到这个文件中)
     */
    private static final String STDERR = WORK_DIR + "stderr.txt";

    /**
     * 编译错误对应的文件(编译出错时的具体原因)
     */
    private static final String COMPILE_ERROR = WORK_DIR + "compile_error.txt";

    public Answer compileAndRun(Question question) throws IOException, InterruptedException {
        Answer answer = new Answer();
        //0.先创建好存放临时文件的目录
        File wordDir = new File(WORK_DIR);
        if (!wordDir.exists()) {
            wordDir.mkdirs();
        }
        //1.根据question去构造一些需要的临时文件
        FileUtil.writeFile(CODE, question.getCode());
        FileUtil.writeFile(STDIN, question.getStdin());
        //2.构造编译命令并执行
            //2.1 形如  javac -encoding UTF-8 ./tmp/Solution.java -d ./tmp/
        String cmd = String.format(
                "javac -encoding UTF-8 %s -d %s", CODE, WORK_DIR
        );
        System.out.println("编译命令:" + cmd);
        CommandUtil.run(cmd, null, COMPILE_ERROR);
            //2.2编译完成之后,判读编译是否出错,如果出错就不需要再执行
            //认为 COMPILE_ERROR 文件为空表示编译未出错,非空表示编译出错
        String compileError = FileUtil.readFile(COMPILE_ERROR);
        if (!"".equals(compileError)) {
            System.out.println("编译出错");
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }
        //3.构造运行命令并执行  形如 java -classpath /.tmp/ Solution
        //为了能让Java命令正确找到类对应的.class文件,需要指定加载路径。-classpath 选项来指定
        cmd = String.format(
            "java -classpath %s %s", WORK_DIR, CLASS
        );
        System.out.println("运行命令:" + cmd);
        CommandUtil.run(cmd, STDOUT, STDERR);
        //判断运行是否出错   查看STDERR 是否为空
        String stdErr = FileUtil.readFile(STDERR);
        if (!"".equals(stdErr)) {
            System.out.println("运行出错");
            answer.setError(2);
            answer.setReason(stdErr);
            return answer;
        }
        //4.将运行结果包装到Answer对象中
        answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
        return answer;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Question question = new Question();
        question.setCode(
                "public class Solution {\n" +
                        " public static void main(String[] args) {\n" +
                            "String s = null;\n" +
                            "System.out.println(s.length());\n" +
                        "}\n" +
                "}\n"
        );
        question.setStdin("");
        Task task = new Task();
        Answer answer = task.compileAndRun(question);
        System.out.println(answer);
    }
}

5.3 数据库设计

package common;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

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

/**
 * 6、
 * 和数据库建立连接,进一步操作数据库
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class DBUtil {
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/online_oj?characterEncoding=utf8&useSSL=true";

    private static final String USERNAME = "root";

    private static final String PASSWORD = "";
    
    private static 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;
    }

    public static Connection getConnection() {
        try {
            //内置了数据库连接池
            return getDataSource().getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void close(Connection connection, PreparedStatement statement, ResultSet result) {
        try {
            if (result != null) {
                result.close();
            }

            if (statement != null) {
                statement.close();
            }

            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

读写文件类设计

package common;

import java.io.*;

/**
 * 工具类,方便读写文件
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class FileUtil {
    /**
     * 读文件:一下把整个文件内容读到String中
     * @param filePath 表示要从哪读数据
     * @return
     */
    public static String readFile(String filePath) {
        //当涉及到编译错误,标准输出结果等文件内容都是文本文件。此处使用字符流方式来实现
        //try() ()中的内容是可以被自动关闭的
        try(FileReader fileReader = new FileReader(filePath);
            BufferedReader br = new BufferedReader(fileReader)) {
            StringBuilder sb = new StringBuilder();
            //按行读取文件内容
            String line = "";
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 写文件:一下把String的内容写到指定文件中
     * @param filePath 表示要把数据写到哪个文件中
     * @param content 表示要写的文件内容
     */
    public static void writeFile(String filePath, String content) {
        try (FileWriter fw = new FileWriter(filePath)) {
            fw.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.4 题目增删查设计

5.4.1 Problem对象设计

package problem;

/**
 * 每个problem对象就对应到oj_table中的一条记录
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class Problem {

    private int id;

    private String title;

    private String level;

    private String description;

    private String templateCode;

    private String testCode;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getTemplateCode() {
        return templateCode;
    }

    public void setTemplateCode(String templateCode) {
        this.templateCode = templateCode;
    }

    public String getTestCode() {
        return testCode;
    }

    public void setTestCode(String testCode) {
        this.testCode = testCode;
    }

    @Override
    public String toString() {
        return "Problem{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", level='" + level + '\'' +
                ", description='" + description + '\'' +
                ", templateCode='" + templateCode + '\'' +
                ", testCode='" + testCode + '\'' +
                '}';
    }
}

5.4.2 Problem对象操作设计

package problem;

import common.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 数据访问层
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class ProblemDAO {

    /**
     * 获取所有题目信息
     */
    public List<Problem> selectAll() {
        List<Problem> ret = new ArrayList<>();
        //1.获取数据库连接
        Connection connection = DBUtil.getConnection();
        //2.拼装sql语句
        String sql = "select * from oj_table";
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            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"));
//                problem.setDescription(resultSet.getString("description"));
//                problem.setTemplateCode(resultSet.getString("templateCode"));
//                problem.setTestCode(resultSet.getString("testCode"));
                ret.add(problem);
            }
            return ret;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5.关闭连接
            DBUtil.close(connection, statement, resultSet);
        }

        return null;
    }

    /**
     * 获取指定id题目信息
     * @param id
     * @return
     */
    public Problem selectOne(int id) {
        //1.建立连接
        Connection connection = DBUtil.getConnection();
        //2.拼装sql语句
        String sql = "select * from oj_table where id = ?";
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.prepareStatement(sql);
            statement.setInt(1, id);
            //3.执行sql语句
            resultSet = statement.executeQuery();
            //4.处理结果集
            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 {
            //5.关闭连接
            DBUtil.close(connection, statement, resultSet);
        }
        return null;
    }

    /**
     * 新增一个题目到数据库中
     * @param problem
     */
    public void insert(Problem problem) {
        //1.获取连接
        Connection connection = DBUtil.getConnection();
        //2.拼装sql语句
        String sql = "insert into oj_table values (null, ?, ?, ?, ?, ?)";
        //3.执行sql语句
        PreparedStatement statement = null;
        try {
            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());
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //4.关闭连接
            DBUtil.close(connection, statement, null);
        }
    }

    /**
     * 删除指定id题目
     * @param id
     */
    public void delete(int id) {
        //1.建立连接
        Connection connection = DBUtil.getConnection();
        //2.拼接sql
        String sql = "delete from oj_table where id = ?";
        //3.执行sql
        PreparedStatement statement = null;
        try {
            statement.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {

            //4.关闭连接
            DBUtil.close(connection, statement, null);
        }
    }

    public static void main(String[] args) {
        String s11 = "abd";
        String s22 = "db2";
        StringBuilder sb3 = new StringBuilder(s11);
        sb3.reverse();
        Problem problem = new Problem();
        problem.setTitle("各位相加");
        problem.setLevel("简单");
        problem.setDescription("给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。\n" +
                "\n" +
                "示例:\n" +
                "\n" +
                "输入: 38\n" +
                "输出: 2 \n" +
                "解释: 各位相加的过程为:3 + 8 = 11, 1 + 1 = 2。 由于 2 是一位数,所以返回 2。\n" +
                "\n");
        problem.setTemplateCode("public class Solution {\n" +
                "    public int addDigits(int num) {\n" +
                "\n" +
                "    }\n" +
                "}");
        problem.setTestCode(
                        "public static void main(String[] args) {\n" +
                        "    Solution s = new Solution();\n" +
                        "    if (s.addDigits(1) == 1 && s.addDigits(38) == 2) {\n" +
                        "        System.out.println(\"Test OK\");\n" +
                        "    } else {\n" +
                        "        System.out.println(\"Test Failed\");\n" +
                        "    }\n" +
                        "}\n");

        ProblemDAO problemDAO = new ProblemDAO();
        problemDAO.insert(problem);
        System.out.println("Insert OK");


        Problem problem1 = new Problem();
        problem1.setTitle("数字颠倒");
        problem1.setLevel("简单");
        problem1.setDescription("输入一个整数,将这个整数以字符串的形式逆序输出" +
                "程序不考虑负数的情况,若数字含有0,则逆序形式也含有0,如输入为100,则输出为001\n" +
                "\n" +
                "示例:\n" +
                "\n" +
                "输入: 1516000\n" +
                "输出: 0006151 \n" +
                "解释: 将这个整数以字符串的形式逆序输出\n" +
                "\n");
        problem1.setTemplateCode("public class Solution {\n" +
                "    public String reverseNumber(int num) {\n" +
                "\n" +
                "    }\n" +
                "}");
        problem1.setTestCode(
                "public static void main(String[] args) {\n" +
                        "    Solution s = new Solution();\n" +
                        "    int num = 1516000000;        \n" +
                        "    String str = \"0000006151\"; \n" +
                        "    if (s.reverseNumber(num).equals(str)) {\n" +
                        "        System.out.println(\"Test OK\");\n" +
                        "    } else {\n" +
                        "        System.out.println(\"Test Failed\");\n" +
                        "    }\n" +
                        "}\n");

        ProblemDAO problemDAO1 = new ProblemDAO();
        problemDAO1.insert(problem1);
        System.out.println("Insert OK");


        Problem problem2 = new Problem();
        problem2.setTitle("字符串反转");
        problem2.setLevel("简单");
        problem2.setDescription("写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)\n" +
                "\n" +
                "示例:\n" +
                "\n" +
                "输入: abcd\n" +
                "输出: dcba \n" +
                "解释: 输出该字符串反转后的字符串\n" +
                "\n");
        problem2.setTemplateCode("public class Solution {\n" +
                "    public String reverseStr(String str) {\n" +
                "\n" +
                "    }\n" +
                "}");
        problem2.setTestCode(
                "public static void main(String[] args) {\n" +
                        "    Solution s = new Solution();\n" +
                        "    String str = \"abcdefghaaa\";\n" +
                        "    String rstr = \"aaahgfedcba\";\n" +
                        "    if (s.reverseStr(str).equals(rstr)) {\n" +
                        "        System.out.println(\"Test OK\");\n" +
                        "    } else {\n" +
                        "        System.out.println(\"Test Failed\");\n" +
                        "    }\n" +
                        "}\n");

        ProblemDAO problemDAO2 = new ProblemDAO();
        problemDAO2.insert(problem2);
        System.out.println("Insert OK");



//        //2.测试selectAll
//        ProblemDAO problemDAO = new ProblemDAO();
//        List<Problem> problems = problemDAO.selectAll();
//        System.out.println("selectAll:" + problems);
//
//        //3.测试selectOne
//        ProblemDAO problemDAO = new ProblemDAO();
//        Problem problem = problemDAO.selectOne(1);
//        System.out.println(problem);

    }


}

5.5 api设计

5.5.1 题目信息API

package api;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import problem.Problem;
import problem.ProblemDAO;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 *
 * @author haozhang
 * @date 2020/09/04
 */
public class ProblemServlet extends HttpServlet {
    private Gson gson = new GsonBuilder().create();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String id = req.getParameter("id");
        if (id == null || "".equals(id)) {
            //没有id这个参数, 执行查找全部
            selectAll(resp);
        } else {
            //存在id这个参数,执行查找指定题目id
            selectOne(Integer.parseInt(id), resp);
        }
    }

    private void selectOne(int problemId, HttpServletResponse resp) throws IOException {
        resp.setContentType("application/json; charset=utf-8");
        ProblemDAO problemDAO = new ProblemDAO();
        Problem problem = problemDAO.selectOne(problemId);
        //测试用例不应该被显示出来,手动改掉
        problem.setTestCode("");
        String json = gson.toJson(problem);
        resp.getWriter().write(json);
    }

    private void selectAll(HttpServletResponse resp) throws IOException {
        //ContentType 描述了body的数据类型是啥样的
        //常见取值
        //html:text/html
        //图片:image/png  image/jpg
        //json:application/json
        //css:text/css
        //javascript:application/javascript
        resp.setContentType("application/json; charset=utf-8");
        ProblemDAO problemDAO = new ProblemDAO();
        List<Problem> problems = problemDAO.selectAll();

        //把结果组织成json结构
        //注意:需要把problems中的某些字段去掉
        String jsonString = gson.toJson(problems);
        resp.getWriter().write(jsonString);

    }
}

5.5.2 提交运行API

package api;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import compile.Answer;
import compile.Question;
import compile.Task;
import problem.Problem;
import problem.ProblemDAO;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

/**
 *
 * @author haozhang
 * @date 2020/09/06
 */
public class CompileServlet extends HttpServlet {
    private Gson gson = new GsonBuilder().create();

    /**
     * 创建两个辅助的类,完成请求解析和响应构建
     *
     * 用来辅助解析 body 中的数据请求
     */

    static class CompileRequest {
        /**
         * 表示题目id
         */
        private int id;

        /**
         * 表示用户提交的代码
         */
        private String code;

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }
    }

    /**
     * 用于构造最终响应数据
     */
    static class CompileResponse {
        /**
         * 表示是否成功
         */
        private int ok;

        /**
         *
         */
        private String reason;

        private String stdout;

        public int getOk() {
            return ok;
        }

        public void setOk(int ok) {
            this.ok = ok;
        }

        public String getReason() {
            return reason;
        }

        public void setReason(String reason) {
            this.reason = reason;
        }

        public String getStdout() {
            return stdout;
        }

        public void setStdout(String stdout) {
            this.stdout = stdout;
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //1.读取 请求 的 body 的所有数据
        String body = readBody(req);

        //2.按照API约定的格式来解析json数据,得到CompileRequest对象
        CompileRequest compileRequest = gson.fromJson(body, CompileRequest.class);

        //3.按照 id 从数据库中读取出对应的测试用例代码
        ProblemDAO problemDAO = new ProblemDAO();
        Problem problem = problemDAO.selectOne(compileRequest.getId());

        //得到该题目的测试代码
        String testCode = problem.getTestCode();

        //得到改题目的用户输入的代码
        String requestCode = compileRequest.getCode();

        //4.把用户输入的代码和测试用例进行组装,组装成一个完整的可以运行编译的代码
        String finalCode = mergeCode(requestCode, testCode);

        //5.创建task对象对刚才拼装的代码进行编译运行
        Question question = new Question();
        question.setCode(finalCode);
        question.setStdin("");
        Task task = new Task();
        Answer answer = null;
        try {
            answer = task.compileAndRun(question);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //6.把运行结果构造成响应数据写回给客户端
        CompileResponse compileResponse = new CompileResponse();
        compileResponse.setOk(answer.getError());
        compileResponse.setReason(answer.getReason());
        compileResponse.setStdout(answer.getStdout());
        String jsonString = gson.toJson(compileResponse);
        resp.setContentType("application/json; charset=utf-8");
        resp.getWriter().write(jsonString);

    }

    /**
     * 把testCode中的main方法内容嵌入到requestCode中
     * @param requestCode 用户的代码
     * @param testCode 测试用例代码
     * @return
     */
    private String mergeCode(String requestCode, String testCode) {
        //合并之前考虑清楚这两部分的代码都是什么样的
        //1.先找到requestCode中最后一个 }
        //2.把requestCode中最后一个 } 删除之后,再把testCode内容拼接上
        //3.拼接完之后再补一个 }
        int pos = requestCode.lastIndexOf('}');
        if (pos == -1) {
            return null;
        }

        return requestCode.substring(0, pos) + testCode + "\n}";
    }

    private String readBody(HttpServletRequest req) {
        //body 的长度在 header 中的一个 Content-Length 字段中
        //contentLength 的单位就是字节
        int contentLength = req.getContentLength();
        byte[] buf = new byte[contentLength];
        try (InputStream is = req.getInputStream()) {
            is.read(buf, 0, contentLength);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return new String(buf);
    }
}

六、webapp设计

七、项目目录

基于Java开发的在线OJ项目

八、项目改进

1、引入用户管理(注册登录、每个人都做了哪写题)
2、引入题目在线录入
3、代码编辑框使用ace.js
4、实现更严格的安全校验(假设用户在代码中写了一个删除所有文件的操作)

九、项目源码

项目github地址

该项目最终是打包放在云服务器上的,关于云服务器配置安装相关环境的一些操作可以参考这篇博客!

本文地址:https://blog.csdn.net/weixin_44780625/article/details/109264232