Java在线OJ项目(三)、前后端交互API模块
- 1. 客户端向服务器请求所有题目 或者 单个题目
- 前端
- 获取所有题目
- 获取一个题目
- 后端
- 2. 后端读取前端提交的代码,进行编译运行,返回结果
- 前端提交代码
- 后端处理
1. 客户端向服务器请求所有题目 或者 单个题目
前端:通过problem的URL地址访问(如果没有其它参数,则是查询所有题目,如果有id参数,就是查询具体题目)
后端:返回题目的具体详情
前端
获取所有题目
<script>
// 在页面加载的时候, 尝试从服务器获取题目列表. 通过 ajax 的方式来进行获取
function getProblems() {
// 1. 先通过 ajax 从服务器获取到题目列表.
$.ajax({
url: "problem",
type: "GET",
success: function(data, status) {
// data 是响应的 body, status 是响应的状态码
// 2. 把得到的响应数据给构造成 HTML 片段
makeProblemTable(data);
}
})
}
// 通过这个函数来把数据转换成 HTML 页面片段
function makeProblemTable(data) {
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>
获取一个题目
<script>
// 通过 ajax 从服务器获取到题目的详情
function getProblem() {
// 1. 通过 ajax 给服务器发送一个请求
$.ajax({
url: "problem" + location.search,
type: "GET",
success: function (data, status) {
makeProblemDetail(data);
}
})
}
function makeProblemDetail(problem) {
// 1. 获取到 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.description;
pre.appendChild(p);
problemDesc.appendChild(pre);
// 2. 把代码的模板填写到编辑框中.
// let codeEditor = document.querySelector("#codeEditor");
// codeEditor.innerHTML = problem.templateCode;
editor.setValue(problem.templateCode)
</script>
后端
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 {
//ObjectMapper类(com.fasterxml.jackson.databind.ObjectMapper)是Jackson的主要类,它可以帮助我们快速的进行各个类型和Json类型的相互转换。
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置返回的状态码 200表示成功
resp.setStatus(200);
//返回的数据类型
resp.setContentType("application/json;charset=utf8");
ProblemDAO problemDAO = new ProblemDAO();
// 尝试获取 id 参数. 如果能获取到, 说明是获取题目详情; 如果不能获取到, 说明是获取题目列表.
String idString = req.getParameter("id");
if (idString == null || "".equals(idString)) {
// 没有获取到 id 字段. 查询题目列表
List<Problem> problems = problemDAO.selectAll();
String respString = objectMapper.writeValueAsString(problems);
resp.getWriter().write(respString);
} else {
// 获取到了题目 id. 查询题目详情
Problem problem = problemDAO.selectOne(Integer.parseInt(idString));
String respString = objectMapper.writeValueAsString(problem);
resp.getWriter().write(respString);
}
}
}
2. 后端读取前端提交的代码,进行编译运行,返回结果
前端提交代码
<script>
// 3. 给提交按钮注册一个点击事件
let submitButton = document.querySelector("#submitButton");
submitButton.onclick = function () {
// 点击这个按钮, 就要进行提交. (把编辑框的内容给提交到服务器上)
let body = {
id: problem.id,
// code: codeEditor.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) {
// 编译运行没有问题, 把 stdout 显示到页面中
problemResult.innerHTML = data.stdout;
} else {
// 编译运行没有问题, 把 reason 显示到页面中
problemResult.innerHTML = data.reason;
}
}
});
}
}
</script>
后端处理
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 class CompileRequest {
public int id;
public String code;
}
static class CompileResponse {
// 约定 error 为 0 表示编译运行 ok, error 为 1 表示编译出错, error 为 2 表示运行异常(用户提交的代码异常了), 3 表示其他错误
public int error;
public String reason;
public String stdout;
}
private ObjectMapper objectMapper = new ObjectMapper();
// {
// "id": 2,
// "code": "class Solution {\n public int[] twoSum(int[] nums, int target) {\n int[] a = {0, 1};\n return a;\n }\n} "
// }
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 临时加一下这个代码, 来获取到 SmartTomcat 的工作目录
System.out.println("用户的当前工作目录: "+System.getProperty("user.dir"));
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);
if (problem == null) {
// 为了统一处理错误, 在这个地方抛出一个异常.
throw new ProblemNotFoundException();
}
// testCode 是测试用例的代码
String testCode = problem.getTestCode();
// requestCode 是用户提交的代码
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();
} catch (ProblemNotFoundException e) {
// 处理题目没有找到的异常
compileResponse.error = 3;
compileResponse.reason = "没有找到指定的题目! id=" + compileRequest.id;
} catch (CodeInValidException e) {
compileResponse.error = 3;
compileResponse.reason = "提交的代码不符合要求!";
} finally {
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. 根据这个位置进行字符串截取
String subStr = requestCode.substring(0, pos);
// 3. 进行拼接
return subStr + testCode + "\n}";
}
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");
}
}