【负载均衡式的在线oj(2.oj

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

    实战项目负载均衡式在线OJ


    • 博主主页桑榆非晚ᴷ

    • 博主能力有限如果有出错的地方希望大家不吝赐教

    • 给自己打气成功没有快车道幸福没有高速路。所有的成功都来自不倦地努力和奔跑所有的幸福都来自平凡的奋斗和坚持✨


    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存失败,源站可能有防盗链机制,建议将图片保存下来直接上传上传(imY8zj1iPfVm-1682861111511)(C:\Users\13916\Pictures\Saved Pictures\壁纸\微信图片_20221128141855.jpg)(C:\Users\13916\Pictures\Saved Pictures\壁纸\微信图片_20221128141855.jpg)]


    负载均衡式在线OJ

    二、oj_server模块

    2.1、oj_server子模块

    2.1.1 、oj_server子模块介绍

    主要完成对用户所请求资源的路由功能分别为获取所有所有题目、获取单个指定题目和对指定题目运行结果的判题。

    2.1.2 、程序编写

    #include <iostream>
    #include "../comm/httplib.h"
    #include "oj_control.hpp"
    
    using std::string;
    using namespace httplib;
    using namespace ns_control;
    
    int main()
    {
        // 用户请求服务器路由的功能
        Server svr;
        Control ctrl;
    
        svr.set_base_dir("./wwwroot");
       
        // 1.功能获取所有的题目列表
        svr.Get("/all_questions", [&ctrl](const Request &req, Response &resp)
        { 
            // 用于测试Get /all_questions资源是否可以成功
            resp.set_content("这是所有题目的列表", "text/plain; charset=utf-8"); 
                    
            // 返回一张包含所有题目的html网页
            // string html;
            // ctrl.AllQuestions(&html);
            // resp.set_content(html, "text/html; charset=utf-8"); 
        });
        
          
        // 2.功能用户根据题目编号获取题目的内容
        // (\d+)正则表达式\d表示一个数字+表式多个数字
        // R"()"保持字符串原貌
        svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp)
        {
            string number = req.matches[1];
            
            // 用于测试Get /question/(\d+)资源是否可以成功 
            resp.set_content("这是指定的一道题" + number, "text/plain; charset=utf-8"); 
    
            // 返回用户要获取指定题目的html网页
            // string html;
            // ctrl.OneQuestion(number, &html);
            // resp.set_content(html, "text/html; charset=utf-8");    
        });
        
        
        // 3.功能用户提交代码使用测试用例进行判题
        svr.Get(R"(/judge/(\d+))", [](const Request &req, Response &resp)
        {
            string number = req.matches[1];
            // 用于测试Get /judge/(\d+)资源是否可以成功
            resp.set_content("指定题目的判题" + number, "text/plain; charset=utf-8"); 
        });
    
        svr.listen("0.0.0.0", 8080);
        return 0;
    }
    
    

    2.1.3 、oj_server子模块测试

    测试用例为上面代码由于相关功能还没有实现所以简单模拟返回三个路由结果来测试路由功能是否正常。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    观察上面测试结果满足我们预期想要的结果所以目前路由功能是没有问题的。

    2.2、oj_model子模块

    2.2.1 oj_model子模块介绍

    oj_model子模块主要用来服务器和浏览器进行数据交互对外提供访问数据的接口。例如浏览器获取所有的oj题目链表和获取指定oj题目的具体内容。

    2.2.2 程序编写

    #pragma once
    // 文件版本
    #include <iostream>
    #include <string>
    #include <vector>
    #include <unordered_map>
    #include <fstream>
    #include <cstdlib>
    #include <cassert>
    #include "../comm/util.hpp"
    #include "../comm/log.hpp"
    
    // 根据题目list文件加载所有的题目信息到内存中
    // model: 主要用来和数据进行交互对外提供访问数据的接口
    
    namespace ns_model
    {
        using std::string;
        using std::vector;
        using std::unordered_map;
        using namespace ns_log;
        using namespace ns_util;
    
        const std::string questins_list = "./questions/questions.list";
        const std::string questins_path = "./questions/";
    
        struct Question
        {
            string number; // 题目编号唯一
            string title;  // 题目的标题
            string star;   // 难度: 简单 中等 困难
            int cpu_limit; // 题目的时间要求(S)
            int mem_limit; // 题目的空间要去(KB)
            string desc;   // 题目的描述
            string header; // 题目预设给用户在线编辑器的代码
            string tail;   // 题目的测试用例需要和header拼接形成完整代码
        };
    
        class Model
        {
        public:
            Model()
            {
                assert(LoadQuestionList(questins_list));
            }
            
            // 加载配置文件: questions/questions.list + 题目编号文件
            // 形成题目编号和题目内容的映射关系保存到unordered_map当中
            bool LoadQuestionList(const string &question_list)
            {
                std::ifstream in(question_list);
                if (!in.is_open())
                {
                    LOG(FATAL) << " 加载题库失败,请检查是否存在题库文件"
                               << "\n";
                    return false;
                }
    
                string line;
                // 按行读取不保留\n
                while (std::getline(in, line))
                {
                    vector<string> tokens;
                    
                    // 将获取的行数据 {1 判断回文数 简单 1 30000} 按照空格切割
                    // 切割产生的五个字符串切割产生的五个字符串存放在tokens当中
                    StringUtil::SplitString(line, &tokens, " ");
                    if (tokens.size() != 5)
                    {
                        LOG(WARNING) << "加载部分题目失败, 请检查文件格式"
                                     << "\n";
                        continue;
                    }
    
                    Question question;
                    question.number = tokens[0];
                    question.title = tokens[1];
                    question.star = tokens[2];
                    question.cpu_limit = atoi(tokens[3].c_str());
                    question.mem_limit = atoi(tokens[4].c_str());
    
                    string path = questins_path;
                    path += question.number;
                    path += "/";
                    // ./questions/ + question.number + desc.txt or header.cpp or tail.cpp
                    FileUtil::ReadFile(path + "desc.txt", &(question.desc));
                    FileUtil::ReadFile(path + "header.cpp", &(question.header));
                    FileUtil::ReadFile(path + "tail.cpp", &(question.tail));
    
                    // 形成题目编号和题目内容的映射关系保存到unordered_map当中
                    _questions.insert({question.number, question});
                }
    
                LOG(INFO) << "加载题库...成功!" << "\n";
                          
                in.close();
                return true;
            }
            
            // 从编号到题目的映射表unordered_map中获取所有题目
            bool GetAllQuestions(vector<Question> *out)
            {
                if (_questions.size() == 0)
                {
                    LOG(ERROR) << "用户获取题库失败"
                               << "\n";
                    return false;
                }
                for (const auto &q : _questions)
                {
                    out->push_back(q.second); // first: key, second: value
                }
    
                return true;
            }
    
            // 从编号到题目的映射表unordered_map中获取指定题目
            bool GetOneQuestion(const std::string &number, Question *q)
            {
                const auto &iter = _questions.find(number);
                if (iter == _questions.end())
                {
                    LOG(ERROR) << "用户获取题目失败, 题目编号: " << number << "\n";
                    return false;
                }
                (*q) = iter->second;
                return true;
            }
        private:
            // 题号 : 题目细节
            unordered_map<string, Question> _questions;
        };
    }
    

    2.2.3 oj_model子模块测试

    在当前工作路径下创建questions目录在questions目录下创建questions.list文件并录入一些题目
    一下文题目编号为1题目的相关内容
    在这里插入图片描述
    接着在questions目录下以题目编号为目录名创建目录并在目录下创建desc.txt、header.cpp和tail.cpp
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    以编号为1的题目为例
    当用户要获取1号题目时服务器会把header.cpp文件中的内容发送给用户浏览器用户在其中进行编写代码最后把编写好的代码提交给服务器服务器把提交上来的代码与tail.cpp拼接把拼接好的代码负载均衡式的分配给后台负责编译运行的服务器进行处理。

    测试代码

    #include "oj_model.hpp"
    #include <vector>
    
    using namespace ns_model;
    using namespace std;
    
    int main()
    {
        Model model;
        vector<Question> all_questions;
        model.GetAllQuestions(&all_questions);
        if (all_questions.size() == 2)
        {
            cout << "*********************************" << endl;
            for (auto &question : all_questions)
            {
                cout << question.number << endl;
                cout << question.title << endl;
                cout << question.star << endl;
                cout << question.cpu_limit << endl;
                cout << question.mem_limit << endl;
                cout << question.mem_limit << endl;
                cout << question.desc << endl;
                cout << question.header << endl;
                cout << question.tail << endl;
            }
            cout << "*********************************" << endl;
        }
        else
        {
            cout << "GetAllQuestion error" << endl;
        }
    
        Question question;
        if (model.GetOneQuestion("1", &question))
        {
            cout << "*********************************" << endl;
            cout << question.number << endl;
            cout << question.title << endl;
            cout << question.star << endl;
            cout << question.cpu_limit << endl;
            cout << question.mem_limit << endl;
            cout << question.mem_limit << endl;
            cout << question.desc << endl;
            cout << question.header << endl;
            cout << question.tail << endl;
            cout << "*********************************" << endl;
        }
        else
        {
            cout << "GetOneQuestion error" << endl;
        }
        return 0;
    }
    

    测试结果

    [lk@VM-8-15-centos oj_server]$ g++ test.cc -std=c++11 
    [lk@VM-8-15-centos oj_server]$ ./a.out 
    [INFO][oj_model.hpp][94][1682948237]加载题库...成功!
    *********************************
    2
    求最大值
    简单
    1
    30000
    30000
    求最大值比如vector v ={1,2,3,4,5,6,12,3,4,-1};
    求最大值, 比如输出 12
    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    class Solution
    {
    public:
        int Max(const vector<int> &v)
        {
            //将你的代码写在下面
    
            return 0;
        }
    };
    #ifndef COMPILER_ONLINE
    #include "header.cpp"
    #endif
    
    void Test1()
    {
        vector<int> v = {1, 2, 3, 4, 5, 6};
        int max = Solution().Max(v);
        if (max == 6)
        {
            std::cout << "Test 1 .... OK" << std::endl;
        }
        else
        {
            std::cout << "Test 1 .... Failed" << std::endl;
        }
    }
    
    void Test2()
    {
        vector<int> v = {-1, -2, -3, -4, -5, -6};
        int max = Solution().Max(v);
        if (max == -1)
        {
            std::cout << "Test 2 .... OK" << std::endl;
        }
        else
        {
            std::cout << "Test 2 .... Failed" << std::endl;
        }
    }
    
    int main()
    {
        Test1();
        Test2();
    
        return 0;
    }
    1
    判断回文数
    简单
    1
    30000
    30000
    判断一个整数是否是回文数。回文数是指正序从左向右和倒序从右向左读都是一样的整数。
    
    示例 1:
    
    输入: 121
    输出: true
    示例 2:
    
    输入: -121
    输出: false
    解释: 从左向右读,-121 。 从右向左读, 为 121- 。因此它不是一个回文数。
    示例 3:
    
    输入: 10
    输出: false
    解释: 从右向左读, 为 01 。因此它不是一个回文数。
    进阶:
    
    你能不将整数转为字符串来解决这个问题吗
    
    #include <iostream>
    #include <string>
    #include <vector>
    #include <map>
    #include <algorithm>
    
    using namespace std;
    
    class Solution{
        public:
            bool isPalindrome(int x)
            {
                //将你的代码写在下面
                
                return true;
            }
    };
    #ifndef COMPILER_ONLINE
    #include "header.cpp"
    #endif
    
    
    void Test1()
    {
        // 通过定义临时对象来完成方法的调用
        bool ret = Solution().isPalindrome(121);
        if(ret){
            std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
        }
        else{
            std::cout << "没有通过用例1, 测试的值是: 121"  << std::endl;
        }
    }
    
    void Test2()
    {
        // 通过定义临时对象来完成方法的调用
        bool ret = Solution().isPalindrome(-10);
        if(!ret){
            std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
        }
        else{
            std::cout << "没有通过用例2, 测试的值是: -10"  << std::endl;
        }
    }
    
    int main()
    {
        Test1();
        Test2();
    
        return 0;
    }
    *********************************
    *********************************
    1
    判断回文数
    简单
    1
    30000
    30000
    判断一个整数是否是回文数。回文数是指正序从左向右和倒序从右向左读都是一样的整数。
    
    示例 1:
    
    输入: 121
    输出: true
    示例 2:
    
    输入: -121
    输出: false
    解释: 从左向右读,-121 。 从右向左读, 为 121- 。因此它不是一个回文数。
    示例 3:
    
    输入: 10
    输出: false
    解释: 从右向左读, 为 01 。因此它不是一个回文数。
    进阶:
    
    你能不将整数转为字符串来解决这个问题吗
    
    #include <iostream>
    #include <string>
    #include <vector>
    #include <map>
    #include <algorithm>
    
    using namespace std;
    
    class Solution{
        public:
            bool isPalindrome(int x)
            {
                //将你的代码写在下面
                
                return true;
            }
    };
    #ifndef COMPILER_ONLINE
    #include "header.cpp"
    #endif
    
    
    void Test1()
    {
        // 通过定义临时对象来完成方法的调用
        bool ret = Solution().isPalindrome(121);
        if(ret){
            std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
        }
        else{
            std::cout << "没有通过用例1, 测试的值是: 121"  << std::endl;
        }
    }
    
    void Test2()
    {
        // 通过定义临时对象来完成方法的调用
        bool ret = Solution().isPalindrome(-10);
        if(!ret){
            std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
        }
        else{
            std::cout << "没有通过用例2, 测试的值是: -10"  << std::endl;
        }
    }
    
    int main()
    {
        Test1();
        Test2();
    
        return 0;
    }
    *********************************
    
    

    阅读测试结果与预期想得到的结果一致所以目前model子模块没有问题。

    2.3、 oj_view子模块

    2.3.1、 oj_view子模块介绍

    oj_view子模块的功能是对数据进行渲染形成html文件。

    2.3.2、 程序编写

    #pragma once
    
    #include <iostream>
    #include <string>
    #include <ctemplate/template.h>
    
    #include "oj_model.hpp"
    // #include "oj_model2.hpp"
    
    namespace ns_view
    {
        using namespace ns_model;
    
        const std::string template_path = "./template_html/";
    
        class View
        {
        public:
            void AllExpandHtml(const vector<Question> &questions, std::string *html)
            {
                // 题目的编号 题目的标题 题目的难度
                // 推荐使用表格显示
                // 1. 形成路径
                std::string src_html = template_path + "all_questions.html";
                // 2. 形成数字典
                ctemplate::TemplateDictionary root("all_questions");
                for (const auto &q : questions)
                {
                    ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");
                    sub->SetValue("number", q.number);
                    sub->SetValue("title", q.title);
                    sub->SetValue("star", q.star);
                }
    
                // 3. 获取被渲染的html
                ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);
    
                // 4. 开始完成渲染功能
                tpl->Expand(html, &root);
            }
    
    
            void OneExpandHtml(const struct Question &q, std::string *html)
            {
                // 1. 形成路径
                std::string src_html = template_path + "one_question.html";
    
                // 2. 形成数字典
                ctemplate::TemplateDictionary root("one_question");
                root.SetValue("number", q.number);
                root.SetValue("title", q.title);
                root.SetValue("star", q.star);
                root.SetValue("desc", q.desc);
                root.SetValue("header", q.header);
    
                // 3. 获取被渲染的html
                // ctemplate::DO_NOT_STRIP 保持代码原样
                ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);
    
                // 4. 开始完成渲染功能
                tpl->Expand(html, &root);
            }
        };
    }
    

    2.3.3、oj_view子模块测试

    这里先不做测试稍后与oj_control联合测试。

    2.4、 oj_control子模块

    2.4.1、 oj_control子模块介绍

    2.4.2、 程序编写

    #pragma once
    #include <iostream>
    #include <string>
    #include <vector>
    #include "oj_model.hpp"
    #include "oj_view.hpp"
    #include "../comm/log.hpp"
    #include "../comm/util.hpp"
    
    namespace ns_control
    {
        using std::cout;
        using std::string;
        using std::vector;
        using namespace ns_log;
        using namespace ns_util;
        using namespace ns_model;
        using namespace ns_view;
    
        class Control
        {
        public:
            bool AllQuestions(string *html)
            {
                vector<Question> all_questions;
                if (_model.GetAllQuestions(&all_questions))
                {
                    LOG(INFO) << "获取所有题目成功" << "\n";
                    // 构建网页返回
                    _view.AllExpandHtml(all_questions, html);
                    return true;
                }
                else
                {
                    LOG(ERROE) << "获取所有题目失败" << "\n";
                    *html = "获取题目失败, 形成题目列表失败";
                    return false;
                }
            }
    
            bool OneQuestion(const string &number, string *html)
            {
                Question question;
                if (_model.GetOneQuestion(number, &question))
                {
                    LOG(INFO) << "获取单个题目成功" << "\n";
                    // 构建网页返回
                    _view.OneExpandHtml(question, html);
                    return true;
                }
                else
                {
                    LOG(ERROE) << "获取单个题目失败" << "\n";
                    *html = "指定题目: " + number + " 不存在!";
                    return false;
                }
            }
    
        private:
            Model _model;
            View _view;
        };
    }
    

    2.4.3、oj_contral子模块测试

    这里对oj_contral子模块测试采用前后端测试即oj_server子模块联动测试

    #include <iostream>
    #include "../comm/httplib.h"
    #include "oj_control.hpp"
    
    using std::string;
    using namespace httplib;
    using namespace ns_control;
    
    int main()
    {
        // 用户请求服务器路由的功能
        Server svr;
        Control ctrl;
    
        svr.set_base_dir("./wwwroot");
       
        // 1.功能获取所有的题目列表
        svr.Get("/all_questions", [&ctrl](const Request &req, Response &resp)
        {         
            // 返回一张包含所有题目的html网页
            string html;
            ctrl.AllQuestions(&html);
            resp.set_content(html, "text/html; charset=utf-8"); 
        });
        
          
        // 2.功能用户根据题目编号获取题目的内容
        // (\d+)正则表达式\d表示一个数字+表式多个数字
        // R"()"保持字符串原貌
        svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp)
        {
            string number = req.matches[1];        
            // 返回用户要获取指定题目的html网页
            string html;
            ctrl.OneQuestion(number, &html);
            resp.set_content(html, "text/html; charset=utf-8");    
        });
        
        
        // 3.功能用户提交代码使用测试用例进行判题
        svr.Get(R"(/judge/(\d+))", [](const Request &req, Response &resp)
        {
            string number = req.matches[1];
            // 用于测试Get /judge/(\d+)资源是否可以成功
            resp.set_content("指定题目的判题" + number, "text/plain; charset=utf-8"); 
        });
    
        svr.listen("0.0.0.0", 8080);
        return 0;
    }
    

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在第二幅图中我们发现题目没有按照升序进行排列其他结果都是预期的所以我们只需要解决这个bug就好。我们只需要在获取到所有题目后对题目进行排序就好。

    在这里插入图片描述
    在这里插入图片描述
    经过这样的测试我们可以确定写到目前为止项目还没有出现问题接下来就是对oj_view子模块的完善了优化一下前端页面。前端页面的优化就不进行讲解了这个项目的主要任务是后端的内容。
    最终前端显示的内容
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6