一、简介
本人的毕业设计是做一个在线编码平台,系统较为完整,集博客系统、在线编码和论坛系统于一体。系统采用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、数据统计界面