1. 数据存储模块
题目的存储:
通过文件存储所有的题目。所有的题目信息(题目序号,题目名称,题目难度,题目所在路径)保存在oj_data/oj_config.cfg文本文件中。每个题目在oj_data目录中就对应着一个目录,目录的名称为题目的id。目录中包含了三个文件,分别是 题目描述,代码框架,代码测试用例对外提供的接口:
struct Question{ std::string id; //题目序号 std::string name; //题目名称 std::string dir; //题目所在路径 std::string star; //题目难度 //一个题目的详细信息 std::string desc; //题目描述 std::string header_cpp; //代码框架 std::string tail_cpp; //测试用例 }; //加载题库到内存中,用map来组织数据 bool Load(); //获取所有题目信息 bool GetAllQuestions(std::vector<Question>* questions); //获取指定id的题目信息 bool GetQuestion(const std::string& id,Question* q);2. 页面显示模块
界面如何展示? 使用Google开源库ctemplate来构造HTML服务端使用第三方库httplib 来搭建对外提供的接口: //根据所有的题目列表信息,生成HTML, 供网站首页显示 static void RenderAllQuestions(const std::vector<Question>& all_questions,std::string* html); //根据指定的question, 生成HTML static void RenderQuestion(const Question& question,std::string* html); //根据运行结果或编译出错的结果,构造生成HTML static void RenderResult(const std::string& str_stdout,const std::string& reason,std::string* html);3. 在线编译模块
设计思路:
根据请求的代码,生成源代码文件创建子进程,父进程进程等待,子进程进行进程替换,调用g++进行编译,把编译结果记录到临时文件中运行可执行程序,执行测试用例代码,把运行结果也记录到临时文件中。此操作也是通过子进程进程替换实现的把结果打包成最终的响应数据,并返回对外提供的接口:
//编译并运行 static bool CompileAndRun(const Json::Value& req,Json::Value* resp); oj_server.cc #include"httplib.h" #include<jsoncpp/json/json.h> #include"util.hpp" #include"oj_model.hpp" #include"oj_view.hpp" #include"compile.hpp" int main(){ //加载题库数据资源 OjModel model; model.Load(); //使用第三方库httplib 来搭建服务器 using namespace httplib; Server server; //项目首页 server.Get("/",[&model](const Request& req, Response& resp){ (void) req; //通过model获取所有的题目信息 std::vector<Question> all_questions; model.GetAllQuestions(&all_questions); //将all_questions的数据转换为html std::string html; OjView::RenderAllQuestions(all_questions,&html); //将后端处理完的请求返回给客户端 resp.set_content(html,"text/html"); }); //具体题目的页面 server.Get(R"(/question/(\d+))",[&model](const Request& req, Response& resp){ //通过model获取指定题目的信息 Question question; model.GetQuestion(req.matches[1].str(),&question); //将question的数据转换为html std::string html; OjView::RenderQuestion(question,&html); //将后端处理完的请求返回给客户端 resp.set_content(html,"text/html"); }); //代码运行结果界面 server.Post(R"(/compile/(\d+))",[&model](const Request& req, Response& resp){ //1. 通过model获取指定题目的信息 Question question; model.GetQuestion(req.matches[1].str(),&question); //2. 解析body, 获取用户提交的代码 std::unordered_map<std::string,std::string> body_kv; UrlUtil::ParseBody(req.body,&body_kv); const std::string& user_code = body_kv["code"]; //3. 构造json格式的参数 Json::Value req_json; // 编译的代码 = 用户提交的代码 + 测试用例代码 req_json["code"] = user_code + question.tail_cpp; req_json["stdin"] = user_code; //4. 调用编译模块进行编译 Json::Value resp_json; Compiler::CompileAndRun(req_json,&resp_json); //5. 将运行结果构造成HTML std::string html; OjView::RenderResult(resp_json["stdout"].asString(), resp_json["reason"].asString(),&html); //6. 将后端处理完的请求返回给客户端 resp.set_content(html,"text/html"); }); server.set_base_dir("./wwwroot"); server.listen("0.0.0.0",9092); return 0;