搭建OJ系统
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搭建OJ系统相关的知识,希望对你有一定的参考价值。
参考技术A [toc]本文介绍如何利用开源OJ系统源码快速搭建OJ系统
开源青岛大学oj的搭建(傻瓜式操作) : Uncle_drew
docker-compose up解决错误ERROR: Couldn't connect to Docker daemon at http+docker://localunixsocket - is it running?
毕设项目:基于BS模型的在线OJ系统
系列文章目录
文章目录
- 系列文章目录
- 前言
- 一、在线OJ系统描述
- 二、在线编译模块
- 三、题目管理模块
- 1.oj_data存放题目的文件夹
- 2.MVC中的M负责存储数据 oj_model.hpp这个oj_model模块做的事情,就是把刚才文件中存储的题目信息加载起来,供服务器使用
- 3.MVC中的C controller: 核心业务逻辑 oj_server.cpp作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑
- 4.oj_server.hpp对于oj_server.cpp的实现部分controller 作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑
- 5.MVC中的v V => view : 负责显示界面 oj_view.hpp根据数据,生成html这个动作,通常叫做网页渲染(render)
- 三、前端结果界面
- 总结
前言
一、在线OJ系统描述
实现一个在线OJ系统类似于力扣或者牛客网的核心部分刷题代码练习功能,提供了用户一个可以在线刷题编写代码并且能够进行编译运行的环境,题目通过序号排序,题目也有难度等级的划分,测试用例等等。在编写代码的同时提供了语法纠错、代码高亮、自动补全等基本功能。
用户可以通过域名加上端口号访问服务器,系统内置了多道编程题,用户点击对应题目就可以进行练习,并且题目内含有大量测试样例。服务器端会根据用户编写代码会进行用例的测试,检测用户代码是否符合题意,并且可以将编译成功结果或者编译出错的原因返回给浏览器端。
二、在线编译模块
在现编译模块的实现:此模块的核心完成"在线",用户把写好的代码通过网页提交到服务器上,服务器调用g++完成编译过程,并且调用刚生成的可执行程序,验证程序结果,返回给用户提交的浏览器上。
1.搭建一个HTTP服务器完成在线编译
搭建一个HTTP服务器来完成在线编译的核心功能。
此处开源的Httplib源代码:cpp-httplib
或者直接git clone: git clone https://github.com/yhirose/cpp-httplib
A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include the httplib.h file in your code!
NOTE: This is a multi-threaded 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
翻译:一个C++11单文件头文件跨平台HTTP/HTTPS库。<--意思就是只有头文件-->
它非常容易安装。只需在代码中包含httplib.h文件即可!
注意:这是一个多线程的“阻塞”HTTP库。如果你正在寻找一个“非阻塞”库,这不是你想要的。
快速上手一个开源项目小技巧:
2.收到HTTP请求,进行数据格式转化(HTTP中body的内容转换为JSON格式的字符串)
3.compile_server.cpp浏览器提交JSON数据请求服务器,服务器调用在线编译模块编译,把结果返回给浏览器
- Json如何从req请求中获取到Json请求?
- 从req对象中获取到。
- Json如何和Http协议结合?
- 需要的请求格式是json格式,HTTP能够提供的格式,是另外一种键值对的 格式,所以要对HTTP提供的格式进行格式的转换。
- 此处由于提交的代码中可能会包含一些特殊符号,这些特殊符号要正确传 输时,就会进行urlencode,这一步由浏览器自动完成。
- 服务器收到请求之后的第一件事就是切分键值对,再urldecode,然后解> 析数据,整理成需要的Json格式。
- Json如何进行解析和构造?
- 使用jsoncpp第三方库。
jsoncpp第三方库获取办法:
1 #include <unordered_map>
2
3 #include "httplib.h"
4 #include "compile.hpp"
5 //#include "jsoncpp/json/json.h"
6
7 #include<jsoncpp/json/json.h>
8
9 int main()
10
11 using namespace httplib;
12
13 Server server;
14
15 // Get注册一个回调函数,这个函数的调用机制是处理Get方法时
16 // lambda表达式 就是一个匿名函数
17
18 // 路由过程
19 // 接收请求对象,根据请求进行处理,并且将响应返回给客户端
20 //
21 // 此处get改成post,代码放到body里面
22 server.Post("/compile", [](const Request& req, Response& resp)
23 // 根据具体的问题场景,根据请求,计算出响应结果
24 (void)req;
25
26 // 如何从req请求中获取到Json请求
27 // Json如何和Http协议结合
28
29 // 需要的请求格式是json格式,HTTP能够提供的格式,是另外一种键值对的格式,所以要对HTTP提供的格式进行格式的转换
30
31 // 此处由于提交的代码中可能会包含一些特殊符号,这些特殊符号要正确传输时,就会进行urlencode,这一步由浏览器自动完成
32 // 服务器收到请求之后的第一件事就是切分键值对,再urldecode,然后解析数据,整理成需要的Json格式
33
34 // 此函数将Http中的body,解析成键值对,存入body_kv
35 std::unordered_map<std::string, std::string> body_kv;
36 UrlUtil::ParseBody(req.body, &body_kv);
37
38 // Json如何进行解析和构造? 使用jsoncpp第三方库
39 //
40 // 下方调用CompileAndRun
41
42 Json::Value req_json; // 从req对象中获取到
43
44 /* for(std::unordered_map<std::string,std::string>::iterator it=body_kv.begin();it!=body_kv.end();++it)
45
46 req_json[it->first]=it->second;
47
48 */
49
50 for (auto e : body_kv)
51
52 // e的类型和 *it 是一致的
53 req_json[e.first] = e.second;
54
55
56 Json::Value resp_json; // resp_json发到响应中
57
58 // resp_json 输出型参数
59 Compiler::CompileAndRun(req_json, &resp_json);
60
61 // 把Json::Value对象序列化成为字符串,才能返回
62 Json::FastWriter writer;
63 resp.set_content(writer.write(resp_json), "text/plain");
64 );
65
66 // 让浏览器能访问到一个静态页面
67 // 静态页面: index.html 不会发生变化
68 // 动态页面: 编译结果 随着参数的不同而发生变化
69 //
70 // 加上这个目录是为了浏览器能够访问到静态页面
71 server.set_base_dir("../wwwroot", "");
72 server.listen("0.0.0.0", 9092);
73
74 return 0;
75
4.util.hpp工具类
1.TimeUtil类时间戳获取工具TimeUtil标识文件的不同
18 class TimeUtil
19
20 public:
21 // 获取当前时间戳
22 static int64_t TimeStamp()
23
24 struct timeval tv;
25 ::gettimeofday(&tv, nullptr);
26
27 return tv.tv_sec;
28
29
30 static int64_t TimeStampMS()
31
32 struct timeval tv;
33 ::gettimeofday(&tv, nullptr);
34
35 return tv.tv_sec * 1000 + tv.tv_usec / 1000;
36
37 ;
2.打印日志的工具
39
40 // 打印日志的工具
41
42 // 期望打印出的日志格式:
43 // [I时间戳 util.hpp:31] hello
44 // 日志的使用方式形如: LOG(INFO) << "hello" << "\\n";
45 // 日志的级别:
46 // FATAL 致命
47 // ERROR 错误
48 // WAENING 警告
49 // INFO 提示
50
51 enum Level
52
53 INFO,
54 WARNING,
55 ERROR,
56 FATAL
57 ;
58
59
60 inline std::ostream& Log(Level level, const std::string& file_name, int line_num)
61
62 //前缀
63 std::string prefix = "[";
64
65 if (level == INFO)
66
67 prefix += "I";
68
69 else if (level == WARNING)
70
71 prefix += "W";
72
73 else if (level == ERROR)
74
75 prefix += "E";
76
77 else if (level == FATAL)
78
79 prefix += "F";
80
81
82 prefix += std::to_string(TimeUtil::TimeStamp());
83 prefix += " ";
84 prefix += file_name;
85 prefix += ":";
86 prefix += std::to_string(line_num);
87 prefix += "] ";
88
89
90 std::cout << prefix;
91 return std::cout;
92
93
94 #define LOG(level) Log(level, __FILE__, __LINE__)
3.文件类FileUtil把文件所有内容读取出来,放到content字符串中
97 /
98 // 文件相关工具类
99
100 class FileUtil
101
102 public:
103
104 // 传入一个文件路径,把文件所有内容读取出来,放到content字符串中
105 // 下面这个函数参数是 输出型参数
106 //
107 // 输入型参数用const引用
108 // 输出型参数用指针
109 // 输入输出型参数用引用
110 //
111 static bool Read(const std::string& file_path, std::string* content)
112
113 //content->clear();
114 (*content).clear();
115 std::ifstream file(file_path.c_str());
116 if (!file.is_open())
117
118 return false;
119
120
121 std::string line;
122 while (std::getline(file, line))
123
124 *content += line + "\\n";
125
126
127 file.close();
128 return true;
129
130
131 static bool Write(const std::string& file_path, const std::string& content)
132
133 std::ofstream file(file_path.c_str());
134 if (!file.is_open())
135
136 return false;
137
138
139 file.write(content.c_str(), content.size());
140
141 file.close();
142 return true;
143
144 ;
4.URL body解析模块
首先安装boost标准库来进行字符串切分。
146 ///
147 // URL / body解析模块
148
149 // 使用boost库中的函数完成字符串某些操作
150 class StringUtil
151
152 public:
153 // 使用boost split进行字符串的切分
154 // aaa bbb ccc 按照1个 空格切分 切分成3个部分
155 // aaa bbb ccc 切分成3或者4
156 // is_any_of 表示多个字符切割 & =
157 // split中有一个参数叫做 token_compress_off 标识是否打开还是关闭分隔符压缩就是4个,如果打开上述就会切分为3部分,token_compress_on
158 static void Split(const std::string& input, const std::string& split_char, std::vector<std::string>* output)
159
160 boost::split(*output, input, boost::is_any_of(split_char), boost::token_compress_off);
161
162 ;
163
164 // 对url body的解析模块
165 class UrlUtil
166
167 public:
168 static void ParseBody(const std::string& body, std::unordered_map<std::string, std::string>* params)
169
170 // 1.先对body字符串进行切分,切分成键值对形式
171 // 1.1 先按照 & 切分
172 // 1.2 再按照 = 切分
173 // 使用boost split进行切分
174 std::vector<std::string> kvs;
175 StringUtil::Split(body, "&", &kvs);
176
177 for (size_t i = 0; i < kvs.size(); i++)
178
179 std::vector<std::string> kv;
180
181 // kvs[i]存的是一个键值对
182 StringUtil::Split(kvs[i], "=", &kv);
183
184 //kv[0]=key kv[1]=value
185 if (kv.size() != 2)
186
187 continue;
188
189
190 // 出参,将切分好的键值对,传给调用位置
191 // unordered_map [] 操作,如果key不存在则新增,如果key存在,则获取到value
192 // 2.对键值对中的转义过的字符进行urldecode
193 // 只用对value转义,key不用转义
194 (*params)[kv[0]] = UrlDecode(kv[1]);
195
196
197
198
199 static unsigned char ToHex(unsigned char x)
200
201 return x > 9 ? x + 55 : x + 48;
202
203
204 static unsigned char FromHex(unsigned char x)
205
206 unsigned char y;
207 if (x >= 'A' && x <= 'Z') y = x - 'A' + 10;
208 else if (x >= 'a' && x <= 'z') y = x - 'a' + 10;
209 else if (x >= '0' && x <= '9') y = x - '0';
210 else assert(0);
211 return y;
212
213
214 static std::string UrlEncode(const std::string& str)
215
216 std::string strTemp = "";
217 size_t length = str.length();
218 for (size_t i = 0; i < length; i++)
219
220 if (isalnum((unsigned char)str[i]) ||
221 (str[i] == '-') ||
222 (str[i] == '_') ||
223 (str[i] == '.') ||
224 (str[i] == '~'))
225 strTemp += str[i];
226 else if (str[i] == ' ')
227 strTemp += "+";
228 else
229
230 strTemp += '%';
231 strTemp += ToHex((unsigned char)str[i] >> 4);
232 strTemp += ToHex((unsigned char)str毕设项目:基于BS模型的在线OJ系统