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

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

1. API 问题

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

1.1 更改 Task 类

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

  1. public class Task {
  2. // 约定临时文件所在的目录
  3. private String WORK_DIR = null;
  4. // 约定代码的类名
  5. private String CLASS = null;
  6. // 约定要编译的代码文件名
  7. private String CODE = null;
  8. // 约定存放编译错误信息的文件名
  9. private String COMPILE_ERROR = null;
  10. // 约定存放运行时的标准输出的文件名
  11. private String STDOUT = null;
  12. // 约定存放运行时的标准错误的文件名
  13. private String STDERR = null;
  14. public Task() {
  15. WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
  16. CLASS = "Solution";
  17. CODE = WORK_DIR + "Solution.java";
  18. COMPILE_ERROR = WORK_DIR + "compileError.txt";
  19. STDOUT = WORK_DIR + "stdout.txt";
  20. STDERR = WORK_DIR + "stderr.txt";
  21. }
  22. // ....以下内容不变
  23. }

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

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

进入这个目录

2. 前端界面

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

2.1 前端主页界面

查看主要代码

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

2.2 前端交互, 交互1

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

  1. <script>
  2. function getProblem(){
  3. $.ajax({
  4. url: "problem",
  5. method: "GET",
  6. success: function(data,status){
  7. makeProblem(data);
  8. }
  9. })
  10. }
  11. function makeProblem(problems){
  12. let tbody = document.querySelector('.tb-body');
  13. for(let problem of problems){
  14. // 创建 tr
  15. let tr = document.createElement('tr');
  16. // 创建 Id列的td
  17. let tdId = document.createElement('td');
  18. // 插入响应中的id
  19. tdId.innerHTML = problem.id;
  20. tr.appendChild(tdId);
  21. // 创建 level列的td
  22. let tdLevel = document.createElement('td');
  23. // 创建 响应中的难度
  24. tdLevel.innerHTML = problem.level;
  25. tr.appendChild(tdLevel);
  26. // 创建 title列的td
  27. let td = document.createElement('td');
  28. // 创建 超链接
  29. let tdTitle = document.createElement('a');
  30. // 内容为 响应的标题
  31. tdTitle.innerHTML = problem.title;
  32. // 这里设置这个题号链接(带上id)
  33. tdTitle.href = "details.html?id=" + problem.id;
  34. // _block是跳转新页面
  35. tdTitle.target = "_block";
  36. td.appendChild(tdTitle);
  37. tr.appendChild(td);
  38. tbody.appendChild(tr);
  39. }
  40. }
  41. getProblem();
  42. </script>

2.3 前端题目详情界面

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

主要代码

2.4 前端交互, 交互2

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

  1. function getProblem() {
  2. $.ajax({
  3. url: "desc" +location.search,
  4. method: "GET",
  5. success: function(data,status){
  6. loading(data);
  7. }
  8. })
  9. }
  10. function loading(problem){
  11. // 获取详情页的方框
  12. let desc = document.querySelector("#myleft");
  13. // 创建一个h2大小的标题
  14. let h = document.createElement("h2");
  15. // 这个标题 展示 id 和 题目名称
  16. h.innerHTML = problem.id+"."+problem.title;
  17. desc.appendChild(h);
  18. // 创建一个p
  19. let level = document.createElement('p');
  20. // 这一段放等级
  21. level.innerHTML = problem.level;
  22. // 根据等级来展现字体颜色.
  23. if(problem.level == "简单"){
  24. level.style = "color:rgb(48, 221, 32)";
  25. }else if(problem.level == "困难"){
  26. level.style = "color:red";
  27. }else{
  28. level.style = "color:gold";
  29. }
  30. desc.appendChild(level);
  31. // 由于直接使用p标签 无法完全正常换行.就外面套一层pre
  32. let pre = document.createElement('pre');
  33. let p = document.createElement('p');
  34. p.innerHTML = problem.description;
  35. pre.appendChild(p);
  36. desc.appendChild(pre);
  37. // 把约定好的代码放入到输入框中
  38. let codeEditor = document.querySelector("#codeEditor");
  39. codeEditor.innerHTML = problem.templateCode;

2.5 前端交互, 交互3

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

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

  1. let submitButton = document.querySelector('.mysubmit');
  2. submitButton.onclick = function() {
  3. let body = {
  4. id: problem.id,
  5. code: problem.code,
  6. };
  7. $.ajax({
  8. url: "result",
  9. method: "post",
  10. data: JSON.stringify(body),
  11. success: function(data,status){
  12. let result = document.querySelector("#result");
  13. if(data.error == 0){
  14. // 编译运行成功
  15. console.log("编译运行成功");
  16. result.innerHTML = data.stdout;
  17. }else{
  18. console.log("编译运行失败");
  19. result.innerHTML = data.reason;
  20. }
  21. }
  22. })
  23. }

2.6 代码输入框优化

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

首先引入 ace.js

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

然后初始化我们的编译器

  1. <script>
  2. function initAce() {
  3. // 参数 editor 就对应到刚才在 html 里加的那个 div 的 id
  4. let editor = ace.edit("editor");
  5. editor.setOptions({
  6. enableBasicAutocompletion: true,
  7. enableSnippets: true,
  8. enableLiveAutocompletion: true
  9. });
  10. // 这是设置背景主题的
  11. editor.setTheme("ace/theme/twilight");
  12. // 这是设置代码的语言的
  13. editor.session.setMode("ace/mode/java");
  14. editor.resize();
  15. document.getElementById('editor').style.fontSize = '20px';
  16. return editor;
  17. }
  18. let editor = initAce();

编辑框要套一层 div

更改交互中的代码

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

3. 测试代码

3.1 查看主页界面

加载没有问题

3.2 查看题目详情界面

能够正确展示题目

3.3 测试提交按钮

能够正确的提交并展示

4. 项目安全性问题

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

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

4.1 checkCodeSafe 方法

  1. private boolean checkCodeSafe(String code) {
  2. List<String> list = new ArrayList<>();
  3. // 防止提交的代码运行恶意程序
  4. list.add("Runtime");
  5. list.add("exec");
  6. // 禁止提交的代码读写文件
  7. list.add("java.io");
  8. // 禁止提交的代码访问网络
  9. list.add("java.net");
  10. for (String str : list){
  11. int post = code.indexOf(str);
  12. if (post >= 0){
  13. return false;
  14. }
  15. }
  16. return true;
  17. }

4.2 加入到 Task 类中

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

相关文章