【C++项目】负载均衡式OJ


一、演示项目

在这里插入图片描述

二、所用技术与开发环境

所用技术:

  • C++ STL 标准库
  • Boost 准标准库(字符串切割)
  • cpp-httplib 第三方开源网络库
  • ctemplate 第三方开源前端网页渲染库
  • jsoncpp 第三方开源序列化、反序列化库
  • 负载均衡设计
  • 多进程、多线程
  • MySQL C connect
  • Ace前端在线编辑器(了解)
  • html/css/js/jquery/ajax (了解)

开发环境

  • Centos 7 云服务器
  • vscode
  • Mysql Workbench

三、项目宏观结构

我们的项目核心是三个模块

  1. comm : 公共模块
  2. compile_server : 编译与运行模块
  3. oj_server : 获取题目列表,查看题目编写题目界面,负载均衡,其他功能

我们的项目宏观结构
在这里插入图片描述

编写思路

  1. 先编写 compile_server
  2. oj_server
  3. version1 基于文件版的在线OJ
  4. 前端的页面设计
  5. version2 基于 MySQL 版的在线OJ

四、compiler 服务设计

提供的服务:编译并运行代码,得到格式化的相关的结果
在这里插入图片描述

4.1 第一个功能 compiler :编译功能

#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "../comm/log.hpp"

#include "../comm/util.hpp"

// 只负责代码的编译
namespace ns_compiler
{
    // 引用路径拼接功能
    using namespace ns_util;
    using namespace ns_log;

    class Compiler
    {
    public:
        Compiler()
        {
        }

        ~Compiler()
        {
        }

        //编译成功发挥TRUE,出错返回FALSE
        //输入参数,编译的文件名
        // test -> ./temp/test.cc
        // test -> ./temp/test.exe
        // test -> ./temp/test.stderr

        static bool Compile(const std::string &file_name)
        {
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(ERROR) << " 创建子进程失败!"
                           << "\n";
                return false;
            }
            // child
            else if (pid == 0)
            {
                umask(0);
                int _stderr = open(PathUtil::CompilError(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
                if (_stderr < 0)
                {
                    LOG(WARNING) << " 没有形成stderr文件!"
                                 << "\n";
                    exit(1);
                }
                // 打开文件成功,重定向标准错误到_stderr
                dup2(_stderr, 2);
                LOG(INFO) << "打开stderr文件成功"
                          << "\n";
                // g++ -o target src -std=c++11
                // 执行程序替换,编译文件
                execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(),
                       PathUtil::Src(file_name).c_str(), "-D", "COMPILER_ONLINE", "-std=c++11", nullptr);
              
                // 程序替换失败返回
                LOG(ERROR) << "启动编译器g++失败!"
                           << "\n";
                exit(2);
            }
            // father
            else
            {
                waitpid(pid, nullptr, 0);
                // 判断编译是否成功,看有没有形成可执行程序
                if (FileUtil::IsFileExists(PathUtil::Exe(file_name)))
                {
                    LOG(INFO) << PathUtil::Exe(file_name).c_str() << "编译成功!"
                              << "\n";
                    return true;
                }
            }
            LOG(ERROR) << "编译失败,没有形成可执行程序!"
                       << "\n";
            return false;
        }
    };
}

Log功能

#pragma once
#include <iostream>
#include <string>
#include "./util.hpp"

namespace ns_log
{
    using namespace ns_util;
    enum
    {
        INFO,    // 正常
        DEBUG,   // 调试信息
        WARNING, // 告警
        ERROR,   // 用户问题导致出错
        TATAL    // 整个系统出错
    };

    inline std::ostream &Log(const std::string &level, const std::string &file_name, const int line)
    {
        std::string message;
        message += "[";
        message += level;
        message += "]";

        message += "[";
        message += file_name;
        message += "]";

        message += "[";
        message += std::to_string(line);
        message += "]";

        message += "[";
        message += TimeUtil::GetTimeStamp();
        message += "]";

        // 开放式 LOG(level)<< "message" << "\n";
        std::cout << message; // 暂存在缓存区
        return std::cout;
    }

#define LOG(level) Log(#level, __FILE__, __LINE__)
}

4.2 第二个功能 runner :运行功能

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "../comm/log.hpp"
#include "../comm/util.hpp"

namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;

    class Runner
    {
    public:
        Runner()
        {
        }

        ~Runner()
        {
        }

    private:
        //提供设置进程占用资源大小的接口
        static void SetProcLimit(int cpu_limit, int mem_limit)
        {
            // cpu设置
            struct rlimit _cpu_rlimit;
            _cpu_rlimit.rlim_cur = cpu_limit;
            _cpu_rlimit.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_CPU, &_cpu_rlimit);

            //内存设置
            struct rlimit _mem_rlimit;
            _mem_rlimit.rlim_cur = mem_limit * 1024;
            _mem_rlimit.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_AS, &_mem_rlimit);
        }

    public:
        // 指明文件名即可,不需要代理路径,不需要带后缀
        /*******************************************
         * 返回值 > 0: 程序异常了,退出时收到了信号,返回值就是对应的信号编号
         * 返回值 == 0: 正常运行完毕的,结果保存到了对应的临时文件中
         * 返回值 < 0: 内部错误
         *
         * cpu_limit: 该程序运行的时候,可以使用的最大cpu资源上限
         * mem_limit: 改程序运行的时候,可以使用的最大的内存大小(KB)
         * *****************************************/
        static int Run(const std::string &file_name, int cpu_limit, int mem_limit)
        {
            /*********************************************
             * 程序运行:
             * 1. 代码跑完,结果正确
             * 2. 代码跑完,结果不正确
             * 3. 代码没跑完,异常了
             * Run需要考虑代码跑完,结果正确与否吗??不考虑!
             * 结果正确与否:是由我们的测试用例决定的!
             * 我们只考虑:是否正确运行完毕
             *
             * 我们必须知道可执行程序是谁?
             * 一个程序在默认启动的时候
             * 标准输入: 不处理
             * 标准输出: 程序运行完成,输出结果是什么
             * 标准错误: 运行时错误信息
             * *******************************************/

            pid_t pid = fork();
            if (pid < 0)
            {
                return -1;
            }
            std::string execute = PathUtil::Exe(file_name);

            umask(0);
            int _stdin_fd = open(PathUtil::Stdin(file_name).c_str(), O_CREAT | O_RDONLY, 0644);
            int _stdout_fd = open(PathUtil::Stdout(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
            int _stderr_fd = open(PathUtil::Stderr(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
            if (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
            {
                return -2;
                LOG(ERROR) << "打开文件失败!"
                           << "\n";
            }

            if (pid < 0)
            {
                LOG(ERROR) << "运行时创建子进程失败!"
                           << "\n";
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                return -3;
            }
            // child
            else if (pid == 0)
            {
                dup2(_stdin_fd, 0);
                dup2(_stdout_fd, 1);
                dup2(_stderr_fd, 2);

                SetProcLimit(cpu_limit, mem_limit);
                execl(execute.c_str(), execute.c_str(), nullptr);
                LOG(ERROR) << "子进程运行时进程替换失败!"
                           << "\n";
                return -4;
            }
            // father
            else
            {
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);

                int status = 0;
                waitpid(pid, &status, 0);
                LOG(INFO) << "进程运行成功,退出信号:" << (status & 0x7F) << "\n";
                return status & 0x7F;
            }
        }
    };
}

测试资源限制:
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>

void Hander(int signo)
{
    std::cout << signo << std::endl;
    exit(1);
}

int main()
{   
    // 通过信号将进程杀掉
    for(int i = 0; i < 32; ++i)
    {
        signal(i, Hander);
    }

    // struct rlimit rl;
    // rl.rlim_cur = 1;
    // rl.rlim_max = RLIM_INFINITY;
    // setrlimit(RLIMIT_CPU, &rl);

    struct rlimit rl;
    rl.rlim_cur = 1024 * 1024 * 20;
    rl.rlim_max = RLIM_INFINITY;
    setrlimit(RLIMIT_AS, &rl);

	
	
    while (true)
    {
        
    }
    return 0;
}

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

4.3 第三个功能 compile_run :编译并运行功能

在这里插入图片描述
//适配用户请求, 定制通信协议字段
//正确的调用compile and run
//形成唯一文件名
测试jsoncpp

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main()
{
    // 序列化
    // Value是一个Json的中间类,可以填充k,v值
    Json::Value root;
    root["code"] = "mycode";
    root["user"] = "ts";
    root["age"]= "20";

    Json::StyledWriter writer1;
    std::string res1 = writer1.write(root);
    std::cout << res1 << std::endl;

    Json::FastWriter writer2;
    std::string res2 = writer2.write(root);
    std::cout << res2 << std::endl;
    return 0;
}

在这里插入图片描述
第三个功能 compile_run

#pragma once
#include "compile.hpp"
#include "runner.hpp"
#include <string>
#include <jsoncpp/json/json.h>
#include "../comm/util.hpp"
#include <unistd.h>

namespace ns_compile_and_run
{
    using namespace ns_compiler;
    using namespace ns_runner;
    using namespace ns_util;

    class CompileAndRun
    {
    public:
        CompileAndRun()
        {
        }

        ~CompileAndRun()
        {
        }

    private:
        static void RemoveTempFile(const std::string &file_name)
        {
            //  unlink()  deletes a name from the file system.
            // If that name was the last link to a file and no processes have the file
            //  open the file is deleted and the space it was using is made available for reuse

            std::string _src = PathUtil::Src(file_name);
            if (FileUtil::IsFileExists(_src))
                unlink(_src.c_str());

            std::string _compileError = PathUtil::CompilError(file_name);
            if (FileUtil::IsFileExists(_compileError))
                unlink(_compileError.c_str());

            std::string _execute = PathUtil::Exe(file_name);
            if (FileUtil::IsFileExists(_execute))
                unlink(_execute.c_str());

            std::string _stdout = PathUtil::Stdout(file_name);
            if (FileUtil::IsFileExists(_stdout))
                unlink(_stdout.c_str());

            std::string _stdin = PathUtil::Stdin(file_name);
            if (FileUtil::IsFileExists(_stdin))
                unlink(_stdin.c_str());

            std::string _stderr = PathUtil::Stderr(file_name);
            if (FileUtil::IsFileExists(_stderr))
                unlink(_stderr.c_str());
        }

        // code>0 进程收到了信号
        // code < 0 整个过程非运行报错
        // code =0 整个过程全部完成
        static std::string CodeToDesc(int status_code, const std::string &file_name)
        {
            std::string desc;
            switch (status_code)
            {
            case -1:
                desc = "代码为空";
                break;
            case -2:
                desc = "未知错误";
                break;
            case -3:
                //代码编译的时候发生了错误
                FileUtil::ReadFile(PathUtil::CompilError(file_name), &desc, true);
                break;
            case 0:
                desc = "代码正常运行成功";
                break;
            case SIGFPE: // 8
                desc = "除零错误";
                break;
            case SIGABRT: // 6
                desc = "内存超出范围";
                break;
            case SIGXCPU: // 24
                desc = "CPU使用超时";
                break;
            default:
                desc = "未知错误:" + std::to_string(status_code);
                break;
            }

            return desc;
        }

        static void GetReturnInfo(int status_code, std::string *out_json, const std::string &file_name)
        {
            Json::Value out_value;
            Json::StyledWriter writer;
            out_value["code"] = status_code;
            out_value["reason"] = CodeToDesc(status_code, file_name);

            if (status_code == 0)
            {
                std::string _stdout;
                FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);
                out_value["stdout"] = _stdout;

                std::string _stderr;
                FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr, true);
                out_value["stderr"] = _stderr;
            }
            *out_json = writer.write(out_value);
            RemoveTempFile(file_name);
        }

    public:
        /***************************************
       * 输入:
       * code: 用户提交的代码
       * input: 用户给自己提交的代码对应的输入,不做处理
       * cpu_limit: 时间要求
       * mem_limit: 空间要求
       * *
       输出:
       * 必填
       * status: 状态码
       * reason: 请求结果
       * 选填:
       * stdout: 我的程序运行完的结果
       * stderr: 我的程序运行完的错误结果
       * *
       参数:
       * in_json: {"code": "#include...", "input": "","cpu_limit":1, "mem_limit":10240}
       * out_json: {必填:"status":"0", "reason":"",选填:"stdout":"","stderr":"",}
       * ************************************/
        static void Start(const std::string &in_json, std::string *out_json)
        {
            // 反序列化
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);

            std::string code = in_value["code"].asString();
            std::string input = in_value["input"].asString();
            int cpu_limit = in_value["cpu_limit"].asInt();
            int mem_limit = in_value["mem_limit"].asInt();

            // 形成的文件名只具有唯一性,没有目录没有后缀
            // 毫秒级时间戳+原子性递增唯一值: 来保证唯一性
            std::string file_name = FileUtil::UniqueFileName();

            int status_code;
            if (code.size() == 0)
            {
                status_code = -1; // 代码为空
                GetReturnInfo(status_code, out_json, file_name);
                return;
            }

            //形成临时src文件,将代码写入到临时文件中
            if (!FileUtil::WriteFile(PathUtil::Src(file_name), code))
            {
                status_code = -2; //未知错误
                GetReturnInfo(status_code, out_json, file_name);
                return;
            }

            // 编译
            if (!Compiler::Compile(file_name))
            {
                status_code = -3; //代码编译的时候发生了错误
                GetReturnInfo(status_code, out_json, file_name);
                return;
            }

            // 运行
            int run_result = Runner::Run(file_name, cpu_limit, mem_limit);
            if (run_result < 0)
            {
                status_code = -2; //未知错误
                GetReturnInfo(status_code, out_json, file_name);
                return;
            }
            else if (run_result > 0)
            {
                status_code = run_result; // 发生错误,信号终止
                GetReturnInfo(status_code, out_json, file_name);
                return;
            }
            else
            {
                status_code = 0; // 正常运行
                GetReturnInfo(status_code, out_json, file_name);
                return;
            }
        }
    };
}

4.4 第四个功能: 把编译并运行功能,形成网络服务

 //提供的编译服务,打包形成一个网络服务
// cpp-httplib
#include "compile_run.hpp"
#include "../comm/httplib.h"
using namespace ns_compile_and_run;
using namespace httplib;

// ./compile_server port
void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << "\tport" << std::endl;;
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    Server ser;
    ser.Get("/hello", [](const Request& req, Response& resp){
        resp.set_content("nihao,shije 你好世界", "text/plain;charset=utf-8"); 
        std::cout << "received a request" << std::endl;
    });
    
    // code #include; input: ""
    ser.Post("/compile_and_run", [](const Request& req, Response& resp){
        std::string in_json = req.body;
        std::string out_json;
        CompileAndRun::Start(in_json, &out_json);
        resp.set_content(out_json, "application/json;charset=utf-8");
        // std::cout << "return info:" << out_json << std::endl; 
    });
    // ser.set_base_dir("./www.root");

    ser.listen("0.0.0.0", atoi(argv[1]));
    return 0;
}

五、基于MVC 结构的oj 服务设计

本质:建立一个小型网站

  1. 获取首页,用题目列表充当
  2. 编辑区域页面
  3. 提交判题功能(编译并运行)

M: Model,通常是和数据交互的模块,比如,对题库进行增删改查(文件版,MySQL)
V: view, 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的(浏览器)
C: control, 控制器,就是我们的核心业务逻辑

5.1 第一个功能:用户请求的服务路由功能

#include <iostream>
#include "../comm/httplib.h"
#include "./oj_control.hpp"

using namespace ns_ctrl;
using namespace httplib;

int main()
{
    // M: Model,通常是和数据交互的模块,比如,对题库进行增删改查(文件版,MySQL)
    // V: view, 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的(浏览器)
    // C: control, 控制器,就是我们的核心业务逻辑

    // 获取题目列表
    Server ser;
    Control ctrl;

    ser.Get("/all_questions", [&ctrl](const Request &req, Response &resp)
            { 
                std::string html;
                ctrl.AllQuestions(&html);
                
                resp.set_content(html, "text/html;charset=utf-8"); });

    // 用户要根据题目编号,获取题目的内容
    ser.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp)
            {
                std::string number = req.matches[1];
                std::string html;
                ctrl.OneQuestion(number, &html);
                resp.set_content(html+number, "text/html;charset=utf-8"); });

    
        // 用户提交代码,使用我们的判题功能(1. 每道题的测试用例 2. compile_and_run)
        ser.Post(R"(/judge/(\d+))", [&ctrl](const Request &req, Response &resp)
                {
        std::string number = req.matches[1];
        std::string out_json;
        ctrl.Judge(number, req.body, &out_json);
        resp.set_content(out_json, "application/json;charset=utf-8"); 
        });

    ser.set_base_dir("./wwwroot");
    ser.listen("0.0.0.0", 8080);
    // std::cout << "hello world" << std::endl;
    return 0;
}

5.2 第二个功能:model功能,提供对数据的操作

// 文件版本
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <cassert>
#include <fstream>
#include <mutex>
#include <cassert>
#include "../comm/util.hpp"
#include "../comm/log.hpp"

namespace ns_model
{
    using namespace ns_util;
    using namespace ns_log;

    const std::string questions_list = "./questions/questions.list";
    const std::string questions_path = "./questions/";

    // 1. 题目的编号
    // 2. 题目的标题
    // 3. 题目的难度
    // 4. 题目的描述,题面
    // 5. 时间要求(内部处理)
    // 6. 空间要求(内部处理)
    class Question
    {
    public:
        std::string number; //题目编号,唯一
        std::string title;  //题目的标题题目的标题
        std::string star;   //难度: 简单 中等 困难
        int cpu_limit;
        int mem_limit; //题目的空间要去(KB)
        std::string desc;
        std::string header; //题目预设给用户在线编辑器的代码
        std::string tail;   //题目的测试用例,需要和header拼接,形成完整代码
    };

    class Model
    {
    private:
        //题号 : 题目细节
        std::unordered_map<std::string, Question> _questions;

    public:
        Model()
        {
            assert(LoadQuestionList(questions_list));
        }

        ~Model()
        {
        }

        bool LoadQuestionList(const std::string &questions_list)
        {
            std::ifstream ifs(questions_list);
            if (!ifs.is_open())
            {
                // std::cout << questions_list << std::endl;
                LOG(FATAL) << "打开文件失败!" << std::endl;
                return false;
            }

            std::string line;
            while (getline(ifs, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, " ");

                // 1 判断回文数 简单 1 30000
                if (tokens.size() != 5)
                {
                    LOG(WARNING) << "获取单个题库失败,请检查文件格式" << std::endl;
                    continue;
                }

                Question q;
                q.number = tokens[0];
                q.title = tokens[1];
                q.star = tokens[2];
                q.cpu_limit = atoi(tokens[3].c_str());
                q.mem_limit = atoi(tokens[4].c_str());
                std::string path = questions_path;
                path += tokens[0];
                path += "/";

                if (!FileUtil::ReadFile(path + "desc.txt", &(q.desc), true))
                {
                    LOG(WARNING) << "获取单个题目描述失败" << std::endl;
                    continue;
                }

                if (!FileUtil::ReadFile(path + "head.cpp", &(q.header), true))
                {
                    LOG(WARNING) << "获取单个题目头失败" << std::endl;
                    continue;
                }

                if (!FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true))
                {
                    LOG(WARNING) << "获取单个题目尾失败" << std::endl;
                    continue;
                }

                _questions.insert({q.number, q});
            }
            LOG(INFO) << "加载题库成功" << std::endl;
            ifs.close();

            // for(const auto& q : _questions)
            // {
            //     std::cout << q.second.number << std::endl;
            // }
            return true;
        }

        bool GetAllQuestions(std::vector<Question> *out)
        {
            if (_questions.size() == 0)
            {
                return false;
            }

            for (const auto &iter : _questions)
            {
                (*out).push_back(iter.second);
            }

            // for(const auto& iter : (*out))
            // {
            //     std::cout << iter.number << std::endl;
            // }

            return true;
        }

        bool GetOneQuestion(const std::string &number, Question *q)
        {
            const auto &it = _questions.find(number);
            if (it == _questions.end())
            {
                return false;
            }

            (*q) = it->second;
            return true;
        }
    };
}

5.3 第三个功能:control,逻辑控制模块

#pragma once
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <jsoncpp/json/json.h>
#include <unistd.h>
#include <algorithm>
#include "../comm/httplib.h"
#include "oj_view.hpp"
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include "./oj_model.hpp"
// #include "./oj_model2.hpp"

namespace ns_ctrl
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    const std::string service_machine = "./conf/service_machine.conf";

    // 提供服务的主机
    class Machine
    {
    public:
        std::string _ip;
        int _port;
        uint64_t _load;   //编译服务的负载
        std::mutex *_mtx; // mutex是禁止拷贝的,使用指针

    public:
        Machine() : _ip(""), _port(0), _load(0), _mtx(nullptr)
        {
        }
        ~Machine()
        {
        }

    public:
        // 增加负载
        void IncLoad()
        {
            if (_mtx)
                _mtx->lock();
            ++_load;
            if (_mtx)
                _mtx->unlock();
        }
        // 减少负载
        void DecLoad()
        {
            if (_mtx)
                _mtx->lock();
            --_load;
            if (_mtx)
                _mtx->unlock();
        }

        // 获取主机负载,没有太大的意义,只是为了统一接口
        uint64_t Load()
        {
            if (_mtx)
                _mtx->lock();
            uint64_t load;
            load = _load;
            if (_mtx)
                _mtx->unlock();
            return _load;
        }
    };

    class LoadBalance
    {
    private:
        // 可以给我们提供编译服务的所有的主机
        // 每一台主机都有自己的下标,充当当前主机的id
        std::vector<Machine> _machines;
        std::vector<int> _online_machine;
        std::vector<int> _offline_machine;
        std::mutex _mtx;

    public:
        LoadBalance()
        {
            assert(LoadConf(service_machine));
            LOG(INFO) << "加载配置文件成功!" << service_machine << std::endl;
        }

        ~LoadBalance()
        {
        }

    public:
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream ifs(machine_conf);
            if (!ifs.is_open())
            {
                LOG(FATAL) << "加载配置文件" << machine_conf << "filed!" << std::endl;
                return false;
            }

            std::string line;
            while (getline(ifs, line))
            {
                std::vector<std::string> results;
                StringUtil::SplitString(line, &results, " ");
                if (results.size() != 2)
                {
                    LOG(WARNING) << "主机:" << line << "分割字符串失败!" << std::endl;
                    continue;
                }

                Machine m;
                m._ip = results[0];
                m._port = std::stoi(results[1]);
                m._load = 0;
                m._mtx = new std::mutex;

                _online_machine.push_back(_machines.size());
                _machines.push_back(m);
            }

            // for(auto m : _machines)
            // {
            //     std::cout << m._ip << " " << m._port << std::endl;
            // }
            ifs.close();
            return true;
        }

        bool SmartChoice(int *id, Machine **m)
        {
            _mtx.lock();
            // 1. 使用选择好的主机(更新该主机的负载)
            // 2. 我们需要可能离线该主机
            int online_num = _online_machine.size();
            // LOG(DEBUG) << "online_num:" << online_num << std::endl;
            if (online_num == 0)
            {
                _mtx.unlock();
                LOG(FATAL) << "所有主机已经离线,请运维的同事进行维护" << std::endl;
                return false;
            }
            // 负载均衡的算法
            // 1. 随机数+hash
            // 2. 轮询+hash

            *id = _online_machine[0];
            *m = &_machines[_online_machine[0]];
            uint64_t min_load = _machines[_online_machine[0]].Load();
            for (int i = 1; i < online_num; ++i)
            {
                uint64_t cur_load = _machines[_online_machine[i]].Load();
                if (min_load > cur_load)
                {
                    min_load = cur_load;
                    *id = _online_machine[i];
                    *m = &_machines[_online_machine[i]];
                }
            }
            _mtx.unlock();
            return true;
        }

        void OfflineMachine(int which)
        {
            _mtx.lock();
            auto iter = _online_machine.begin();
            while (iter != _online_machine.end())
            {
                if (*iter == which)
                {
                    _online_machine.erase(iter);
                    _offline_machine.push_back(which);
                    break; //因为break的存在,所有我们暂时不考虑迭代器失效的问题
                }
                ++iter;
            }

            _mtx.unlock();
        }

        void OnlineMachine()
        {
        }

        void ShowMachines()
        {
            _mtx.lock();
            std::cout << "在线主机列表:";
            for (auto id : _online_machine)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;

            std::cout << "离线主机列表:";
            for (auto id : _offline_machine)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            _mtx.unlock();
        }
    };

    class Control
    {
    private:
        Model _model;              //提供后台数据
        View _view;                //提供html渲染功能
        LoadBalance _load_balance; //核心负载均衡器

    public:
        Control()
        {
        }

        ~Control()
        {
        }

    public:
        bool AllQuestions(std::string *html)
        {
            std::vector<Question> all;
            if (_model.GetAllQuestions(&all))
            {
                // 获取的所有题目进行网页渲染
                // std::cout << "get_all_question success!" << std::endl;
                std::sort(all.begin(), all.end(), [](const Question &q1, const Question &q2)
                          { return std::stoi(q1.number) < std::stoi(q2.number); });
                _view.AllExpandHtml(all, html);
            }

            else
            {
                *html = "获取获取所有题目失败";
                std::cout << "获取所有题目失败" << std::endl;
                return false;
            }

            return true;
        }

        bool OneQuestion(const std::string &number, std::string *html)
        {
            Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 获取的单个题目进行网页渲染
                _view.OneExpandHtml(q, html);
            }

            else
            {
                *html = "获取单个题目失败";
                std::cout << "获取单个题目失败" << std::endl;
                return false;
            }

            return true;
        }

        bool Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 0 根据用户题号得到对应的题目
            Question q;
            _model.GetOneQuestion(number, &q);

            // 1. in_json进行反序列化,得到题目的id,得到用户提交源代码,input
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();
            std::string input = in_value["input"].asString();

            // 2. 重新拼接用户代码+测试用例代码,形成新的代码
            code += "\n";
            code += q.tail;
            Json::Value compile_value;
            Json::FastWriter writer;
            compile_value["code"] = code;
            compile_value["input"] = input;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;

            std::string compile_string = writer.write(compile_value);

            // std::cout << compile_string << std::endl;
            // 3. 选择负载最低的主机(差错处理)
            while (true)
            {
                int id = 0;
                Machine *m = nullptr;

                if (!_load_balance.SmartChoice(&id, &m))
                {
                    break;
                }
                m->IncLoad();
                LOG(INFO) << "主机选择成功,详情:主机:" << m->_ip << " 端口:" << m->_port << " 负载:" << m->Load() << std::endl;
                // 4. 然后发起http请求,得到结果
                Client cli(m->_ip, m->_port);
                if (auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
                {
                    if (res->status == 200)
                    {
                        // 5. 将结果赋值给out_json
                        *out_json = res->body;
                        m->DecLoad();
                        LOG(INFO) << "编译和运行成功" << std::endl;
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    _load_balance.OfflineMachine(id);
                    LOG(INFO) << "主机选择失败,详情:主机:" << m->_ip << " 端口:" << m->_port << " 负载:" << m->Load() << std::endl;

                    _load_balance.ShowMachines();
                    // sleep(1);
                }
            }
            return true;
        }
    };
}

5.4 附加功能:需要有数据渲染

在这里插入图片描述

#include <iostream>
#include <vector>
#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:
        View()
        {
        }
        ~View()
        {
        }

    public:
        //     std::string number; //题目编号,唯一
        //     std::string title; //题目的标题题目的标题
        //     std::string star; //难度: 简单 中等 困难
        //     int cpu_limit;
        //     int mem_limit; //题目的空间要去(KB)
        //     std::string desc;
        //     std::string header; //题目预设给用户在线编辑器的代码
        //     std::string tail; //题目的测试用例,需要和header拼接,形成完整代码
        void AllExpandHtml(const std::vector<Question> &all, std::string *out_html)
        {
            // 题目编号 标题 难度
            // 1.形成路径
            std::string src_html = template_path + "all_questions.html";
            std::cout << src_html << std::endl;
            // 2. 形成数据字典
            ctemplate::TemplateDictionary root("all_questions");
            for(const auto& q : all)
            {
                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(out_html, &root);
        }

        void OneExpandHtml(const Question& q, std::string* out_html)
        {
            // 1.形成路径
            std::string src_html = template_path + "one_question.html";
            // 形成数据字典
            ctemplate::TemplateDictionary root("one_question");
            root.SetValue("number", q.number);
            root.SetValue("title", q.title);
            root.SetValue("star", q.star);
            root.SetValue("pre_code", q.header);
            root.SetValue("desc", q.desc);
            // 获取被渲染的HTML
            ctemplate::Template* tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);
            //开始渲染
            tpl->Expand(out_html, &root);
        }
    };
}

六、version1 文件版题目设计

  1. 题目的编号
  2. 题目的标题
  3. 题目的难度
  4. 题目的描述,题面
  5. 时间要求(内部处理)
  6. 空间要求(内部处理)

两批文件构成

  • 第一个:questions.list : 题目列表(不需要题目的内容)
  • . 第二个:题目的描述,题目的预设置代码(header.cpp), 测试用例代码(tail.cpp)
  • 这两个内容是通过题目的编号,产生关联的
  1. 当用户提交自己的代码的时候:header.cpp
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution
{
    public:
    int Max(const vector<int>& nums)
    {
        //code
        return 0;
    }
};
  1. OJ不是只把上面的代码提交给compile_and_run, 而是
#ifndef COMPILER_ONLINE
#include "head.cpp"

#endif

void Test1()
{
    vector<int> v{1, 2, 3, 4, 5, 6};
    int ret = Solution().Max(v);
    if(ret == 6)
    {
        std::cout << "测试{1,2,3,4,5,6}结果正确" << std::endl;
    }
    else
    {
        std::cout << "测试结果{1,2,3,4,5,6}错误" << std::endl;
    }
}

void Test2()
{
    vector<int> v{-1, -2, -3, -4, -5, -6};
    int ret = Solution().Max(v);
    if(ret == -1)
    {
        std::cout << "测试{-1, -2, -3, -4, -5, -6}结果正确" << std::endl;
    }
    else
    {
        std::cout << "测试结果{-1, -2, -3, -4, -5, -6};错误" << std::endl;
    }
}

int main()
{
    Test1();
    Test2();
    return 0;
}

七、前端页面设计

后端开发需要关系前端页面?根本不需要!如果后续大家不想写,直接复制粘贴即可

  1. 任何项目,都要有前后端
  2. 后端虽然不关心所谓的页面,但是需要了解一下前后端是如何交互的

7.1 丐版的首页

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>这是我的个人OJ系统</title>
    <style>
        /* 起手式, 100%保证我们的样式设置可以不受默认影响 */
        * {
            /* 消除网页的默认外边距 */
            margin: 0px;
            /* 消除网页的默认内边距 */
            padding: 0px;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            /* 给父级标签设置overflow,取消后续float带来的影响 */
            overflow: hidden;
        }

        .container .navbar a {
            /* 设置a标签是行内块元素,允许你设置宽度 */
            display: inline-block;
            /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
            width: 80px;
            /* 设置字体颜色 */
            color: white;
            /* 设置字体的大小 */
            font-size: large;
            /* 设置文字的高度和导航栏一样的高度 */
            line-height: 50px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置a标签中的文字居中 */
            text-align: center;
        }
        /* 设置鼠标事件 */
        .container .navbar a:hover {
            background-color: green;
        }
        .container .navbar .login {
            float: right;
        }

        .container .content {
            /* 设置标签的宽度 */
            width: 800px;
            /* 用来调试 */
            /* background-color: #ccc; */
            /* 整体居中 */
            margin: 0px auto;
            /* 设置文字居中 */
            text-align: center;
            /* 设置上外边距 */
            margin-top: 200px;
        }

        .container .content .font_ {
            /* 设置标签为块级元素,独占一行,可以设置高度宽度等属性 */
            display: block;
            /* 设置每个文字的上外边距 */
            margin-top: 20px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置字体大小
            font-size: larger; */
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏, 功能不实现-->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>
        <!-- 网页的内容 -->
        <div class="content">
            <h1 class="font_">欢迎来到我的OnlineJudge平台</h1>
            <p class="font_">这个我个人独立开发的一个在线OJ平台</p>
            <a class="font_" href="/all_questions">点击我开始编程啦!</a>
        </div>
    </div>
</body>

</html>

7.2 所有题目的列表

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>在线OJ-题目列表</title>
    <style>
        /* 起手式, 100%保证我们的样式设置可以不受默认影响 */
        * {
            /* 消除网页的默认外边距 */
            margin: 0px;
            /* 消除网页的默认内边距 */
            padding: 0px;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            /* 给父级标签设置overflow,取消后续float带来的影响 */
            overflow: hidden;
        }

        .container .navbar a {
            /* 设置a标签是行内块元素,允许你设置宽度 */
            display: inline-block;
            /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
            width: 80px;
            /* 设置字体颜色 */
            color: white;
            /* 设置字体的大小 */
            font-size: large;
            /* 设置文字的高度和导航栏一样的高度 */
            line-height: 50px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置a标签中的文字居中 */
            text-align: center;
        }

        /* 设置鼠标事件 */
        .container .navbar a:hover {
            background-color: green;
        }

        .container .navbar .login {
            float: right;
        }

        .container .question_list {
            padding-top: 50px;
            width: 800px;
            height: 100%;
            margin: 0px auto;
            /* background-color: #ccc; */
            text-align: center;
        }

        .container .question_list table {
            width: 100%;
            font-size: large;
            font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
            margin-top: 50px;
            background-color: rgb(243, 248, 246);
        }

        .container .question_list h1 {
            color: green;
        }
        .container .question_list table .item {
            width: 100px;
            height: 40px;
            font-size: large;
            font-family:'Times New Roman', Times, serif;
        }
        .container .question_list table .item a {
            text-decoration: none;
            color: black;
        }
        .container .question_list table .item a:hover {
            color: blue;
            text-decoration:underline;
        }
        .container .footer {
            width: 100%;
            height: 50px;
            text-align: center;
            line-height: 50px;
            color: #ccc;
            margin-top: 15px;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏, 功能不实现-->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>
        <div class="question_list">
            <h1>OnlineJuge题目列表</h1>
            <table>
                <tr>
                    <th class="item">编号</th>
                    <th class="item">标题</th>
                    <th class="item">难度</th>
                </tr>
                {{#question_list}}
                <tr>
                    <td class="item">{{number}}</td>
                    <td class="item"><a href="/question/{{number}}">{{title}}</a></td>
                    <td class="item">{{star}}</td>
                </tr>
                {{/question_list}}
            </table>
        </div>
        <div class="footer">
            <!-- <hr> -->
            <h4>@比特就业课</h4>
        </div>
    </div>

</body>

</html>

7.3 指定题目的编写代码的页面+代码提交

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{number}}.{{title}}</title>
    <!-- 引入ACE插件 -->
    <!-- 官网链接:https://ace.c9.io/ -->
    <!-- CDN链接:https://cdnjs.com/libraries/ace -->
    <!-- 使用介绍:https://www.iteye.com/blog/ybc77107-2296261 -->
    <!-- https://justcode.ikeepstudying.com/2016/05/ace-editor-%E5%9C%A8%E7%BA%BF%E4%BB%A3%E7%A0%81%E7%BC%96%E8%BE%91%E6%9E%81%E5%85%B6%E9%AB%98%E4%BA%AE/ -->
    <!-- 引入ACE CDN -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript"
        charset="utf-8"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js" type="text/javascript"
        charset="utf-8"></script>
    <!-- 引入jquery CDN -->
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            /* 给父级标签设置overflow,取消后续float带来的影响 */
            overflow: hidden;
        }

        .container .navbar a {
            /* 设置a标签是行内块元素,允许你设置宽度 */
            display: inline-block;
            /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
            width: 80px;
            /* 设置字体颜色 */
            color: white;
            /* 设置字体的大小 */
            font-size: large;
            /* 设置文字的高度和导航栏一样的高度 */
            line-height: 50px;
            /* 去掉a标签的下划线 */
            text-decoration: none;
            /* 设置a标签中的文字居中 */
            text-align: center;
        }

        /* 设置鼠标事件 */
        .container .navbar a:hover {
            background-color: green;
        }

        .container .navbar .login {
            float: right;
        }
        
        .container .part1 {
            width: 100%;
            height: 600px;
            overflow: hidden;
        }

        .container .part1 .left_desc {
            width: 50%;
            height: 600px;
            float: left;
            overflow: scroll;
        }

        .container .part1 .left_desc h3 {
            padding-top: 10px;
            padding-left: 10px;
        }

        .container .part1 .left_desc pre {
            padding-top: 10px;
            padding-left: 10px;
            font-size: medium;
            font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
        }

        .container .part1 .right_code {
            width: 50%;
            float: right;
        }

        .container .part1 .right_code .ace_editor {
            height: 600px;
        }
        .container .part2 {
            width: 100%;
            overflow: hidden;
        }

        .container .part2 .result {
            width: 300px;
            float: left;
        }

        .container .part2 .btn-submit {
            width: 120px;
            height: 50px;
            font-size: large;
            float: right;
            background-color: #26bb9c;
            color: #FFF;
            /* 给按钮带上圆角 */
            /* border-radius: 1ch; */
            border: 0px;
            margin-top: 10px;
            margin-right: 10px;
        }
        .container .part2 button:hover {
            color:green;
        }

        .container .part2 .result {
            margin-top: 15px;
            margin-left: 15px;
        }

        .container .part2 .result pre {
            font-size: large;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏, 功能不实现-->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>
        <!-- 左右呈现,题目描述和预设代码 -->
        <div class="part1">
            <div class="left_desc">
                <h3><span id="number">{{number}}</span>.{{title}}_{{star}}</h3>
                <pre>{{desc}}</pre>
            </div>
            <div class="right_code">
                <pre id="code" class="ace_editor"><textarea class="ace_text-input">{{pre_code}}</textarea></pre>
            </div>
        </div>
        <!-- 提交并且得到结果,并显示 -->
        <div class="part2">
            <div class="result"></div>
            <button class="btn-submit" onclick="submit()">提交代码</button>
        </div>
    </div>
    <script>
        //初始化对象
        editor = ace.edit("code");

        //设置风格和语言(更多风格和语言,请到github上相应目录查看)
        // 主题大全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.html
        editor.setTheme("ace/theme/monokai");
        editor.session.setMode("ace/mode/c_cpp");

        // 字体大小
        editor.setFontSize(16);
        // 设置默认制表符的大小:
        editor.getSession().setTabSize(4);

        // 设置只读(true时只读,用于展示代码)
        editor.setReadOnly(false);

        // 启用提示菜单
        ace.require("ace/ext/language_tools");
        editor.setOptions({
            enableBasicAutocompletion: true,
            enableSnippets: true,
            enableLiveAutocompletion: true
        });

        function submit(){
            // alert("嘿嘿!");
            // 1. 收集当前页面的有关数据, 1. 题号 2.代码
            var code = editor.getSession().getValue();
            // console.log(code);
            var number = $(".container .part1 .left_desc h3 #number").text();
            // console.log(number);
            var judge_url = "/judge/" + number;
            // console.log(judge_url);
            // 2. 构建json,并通过ajax向后台发起基于http的json请求
            $.ajax({
                method: 'Post',   // 向后端发起请求的方式
                url: judge_url,   // 向后端指定的url发起请求
                dataType: 'json', // 告知server,我需要什么格式
                contentType: 'application/json;charset=utf-8',  // 告知server,我给你的是什么格式
                data: JSON.stringify({
                    'code':code,
                    'input': ''
                }),
                success: function(data){
                    //成功得到结果
                    // console.log(data);
                    show_result(data);
                }
            });
            // 3. 得到结果,解析并显示到 result中
            function show_result(data)
            {
                // console.log(data.status);
                // console.log(data.reason);
                // 拿到result结果标签
                var result_div = $(".container .part2 .result");
                // 清空上一次的运行结果
                result_div.empty();

                // 首先拿到结果的状态码和原因结果
                var _status = data.status;
                var _reason = data.reason;

                var reason_lable = $( "<p>",{
                       text: _reason
                });
                reason_lable.appendTo(result_div);

                if(status == 0){
                    // 请求是成功的,编译运行过程没出问题,但是结果是否通过看测试用例的结果
                    var _stdout = data.stdout;
                    var _stderr = data.stderr;

                    var stdout_lable = $("<pre>", {
                        text: _stdout
                    });

                    var stderr_lable = $("<pre>", {
                        text: _stderr
                    })

                    stdout_lable.appendTo(result_div);
                    stderr_lable.appendTo(result_div);
                }
                else{
                    // 编译运行出错,do nothing
                }
            }
        }
    </script>
</body>

</html>

八、version2 MySQL版题目设计

  1. 在数据库中设计可以远程登陆的MySQL用户,并给他赋权
  2. 设计表结构数据库:oj, 表:oj_questions
  3. 开始编码连接访问数据库使用第三方引入的方式,我们的oj_server基于MVC模式的,和数据打交道的只有一个oj_model模块,只需要更改该文件即可!!计划是一张表,结束我们的所有的需求!

oj_model.hpp

//数据库版本

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <cassert>
#include <fstream>
#include <mutex>
#include <cassert>
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include "./include/mysql.h"

namespace ns_model
{
    using namespace ns_util;
    using namespace ns_log;

    const std::string oj_question = "oj_question";
    const std::string host = "127.0.0.1";
    const std::string user = "oj_user";
    const std::string passwd = "FSFgudg!@#5_$@3dgdkdg342343";
    const std::string db = "oj";
    const uint32_t port = 3306;

    // 1. 题目的编号
    // 2. 题目的标题
    // 3. 题目的难度
    // 4. 题目的描述,题面
    // 5. 时间要求(内部处理)
    // 6. 空间要求(内部处理)
    class Question
    {
    public:
        std::string number; //题目编号,唯一
        std::string title;  //题目的标题题目的标题
        std::string star;   //难度: 简单 中等 困难
        int cpu_limit;
        int mem_limit; //题目的空间要去(KB)
        std::string desc;
        std::string header; //题目预设给用户在线编辑器的代码
        std::string tail;   //题目的测试用例,需要和header拼接,形成完整代码
    };

    class Model
    {
    private:
    public:
        Model()
        {
        }

        ~Model()
        {
        }

    private:
        bool QueryMysql(const std::string &sql, std::vector<Question> *out)
        {
            // 1.创建mysql句柄
            MYSQL *my = mysql_init(nullptr);
            // 2.连接数据库
            if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(),
                                              passwd.c_str(), db.c_str(), port, nullptr, 0))
            {
                LOG(FATAL) << "连接数据库失败!" << std::endl;
                return false;
            }
            mysql_set_character_set(my, "utf8");
            // 数据库操作:读取
            if (0 != mysql_query(my, sql.c_str()))
            {
                LOG(WARNING) << sql << "execute failed!" << std::endl;
                return false;
            }
            // 获取结果
            // 该函数malloc了一片内存空间来存储查询过来的数据
            MYSQL_RES *result = mysql_store_result(my);
            int row = mysql_num_rows(result);
            int col = mysql_num_fields(result);

            Question q;
            for (int i = 0; i < row; ++i)
            {
                MYSQL_ROW line = mysql_fetch_row(result);
                q.number = line[0];
                q.title = line[1];
                q.star = line[2];
                q.desc = line[3];
                q.header = line[4];
                q.tail = line[5];
                q.cpu_limit = std::stoi(line[6]);
                q.mem_limit = std::stoi(line[7]);

                out->push_back(q);
            }

            free(result);
            // 关闭数据库
            mysql_close(my);
            return true;
        }

    public:
        bool GetAllQuestions(std::vector<Question> *out)
        {
            std::string sql = "select * from ";
            sql += oj_question;
            return QueryMysql(sql, out);
        }

        bool GetOneQuestion(const std::string &number, Question *q)
        {
            bool ret = false;
            std::string sql = "select * from ";
            sql += oj_question;
            sql += " where number=";
            sql += number;

            std::vector<Question> result;
            if (QueryMysql(sql, &result))
            {
                if (result.size() == 1)
                {
                    *q = result[0];
                    ret = true;
                }
            }
            return ret;
        }
    };
}

九、项目扩展思路(学有余力)

  1. 基于注册和登陆的录题功能
  2. 业务扩展,自己写一个论坛,接入到在线OJ中
  3. 即便是编译服务在其他机器上,也其实是不太安全的,可以将编译服务部署在docker
  4. 目前后端compiler的服务我们使用的是http方式请求(仅仅是因为简单),但是也可以将我们的compiler服务,设计成为远程过程调用,推荐:rest_rpc,替换我们的httplib(建议,可以不做)
  5. 功能上更完善一下,判断一道题目正确之后,自动下一道题目
  6. navbar中的功能可以一个一个的都实现一下
  7. 其他
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小唐学渣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值