项目Gitee源码链接:https://gitee.com/zx201014/ojproject 项目GitHub源码链接:https://github.com/zx04180521/OJProject
项目简介
在线OJ系统就是大家平时刷题的网站,比如力扣,我的这个系统就是模仿力扣网站做了一个简单的OJ系统,不过目前只实现了部分功能,比如展示题目列表,查看题目详情,编写代码,提交运行
1.项目实现步骤
1.1数据库的创建
这个项目的数据库比较简单,只有一张表:题目表,其中表的字段有题目id、题目名称、题目难度、题目描述、题目模板代码、题目测试代码。
解释:题目的模板代码指的是用户可以看到的代码,也就是给用户提供了一个实现的函数,题目的测试代码指的是用户提交代码之后,进行运行校验结果时的测试代码,也就是主函数
创建数据库代码:
create database if not exists ojProject;
use ojProject;
create table oj_table(
id int primary key auto_increment,
title varchar(50),
level varchar(20),
description varchar(4096),
templateCode varchar(4096),
testCode varchar(4096)
);
1.2创建数据库工具类Util
在这个类中,能够向其他类提供建立数据库连接的接口以及关闭数据库连接的接口
public class DBUtil {
public static String URL = "jdbc:mysql://localhost:3306/ojProject?characterEncoding=utf8&useSSL=true";
public static String USER = "root";
public static String PASSWORD = "123456";
public static volatile MysqlDataSource dataSource = null;
public static MysqlDataSource getDataSource() {
if (dataSource == null) {
synchronized (DBUtil.class) {
if (dataSource == null) {
dataSource = new MysqlDataSource();
dataSource.setURL(URL);
dataSource.setUser(USER);
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 resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
1.3创建能够实现运行命令的类CommandUtil
在这个类中,对外提供了一个run(String cmd, String stdoutFile, String stderrFile)方法。 其中第一个参数cmd表示需要执行的指令,比如javac、java等等,运行这些指令之后会有标准错误以及标准输出的内容,比如运行java指令之后的标准输出指的是程序运行的结果,对应的标准错误指的是程序运行出错的原因。 第二个参数表示运行指令之后将运行的标准输出存储到参数指定的文件中 第三个参数表示运行指令之后将运行的标准错误存储到参数指定的文件中
public class CommandUtil {
public static int run(String cmd, String stdoutFile,
String stderrFile) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(cmd);
if (stdoutFile != null) {
InputStream stdoutFrom = process.getInputStream();
FileOutputStream stdoutTo = new FileOutputStream(stdoutFile);
while (true) {
int c = stdoutFrom.read();
if (c == -1) {
break;
}
stdoutTo.write(c);
}
stdoutFrom.close();
stdoutTo.close();
}
if (stderrFile != null) {
InputStream stderrFrom = process.getErrorStream();
FileOutputStream stderrTo = new FileOutputStream(stderrFile);
while (true) {
int c = stderrFrom.read();
if (c == -1) {
break;
}
stderrTo.write(c);
}
stderrFrom.close();
stderrTo.close();
}
int exitCode = process.waitFor();
return exitCode;
}
}
1.4创建生成目录以及构造指令的类Task
在这个类中,我们需要在构造方法中每次生成独立目录,目的就是为了多次执行指令两两之间不会互相影响 生成随机目录我用的方法是UUID.randomUUID() 1.这个类涉及到最终的编译运行,左移方法参数中需要传进来最终需要编译运行的代码,这个代码通过自定义的类Quesition来表示,里面只要一个字符串属性 code 表示最终的代码,还有对应的get,set方法 2. 将最终的响应结果也包装成一个Answer类,属性有整型的错误码error,0表示运行成功,1表示编译出错,2表示运行出错,还有属性reason 表示错误的原因,stdout标准输出的内容。
Quesition类的代码:
public class Question {
private String code;
public void setCode(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
Answer类:
package compile;
public class Answer {
private int error;
private String reason;
private String stdout;
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;
}
@Override
public String toString() {
return "Answer{" +
"error=" + error +
", reason='" + reason + '\'' +
", stdout='" + stdout + '\'' +
'}';
}
}
Tast类
package compile;
import Util.FileUtil;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
public class Task {
private String WORK_DIR;
private String CLASS = "Solution";
private String CODE;
private String STDOUT;
private String STDERR;
private String COMPILE_ERROR;
public Task(){
WORK_DIR="./tmp/"+ UUID.randomUUID().toString()+"/";
CODE=WORK_DIR+CLASS+".java";
STDOUT=WORK_DIR+"stdout.txt";
STDERR=WORK_DIR+"stderr.txt";
COMPILE_ERROR=WORK_DIR+"compile_error.txt";
}
public Answer compileAndRun(Question question) throws IOException, InterruptedException {
Answer answer=new Answer();
File file=new File(WORK_DIR);
if(!file.exists()){
file.mkdirs();
}
FileUtil.writeFile(CODE,question.getCode());
String compileCmd=String.format("javac -encoding utf-8 %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.setError(1);
answer.setReason(compileError);
return answer;
}
String runCmd=String.format("java -classpath %s %s",WORK_DIR,CLASS);
System.out.println(runCmd);
CommandUtil.run(runCmd,STDOUT,STDERR);
String runErr=FileUtil.readFile(STDERR);
if(!runErr.equals("")){
answer.setError(2);
answer.setReason(runErr);
return answer;
}
String stdOut=FileUtil.readFile(STDOUT);
answer.setError(0);
answer.setStdout(stdOut);
return answer;
}
public static void main(String[] args) throws IOException, InterruptedException {
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\");\n" +
" }\n" +
"}");
System.out.println(task.compileAndRun(question));
}
}
1.5创建数据库的实体类Problem以及数据库操作类ProblemDAO
实体类Problem就是数据库表对应的类,参数就是表中的属性 数据库操作类ProblemDAO就是JDBC代码,主要有插入操作,删除操作,查询一个题目详情操作,操作所有题目列表操作
ProblemDAO代码
public class ProblemDAO {
public void insert(Problem problem){
Connection connection= DBUtil.getConnection();
PreparedStatement statement=null;
try {
String sql="insert into oj_table values(null,?,?,?,?,?)";
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 {
DBUtil.close(connection,statement,null);
}
}
public boolean delete(int problemId){
Connection connection=DBUtil.getConnection();
PreparedStatement statement=null;
String sql="delete from oj_table where id=?";
int res=0;
try {
statement=connection.prepareStatement(sql);
statement.setInt(1,problemId);
res=statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection,statement,null);
return res>0;
}
}
public List<Problem> selectAll(){
List<Problem> problems=new ArrayList<>();
Connection connection=DBUtil.getConnection();
PreparedStatement statement=null;
String sql="select id,title,level from oj_table";
try {
statement=connection.prepareStatement(sql);
ResultSet resultSet=statement.executeQuery();
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,null);
}
return problems;
}
public Problem selectOne(int problemId){
Connection connection=DBUtil.getConnection();
PreparedStatement statement=null;
ResultSet resultSet=null;
String sql="select * from oj_table where id=?";
try {
statement=connection.prepareStatement(sql);
statement.setInt(1,problemId);
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 throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
Problem实体类代码
package problem;
public class Problem {
private int id;
private String title;
private String level;
private String description;
private String templateCode;
@Override
public String toString() {
return "Problem{" +
"id=" + id +
", title='" + title + '\'' +
", level='" + level + '\'' +
", description='" + description + '\'' +
", templateCode='" + templateCode + '\'' +
", testCode='" + testCode + '\'' +
'}';
}
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;
}
}
1.6 创建Servlet代码
- 请求的实体类 CompileRequest,主要就是将从前端请求的数据包装成一个类对象,属性有题目的id,请求的代码code
- 响应的实体类CompileResponse,这个和刚才的Answer一样的功能,主要就是为了区分
- CompileServlet,主要功能就是接受前端数据并解析,第一步就是解析前端数据的body并创建成CompileRequest对象,第二步根据题目的id得到题目的测试代码,第三步将测试代码和前端请求的代码进行字符串拼接,形成完整的代码并构造成Question对象,第四步创建Task类,调用run()方法,将构造的Question作为参数传入,run的返回值为Answer对象,在通过这个对象构造CompileResponse对象,序列化为json格式字符串返回给前端页面
- ProblemServlet数据库操作的Servlet类
CompileRequest
package api;
public class CompileRequest {
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;
}
}
CompileResponse
package api;
public class CompileResponse {
private int error;
private String reason;
private String stdout;
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;
}
}
CompileServlet
package api;
import Util.HttpUtil;
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.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/compile")
public class CompileServlet extends HttpServlet {
private Gson gson=new GsonBuilder().create();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String body= HttpUtil.readBody(req);
CompileRequest compileRequest=gson.fromJson(body,CompileRequest.class);
ProblemDAO problemDAO=new ProblemDAO();
Problem problem=problemDAO.selectOne(compileRequest.getId());
String testCode=problem.getTestCode();
String requestCode=compileRequest.getCode();
String finalCode=merge(requestCode,testCode);
Task task=new Task();
Question question=new Question();
question.setCode(finalCode);
Answer answer=null;
try {
answer=task.compileAndRun(question);
} catch (InterruptedException e) {
e.printStackTrace();
}
CompileResponse compileResponse=new CompileResponse();
compileResponse.setError(answer.getError());
compileResponse.setReason(answer.getReason());
compileResponse.setStdout(answer.getStdout());
String respString=gson.toJson(compileResponse);
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write(respString);
}
private String merge(String requestCode,String testCode){
int pos=requestCode.lastIndexOf("}");
if(pos==-1){
return null;
}
return requestCode.substring(0,pos)+testCode+"}";
}
}
ProblemServlet
package api;
import Util.HttpUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import problem.Problem;
import problem.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 Gson gson=new GsonBuilder().create();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("application/json;charset=utf-8");
String id=req.getParameter("id");
if(id==null||id.equals("")){
selectAll(resp);
}else{
selectOne(Integer.parseInt(id),resp);
}
}
private void selectOne(int parseInt, HttpServletResponse resp) throws IOException {
ProblemDAO problemDAO=new ProblemDAO();
Problem problem=problemDAO.selectOne(parseInt);
String respString=gson.toJson(problem);
resp.getWriter().write(respString);
}
private void selectAll(HttpServletResponse resp) throws IOException {
ProblemDAO problemDAO=new ProblemDAO();
List<Problem> problems=problemDAO.selectAll();
String respString=gson.toJson(problems);
resp.getWriter().write(respString);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String body= HttpUtil.readBody(req);
Problem problem=gson.fromJson(body,Problem.class);
ProblemDAO problemDAO=new ProblemDAO();
problemDAO.insert(problem);
req.setCharacterEncoding("utf-8");
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{\"ok\":1}");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("application/json;charset=utf-8");
String id=req.getParameter("id");
if(id==null||id.equals("")){
resp.getWriter().write("{\"ok\":0,\"reason\":\"id不存在\"}");
return;
}
ProblemDAO problemDAO=new ProblemDAO();
problemDAO.delete(Integer.parseInt(id));
resp.getWriter().write("{\"ok\":1}");
}
}
1.7代码中用到的一些文件操作工具类
文件读写操作:FileUtil
package Util;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileUtil {
public static void writeFile(String filePath,String content){
FileOutputStream fileOutputStream=null;
try {
fileOutputStream=new FileOutputStream(filePath);
fileOutputStream.write(content.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String readFile(String filePath){
StringBuffer stringBuffer=new StringBuffer();
FileInputStream fileInputStream=null;
try {
fileInputStream=new FileInputStream(filePath);
while(true){
int c=fileInputStream.read();
if(c==-1){
break;
}
stringBuffer.append((char)c);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return stringBuffer.toString();
}
}
读取请求body数据操作HttpUtil
package Util;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class HttpUtil {
public static String readBody(HttpServletRequest req) throws UnsupportedEncodingException {
int contentLength=req.getContentLength();
byte[] buf=new byte[contentLength];
ServletInputStream inputStream=null;
try {
inputStream =req.getInputStream();
inputStream.read(buf);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new String(buf,"utf-8");
}
}
2 单元测试
2.1插入数据测试
插入题目无重复字符的最长子串 代码:
@Test
public void insert() {
Problem problem = new Problem();
problem.setTitle("无重复字符的最长子串");
problem.setLevel("中等");
problem.setDescription("给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。\n" +
"\n" +
" \n" +
"\n" +
"示例 1:\n" +
"\n" +
"输入: s = \"abcabcbb\"\n" +
"输出: 3 \n" +
"解释: 因为无重复字符的最长子串是 \"abc\",所以其长度为 3。\n" +
"示例 2:\n" +
"\n" +
"输入: s = \"bbbbb\"\n" +
"输出: 1\n" +
"解释: 因为无重复字符的最长子串是 \"b\",所以其长度为 1。\n" +
"示例 3:\n" +
"\n" +
"输入: s = \"pwwkew\"\n" +
"输出: 3\n" +
"解释: 因为无重复字符的最长子串是 \"wke\",所以其长度为 3。\n" +
" 请注意,你的答案必须是 子串 的长度,\"pwke\" 是一个子序列,不是子串。\n" +
"示例 4:\n" +
"\n" +
"输入: s = \"\"\n" +
"输出: 0\n" +
" \n" +
"\n" +
"提示:\n" +
"\n" +
"0 <= s.length <= 5 * 104\n" +
"s 由英文字母、数字、符号和空格组成");
problem.setTemplateCode("class Solution {\n" +
" public int lengthOfLongestSubstring(String s) {\n" +
"\n" +
" }\n" +
"}");
problem.setTestCode(" public static void main(String[] args) {\n" +
" Solution solution = new Solution();\n" +
" String str=\"pwwkew\";\n" +
" int result = solution.lengthOfLongestSubstring(str);\n" +
" if (result==3) {\n" +
" System.out.println(\"TestCase OK!\");\n" +
" } else {\n" +
" System.out.println(\"TestCase Failed! String=\\\"pwwkew\\\"\");\n" +
" }\n" +
"\n" +
" String str2=\"abcabcbb\";\n" +
" int result2 = solution.lengthOfLongestSubstring(str);\n" +
" if (result2==3) {\n" +
" System.out.println(\"TestCaseOK!\");\n" +
" } else {\n" +
" System.out.println(\"TestCase Failed! String=\\\"abcabcbb\\\"\");\n" +
" }\n" +
" }");
ProblemDAO problemDAO = new ProblemDAO();
problemDAO.insert(problem);
}
数据库表 插入题目也有可能超出字段长度的范围
2.2删除数据测试
删除题目无重复字符的最长子串 代码
@Test
public void delete() {
ProblemDAO problemDAO = new ProblemDAO();
boolean res=problemDAO.delete(10);
System.out.println(res);
}
运行结果 true 数据库表:
2.3 查询所有题目列表
@Test
public void selectAll() {
ProblemDAO problemDAO = new ProblemDAO();
List<Problem> problems = problemDAO.selectAll();
System.out.println(problems);
}
运行结果 因为查询所有题目我们只需要题目id,题目标题,题目难度三个字段,所以其他字段为空
2.4 查询题目详情
@Test
public void selectOne() {
ProblemDAO problemDAO = new ProblemDAO();
Problem problem = problemDAO.selectOne(4);
System.out.println(problem);
}
2.5 Task编译运行的测试
@Test
public void compileAndRun() throws IOException, InterruptedException {
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\");\n" +
" }\n" +
"}");
System.out.println(task.compileAndRun(question));
}
运行结果 第一行的指令javac表示编译的指令,后面的参数是.java文件的目录以及文件名,第二个参数指生成的.class文件的存放位置 第二行的指令java表示运行指令,参数表示.class文件的位置以及类名 第三行表示运行之后的结果,error=0表示运行成功,标准输出是hello,也就是我的代码的运行结果
|