在线 OJ
Posted Kirl z
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在线 OJ相关的知识,希望对你有一定的参考价值。
在线 OJ
项目源代码
在线oj系统类似于LeetCode, 牛客这样的网站, 实现在页面上编写代码, 并提交运行~~
1. 实现功能
- 能够保存题目(数据库)
- 展示题目列表
- 展示题目详细信息(标题, 难度, 题目的描述, 题目代码的模板)
- 能够在线编辑代码, 并提交运行
2. java多线程编程
编译运行 java 程序
- javac 命令进行编译
- java 命令进行运行
当我们输入 javac, 或者 java 这些命令的时候, 其实操作系统也是创建出了一个相应进程 (javac 和 java 命令是两个进程), 去执行对应的编译或者运行任务
Process process= Runtime.getRuntime().exec(cmd);
在 OJ 系统中, 用户在网页输入代码通过 HTTP 请求发送到服务器, 服务器就需要创建出一个 javac 进程, 通过 javac 进程把这段代码编译成 .class 文件。 在创建出一个 java 进程, 通过 java 进程来执行这个 .class 文件, 并运行里面的测试用例
3. 准备工作
3.1 创建一些新的类
- Question类: 表示要进行编译运行的代码
- Answer类: 表示运行的结果
- Task类: 执行过程中需要的临时文件 (编译执行过程中的中间结果)
以文件的方式保存, 目的是为了方便 "进程间通信"
3.2 唯一ID
(1) 针对多个用户一起给服务器提交代码, 给这些文件写到不同的目录里面
用户 A 写入的文件目录 ./tmp1/
用户 B 写入的文件目录 ./tmp2/
(2) 自增主键
时间戳 + 机器编号 + 机房序号 + 随机因子
(3) UUID
自动生成一个唯一的ID
创建目录使用 mkdir (这个命令只能创建一级目录)
如果一次要创建多级目录, 要是用 mkdirs
每处理一个请求, 都生成一个新的目录以及一堆新的文件, 请求多了内存会不会就不够了?
- 一定会, 在实际开发中, 这些临时文件是必要保存的, 不管是为了进程间通信, 也是为了未来解决 BUG 的重要依据
- 数据是要保留的, 但是定期清理, 定期备份即可
3.3 Task.compileAndRun
- 先创建出目录, 为了让每个用户每次请求的目录都不同, 互不影响, 使用了 UUID 来辅助生成目录
- 把用户提交的代码构建成 .java 文件
- 构建编译指令, 创建子进程执行编译的过程, 把编译出错的结果生成到 compile_error.txt 文件中,
通过判定这个文件是否为空来感知到当前编译是否出错~~ - 构建运行命令, 创建子进程执行代码, 把运行的结果放到 stdout.txt 和 stderr.txt 中, 如果程序抛出异常,
异常信息就会通过标准错误写入到 stderr.txt 里, 然后父进程读取该文件, 就能知道异常信息是什么了 - 把运行结果包装成 Answer 对象, 返回到前端网页
3.4 数据库的建立
create database if not exists oj;
use oj;
drop table if exists oj_table;
create table oj_table (
id int primary key auto_increment,
title varchar(50),
level varchar(50),
description varchar(4096),
templateCode varchar(4096),
testCode varchar(4096)
);
title: 题目
level: 难度
description: 题目的详细描述
templateCode: 代码模板
testCode: 测试代码
- 当用户查看题目详情页的时候, 页面上就会从数据库取出这些数据, 并显示出来(测试用例代码不会显示)
- 用户就在网页进行代码编辑
- 当用户点击提交的时候, 就把一个新的代码提交到服务器
- 在服务器把用户提交的代码和测试代码拼接在一起, 构成一个完整地 Solution.java 文件, 再通过 Task 类进行编译执行
3.5 JDBC 操作数据库
- 引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
- 创建数据源 DataSource (指定服务器的地址, 用户名, 密码…)
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUser("root"); // 默认为 root
((MysqlDataSource)dataSource).setPassword("1234"); // 根据自己数据库
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/oj?characterEncoding=utf8&useSSL=true");
- 和数据库建立连接, DataSource.getConnection
- 拼接并执行 SQL 语句
- 遍历结果集 (对于 select 需要, 对于 insert, update, delete 不太需要)
- 断开连接并回收资源
3.6 前后端交互 API (Restful风格)
(1) 使用不同的 HTTP 方法表示不同的操作类型
请求方法 | 操作类型 |
---|---|
GET | 表示获取数据 |
POST | 表示新增数据 |
PUT | 表示修改数据 |
DELETE | 表示删除数据 |
(2) 使用 url 中的路径表示要操作的对象
请求方法 | 操作对象类型 |
---|---|
GET / problem | 获取题目信息 |
POST / problem | 新增一个题目 |
PUT / problem | 修改题目信息 |
DELETE / problem | 删除题目 |
(3) 使用 JSON 格式来表示 HTTP body 中的对象
JSON 最大优势在于可读性好
JSON 最大劣势在于效率比较低 (网络传输效率)
(4) 用 HTTP 的状态码表示执行结果
4. 前后端交互接口
4.1 获取题目列表
请求:
GET / problem
响应:
HTTP / 1.1 200 OK
[
{
id: 1,
title: "两数之和",
level: "简单",
},
{
id: 2,
title: "各位相加",
level: "简单",
}
...
]
4.2 获取指定题目的详细信息
请求:
GET / problem?id=10
响应:
HTTP / 1.1 200 OK
{
id: 1,
title: "两数之和",
level: "简单",
description: "...",
templateCode: "...",
testCode: "...",
}
4.3 新增题目
请求:
POST / problem
{
title: "两数之和",
level: "简单",
description: "...",
templateCode: "...",
testCode: "...",
}
响应:
HTTP / 1.1 200 OK
{
ok:1,
}
4.4 删除题目
DELETE / problem?id=1
响应:
HTTP / 1.1 200 OK
{
ok:1,
}
4.5 提交代码并运行
POST / compile
{
id: 1,
code: "...",
}
响应:
HTTP / 1.1 200 OK
{
errno: 0, // 0 表示编译运行都 ok, 1 表示编译出错, 2 表示运行出错
reason: "...",
stdout: "...", // 程序执行的打印结果
}
4.6 Content-Type 常见选项
text / html;
text / plain 纯文本
text / css
application / javascript
application / json
image / png
image / jpg
以上是关于在线 OJ的主要内容,如果未能解决你的问题,请参考以下文章