在线编码平台

一、简介

本人的毕业设计是做一个在线编码平台,系统较为完整,集博客系统、在线编码和论坛系统于一体。系统采用B/S架构,基于SpringMVC这样一个使用MVC设计模式的轻量级Web开发框架。前端采用Vue.js和ElementUI,图表的展示使用了ECharts,文章编辑器使用了富文本编辑器mavonEditor,它是一个基于Vue的Markdown编辑器组件,代码编辑器使用了Monaco Editor,通过配置可以实现代码的高亮和自动补全。后端采用Spring Boot和MyBatis,数据库使用MySQL和Redis,Redis实现了登录的缓存。

二、系统结构

用户可以注册、登录、查看书籍、阅读文章、查看题库、阅读题目、上传代码、发布讨论、发表题解、修改个人信息、查看个人主页,并在个人主页对自己的网站数据进行管理和查看统计数据。

管理员可以管理题目,包括新增题目、增加测试用例、删除题目、删除测试用例;可以管理被举报的评论,包括话题讨论评论和问题评论,删除不合法的评论;可以管理文章,包括文章的增加、修改和删除;可以管理用户,包括是否封禁用户、查看用户的统计数据等;可以按照专业、年级查看相应的统计数据。

三、数据库设计E-R图

文章阅读模块

话题讨论模块

答题模块

 四、核心——判题模块

参考基于Java开发的在线OJ项目_:-D:)的博客-CSDN博客_在线oj系统java实现

流程图:

实现过程

(1)Service中的方法

/**
 * 编译运行代码
 * @param problemSubmit
 * @return
 */
public Result compileAndRun(ProblemSubmit problemSubmit) {
    // 保存代码
    Integer submit = problemDao.submitCode(
            problemSubmit.getUsername(),
            problemSubmit.getProblem_id(),
            problemSubmit.getLanguage(),
            problemSubmit.getCode());
    if (submit == 0) {
        return ResultFactory.buildFailResult("提交失败");
    } else {
        Problem problem = problemDao.getProblemDetail(problemSubmit.getProblem_id());
        String requestCode = problemSubmit.getCode();
        String testCode = problem.getTestCode();
        // 把用户输入的代码和测试用例进行组装,组装成一个完整的可以运行编译的代码
        String finalCode = mergeCode(requestCode, testCode);
        System.out.println("compile_and_run_code: \n" + finalCode);

        // 创建task对象对刚才拼装的代码进行编译运行
        TaskUtil taskUtil = new TaskUtil();
        ProblemSubmitResult result = null;
        try {
            result = taskUtil.compileAndRun(finalCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 查看是否有答题记录,若有则删除
        Integer exist = problemDao.ifExistAnsRes(
                problemSubmit.getUsername(),
                problemSubmit.getProblem_id());
        if (exist > 0) {
            problemDao.deleteAnsRes(
                    problemSubmit.getUsername(),
                    problemSubmit.getProblem_id());
        }

        // 保存运行结果
        Integer saveAnsRes = problemDao.saveAnsRes(
                problemSubmit.getUsername(),
                problemSubmit.getProblem_id(),
                problemSubmit.getLanguage(),
                result.getError(),
                result.getReason(),
                result.getStdout(),
                result.getStderr()
        );
        if (saveAnsRes == 0){
            return ResultFactory.buildFailResult("保存失败");
        }
        return ResultFactory.buildSuccessResult(result);
    }
}

(2)将提交的代码与测试代码进行拼接,形成完整的代码

/**
 * 把testCode中的main方法内容嵌入到requestCode中
 * @param requestCode
 * @param testCode
 * @return
 */
public String mergeCode(String requestCode, String testCode) {
    /**
     * 1.先找到requestCode中最后一个 '}' 并删除
     * 2.再拼接上testCode内容
     * 3.拼接完之后再补一个 '}'
     */
    int pos = requestCode.lastIndexOf('}');
    if (pos == -1) {
        return null;
    }
    return requestCode.substring(0, pos) + testCode + "\n}";
}

(3)FileUtil类

public class FileUtil {
    public static String readFile(String filePath) throws UnsupportedEncodingException {
        // 当涉及到编译错误,标准输出结果等文件内容都是文本文件,此处使用字符流方式来实现
        // try() ()中的内容是可以被自动关闭的
        File file = new File(filePath);
        try (FileInputStream fileInputStream = new FileInputStream(file);
             InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK");
             BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
            StringBuilder stringBuilder = new StringBuilder();
            // 按行读取文件内容
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            return stringBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void writeFile(String filePath, String content) throws UnsupportedEncodingException {
        File file = new File(filePath);
        try(FileOutputStream outputStream = new FileOutputStream(file);
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "GBK");
            BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
        ) {
            bufferedWriter.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

(4)CommandUtil类

/**
 * 借助这个类让Java代码能够去执行一个具体的指令
 * 如 javac Test.java
 */
public class CommandUtil {
    public static int run(String cmd, String stdoutFile, String stderrFile) throws IOException, InterruptedException {
        // 1.获取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;
    }
}

(5)TaskUtil类

public class TaskUtil {
    /**
     * 所有临时文件都放在 tmp 中
     */
    private static final String WORK_DIR = "src/tmp/";

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

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

    /**
     *标准输出对应的文件(编译执行的代码结果放到这个文件中)
     */
    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 ProblemSubmitResult compileAndRun(String finalCode) throws IOException, InterruptedException {
        ProblemSubmitResult problemSubmitResult = new ProblemSubmitResult();

        // 创建好存放临时文件的目录
        File wordDir = new File(WORK_DIR);
        if(!wordDir.exists()) {
            wordDir.mkdirs();
        }

        // 根据输入代码构建需要的临时文件
        FileUtil.writeFile(CODE, finalCode);

        // 构造编译命令并执行
        // 形如  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);

        // 编译完成后,判断编译是否出错,如果出错就不需要再执行
        // 认为 COMPILE_ERROR 文件为空表示编译未出错,非空表示编译出错
        String compileError = FileUtil.readFile(COMPILE_ERROR);
        if (!"".equals(compileError)) {
            System.out.println("编译出错");
            problemSubmitResult.setError(1);
            problemSubmitResult.setReason(compileError);
            return problemSubmitResult;
        }

        // 构造运行命令并执行 形如 java -classpath src/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是否为空或者STDOUT结果是否为Test Failed
        String stdErr = FileUtil.readFile(STDERR);
        String stdOut = FileUtil.readFile(STDOUT);
        if (!"".equals(stdErr) || "Test Failed".equals(stdOut)) {
            System.out.println("运行出错");
            problemSubmitResult.setError(2);
            problemSubmitResult.setReason(stdErr);
            return problemSubmitResult;
        }

        if ("Test OK".equals(stdOut)) {
            // 将运行结果包装到 ProblemSubmitResult 对象中
            problemSubmitResult.setError(0);
            problemSubmitResult.setStdout(FileUtil.readFile(STDOUT));
            return problemSubmitResult;
        }

        // 将运行结果包装到 ProblemSubmitResult 对象中
        problemSubmitResult.setError(-1);
        problemSubmitResult.setStdout("Other Wrong!");
        return problemSubmitResult;

    }

}

  五、系统展示

1、用户登录界面

 2、书籍展示界面

3、文章列表展示界面

 4、文章内容详情界面

 5、题目列表展示界面

 6、作答界面

 

 7、话题讨论列表界面

 8、话题讨论详情界面

 9、编写话题讨论界面

 10、个人主页界面

 11、管理员主页

 12、数据统计界面

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值