系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、在线OJ系统描述
  • 二、在线编译模块
    • 1.搭建一个HTTP服务器完成在线编译
    • 2.收到HTTP请求,进行数据格式转化(HTTP中body的内容转换为JSON格式的字符串)
    • 3.compile_server.cpp浏览器提交JSON数据请求服务器,服务器调用在线编译模块编译,把结果返回给浏览器
    • 4.util.hpp工具类
      • 1.TimeUtil类时间戳获取工具TimeUtil标识文件的不同
      • 2.打印日志的工具
      • 3.文件类FileUtil把文件所有内容读取出来,放到content字符串中
      • 4.URL body解析模块
      • 5.查找用户代码中是否有危害服务器的语句
    • 5.compile.hpp在线编译类
      • 1.源代码的文件
      • 2.编译错误文件
      • 3.可执行程序文件
      • 4.标准输入文件
      • 5.标准输出文件
      • 6.标准错误文件
      • 7.CompileAndRun函数
      • 8.WriteTmpFile函数
      • 9.Compile函数
      • 10.Run函数
    • 6.post提交到所有问题界面
    • 7.在线编译模块小结
  • 三、题目管理模块
    • 1.oj_data存放题目的文件夹
      • 1.oj_config.cfg每一行都代表一个题目
      • 2.header.cpp代码框架
      • 3.tail.cpp代码测试用例
      • 4.desc.txt题目详细描述
    • 2.MVC中的M负责存储数据 oj_model.hpp这个oj_model模块做的事情,就是把刚才文件中存储的题目信息加载起来,供服务器使用
      • 1.描述题目的类question
      • 2.load函数表示把文件中的数据加载到内存中, 加到哈希表中
      • 3.GetAllQuestion获取所有题目
      • 4.GetQuestion获取某个具体的题目
    • 3.MVC中的C controller: 核心业务逻辑 oj_server.cpp作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑
      • 1.all_questions核心业务逻辑
      • 2.question核心业务逻辑
      • 3.compile核心业务逻辑
    • 4.oj_server.hpp对于oj_server.cpp的实现部分controller 作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑
      • 1.server.Get("/all_questions")
      • 2.server.Get(R"(/question/(\d+))"
      • 3.server.Post(R"(/compile/(\d+))"
    • 5.MVC中的v V => view : 负责显示界面 oj_view.hpp根据数据,生成html这个动作,通常叫做网页渲染(render)
      • 1.RenderAllQuestion渲染所有问题界面
      • 2.RenderQuestion渲染单个问题界面
      • 3.RenderCompileResult渲染结果界面
  • 三、前端结果界面
    • 1.all_question.html
    • 2.question.html
    • 3.result.html
  • 总结

前言


一、在线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_kv35          std::unordered_map<std::string, std::string> body_kv;36           UrlUtil::ParseBody(req.body, &body_kv);37   38          // Json如何进行解析和构造? 使用jsoncpp第三方库39           // 40           // 下方调用CompileAndRun41          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 TimeUtil19 {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] hello44  // 日志的使用方式形如: 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        FATAL57 };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 StringUtil151 {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_on158     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 UrlUtil166    {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]=value185                if (kv.size() != 2)186             {187                    continue;188                }189    190             // 出参,将切分好的键值对,传给调用位置191              // unordered_map [] 操作,如果key不存在则新增,如果key存在,则获取到value192                // 2.对键值对中的转义过的字符进行urldecode193             // 只用对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 CheckUserCode260  {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             // 虽然是编译出错,这样的错误是用户的错误,并不是服务器的错误,对于服务器来说,这样的编译错误不是错误,所以把它叫做info134              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             // 虽然是编译出错,这样的错误是用户的错误,并不是服务器的错误,对于服务器来说,这样的编译错误不是错误,所以把它叫做info153              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++11201          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            else224         {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         #abc54              {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/12   2   测试题 困难  ../oj_data/23   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 = 1216 输出:true7 8   示例?2:9   输入:x = -12110   输出:false11   解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。12  13  示例 3:14  输入:x = 1015 输出:false16   解释:从右向左读, 为 01 。因此它不是一个回文数。17    18  示例 4:19  输入:x = -10120   输出:false21   ?22 提示:23    -231?<= x <= 231?- 124  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 once7   8   #include <string>9    #include <vector>10   #include <map>11  #include <fstream>12  13  #include "util.hpp"14 15  // model这个模块做的事情,就是把刚才文件中存储的题目信息加载起来,供服务器使用16 17  //基于文件的方式完成题目的存储18  //约定每个题目对应一个目录,目录的名字就是题目的id19    //目录里面包含一下几个文件: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个字段,字段间的分隔是 \tstd::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存在就查找对应的valuemodel_[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          // 数据来自于Model25         std::vector<Question> all_question;26         model.GetAllQuestion(&all_question);27          28          // 如何借助 all_question 数据得到最终的html,用oj_view.hpp29         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_kv61         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 once2   #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 909215  16  class OJServer17    {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             else72              {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                       *         需要返回会话ID104                       * */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                       // 数据来自于Model146                        std::vector<Question> all_question;147                        model.GetAllQuestion(&all_question);148 149                     // 如何借助 all_question 数据得到最终的html150                     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_kv175                     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                        else189                     {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 种的server212          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 once2   // google 开源库3  #include <ctemplate/template.h>4  #include "oj_model.hpp"5  6   class OjView7   {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>

总结


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

  1. SSM毕设项目 - 基于SSM的电影院在线售票系统(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 开发环境 3.3 系统流程 3.3.1 系统开发流程 3.3.2 用户登录流程 3.3.3 系统操作流程 3 ...

  2. node.js+uni计算机毕设项目基于微信小程序在线抽签系统(程序+小程序+LW)

    该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程.欢迎交流 项目运行 环境配置: Node.js+ Vscode + Mysql5.7 + HBuilderX+Navicat11+Vue ...

  3. ssm毕设项目基于Java的医疗器械销售系统oy281(java+VUE+Mybatis+Maven+Mysql+sprnig)

    ssm毕设项目基于Java的医疗器械销售系统oy281(java+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mys ...

  4. ssm毕设项目基于JAVAEE的车检预约系统846ks(java+VUE+Mybatis+Maven+Mysql+sprnig)

    ssm毕设项目基于JAVAEE的车检预约系统846ks(java+VUE+Mybatis+Maven+Mysql+sprnig) 项目运行 环境配置: Jdk1.8 + Tomcat8.5 + Mys ...

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

    文章目录 项目演示 预先知识 请问 在处理用户同时提交代码时是 多进程处理还是 多线程处理? 你是如何创建多进程的逻辑的 如何获取到编译与运行后的结果? 编译运行模块 子进程之间如何并发? 文件读写操 ...

  6. Java项目-基于Springboot实现英语在线学习系统

     项目编号:BS-GX-025 运行环境: 开发工具:IDEA /ECLIPSE 数据库:MYSQL5.7 应用服务:Tomcat8.5.31 项目构建:Maven 后台开发技术:Springboot ...

  7. VUE毕设项目 - 基于SSM的网上租车系统(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 系统流程 3.2.1 系统开发流程 3.2.2 登录流程 3.3 系统结构设计 4 项目获取 1 项目简介 ...

  8. SSM毕设项目 - 基于SSM的大学生兼职跟踪系统(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 系统流程 3.2.1 系统开发流程 3.2.2 学生登录流程 3.2.3 系统操作流程 3.3 系统结构设计 ...

  9. 毕设项目 - 基于SSM的大学生兼职跟踪系统(含源码+论文)

    文章目录 1 项目简介 2 实现效果 2.1 界面展示 3 设计方案 3.1 概述 3.2 系统流程 3.2.1 系统开发流程 3.2.2 学生登录流程 3.2.3 系统操作流程 3.3 系统结构设计 ...

最新文章

  1. vs oracle带参数更新,Oracle vs PostgreSQL Develop(23) - PL(pg)sql(参数声明)
  2. 七、Java编码字符集和转义符介绍
  3. 读取带空格字符串小结
  4. mysql把select结果存到变量中_mysql实例 select into保存到变量的例子
  5. 成员变量(全局变量)和局部变量区别
  6. 如何正确的开始用Go编程
  7. python绘制函数图像
  8. SSM服装管理系统毕业设计源码080948
  9. 基于STM32F103系列单片机四路定时器电机编码器模式配置过程附源码
  10. 牧师与魔鬼 -- version2 动作分离
  11. power_supply子系统
  12. Broadcom BCM4312 无线网卡驱动安装
  13. 再摘一枚重要奖项!腾讯安全获得云安全联盟CSA 2022安全金盾奖
  14. Weblogic创建domain域
  15. js实现table 表头顶部固定方法
  16. 2022 考研上岸中科大大数据学院一点点经验
  17. 普朗克公式matlab,用MATLAB实现普朗克函数积分的快捷计算.pdf
  18. Mastercam 2017四轴五轴编程加工实例视频教程
  19. html页面加载蒙版_javascript 窗口加载蒙板 内嵌网页内容
  20. C语言职工管理系统课程设计

热门文章

  1. linux加载虚拟sriov网卡,网卡直通SR-IOV技术
  2. 下一代互联网:三网融合下的美丽画卷
  3. ttyLinux安装完整指南
  4. windows系统下载安装JDK8
  5. 抢滩大数据金融“蓝海”
  6. 抽样检验规范——多次(多轮)抽样的理解
  7. 量子计算机 基因工程,2020 ASC世界大学生超级计算机竞赛聚焦量子计算和语言智能...
  8. Windows Vista With Service Pack 2(x86 / x64)官方简体中文版(ISO)光盘镜像
  9. MySQL 中的系统库之sys 系统库
  10. Windows下Tomcat的搭建步骤