毕设项目:基于BS模型的在线OJ系统

x33g5p2x  于2022-03-28 转载在 其他  
字(35.1k)|赞(0)|评价(0)|浏览(378)

系列文章目录

前言

一、在线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[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	};
5.查找用户代码中是否有危害服务器的语句
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	};

5.compile.hpp在线编译类

该程序会生成以下的文件: 此处本质使用文件完成进程间通信。

1.源代码的文件
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     }
2.编译错误文件
51     //  2.编译错误文件
 52     static std::string CompileErrorPath(const std::string& name)
 53     {
 54         return "../tmp_files/" + name + ".compile_err";
 55     }
3.可执行程序文件
56     //  3.可执行程序文件
 57     static std::string ExePath(const std::string& name)
 58     {
 59         return "../tmp_files/" + name + ".exe";
 60     }

4.标准输入文件
61     //  4.标准输入文件
 62     static std::string StdinPath(const std::string& name)
 63     {
 64         return "../tmp_files/" + name + ".stdin";
 65     }
5.标准输出文件
66     //  5.标注输出文件
 67     static std::string StdoutPath(const std::string& name)
 68     {
 69         return "../tmp_files/" + name + ".stdout";
 70     }
6.标准错误文件
71     //  6.标准错误文件
 72     static std::string StderrPath(const std::string& name)
 73     {
 74         return "../tmp_files/" + name + ".stderr";
 75     }

4 5 6号文件目的是为了通知可执行程序文件,所以可以使用进程间通信进行 ,完成可以不需要创建文件。

7.CompileAndRun函数
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	    }
8.WriteTmpFile函数

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     }
9.Compile函数

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	    }

10.Run函数
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	};

6.post提交到所有问题界面

加上这个目录是为了浏览器能够访问到静态页面,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>

7.在线编译模块小结

三、题目管理模块

1.oj_data存放题目的文件夹

1.oj_config.cfg每一行都代表一个题目

按行读取,每一行就是一个题目。

1	1	回文数	简单	../oj_data/1
     2	2	测试题	困难	../oj_data/2
     3	3   两数之和 简单   ../oj_data/3
2.header.cpp代码框架

1	#include<iostream>
     2	#include<string>
     3	#include<vector>
     4	
     5	class Solution {
     6	public:
     7	    bool isPalindrome(int x) {
     8	
     9	    }
    10	};
3.tail.cpp代码测试用例

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	}
4.desc.txt题目详细描述

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	进阶:你能不将整数转为字符串来解决这个问题吗?

2.MVC中的M负责存储数据 oj_model.hpp这个oj_model模块做的事情,就是把刚才文件中存储的题目信息加载起来,供服务器使用

1.描述题目的类question

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	};
2.load函数表示把文件中的数据加载到内存中, 加到哈希表中

// 完成核心操作,获取题目信息
// 函数中的参数 &输入性参数  *输出型参数
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;
    }
3.GetAllQuestion获取所有题目

// 获取所有题目
    bool GetAllQuestion(std::vector<Question>* question/*输出型参数*/) const
    {
        // 遍历哈希表就可以拿到所有题目
        question->clear();
        for (const auto& kv : model_)
        {
            // auto推导出的类型是一个键值对
            question->push_back(kv.second);
        }

        return true;
    }
4.GetQuestion获取某个具体的题目
// 获取某个具体的题目
    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对应的题目信息的键值对
};

3.MVC中的C controller: 核心业务逻辑 oj_server.cpp作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑

1.all_questions核心业务逻辑
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	    });
2.question核心业务逻辑
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	    });
3.compile核心业务逻辑
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 }

4.oj_server.hpp对于oj_server.cpp的实现部分controller 作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑

1.server.Get("/all_questions")
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	            });
2.server.Get(R"(/question/(\d+))"
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	                    });
3.server.Post(R"(/compile/(\d+))"
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	};

5.MVC中的v V => view : 负责显示界面 oj_view.hpp根据数据,生成html这个动作,通常叫做网页渲染(render)

这里要借用首先包含 google 开源库。

开源库下载地址:google开源库

1.RenderAllQuestion渲染所有问题界面

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	    }
2.RenderQuestion渲染单个问题界面

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	    }
3.RenderCompileResult渲染结果界面

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.all_question.html

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>

2.question.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;">&nbsp;&nbsp;&nbsp;&nbsp;代&nbsp;码&nbsp;编&nbsp;写:</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>

3.result.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>

总结

相关文章

目录