IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 在线OJ系统--第二章(业务层编写) -> 正文阅读

[开发测试]在线OJ系统--第二章(业务层编写)

题目管理模块

数据库设计

根据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

与数据库对应实体类

//实体类,每个Problem对象就对应到system_oj中的一条记录
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<>();
        //1. 获取数据库连接
        Connection connection = DBUtil.getConection();
        //2. 拼装SQL语句
        String sql = "select * from oj_table";
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.prepareStatement(sql);
            //3. 执行SQL语句
            resultSet = statement.executeQuery();
            //4. 遍历结果集
            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 {
            //5. 关闭释放资源
            DBUtil.close(connection,statement,resultSet);
        }
        return result;
    }

    //获取指定id的题目信息
    public Problem selectOne(int id){
        //1. 获取数据库连接
        Connection connection = DBUtil.getConection();
        //2. 拼接SQL语句
        String sql = "select * from oj_table where id = ?";
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.prepareStatement(sql);
            statement.setInt(1,id);
            //3. 执行SQL语句
            resultSet = statement.executeQuery();
            //4. 封装结果集
            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){
        //1. 获取数据库连接
        Connection connection = DBUtil.getConection();
        PreparedStatement statement = null;
        //2. 拼装SQL语句
        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);
            //3. 执行SQL语句
            statement.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            //4. 关闭释放相关资源
            DBUtil.close(connection,statement,null);
        }
    }

    //删除指定题目信息
    public void delete(int id){
        //1. 获取链接
        Connection connection = DBUtil.getConection();
        //2. 拼接SQL
        String sql = "delete from oj_table where id = ?";
        PreparedStatement statement = null;
        try {
            statement = connection.prepareStatement(sql);
            //3. 执行sql语句
            statement.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            //4. 关闭释放资源
            DBUtil.close(connection,statement,null);
        }
    }

    public static void main(String[] args) {
        ProblemDAO problemDAO = new ProblemDAO();
        //1. 验证insert 操作
        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

首先我们需要对需求进行设定

  1. 需要有个主页,包含了OJ题目列表,则需要有一个API能查到所有的题目信息(标题,id,难度)
  2. 点击主页中的某个题目标题,会进入详情页,则需要有一个API查到题目的详细信息(id,标题,难度,题目描述,代码模板)
  3. 点击提交,会把当前编辑框中的代码提交到服务器上并编译运行,则需要有一个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)){
            //没有id这个参数,执行"查找全部"逻辑
            selectAll(resp);
        }else {
            //存在id,查找指定题目
            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("");
        //转换为json格式
        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();
        //把结果组织成json格式,
        //只需要id,标题,难度三个属性
        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中增添两个静态内部类以方便使用。

整体的请求与响应过程:

  1. 首先读取请求的body所有数据
  2. 按照API约定的格式来解析JSON数据,得到CompileRequest对象
  3. 按照id从数据库中读取出对应的测试用例代码
  4. 把用户输入的代码和测试用例代码进行组装,组装成一个完整的可以编译运行的代码
  5. 创建Task对象,对组装好的代码进行编译运行
  6. 把运行结果构造成响应数据,并写回客户端中
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();

    //创建两个辅助的类,用来完成请求解析和响应构建
    //这个类用于辅助解析body中的数据请求
    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;
        }
    }

    //这个类用于辅助构造最终响应的body数据
    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 {
        //1.读取请求的body所有数据
        String body = readBody(req);
        //2.按照API约定的格式来解析JSON数据,得到CompileRequest对象
        CompileRequest compileRequest = gson.fromJson(body, CompileRequest.class);
        //3.按照id从数据库中读取出对应的测试用例代码
        ProblemDAO problemDAO = new ProblemDAO();
        Problem problem = problemDAO.selectOne(compileRequest.getId());
        String testCode = problem.getTestCode();//得到该题目的测试代码
        String requestCode = compileRequest.getCode();//得到改题目的用户输入代码
        //4.把用户输入的代码和测试用例代码进行组装,组装成一个完整的可以编译运行的代码
        String finalCode = merge(requestCode,testCode);
        //5.创建Task对象,对刚才组装好的代码进行编译运行
        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();
        }
        //6.把运行结果构造成响应数据,并写回客户端
        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;
        }
        //先找到requestCode的最后一个大括号的位置
        int i = requestCode.lastIndexOf('}');
        return requestCode.substring(0,i)+testCode+"\n}";
    }

    private String readBody(HttpServletRequest req) {
        //body的长度在header中的一个Content-Length字段中
        //contentLength单位就是字节
        int contentLength = req.getContentLength();
        byte[] buf = new byte[contentLength];
        //try后面的(),可以自动关闭资源
        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工具来帮助我们,下面就是测试的结果。

在这里插入图片描述

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 15:54:49  更:2022-03-04 15:55:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 2:29:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码