JavaWeb 项目 --- 在线 OJ 平台 (四)

x33g5p2x  于2022-05-27 转载在 Java  
字(4.9k)|赞(0)|评价(0)|浏览(640)

1. API 问题

之前的临时文件都是存储在tmp目录下的.当多个请求并发进行的时候, 可能就分不清哪个请求对应哪个文件了.
解决办法: 使用 Java 中的 UUID 这个类就能生成一个 UUID了

1.1 更改 Task 类

采用构造方法的方式, 每次调用 Task 类的时候就生成一个 UUID ,这个UUID就是存放这些文件的上级目录.

public class Task {
    // 约定临时文件所在的目录
    private String WORK_DIR = null;
    // 约定代码的类名
    private String CLASS = null;
    // 约定要编译的代码文件名
    private String CODE = null;
    // 约定存放编译错误信息的文件名
    private String COMPILE_ERROR = null;
    // 约定存放运行时的标准输出的文件名
    private String STDOUT = null;
    // 约定存放运行时的标准错误的文件名
    private String STDERR = null;

    public Task() {
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
        CLASS = "Solution";
        CODE = WORK_DIR + "Solution.java";
        COMPILE_ERROR = WORK_DIR + "compileError.txt";
        STDOUT = WORK_DIR + "stdout.txt";
        STDERR = WORK_DIR + "stderr.txt";
    }
	
	// ....以下内容不变
}

1.2 如何查找这个临时文件在哪

使用 System.getProperty("user.dir") 这个方法可以获取当前的工作目录
将这段代码加入到 ResultServlet 中
启动服务器, 并使用postman发送一个请求

进入这个目录

2. 前端界面

这里我的前端模板是从网上下载过来的. 然后保留需要的地方, 不需要的就删除了.

2.1 前端主页界面

查看主要代码

这里主要的思路就是访问主页的时候, 进行交互1, 读取题目列表页, 然后构建表格.

2.2 前端交互, 交互1

之前约定的是 GET 请求, 交互的mehod为problem.

<script>
        function getProblem(){
            $.ajax({
                url: "problem",
                method: "GET",
                success: function(data,status){
                    makeProblem(data);
                }
            })
        }

        function makeProblem(problems){
            let tbody = document.querySelector('.tb-body');
            for(let problem of problems){
                // 创建 tr
                let tr = document.createElement('tr');
                // 创建 Id列的td
                let tdId = document.createElement('td');
                // 插入响应中的id
                tdId.innerHTML = problem.id;
                tr.appendChild(tdId);
                // 创建 level列的td
                let tdLevel = document.createElement('td');
                // 创建 响应中的难度
                tdLevel.innerHTML = problem.level;
                tr.appendChild(tdLevel);
                // 创建 title列的td
                let td = document.createElement('td');
                // 创建 超链接
                let tdTitle = document.createElement('a');
                // 内容为 响应的标题
                tdTitle.innerHTML = problem.title;
                // 这里设置这个题号链接(带上id)
                tdTitle.href = "details.html?id=" + problem.id;
                // _block是跳转新页面
                tdTitle.target = "_block";
                td.appendChild(tdTitle);
                tr.appendChild(td);
                tbody.appendChild(tr);
            }
        }
        getProblem();
    </script>

2.3 前端题目详情界面

这个界面主要就是加载对应id的题目的内容. 并有一输入框, 提交按钮, 以及对应的结果展示.

主要代码

2.4 前端交互, 交互2

这里是 GET请求, method为 desc. 但是desc后面会带id
js中 可以使用 location.search 来表示 ?id=1 这部分

function getProblem() {
            $.ajax({
                url: "desc" +location.search,
                method: "GET",
                success: function(data,status){
                    loading(data);
                }
            })
        }
        function loading(problem){
            // 获取详情页的方框
            let desc = document.querySelector("#myleft");
            // 创建一个h2大小的标题
            let h = document.createElement("h2");
            // 这个标题 展示 id 和 题目名称
            h.innerHTML = problem.id+"."+problem.title;
            desc.appendChild(h);
            // 创建一个p
            let level = document.createElement('p');
            // 这一段放等级
            level.innerHTML = problem.level;
            // 根据等级来展现字体颜色.
            if(problem.level == "简单"){
                level.style = "color:rgb(48, 221, 32)";
            }else if(problem.level == "困难"){
                level.style = "color:red";
            }else{
                level.style = "color:gold";
            }
            desc.appendChild(level);
            // 由于直接使用p标签 无法完全正常换行.就外面套一层pre
            let pre = document.createElement('pre');
            let p = document.createElement('p');
            p.innerHTML = problem.description;
            pre.appendChild(p);
            desc.appendChild(pre);
			
			// 把约定好的代码放入到输入框中
            let codeEditor = document.querySelector("#codeEditor");
            codeEditor.innerHTML = problem.templateCode;

2.5 前端交互, 交互3

再点击按钮之后, 会有一个 post 请求, method为result.,将响应的内容写入结果框中.

继续在刚刚的交互2中写入以下代码

let submitButton = document.querySelector('.mysubmit');
            submitButton.onclick = function() {
                let body = {
                    id: problem.id,
                    code: problem.code,
                };
                $.ajax({
                    url: "result",
                    method: "post",
                    data: JSON.stringify(body),
                    success: function(data,status){
                        let result = document.querySelector("#result");
                        if(data.error == 0){
                            // 编译运行成功
                            console.log("编译运行成功");
                            result.innerHTML = data.stdout;
                        }else{
                            console.log("编译运行失败");
                            result.innerHTML = data.reason;
                        }
                    }
                })
            }

2.6 代码输入框优化

这里我们发现 这里的代码输入框非常不好用.
可以采用前端的 ace 库

首先引入 ace.js

<script src="https://cdn.bootcss.com/ace/1.2.9/ace.js"></script>
    <script src="https://cdn.bootcss.com/ace/1.2.9/ext-language_tools.js"></script>

然后初始化我们的编译器

<script>
        function initAce() {
            // 参数 editor 就对应到刚才在 html 里加的那个 div 的 id
            let editor = ace.edit("editor");
            editor.setOptions({
                enableBasicAutocompletion: true,
                enableSnippets: true,
                enableLiveAutocompletion: true
            });
            // 这是设置背景主题的
            editor.setTheme("ace/theme/twilight");
            // 这是设置代码的语言的
            editor.session.setMode("ace/mode/java");
            editor.resize();
            document.getElementById('editor').style.fontSize = '20px';

            return editor;
        }

        let editor = initAce();

编辑框要套一层 div

更改交互中的代码

这个是为了在代码框中显示当前的代码

3. 测试代码

3.1 查看主页界面

加载没有问题

3.2 查看题目详情界面

能够正确展示题目

3.3 测试提交按钮

能够正确的提交并展示

4. 项目安全性问题

由于你不知道你的用户是使用什么代码, 可能运行会制造一些危险代码.
如: Runtime.getRuntime().exec("rm -rf /"); 就可能把服务器上东西都删了

那么为了防止这样的情况
可以提前将危险代码记录下来, 然后在提交的时候, 比较以下这个代码, 如果是危险代码, 就直接返回错误.

4.1 checkCodeSafe 方法

private boolean checkCodeSafe(String code) {
        List<String> list = new ArrayList<>();
        // 防止提交的代码运行恶意程序
        list.add("Runtime");
        list.add("exec");
        // 禁止提交的代码读写文件
        list.add("java.io");
        // 禁止提交的代码访问网络
        list.add("java.net");
        for (String str : list){
            int post = code.indexOf(str);
            if (post >= 0){
                return false;
            }
        }
        return true;
    }

4.2 加入到 Task 类中

// 安全性判定
        if (!checkCodeSafe(question.getCode())){
            System.out.println("用户提交了不安全的代码");
            answer.setError(3);
            answer.setReason("您提交的代码不安全, 危害了服务器!");
            return answer;
        }

相关文章