题目管理模块
数据库设计
根据LeetCode刷题的页面来看,对于题目表的设计应该有以下关键字:主键、标题、难度级别、题目描述、题目模板代码和测试用例。
create table oj_table(
id int primary key auto_increment, //主键
title varchar(50), //标题
level varchar(50), //难度级别
description text, //题目描述
templateCode text, //题目模板代码
testCode text //测试用例
);
数据库表对应表示关系:
注:主要要思考的是测试用例,其实测试用例都是我们自己去编写判定的,不同的题目,给定不同的测试用例,不过也并不难,无非就是if/else的判定。
数据库连接
其实这里用mybatis非常方便,但是就当做复习,就直接用原生jdbc了。
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/system_oj?characterEncoding=utf8&useSSL=true";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
private static volatile DataSource dataSource = null;
private DBUtil(){}
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 getConection(){
try {
return getDataSource().getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
try {
if(resultSet != null){
resultSet.close();
}
if(statement != null){
statement.close();
}
if(connection != null){
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
注:主要注意两点,一:获取dataSource是单例的,用到双重校验锁;二:关闭资源是反向关闭。
建立Model
与数据库对应实体类
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 + '\'' +
'}';
}
}
数据访问层DAO
jdbc相关操作,也就不做说明了。
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;
public class ProblemDAO {
public List<Problem> selectAll(){
List<Problem> result = new ArrayList<>();
Connection connection = DBUtil.getConection();
String sql = "select * from oj_table";
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement(sql);
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"));
problem.setDescription(resultSet.getString("description"));
problem.setTemplateCode(resultSet.getString("templateCode"));
problem.setTestCode(resultSet.getString("testCode"));
result.add(problem);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(connection,statement,resultSet);
}
return result;
}
public Problem selectOne(int id){
Connection connection = DBUtil.getConection();
String sql = "select * from oj_table where id = ?";
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement(sql);
statement.setInt(1,id);
resultSet = statement.executeQuery();
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;
}
public void insert(Problem problem){
Connection connection = DBUtil.getConection();
PreparedStatement statement = null;
String sql = "insert into oj_table values (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());
System.out.println("insert: "+statement);
statement.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(connection,statement,null);
}
}
public void delete(int id){
Connection connection = DBUtil.getConection();
String sql = "delete from oj_table where id = ?";
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(sql);
statement.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(connection,statement,null);
}
}
public static void main(String[] args) {
ProblemDAO problemDAO = new ProblemDAO();
Problem problem = new Problem();
problem.setTitle("两数之和");
problem.setDescription("给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。\n" +
"\n" +
"你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。\n" +
"\n" +
"你可以按任意顺序返回答案。\n" +
"\n" +
"来源:力扣(LeetCode)\n" +
"链接:https://leetcode-cn.com/problems/two-sum\n" +
"著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。");
problem.setLevel("简单");
problem.setTemplateCode("class Solution {\n" +
" public int[] twoSum(int[] nums, int target) {\n" +
"\n" +
" }\n" +
"}");
problem.setTestCode("public class TestCode {\n" +
" public static void main(String[] args) {\n" +
" Solution solution = new Solution();\n" +
" int[] re = null;\n" +
" int[] arr1 = new int[]{2,7,11,15};\n" +
" int target1 = 9;\n" +
" int[] arr2 = new int[]{3,2,4};\n" +
" int target2 = 6;\n" +
" if((re = solution.twoSum(arr1,target1)) != null && re.length == arr1.length){\n" +
" for (int i = 0; i < re.length; i++) {\n" +
" if(re[i] != arr1[i]){\n" +
" System.out.println(\"{2,7,11,15},9 不正确\");\n" +
" }\n" +
" }\n" +
" }else if((re = solution.twoSum(arr2,target2)) != null && re.length == arr2.length){\n" +
" for (int i = 0; i < re.length; i++) {\n" +
" if(re[i] != arr2[i]){\n" +
" System.out.println(\"{3,2,4},6 不正确\");\n" +
" }\n" +
" }\n" +
" }else{\n" +
" System.out.println(\"用例全部通过\");\n" +
" }\n" +
" }\n" +
"}");
problemDAO.insert(problem);
System.out.println(problem);
}
}
服务器API
JSON
准备工作:使用json来辅助描述API,所以就需要将对象转换为json格式,以及将json转换为对象。这里我们使用Gson来帮我们完成该操作。
导包
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
它已经帮我们封装好了,只需要使用它的两个API就可以使用了。构建Gson对象是new GsonBuilder,调用create方法。对象转换为JSON格式,是调用toJson()方法,JSON格式转换为对象是调用fromJson()方法,需要传入字符串和类对象。
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import problem.Problem;
public class TestJson {
public static void main(String[] args) {
Problem problem = new Problem();
problem.setId(1);
problem.setLevel("简单");
problem.setDescription("描述");
problem.setTemplateCode("模板");
problem.setTestCode("null");
Gson gson = new GsonBuilder().create();
String s = gson.toJson(problem);
System.out.println(s);
Problem problem1 = gson.fromJson(s,Problem.class);
System.out.println(problem1);
}
}
前后端交互API
首先我们需要对需求进行设定
- 需要有个主页,包含了OJ题目列表,则需要有一个API能查到所有的题目信息(标题,id,难度)
- 点击主页中的某个题目标题,会进入详情页,则需要有一个API查到题目的详细信息(id,标题,难度,题目描述,代码模板)
- 点击提交,会把当前编辑框中的代码提交到服务器上并编译运行,则需要有一个API提供编译运行功能。
针对于前两个需求,可以直接用get请求来调用对应的problemDAO中的方法,如果直接访问problem则代表查询全部,如果有id参数,则代表查询id对应的单条数据。这里直接使用了Servlet来写,需要注意的是字符集编码格式,所以得设置一下。
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
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.util.List;
public class ProblemServlet extends HttpServlet {
Gson gson = new GsonBuilder().create();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter("id");
if(id == null || "".equals(id)){
selectAll(resp);
}else {
selectOne(Integer.parseInt(id),resp);
}
}
private void selectOne(int id, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json; charset=utf-8");
ProblemDAO problemDAO = new ProblemDAO();
Problem problem = problemDAO.selectOne(id);
problem.setTestCode("");
String s = gson.toJson(problem);
resp.getWriter().write(s);
}
private void selectAll(HttpServletResponse resp) throws IOException {
resp.setContentType("application/json; charset=utf-8");
ProblemDAO problemDAO = new ProblemDAO();
List<Problem> problems = problemDAO.selectAll();
String jsonString = gson.toJson(problems);
resp.getWriter().write(jsonString);
}
}
小技巧课堂
我是直接将war包部署到云服务器上的,如果是手动部署,需要先用maven工具进行clean、package,之后再上传到云服务器上。有个更简便的方法是使用Alibaba Cloud View插件。
之后,再需要打包并上传进行部署测试,就可以直接点击右上角运行了
Tomcat中出现404怎么办?
如果访问的是一个静态资源,检查war包中是否包含对应的文件,名字拼写,路径拼写是否正确。
如果访问的是一个servlet,检查web.xml配置是否正确,进一步可以查看Tomcat的日志,看看是否存在一些异常信息。
需求三:编译运行代码
需求三的请求与响应都应该有对应的格式,如下图所示。
这里我们使用post方式请求,为此新增一个CompileServlet,根据上面各式需求,在这个Servlet中增添两个静态内部类以方便使用。
整体的请求与响应过程:
- 首先读取请求的body所有数据
- 按照API约定的格式来解析JSON数据,得到CompileRequest对象
- 按照id从数据库中读取出对应的测试用例代码
- 把用户输入的代码和测试用例代码进行组装,组装成一个完整的可以编译运行的代码
- 创建Task对象,对组装好的代码进行编译运行
- 把运行结果构造成响应数据,并写回客户端中
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;
public class CompileServlet extends HttpServlet {
private Gson gson = new GsonBuilder().create();
static 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;
}
}
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 {
String body = 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);
question.setStdin("");
Answer answer = null;
try {
answer = task.compileAndRun(question);
} catch (InterruptedException e) {
e.printStackTrace();
}
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);
}
private String merge(String requestCode, String testCode) {
if(requestCode == null){
return null;
}
int i = requestCode.lastIndexOf('}');
return requestCode.substring(0,i)+testCode+"\n}";
}
private String readBody(HttpServletRequest req) {
int contentLength = req.getContentLength();
byte[] buf = new byte[contentLength];
try(InputStream inputStream = req.getInputStream()){
inputStream.read(buf,0,contentLength);
}catch (IOException e) {
e.printStackTrace();
}
return new String(buf);
}
}
最后还需要针对CompileServlet配置映射
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">
<servlet>
<servlet-name>ProblemServlet</servlet-name>
<servlet-class>api.ProblemServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ProblemServlet</servlet-name>
<url-pattern>/problem</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>CompileServlet</servlet-name>
<servlet-class>api.CompileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CompileServlet</servlet-name>
<url-pattern>/Comp</url-pattern>
</servlet-mapping>
</web-app>
到目前为止,后端逻辑基本就通了,那我们该如何去测试呢?get请求可以直接用浏览器访问URL进行测试,post请求可以使用postman工具来帮助我们,下面就是测试的结果。
|