在线 OJ

Posted Kirl z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在线 OJ相关的知识,希望对你有一定的参考价值。

项目源代码

项目源代码

在线oj系统类似于LeetCode, 牛客这样的网站, 实现在页面上编写代码, 并提交运行~~

1. 实现功能

  1. 能够保存题目(数据库)
  2. 展示题目列表
  3. 展示题目详细信息(标题, 难度, 题目的描述, 题目代码的模板)
  4. 能够在线编辑代码, 并提交运行

2. java多线程编程

编译运行 java 程序

  1. javac 命令进行编译
  2. java 命令进行运行

当我们输入 javac, 或者 java 这些命令的时候, 其实操作系统也是创建出了一个相应进程 (javac 和 java 命令是两个进程), 去执行对应的编译或者运行任务

Process process= Runtime.getRuntime().exec(cmd);

在 OJ 系统中, 用户在网页输入代码通过 HTTP 请求发送到服务器, 服务器就需要创建出一个 javac 进程, 通过 javac 进程把这段代码编译成 .class 文件。 在创建出一个 java 进程, 通过 java 进程来执行这个 .class 文件, 并运行里面的测试用例

java多进程编程

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

  1. 先创建出目录, 为了让每个用户每次请求的目录都不同, 互不影响, 使用了 UUID 来辅助生成目录
  2. 把用户提交的代码构建成 .java 文件
  3. 构建编译指令, 创建子进程执行编译的过程, 把编译出错的结果生成到 compile_error.txt 文件中,
    通过判定这个文件是否为空来感知到当前编译是否出错~~
  4. 构建运行命令, 创建子进程执行代码, 把运行的结果放到 stdout.txt 和 stderr.txt 中, 如果程序抛出异常,
    异常信息就会通过标准错误写入到 stderr.txt 里, 然后父进程读取该文件, 就能知道异常信息是什么了
  5. 把运行结果包装成 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 操作数据库

  1. 引入依赖
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.45</version>
</dependency>
  1. 创建数据源 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");
  1. 和数据库建立连接, DataSource.getConnection
  2. 拼接并执行 SQL 语句
  3. 遍历结果集 (对于 select 需要, 对于 insert, update, delete 不太需要)
  4. 断开连接并回收资源

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的主要内容,如果未能解决你的问题,请参考以下文章

项目:在线OJ--MinMIn‘s Online OJ

JavaWeb项目——基于Servlet实现的在线OJ平台 (项目问答+代码详解)

oj集

从头开始制作OJ-在线IDE的搭建

关于在线OJ训练营项目的设计思路

关于在线OJ训练营项目的设计思路