实现一个在线OJ系统类似于力扣或者牛客网的核心部分刷题代码练习功能,提供了用户一个可以在线刷题编写代码并且能够进行编译运行的环境,题目通过序号排序,题目也有难度等级的划分,测试用例等等。在编写代码的同时提供了语法纠错、代码高亮、自动补全等基本功能。
用户可以通过域名加上端口号访问服务器,系统内置了多道编程题,用户点击对应题目就可以进行练习,并且题目内含有大量测试样例。服务器端会根据用户编写代码会进行用例的测试,检测用户代码是否符合题意,并且可以将编译成功结果或者编译出错的原因返回给浏览器端。
**在现编译模块的实现:**此模块的核心完成"在线",用户把写好的代码通过网页提交到服务器上,服务器调用g++完成编译过程,并且调用刚生成的可执行程序,验证程序结果,返回给用户提交的浏览器上。
搭建一个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库。如果你正在寻找一个“非阻塞”库,这不是你想要的。
快速上手一个开源项目小技巧:
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 }
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 };
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__)
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 };
首先安装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[i] % 16);
233 }
234 }
235 return strTemp;
236 }
237
238 static std::string UrlDecode(const std::string& str)
239 {
240 std::string strTemp = "";
241 size_t length = str.length();
242 for (size_t i = 0; i < length; i++)
243 {
244 if (str[i] == '+') strTemp += ' ';
245 else if (str[i] == '%')
246 {
247 assert(i + 2 < length);
248 unsigned char high = FromHex((unsigned char)str[++i]);
249 unsigned char low = FromHex((unsigned char)str[++i]);
250 strTemp += high*16 + low;
251 }
252 else strTemp += str[i];
253 }
254 return strTemp;
255 }
256 };
258 // 查找用户代码中是否有危害服务器的语句
259 class CheckUserCode
260 {
261 public:
262 // 目前只屏蔽了system,后续可以继续添加屏蔽字
263 static bool isHaveSystem(const std::string& user_code)
264 {
265 if (std::string::npos != user_code.find("system"))
266 {
267 return true;
268 }
269
270 return false;
271 }
272 };
该程序会生成以下的文件: 此处本质使用文件完成进程间通信。
42 // 1.源代码文件,此处的name表示当前的请求名字
43
44 // 请求和请求之间name必须不同
45 // name必须唯一,因为可能有多个请求
46 // 形如: tmp_时间戳.计数器序号.cpp
47 static std::string SrcPath(const std::string& name)
48 {
49 return "../tmp_files/" + name + ".cpp";
50 }
51 // 2.编译错误文件
52 static std::string CompileErrorPath(const std::string& name)
53 {
54 return "../tmp_files/" + name + ".compile_err";
55 }
56 // 3.可执行程序文件
57 static std::string ExePath(const std::string& name)
58 {
59 return "../tmp_files/" + name + ".exe";
60 }
61 // 4.标准输入文件
62 static std::string StdinPath(const std::string& name)
63 {
64 return "../tmp_files/" + name + ".stdin";
65 }
66 // 5.标注输出文件
67 static std::string StdoutPath(const std::string& name)
68 {
69 return "../tmp_files/" + name + ".stdout";
70 }
71 // 6.标准错误文件
72 static std::string StderrPath(const std::string& name)
73 {
74 return "../tmp_files/" + name + ".stderr";
75 }
4 5 6号文件目的是为了通知可执行程序文件,所以可以使用进程间通信进行 ,完成可以不需要创建文件。
90 static bool CompileAndRun(const Json::Value& req, Json::Value* resp/*出参*/)
91 {
92 // 1.根据json请求对象,生成源代码文件和标准输入文件
93 // 2.调用 g++进行编译(fork+exec /system)
94 // 生成可执行程序
95 // 如果编译出错,
96 // 需要把编译错误记录下来(重定向到文件中)
97 // 3.调用可执行程序,把标准输入记录到文件中,然后把文件
98 // 中的内容重定向到可执行程序中,可执行程序的标准输出
99 // 和标准错误内容也要重定向输出记录到文件中
100 // 4.把程序最终结果进行返回,构造resp对象
101
102
103 // 1.根据json请求对象,生成源代码文件
104 // 代码段为空的情况下
105 if (req["code"].empty())
106 {
107 (*resp)["error"] = 3;
108 (*resp)["reason"] = "code empty";
109 LOG(ERROR) << "code empty" << std::endl;
110 return false;
111 }
112
113 // req["code"] 根据可以取出value,value类型也是Json::Value, 通过asSting() 转成字符串
114 const std::string& code = req["code"].asString(); // 不会进行拷贝操作,提高代码效率
115
116 // std::string& code = req["code"].asString();
117
118 // 通过这个函数将用户提交代码和用户输入写到文件中去
119 std::string file_name = WriteTmpFile(code, req["stdin"].asString());
120
121 // 2.调用g++进行编译(fork + exec)
122 // 生成一个可执行程序,如果编译出错需要记录下来(重定向到文件)
123 bool ret = Compile(file_name);
124 if (!ret)
125 {
126 // 错误处理
127 (*resp)["error"] = 1;
128 // 从文件中读取错误原因
129 std::string reason;
130 FileUtil::Read(CompileErrorPath(file_name), &reason);
131 (*resp)["reason"] = reason;
132
133 // 虽然是编译出错,这样的错误是用户的错误,并不是服务器的错误,对于服务器来说,这样的编译错误不是错误,所以把它叫做info
134 LOG(INFO) << "Compile failed!" << std::endl;
135 return false;
136 }
137
138 // 3.调用可执行程序,
139 // 把标准输入记录到文件中,把文件中的内容重定向给可执行程序,
140 // 可执行程序的标准输出重定向至文件
141 //
142 // 返回一个信号
143 int sig = Run(file_name);
144 if (sig != 0)
145 {
146 // 错误处理
147 (*resp)["error"] = 2;
148
149 // 运行时错误,程序终止进行一定会发出信号,所以信号可以判断进程是因为什么原因退出
150 (*resp)["reason"] = "Program exit by signo: " +std::to_string(sig);
151
152 // 虽然是编译出错,这样的错误是用户的错误,并不是服务器的错误,对于服务器来说,这样的编译错误不是错误,所以把它叫做info
153 LOG(INFO) << "Program exit by signo: " << std::to_string(sig) << std::endl;
154 return false;
155 }
156
157 // 4.没有错误,把程序中的最终结果进行返回,构造resp_json对象
158 (*resp)["error"] = 0;
159 (*resp)["reason"] = "";
160
161 // 运行的最终结果也在文件中存储
162 std::string str_stdout;
163 FileUtil::Read(StdoutPath(file_name), &str_stdout);
164 (*resp)["stdout"] = str_stdout;
165
166 std::string str_stderr;
167 FileUtil::Read(StderrPath(file_name), &str_stderr);
168 (*resp)["stderr"] = str_stderr;
169
170 LOG(INFO) << "Program " << file_name << " Done" << std::endl;
171 return true;
172 }
174 private:
175
176 // 1.将代码写到文件中
177 // 2.给这个请求分配唯一的一个名字,通过返回值返回
178 // 分配名字形如: tmp_时间戳.计数器序号.xxx
179 static std::string WriteTmpFile(const std::string& code, const std::str ing& str_stdin)
180 {
181 // 将id变为原子性的操作,可以省去互斥锁的带来的开销
182 // 原子操作依赖于CPU的支持,保持每个请求的唯一性
183 static std::atomic_int id(0);
184 ++id;
185
186 std::string file_name = "tmp_" + std::to_string(TimeUtil::TimeStamp ()) + "." + std::to_string(id);
187
188 // 将代码写到文件中
189 FileUtil::Write(SrcPath(file_name), code);
190
191 // 把用户的输入也写到文件中
192 FileUtil::Write(StdinPath(file_name), str_stdin);
193
194 return file_name;
195 }
197 static bool Compile(const std::string& file_name)
198 {
199 // 1.先构造出编译指令
200 // g++ file_name.cpp -o file_name.exe -std=c++11
201 char* command[20] = { 0 };
202 // 必须保证command的指针都是指向有效内存
203 char buf[20][50] = { {0} };
204 for (int i = 0; i < 20; i++)
205 {
206 command[i] = buf[i];
207 }
208
209 sprintf(command[0], "%s", "g++");
210 sprintf(command[1], "%s", SrcPath(file_name).c_str());
211 sprintf(command[2], "%s", "-o");
212 sprintf(command[3], "%s", ExePath(file_name).c_str());
213 sprintf(command[4], "%s", "-std=c++11");
214 command[5] = nullptr;
215
216 // 2.创建子进程
217 int ret = fork();
218 if (ret > 0)
219 {
220 // 3.父进程进行进行等待,防止出现僵尸
221 waitpid(ret, nullptr, 0);
222 }
223 else
224 {
225 // 4.子进程进行程序替换
226 // 将编译时错误的信息输出到文件中
227 int fd = open(CompileErrorPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666);
228 if (fd < 0)
229 {
230 LOG(ERROR) << "Open Compile file error" << std::endl;
231 exit(1);
232 }
233 // 将文件描述符中的标准错误的内容,拷贝至文件中
234 dup2(fd, 2);
235
236 execvp(command[0], command); //command[0]是g++
237
238 // 此处子进程执行失败直接退出
239 exit(0);
240 }
241
242 // 5.代码执行到这里,如何知道编译成功?
243 // 可以判定可执行文件是否存在, stat(ls),stat与ls的功能差不多
244 // ls指令基于stat实现的
245 struct stat st;
246 ret = stat(ExePath(file_name).c_str(), &st);
247 if (ret < 0)
248 {
249 // stat 执行失败,说明该文件不存在
250 LOG(INFO) << "Compile " << file_name << " Failed!" << std::endl;
251 return false;
252 }
253 LOG(INFO) << "Compile " << file_name << " OK!" << std::endl;
254
255 return true;
256 }
258 static int Run(const std::string& file_name)
259 {
260 // 1.创建子进程
261 int ret = fork();
262
263 if (ret > 0)
264 {
265 // 2.父进程进行等待
266 // 如果程序执行异常,则返回状态码,根据状态码判断出错原因
267 int status = 0;
268 waitpid(ret, &status, 0);
269
270 // 取出位图的后7位
271 return status & 0x7f;
272 }
273 else
274 {
275 // 3.重定向 标准输入,标准输出,标准错误
276 int fd_stdin = open(StdinPath(file_name).c_str(), O_RDONLY);
277 dup2(fd_stdin, 0);
278
279 int fd_stdout = open(StdoutPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666);
280 dup2(fd_stdout, 1);
281
282 int fd_stderr = open(StderrPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666);
283 dup2(fd_stderr, 2);
284
285 // 4.子进程进行程序替换
286 execl(ExePath(file_name).c_str(), ExePath(file_name).c_str(), nullptr);
287 exit(0);
288 }
289 }
290 };
加上这个目录是为了浏览器能够访问到静态页面,index.html 。
这里点击会跳转下一个所有问题界面。
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="UTF-8">
5 <title>online-OJ</title>
6 <style type="text/css">
7 * {
8 margin: 0;
9 padding: 0;
10 list-style: none;
11 }
12
13 body {
14 /*background-color: #9a9a9a;*/
15 background: rgba(29, 111, 145, 0.9);
16 }
17
18 #login_page {
19 width: 500px;
20 height: 420px;
21 background-color:white;
22 position: absolute;
23 top: 50%;
24 left: 50%;
25 transform: translate(-50%, -50%);
26 }
27 #login_inner {
28 width: 400px;
29 position: relative;
30 margin: 0 auto;
31 }
32 p {
33 margin-top: 50px;
34 font-family: "黑体";
35 font-size: 30px;
36 color: black;
37 text-align: center;
38 }
39 .btn {
40 width: 400px;
41 height: 50px;
42 font-size: 16px;
43 font-family: "黑体";
44 margin-top: 10px;
45 border: none;
46 }
47 #loginbtn {
48 background-color:green;
49 color: white;
50 margin-top: 40px;
51 margin-bottom: 10px;
52 }
53 #abc
54 {
55
56 background-color:green;
57 color: white;
58 width: 400px;
59 height: 50px;
60 }
61 </style>
62 </head>
63 <body>
64 <div id="login_page">
65 <div id="login_inner">
66 <p>欢迎来到在线OJ训练营</p>
67 <p>点击下方按钮即刻开始旅程</p>
68 <form action="/all_questions" method="GET">
69 <input type="submit" class="btn" id="loginbtn" value="开启OJ之旅">
70 </form>
71 <center> <button id="abc" onclick="window.location.href='https://blog.csdn.net/qq_44918090?spm=1019.2139.3001.5343'">在线OJ博客地址</button>
72 </center>
73 </div>
74 </div>
75 </body>
76 </html>
按行读取,每一行就是一个题目。
1 1 回文数 简单 ../oj_data/1
2 2 测试题 困难 ../oj_data/2
3 3 两数之和 简单 ../oj_data/3
1 #include<iostream>
2 #include<string>
3 #include<vector>
4
5 class Solution {
6 public:
7 bool isPalindrome(int x) {
8
9 }
10 };
1
2 // tail.cpp 不给用户看,最终编译时,会把用户提交的代码和tail.cpp合并成一个文件再进行编译
3
4 void Test1()
5 {
6 Solution s;
7 bool ret = s.isPalindrome(121);
8 if (ret)
9 {
10 std::cout << "Test1 OK" << std::endl;
11 }
12 else
13 {
14 std::cout << "Test1 failed" << std::endl;
15 }
16 }
17
18 void Test2()
19 {
20 Solution s;
21 bool ret = s.isPalindrome(-121);
22 if (!ret)
23 {
24 std::cout << "Test2 OK" << std::endl;
25 }
26 else
27 {
28 std::cout << "Test2 failed" << std::endl;
29 }
30 }
31
32 int main()
33 {
34 Test1();
35 Test2();
36
37 return 0;
38 }
1 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
2 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。
3
4 示例 1:
5 输入:x = 121
6 输出:true
7
8 示例?2:
9 输入:x = -121
10 输出:false
11 解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
12
13 示例 3:
14 输入:x = 10
15 输出:false
16 解释:从右向左读, 为 01 。因此它不是一个回文数。
17
18 示例 4:
19 输入:x = -101
20 输出:false
21 ?
22 提示:
23 -231?<= x <= 231?- 1
24
25 进阶:你能不将整数转为字符串来解决这个问题吗?
1 // MVC(软件设计方式)
2 // M => model: 负责数据存储
3 // V => view : 负责显示界面
4 // C => controller: 核心业务逻辑
5
6 #pragma once
7
8 #include <string>
9 #include <vector>
10 #include <map>
11 #include <fstream>
12
13 #include "util.hpp"
14
15 // model这个模块做的事情,就是把刚才文件中存储的题目信息加载起来,供服务器使用
16
17 //基于文件的方式完成题目的存储
18 //约定每个题目对应一个目录,目录的名字就是题目的id
19 //目录里面包含一下几个文件:
20 //1)header.cpp 代码框架
21 //2)tail.cpp 代码测试用例
22 //desc.txt 题目的详细描述
23 //除此之外,在搞一个oj_config.cfg文件,作为一个总的入口文件
24 //这个文件是一个行文本文件
25 //这个文件的每一行对应一个需要被服务器加载起来的题目
26 //这一行里面包含以下几个信息:题目的id,题目的名字,题目的难度,题目对应的目录
27 //
28
29 struct Question
30 {
31 std::string id;
32 std::string name;
33 std::string dir; // 题目对应的目录, 目录就包含了题目的描述 题目的框架 题目的测试用例
34 std::string star; // 题目的难度
35
36 // 以下字段通过 dir 进行获取
37 std::string desc; // 题目的描述
38 std::string header_cpp; // 题目的代码框架中的代码
39 std::string tail_cpp; // 题目的测试用例代码
40 };
// 完成核心操作,获取题目信息
// 函数中的参数 &输入性参数 *输出型参数
class OjModel
{
public:
// 把文件中的数据加载到内存中, 加到哈希表中
bool Load()
{
// 1.先打开oj_config.cfg文件
std::ifstream file("../oj_data/oj_config.cfg");
if (!file.is_open())
{
LOG(ERROR) << "open oj_config failed!" << std::endl;
return false;
}
// 2.按行读取 oj_config.cfg文件,并进行解析,
// 一行包含4个字段,字段间的分隔是 \t
std::string line;
while (std::getline(file, line))
{
// 3.根据解析结果拼装成 Question结构体
std::vector<std::string> tokens;
StringUtil::Split(line, "\t", &tokens);
// 因为设定的是4个部分
if (tokens.size() != 4)
{
LOG(ERROR) << "config file format error!\n";
continue;
}
// 4.把结构体加入到hash表
Question q;
q.id = tokens[0];
q.name = tokens[1];
q.star = tokens[2];
q.dir = tokens[3];
FileUtil::Read(q.dir + "/desc.txt", &q.desc);
FileUtil::Read(q.dir + "/header.cpp", &q.header_cpp);
FileUtil::Read(q.dir + "/tail.cpp", &q.tail_cpp);
// 将数据插入值哈希表中
// [] 如果可以不存在就创建新的键值对,如果key存在就查找对应的value
model_[q.id] = q;
}
file.close();
LOG(INFO) << "Load " << model_.size() << " question" << std::endl;
return true;
}
// 获取所有题目
bool GetAllQuestion(std::vector<Question>* question/*输出型参数*/) const
{
// 遍历哈希表就可以拿到所有题目
question->clear();
for (const auto& kv : model_)
{
// auto推导出的类型是一个键值对
question->push_back(kv.second);
}
return true;
}
// 获取某个具体的题目
bool GetQuestion(const std::string& id, Question* q) const
{
// 返回值是一个迭代器
auto pos = model_.find(id);
if (pos == model_.end())
{
return false;
}
*q = pos->second;
return true;
}
private:
// MD5 SHA1 计算字符串的哈希值
// 1.计算的哈希值非常均匀(两个字符串哪怕只差一个字符,计算得到的哈希值,差别也会很大)
// 2.不可逆,通过字符串算hash值很容易,但是给你hash值找到对应的原串,理论上是不可能的
// 3.固定长度(不管字符串多长,得到的哈希值都是固定长度)
std::map<std::string, Question> model_; // 存放题目id对应的题目信息的键值对
};
1 #include "httplib.h"
2 #include <jsoncpp/json/json.h>
3
4 #include "compile.hpp"
5 #include "util.hpp"
6 #include "oj_model.hpp"
7 #include "oj_view.hpp"
8
9 // controller 作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑
10
11 int main()
12 {
13 // 服务器启动只加载一次数据
14 OjModel model;
15 model.Load();
16
17 using namespace httplib;
18
19 Server server;
20 // [&model] lamda表达式按照引用来捕获匿名函数外的变量
// 按引用来捕获,不写&就是按值来捕获
21 server.Get("/all_questions", [&model](const Request& req, Response& resp) {
22 (void)req;
23
24 // 数据来自于Model
25 std::vector<Question> all_question;
26 model.GetAllQuestion(&all_question);
27
28 // 如何借助 all_question 数据得到最终的html,用oj_view.hpp
29 std::string html;
30 OjView::RenderAllQuestion(all_question, &html);
31 resp.set_content(html, "text/html");
32 });
34 // 登录请求
35 // server.Get();
36
37 // 注册请求
38 // server.Get();
39
40 // R"()" C++11引入的语法,标识原始字符串(忽略字符串中的转义字符)
41 // \d+ 正则表达式,用特殊符号表示字符串满足啥样的条件
42 server.Get(R"(/question/(\d+))", [&model](const Request& req, Response& resp) {
43 Question question;
44 model.GetQuestion(req.matches[1].str(), &question);
45
46 std::string html;
47 OjView::RenderQuestion(question, &html);
48
49 resp.set_content(html, "text/html");
50 });
server.Post(R"(/compile/(\d+))", [&model](const Request& req, Response& resp) {
54 // 1.先根据id获取到题目信息
55 Question question;
56 model.GetQuestion(req.matches[1].str(), &question);
57
58 // 2.解析body,获取到用户提交的代码
59 // 此处实现代码与compile_server类似
60 // 此函数将Http中的body,解析成键值对,存入body_kv
61 std::unordered_map<std::string, std::string> body_kv;
62 UrlUtil::ParseBody(req.body, &body_kv);
63 const std::string& user_code = body_kv["code"];
64
65 // 3.构造JSon结构的参数
66 Json::Value req_json; // 从req对象中获取到
67 // 需要编译的代码,是用户提交代码 + 题目的测试用例代码
68 req_json["code"] = user_code + "#define system void\n" + question. tail_cpp;
69
70 // 4.调用编译模块进行编译
71 Json::Value resp_json;
72 Compiler::CompileAndRun(req_json, &resp_json);
73
74 // 5.根据编译结果构成最终的网页
75 std::string html;
76 OjView::RenderCompileResult(resp_json["stdout"].asString(), resp_js on["reason"].asString(), &html);
77
78 resp.set_content(html, "text/html");
79 });
80
81 server.set_base_dir("../wwwroot", "");
82 server.listen("0.0.0.0", 9092);
83 }
1 #pragma once
2 #include "httplib.h"
3 #include <jsoncpp/json/json.h>
4
5 #include "compile.hpp"
6 #include "util.hpp"
7 #include "oj_model.hpp"
8 #include "oj_view.hpp"
9 #include "database.hpp"
10 #include "session.hpp"
11 // controller 作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑
12
13 #define OJ_SVR_IP "0.0.0.0"
14 #define OJ_SVR_PORT 9092
15
16 class OJServer
17 {
18 public:
19 /*
20 OJServer()
21 {
22 svr_ip_ = OJ_SVR_IP;
23 svr_port_ = OJ_SVR_PORT;
24
25 db_svr_ = nullptr;
26 all_sess_ = nullptr;
27 }
28 */
29 ~OJServer()
30 {}
31
32 /*
33 int InitOJServer(std::string db_ip, std::string db_user, std::string db_password, std::string db_db, std::string ip = OJ_SVR_IP, std::uint16_t port = OJ_SVR_PORT)
34 {
35 svr_ip_ = ip;
36 svr_port_ = port;
37
38 // 数据库连接初始化,传入数据库连接所需要的参数
39 db_svr_ = new DataBaseSvr(db_ip, db_user, db_password, db_db);
40 if (db_svr_ == nullptr)
41 {
42 return -1;
43 }
44 if(db_svr_->ConnectToMysql() < 0)
45 {
46 return -2;
47 }
48 all_sess_ = new AllSessionInfo();
49 if (all_sess_ == nullptr)
50 {
51 LOG(INFO) << "sessionInfo Init failed!" << std::endl;
52 return -3;
53 }
54
55 return 0;
56 }
57 */
58 void StartOJServer()
59 {
60 using namespace httplib;
61 // 服务器启动只加载一次数据
62 OjModel model;
63 model.Load();
64
65 /*
66 // 连接数据库
67 if(this->db_svr_->ConnectToMysql())
68 {
69 LOG(ERROR) << "MySQL connect failed" << std::endl;
70 }
71 else
72 {
73 LOG(INFO) << "MySQL connect success!" << std::endl;
74 }
75 */
76
77 /*
78 // [&model] lamda表达式按照引用来捕获匿名函数外的变量
79 server.Post("/register", [this](const Request& req, Response& resp){
80 //1.将用户提交的数据, 继续反序列化, 拿到一个json对象
81 Json::Reader r;
82 Json::Value v;
83 r.parse(req.body, v);
84
85 Json::Value resp_json;
86 //2.需要将浏览器提交的数据继续持久化(保存在数据库当中)
87 resp_json["status"] = db_svr_->InsertUserInfo(v);
88
89 Json::FastWriter w;
90 resp.body = w.write(resp_json);
91 resp.set_header("Content-Type", "application/json");
92 });
93
94 server.Post("/login", [this](const Request& req, Response& resp){
95 /*
96 * 1.将浏览器提交的json串,反序列化成为json对象
97 * 2.调用数据库模块的函数, 进行查找和比对
98 * 有邮箱继续查找, 用密码进行比对
99 * 3.根据登录状态, 判断是否生成会话信息
100 * 4.返回给浏览器一个结果
101 * 4.1 登录失败
102 * 4.2 登录成功
103 * 需要返回会话ID
104 * */
105 /*
106 Json::Reader r;
107 Json::Value v;
108 r.parse(req.body, v);
109
110 Json::Value resp_json;
111 int user_id = db_svr_->QueryUserExist(v);
112 string session_id = "";
113 if(user_id > 0)
114 {
115 Session sess(v, user_id);
116 //保存该会话信息
117 session_id = sess.GetSessionID();
118 all_sess_->InsertSessionInfo(session_id, sess);
119 }
120 resp_json["login_status"] = user_id > 0 ? true : false;
121 Json::FastWriter w;
122 resp.body = w.write(resp_json);
123 resp.set_header("Set-Cookie", session_id.c_str());
124 resp.set_header("Content-Type", "application/json");
125 });
126
127 */
128
129 server.Get("/all_questions", [&model, this](const Request& req, Response& resp) {
130
131 int loginStatus = 0;
132 // std::string username = "";
133
134 // int user_id = all_sess_->CheckSession(req);
135 /*
136 if (user_id > 0)
137 {
138 // 会话校验成功,用户登录则显示个人中心
139 loginStatus = 1;
140
141 // 并且根据用户ID查询对应的用户名,发送给前台页面进行显示
142 username = db_svr_->SelectUsernameByID(user_id);
143 }
144 */
145 // 数据来自于Model
146 std::vector<Question> all_question;
147 model.GetAllQuestion(&all_question);
148
149 // 如何借助 all_question 数据得到最终的html
150 std::string html;
151 OjView::RenderAllQuestion(all_question, &html);
152 resp.set_content(html, "text/html");
153 });
155 // R"()" C++11引入的语法,标识原始字符串(忽略字符串中的转义字符)
156 // \d+ 正则表达式,用特殊符号标识字符串满足啥样的条件
157 server.Get(R"(/question/(\d+))", [&model](const Request& req, Response& resp) {
158 Question question;
159 model.GetQuestion(req.matches[1].str(), &question);
160
161 std::string html;
162 OjView::RenderQuestion(question, &html);
163
164 resp.set_content(html, "text/html");
165 });
server.Post(R"(/compile/(\d+))", [&model](const Request& req, Response& resp) {
168 // 1.先根据id获取到题目信息
169 Question question;
170 model.GetQuestion(req.matches[1].str(), &question);
171
172 // 2.解析body,获取到用户提交的代码
173 // 此处实现代码与compile_server类似
174 // 此函数将Http中的body,解析成键值对,存入body_kv
175 std::unordered_map<std::string, std::string> body_kv;
176 UrlUtil::ParseBody(req.body, &body_kv);
177 const std::string& user_code = body_kv["code"];
178
179 // 2.1 检测用户代码中是否有敏感词汇,防止对服务器造成伤害
180 bool sesitiveFlag = CheckUserCode::isHaveSystem(user_code);
181 std::string html;
182
183 if (sesitiveFlag)
184 {
185 // 包含有敏感词汇
186 OjView::RenderCompileResult("无法完成编译", "代码中含有非法语句,请仔细检查代码!", &html);
187 }
188 else
189 {
190 // 3.构造JSon结构的参数
191 Json::Value req_json; // 从req对象中获取到
192 // 需要编译的代码,是用户提交代码 + 题目的测试用例代码
193 req_json["code"] = user_code + "#define system void\n" + question.tail_cpp;
194
195 // 4.调用编译模块进行编译
196 Json::Value resp_json;
197 Compiler::CompileAndRun(req_json, &resp_json);
198
199 // 5.根据编译结果构成最终的网页
200 OjView::RenderCompileResult(resp_json["stdout"].asString(), resp_json["reason"].asString(), &html);
201 }
202
203 resp.set_content(html, "text/html");
204 });
205
206 server.set_base_dir("../wwwroot", "");
207 server.listen(svr_ip_.c_str(), svr_port_);
208 }
209
210 private:
211 // httplib 种的server
212 httplib::Server server;
213
214 std::string svr_ip_;
215 uint16_t svr_port_;
216
217 // 数据库操作
218 // DataBaseSvr* db_svr_;
219
220 // 关于所有登录用户的session的类
221 // AllSessionInfo* all_sess_;
222 };
这里要借用首先包含 google 开源库。
开源库下载地址:google开源库
1 #pragma once
2 // google 开源库
3 #include <ctemplate/template.h>
4 #include "oj_model.hpp"
5
6 class OjView
7 {
8 public:
9 // 根据数据,生成html这个动作,通常叫做网页渲染(render)
10 static void RenderAllQuestion(const std::vector<Question>& all_question, std::string* html)
11 {
12 ctemplate::TemplateDictionary dict("all_question");
13 dict.SetValue("rows", std::to_string(all_question.size()));
14
15 // 根据传入的用户名判断用户是否登录,如果登录显示用户名,不显示登录注册按钮
16 dict.SetValue("hiddenFlag", "false");
17 /*
18 if (username != "")
19 {
20 dict.SetValue("hiddenFlag", "true");
21 dict.SetValue("username", username);
22 }
23 */
24 for (const auto& question : all_question)
25 {
26 ctemplate::TemplateDictionary* table_dict = dict.AddSectionDictionary("question");
27 table_dict->SetValue("id", question.id);
28 table_dict->SetValue("name", question.name);
29 table_dict->SetValue("star", question.star);
30 }
31
32 ctemplate::Template* tpl;
33 tpl = ctemplate::Template::GetTemplate("../template/all_question.html", ctemplate::DO_NOT_STRIP);
34 tpl->Expand(html, &dict);
35 }
37 static void RenderQuestion(const Question& question, std::string* html)
38 {
39 ctemplate::TemplateDictionary dict("question");
40 dict.SetValue("id", question.id);
41 dict.SetValue("name", question.name);
42 dict.SetValue("star", question.star);
43 dict.SetValue("desc", question.desc);
44 dict.SetValue("header", question.header_cpp);
45
46 ctemplate::Template* tpl;
47 tpl = ctemplate::Template::GetTemplate("../template/question.html", ctemplate::DO_NOT_STRIP);
48 tpl->Expand(html, &dict);
49 }
51 static void RenderCompileResult(const std::string& str_stdout, const std::string& reason, std::string* html)
52 {
53 ctemplate::TemplateDictionary dict("result");
54 dict.SetValue("stdout", str_stdout);
55 dict.SetValue("reason", reason);
56
57 ctemplate::Template* tpl;
58 tpl = ctemplate::Template::GetTemplate("../template/result.html", ctemplate::DO_NOT_STRIP);
59 tpl->Expand(html, &dict);
60 }
61 };
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>OJ系统</title>
6 <meta name="description" content="Charcoal is a free Bootstrap 4 UI kit build by @attacomsian at Wired Dots." />
7 <meta name="viewport" content="width=device-width, initial-scale=1.0">
8 <!--Bootstrap 4-->
9 <link rel="stylesheet" href="css/bootstrap.min.css">
10 <style>
11 body{
12 background-color:#EE82EE;
13
14
15
16 }
17 </style>
18 </head>
19 <body background="./images/3.png">
20
21 <nav class="navbar navbar-expand-md navbar-dark fixed-top sticky-navigation">
22 <a class="navbar-brand font-weight-bold" href="#">OJ系统</a>
23
24
25
26 </nav>
27
28 <!--hero section-->
29 <section class="bg-hero">
30 <div class="container">
31 <div class="row vh-100">
32 <div class="col-sm-12 my-auto text-center">
33 <h1>OJ系统</h1>
34 <p class="lead text-capitalize my-4">
35 基于C++实现的在线OJ系统
36 </p>
37 <a href="https://blog.csdn.net/qq_44918090?spm=1010.2135.3001.5343" class="btn btn-outline-light btn-radius btn-lg">博客地址</a>
38 </div>
39 </div>
40 </div>
41 </section>
42
43 <!--components-->
44 <section class="my-5 pt-5">
45 <div class="container">
46
47
48
49 <!-- Tables -->
50 <div class="row mb-5" id="tables">
51 <div class="col-sm-12">
52
53 <div class="mt-3 mb-5">
54 <h3>题目列表</h3>
55 <table class="table table-hover">
56 <thead>
57 <tr>
58 <th>题目序号</th>
59 <th>题目名称</th>
60 <th>题目难度</th>
61 </tr>
62 </thead>
63
64 <tbody>
65 {{#question}}
66 <tr>
67 <td>{{id}}</td>
68 <td><a href="/question/{{id}}">{{name}}</a></td>
69 <td>{{star}}</td>
70 </tr>
71 {{/question}}
72
73 </table>
74 </div>
75
76
77
78 </div>
79 </div>
80
81
82
83 </div>
84 </section>
85
86 <!--footer-->
87 <section class="py-5 bg-dark">
88 <div class="container">
89 <div class="row">
90 <div class="col-md-6 offset-md-3 col-sm-8 offset-sm-2 col-xs-12 text-center">
91 <!-- <h3>Upgrade to Pro Version</h3>
92 <p class="pt-2">
93 We are working on <b>Charcoal Pro</b> which will be released soon. The pro version
94 will have a lot more components, sections, icons, plugins and example pages.
95 Join the waiting list to get notified when we release it (plus discount code).
96 </p>
97 <a class="btn btn-warning" href="https://wireddots.com/newsletter">Join Waiting List</a>
98 <hr class="my-5"/> -->
99 <center>
100 <p class="pt-2 text-muted">
101 归属@2022->CSDN万粉博主:森明帮大于黑虎帮
102 </p>
103
104 </center>
105 </div>
106 </div>
107 </div>
108 </section>
109
110 <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
111 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
112 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
113 <script src="js/app.js"></script>
114 </body>
115 </html>
1 <html>
2 <head>
3 <meta charset="utf-8">
4 <title>{{id}}.{{name}}</title>
5 </head>
6
7 <style type="text/css">
8 * {
9 margin: 0;
10 padding: 0;
11 list-style: none;
12 }
13
14 #left {
15 width: 38%;
16 float: left;
17 word-wrap: break-word;
18
19 position: absolute;
20 top: 10px;
21 left: 20px;
22 }
23 #right {
24 width: 60%;
25 height: 100%;
26 float: right;
27 background-color: #F7F7F7;
28 }
29 pre {
30 white-space:pre-wrap;
31 white-space:-moz-pre-wrap;
32 white-space:-pre-wrap;
33 white-space:-o-pre-wrap;
34 word-wrap:break-word;
35 }
36
37 #codeEditor {
38 display: flex;
39 align-items: stretch;
40
41 width: 94%;
42 height: 85%;
43 border: 0;
44 border-radius: 5px;
45
46 position: relative;
47 left: 0;
48 right: 0;
49 bottom: 0;
50 top:10px;
51 margin: auto;
52 }
53
54 #codeArea {
55 width: 95%;
56 padding-top: 5px;
57 }
58
59 #subbutton {
60 border: 0;
61 border-radius: 5px;
62 width: 120px;
63 height: 40px;
64 color: white;
65 font-size: 15px;
66 background-color: #2DB55D;
67
68 float: right;
69 position: absolute;
70 right: 20px;
71 bottom: 10px;
72 }
73
74 </style>
75
76 <body >
77
78 <div id="left">
79 <h2 style="border-bottom:2px; width:100%;">题目描述</h2>
80 <br>
81
82 <div><font style="font-size: 23px">{{id}}.{{name}}</font></div>
83 <font style="font-size:18px">难度:</font> {{star}}<br><br><br>
84 <div><pre>{{desc}}</pre></div>
85 </div>
86
87 <div id="right">
88 <form action="/compile/{{id}}" method="POST">
89 <font style="font-size:20px; padding-top: 10px;"> 代 码 编 写:</font>
90
91 <div id="codeEditor" style="min-height:400px"><textarea id="codeArea" type="text" name="code" rows="30" cols="100">{{header}}</textarea>
92 </div>
93
94 <input id="subbutton" type="button" value="提交代码" onclick="submit({{id}})">
95 </form>
96 </div>
97
98 </body>
99
100 </html>
1 <html>
2 <head>
3 <meta charset="utf-8">
4
5 <style type="text/css">
6 body {
7 background-color: rgba(29, 111, 145, 0.9);
8 font-family: "黑体";
9 }
10
11 div {
12 width: 100%;
13 text-align: center;
14 font-size: 25px;
15 }
16 </style>
17
18 </head>
19
20 <body>
21 <h1 align="center" style="color: white">运行结果</h2>
22 <div>
23 <pre style="color:yellow">{{stdout}}</pre>
24 </div>
25 <div>
26 <pre style="color:orange">{{reason}}</pre>
27 </div>
28 </body>
29 </html>
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_44918090/article/details/123696915
内容来源于网络,如有侵权,请联系作者删除!