C++ -- 负载均衡式在线OJ (三)

news2024/9/20 8:42:12

文章目录

  • 四、oj_server模块
    • 1. oj_server的功能路由
    • 2. 建立文件版的题库
    • 3. model模块
    • 4.controller模块
    • 5.judge模块(负载均衡)
    • 6.view模块整体代码结构(前端的东西,不是重点)
  • 五、最终效果
  • 项目源码

前面部分请看这里C++ – 负载均衡式在线OJ (二)

四、oj_server模块

oj_server说白了就是一个网站。oj_server的功能如下

  • 1.获取首页
  • 2.获取题目列表
  • 3.获取单道题目,并提供编辑功能
  • 4.提交判题功能(背后依靠的就是提供编译运行服务的服务器)

我们想采用的是基于MVC的一种架构模式

  • M model 与数据交互的模块
  • V view 视图,指用户界面,就是用来与用户进行交互的,模块
  • C controller 控制器,核心的业务逻辑都在这里实现,合理调配model和view模块。

oj_server承担的就是负载均衡式的去调用后端的一个个编译服务,然后展现给用户,所以oj_server更靠近用户

1. oj_server的功能路由

我们设计的oj_server一共能提供给用户的是3个功能路由

  • 1.题目列表的功能路由
  • 2.单道题目的功能路由
  • 3.提交代码进行判题的功能路由
#include <iostream>
#include <signal.h>

#include "../comm/httplib.h"
#include "oj_control.hpp"

using namespace httplib;
using namespace ns_control;

static Control *ctrl_ptr = nullptr;


void Recovery(int signo)
{
    ctrl_ptr->RecoveryMachine();
}

int main()
{
    signal(SIGQUIT, Recovery);
    //用户请求的服务路由功能
    Server svr;

    Control ctrl;// 当用户请求时就直接调用controller当中的方法,交互数据model也被controller包含在内
    ctrl_ptr = &ctrl;

    // 获取所有的题目列表
    svr.Get("/all_questions", [&ctrl](const Request &req, Response &resp){
        //返回一张包含有所有题目的html网页
        std::string html;
        ctrl.AllQuestions(&html);
        //用户看到的是什么呢??网页数据 + 拼上了题目相关的数据
        resp.set_content(html, "text/html; charset=utf-8");
    });

    // 用户要根据题目编号,获取题目的内容
    // /question/100 -> 正则匹配
    // R"()", 原始字符串raw string,保持字符串内容的原貌,不用做相关的转义
    svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp){
        std::string number = req.matches[1];
        std::string html;
        ctrl.Question(number, &html);
        resp.set_content(html, "text/html; charset=utf-8");
    });

    // 用户提交代码,使用我们的判题功能(1. 每道题的测试用例 2. compile_and_run)
    svr.Post(R"(/judge/(\d+))", [&ctrl](const Request &req, Response &resp){
        std::string number = req.matches[1];
        std::string result_json;
        ctrl.Judge(number, req.body, &result_json);
        resp.set_content(result_json, "application/json;charset=utf-8");
        // resp.set_content("指定题目的判题: " + number, "text/plain; charset=utf-8");
    });
    
    // 设置Web根目录
    svr.set_base_dir("./wwwroot");
    // 启动服务器
    svr.listen("0.0.0.0", 8080);
    return 0;
}

注意:

  • 1.set_base_dir其实是提供给首页的,我们的url如果是http://101.42.249.66/的话就代表想要的资源是/,这个其实就代表的是访问的我们的web根目录(我们命名为wwwroot),而一般,这样的访问代表首页,我们会在web根目录下放置一个index.html供用户访问

  • 2.R"()"上面以及说过了,就是row string,保持()中字符串原貌。

  • 3.然后(\d+)代表的是正则表达式,+代表有多少就匹配多少,\d是匹配数字

  • 4.上面使用到了Request当中的mathes对象,其实matches对象就是将我们的资源申请做了切分,比如说\question\100,question就放到了matches[0],100就放到了matches[1]当中。

我们提供了三个功能路由就分别对应三种资源申请

  • http://110.42.249.66:8080/all_questions

在这里插入图片描述

  • http://110.42.249.66:8080/question/1在这里插入图片描述

2. 建立文件版的题库

首先,我们的题目需要的东西有

  • 1.题号 number
  • 2.标题 title
  • 3.难度 star
  • 4.描述 desc
  • 5.时间要求 cpu_limit
  • 6.空间要求 mem_limit

在oj_server目录下,我们需要一个questions目录对题目的所有东西进行存储。
而我们需要一个questions.list配置文件来读取所有题目(我们打算将题目构建成一个Question对象)
然后更具体的东西,比如题目的描述,预设给用户的代码,测试用例单独放在一个目录里
在这里插入图片描述

在questions.list配置文件中的存储方式

在这里插入图片描述

header.cpp

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution
{
public:
    int Max(const vector<int> &v)
    {
        //将你的代码写在下面

        return 0;
    }
};

tail.cpp测试用例部分

所以我们需要给header.cpp中的代码进行合并,进行合并的代码就放在tail.cpp当中

所谓测试用例,其实就是把你在代码编辑框中的代码提交上来,然后和另外一个代码进行合并。这个代码里差的就是对你写的那部分函数。所以两个合在一起,才形成了完整的一个程序。

#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;
}

注意:

  • 条件编译的原因是:这部分代码因为缺少用户提交的那部分函数,所以我们在编译oj_server的时候,是会报错的,因为少了函数,跑不了可以理解。所以我们需要加一个条件编译,让这个.cc文件知道我们有该函数,不要报错。
  • 这个条件编译到时候我们再通过给其他方式去掉,我们可以在调用g++的时候加选项,比如我们上面的宏是 COMPILER_ONLINE,那么到时候,我们直接 gcc … -D COMPILER_ONLIEN就可以去掉了。-D选项就是在命令行进行宏定义的方式

3. model模块

model模块主要是用来和数据交互的,对外提供访问数据的接口

我们在model模块当中,因为我们的数据就是题目,所以一上来我们就要把题目读出来。
我们会有一个Question类,用它来描述该题目的信息

// 根据题⽬list⽂件,加载所有的题⽬信息到内存中
// model: 主要⽤来和数据进⾏交互,对外提供访问数据的接⼝
namespace ns_model
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;

    struct Question
    {
        std::string number; // 题⽬编号,唯⼀
        std::string title;  // 题⽬的标题
        std::string star;   // 难度: 简单 中等 困难
        int cpu_limit;      // 题⽬的时间要求(S)
        int mem_limit;      // 题⽬的空间要去(KB)
        std::string desc;   // 题⽬的描述
        std::string header; // 题⽬预设给⽤⼾在线编辑器的代码
        std::string tail;   // 题⽬的测试⽤例,需要和header拼接,形成完整代码
    };
}

选择用unordered_map<string,Question>的结构体来存储生成的Question,建立题目(字符串)与Question的映射。

使用boost准标准库当中的split进行字符串分割

class StringUtil
{
public:
      /**
       * str:输入性参数,要切分的字符串
       * target:输出型参数,保存并返回切分完毕的结果
       * sep:separator分隔符
       */
       static void SplitString(const std::string &str,std::vector<std::string>* target,std::string sep)
       {
           //使用C++准标准库boost   当中的split进行字符串分割
           boost::split((*target),str,boost::is_any_of(sep),boost::algorithm::token_compress_on);
           //is_any_of代表sep分隔符字符串当中的任意一个字符都能用来分割
           //token_compress_on代表我是否需要进行压缩
           //调用这个接口就自动的帮我们完成了字符串切分
        }
    };

按行读取配置文件形成Question对象

  • 1.用C++的文件流的方式创建ifstream对象,打开文件流
  • 2.使用getline进行按行读取,getline的注意事项上面以及说过,不再重复
  • 3.使用字符串工具类中封装好的函数进行字符串切割放入tokens数组
  • 4.利用该数组进行Question结构体的创建
// 根据题⽬list⽂件,加载所有的题⽬信息到内存中
// model: 主要⽤来和数据进⾏交互,对外提供访问数据的接⼝
namespace ns_model
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;

    const std::string questins_list = "./questions/questions.list";
    const std::string questins_path = "./questions/";

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

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

        bool LoadQuestionList(const string &question_list)
        {
            // 加载配置⽂件: questions/questions.list + 题⽬编号⽂件
            ifstream in(question_list);
            if (!in.is_open())
            {
                LOG(FATAL) << " 加载题库失败,请检查是否存在题库⽂件" << "\n";
                return false;
            }

            string line;
            while (getline(in, line))
            {
                vector<string> tokens;
                StringUtil::SplitString(line, &tokens, " ");
                // 1 判断回⽂数 简单 1 30000
                if (tokens.size() != 5)
                {
                    LOG(WARNING) << "加载部分题⽬失败, 请检查⽂件格式" << "\n";
                    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());

                string path = questins_path;
                path += q.number;
                path += "/";

                FileUtil::ReadFile(path + "desc.txt", &(q.desc), true);
                FileUtil::ReadFile(path + "header.cpp", &(q.header), true);
                FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true);

                questions.insert({q.number, q});
            }
            LOG(INFO) << "加载题库...成功!" << "\n";
            in.close();
        }

        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;
        }

        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;
        }

        ~Model()
        {
        }
    };
}

4.controller模块

controller模块整体结构
Controller模块是MVC架构模式当中的C,主要负责核心逻辑的编写。
比如model模块和view模块的调用将来都是在controller模块

在这里插入图片描述
我们以及有了功能路由,但是如果向访问到页面,就需要用到view模块(前端页面)和model模块(数据获取)。所以功能路由一定是通过创建controller对象去进行调用。(controller的类当中就会合理的调用model模块还要view模块,就会有一个渲染好的html显示给用户)

5.judge模块(负载均衡)

用户在编辑器中编写的代码提交给oj_server之后,oj_server是需要做负载均衡的,也就是选择负载最少的主机进行访问

在这里插入图片描述
那么我们就在controller增加一个判题的功能。当客户端把代码提交上来之后,judge模块就要进行主机的选择,然后序列化成compile_server需要的json串发过去。(不要忘记需要拼接测试用例)

现在看来,用户提交的json串,有三部分构成

  • 1.首先需要题目的id,让我们可以进行测试用例的拼接
  • 2.code,这个就是用户编辑的那部分代码
  • 3.input,其实是可以有自测输入的,不过我们今天不支持,反正也不难

收到json串的code之后,judge模块就会根据读取配置文件建立好的unordered_map来找到对应的题目细节,然后拿到题目对应的测试用例,进行拼接。

那么有哪些主机可以供我们选择呢?我们又怎么去选择负载最低的呢?

所以我们就需要给一个配置文件,里面配置的就是主机的信息,比如IP,端口,然后我们还需要再oj_server当中维护对应主机的负载情况,以便我们进行选择。

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

Machine类

namespace ns_control
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    // 提供服务的主机
    class Machine
    {
    public:
        std::string ip;  // 编译服务的ip
        int port;        // 编译服务的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();
        }
        void ResetLoad()
        {
            if (mtx)
                mtx->lock();
            load = 0;
            if (mtx)
                mtx->unlock();
        }
        // 获取主机负载,没有太大的意义,只是为了统一接口
        uint64_t Load()
        {
            uint64_t _load = 0;
            if (mtx)
                mtx->lock();
            _load = load;
            if (mtx)
                mtx->unlock();

            return _load;
        }
    };
}

注意:

一旦连接我,拼接完之后就要对主机进行选择,所以这里是要加锁包的,为了负载均衡,我们维护的有load,我们要选择load最小的去进行服务。

负载均衡函数

namespace ns_control
{
    using namespace std;
    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 LoadBlance
    {
    private:
        // 可以给我们提供编译服务的所有的主机
        // 每一台主机都有自己的下标,充当当前主机的id
        std::vector<Machine> machines;
        // 所有在线的主机id
        std::vector<int> online;
        // 所有离线的主机id
        std::vector<int> offline;
        // 保证LoadBlance它的数据安全
        std::mutex mtx;

    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
            LOG(INFO) << "加载 " << service_machine << " 成功"
                      << "\n";
        }
        ~LoadBlance()
        {
        }

    public:
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(FATAL) << " 加载: " << machine_conf << " 失败"
                           << "\n";
                return false;
            }
            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                if (tokens.size() != 2)
                {
                    LOG(WARNING) << " 切分 " << line << " 失败"
                                 << "\n";
                    continue;
                }
                Machine m;
                m.ip = tokens[0];
                m.port = atoi(tokens[1].c_str());
                m.load = 0;
                m.mtx = new std::mutex();

                online.push_back(machines.size());
                machines.push_back(m);
            }

            in.close();
            return true;
        }

        // id: 输出型参数
        // m : 输出型参数
        bool SmartChoice(int *id, Machine **m)
        {
            // 1. 使用选择好的主机(更新该主机的负载)
            // 2. 我们需要可能离线该主机
            mtx.lock();
            // 负载均衡的算法
            // 1. 随机数+hash
            // 2. 轮询+hash
            int online_num = online.size();
            if (online_num == 0)
            {
                mtx.unlock();
                LOG(FATAL) << " 所有的后端编译主机已经离线, 请运维的同事尽快查看"
                           << "\n";
                return false;
            }
            // 通过遍历的方式,找到所有负载最小的机器
            *id = online[0];
            *m = &machines[online[0]];
            uint64_t min_load = machines[online[0]].Load();
            for (int i = 1; i < online_num; i++)
            {
                uint64_t curr_load = machines[online[i]].Load();
                if (min_load > curr_load)
                {
                    min_load = curr_load;
                    *id = online[i];
                    *m = &machines[online[i]];
                }
            }
            mtx.unlock();
            return true;
        }

        void OfflineMachine(int which)
        {
            mtx.lock();
            for (auto iter = online.begin(); iter != online.end(); iter++)
            {
                if (*iter == which)
                {
                    machines[which].ResetLoad();
                    // 要离线的主机已经找到啦
                    online.erase(iter);
                    offline.push_back(which);
                    break; // 因为break的存在,所有我们暂时不考虑迭代器失效的问题
                }
            }
            mtx.unlock();
        }

        void OnlineMachine()
        {
            // 我们统一上线,后面统一解决
            mtx.lock();
            online.insert(online.end(), offline.begin(), offline.end());
            offline.erase(offline.begin(), offline.end());
            mtx.unlock();

            LOG(INFO) << "所有的主机有上线啦!" << "\n";
        }

        // for test
        void ShowMachines()
        {
            mtx.lock();
            std::cout << "当前在线主机列表: ";
            for (auto &id : online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            std::cout << "当前离线主机列表: ";
            for (auto &id : offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            mtx.unlock();
        }
    };

}

6.view模块整体代码结构(前端的东西,不是重点)

在这里插入图片描述

index.html

<!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>

one_question

<!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>

all_questions

<!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>

五、最终效果

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

项目源码

Gitee:https://gitee.com/niu-zanqi/aries.c-warehouse.2/tree/master/OnlineJudge

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2044181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unite Shanghai 2024 团结引擎专场 | 团结引擎实时全局光照

在 2024 年 7 月 24 日的 Unite Shanghai 2024 团结引擎专场演讲中&#xff0c;Unity 中国高级技术经理周赫带大家深入解析了团结引擎的实时全局光照系统。该系统支持完全动态的场景、动态材质和动态灯光的 GI 渲染&#xff0c;包括无限次弹射的漫反射和镜面反射 GI。 周赫&…

2024年职场常备!3款高效数据恢复软件免费版,让打工人工作无忧

嘿&#xff0c;职场的朋友们&#xff01;咱们现在工作&#xff0c;数据就跟空气一样重要&#xff0c;对吧&#xff1f;但有时候&#xff0c;手一滑&#xff0c;文件没了&#xff0c;硬盘突然就挂了&#xff0c;系统也闹点小情绪&#xff0c;那心情&#xff0c;比股市大跌还难受…

基于Django的boss直聘数据分析可视化系统的设计与实现

研究背景 随着互联网的发展&#xff0c;在线招聘平台逐渐成为求职者与企业之间的重要桥梁。Boss直聘作为国内领先的招聘平台&#xff0c;以其独特的直聊模式和高效的匹配算法&#xff0c;吸引了大量的用户。然而&#xff0c;随着平台用户的增长&#xff0c;海量的招聘数据带来…

基于Faster-RCNN的停车场空位检测,支持图像和视频检测(pytorch框架)【python源码+UI界面+功能源码详解】

功能演示&#xff1a; 基于Faster-RCNN的停车场空位检测系统&#xff0c;支持图像检测和视频检测&#xff08;pytorch框架&#xff09;_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于Faster-RCNN的停车场空位检测系统是在pytorch框架下实现的&#xff0c;这是一个…

YB5214B 同步开关型降压锂电池充电管理芯片

概述&#xff1a; 是一款支持 4.5-16V 输入电压范围&#xff0c;最大输出为 2A 电流的同步降压锂电池充电管理芯片。芯片内部集成了低阻功率 MOSFETS&#xff0c;采用 500kHz的开关频率以实现较小的元件尺寸和较高的充电效率。 内部还集成了多重保护功能&#xff0c;能够最大程…

基于NXP IMX6Q+FPGA全自动血液分析仪解决方案

全自动血细胞分析仪 &#xff0c;临床又称血常规检测仪、血液分析仪、血球分析仪、血液细胞分析仪、血球计数仪&#xff0c;是指对一定体积全血内血细胞异质性进行自动分析的临床检验常规仪器。 NXP IMX6Q核心板采用四核Cortex-A9架构&#xff0c;主频1GHz&#xff0c;12层PCB…

知识竞赛中风险题环节竞赛规则有哪些设计方案

风险题环节是知识竞赛活动中一个高潮环节&#xff0c;很多时候都是放到最后压轴&#xff0c;选手会根据之前的成绩进行最后一博。那么&#xff0c;常用的风险题环节规则应怎么设计呢&#xff1f;下面列出的这些大家可以参考一下。 1.设置不同分值的题&#xff0c;由选手根据自…

CSS——字体背景(Font Background)

一、字体族 1、字体的相关样式&#xff1a; ① color 用来设置字体颜色&#xff08;前景颜色&#xff09; ② font-size 字体的大小 和font-size相关的单位&#xff1a; em 相对于当前元素的一个font-size rem 相对于根元素的一个font-size ③ font-family 字体族&#x…

软件测试第4章 白盒测试方法(逻辑覆盖测试)

一、白盒测试方法 二、白盒测试 VS 静态测试 【在不运行程序的情况下(即静态测试&#xff0c;程序审查)】 三、白盒测试方法 1、程序控制流图 2、逻辑覆盖测试 测试覆盖率 用于确定测试所执行到的覆盖项的百分比&#xff0c;其中覆盖项是指作为测试基础的一个入口或属性&am…

异常信息转储笔记-demangle函数名字符

前情 上一篇笔记留下了两个待解决问题&#xff0c;其中之一是输出的函数名被奇怪字符覆盖了一部分&#xff0c;本篇笔记即将解决这个问题&#xff08;下图问题1&#xff09;。 问题描述 如上&#xff0c;使用libunwind输出core堆栈信息时&#xff0c;有部分字符被覆盖&#x…

fetch_20newsgroups报错403的两种解决办法

在使用sklearn机器学习库使用fetch_20newsgroups调用数据集时候&#xff08;如下方代码所示&#xff09;&#xff0c;报错403怎么办&#xff0c;本人亲测两种方法&#xff0c;分享大家&#xff1a; data fetch_20newsgroup(subset"train")一、第一种方法 1.下载压…

十大护眼落地灯品牌中护眼效果最好的是哪款?落地灯十大知名品牌

根据不完全统计&#xff0c;我国儿童青少年的近视率达到了52.7%&#xff0c;也就是说10个孩子中有5个以上的视力都处于一个亚健康的状态&#xff0c;这和户外运动少及室内灯光差有很大关系&#xff0c;在面临这种现状下&#xff0c;很多家长对日常用眼时的光线质量越来越重视&a…

智慧叉车监管系统,司机权限启动车辆,杜绝无证驾驶!

叉车广泛应用于各种生产场所&#xff0c;是常见的一种作业工具。叉车作业具有较大的危险性&#xff0c;司机的不安全操作行为是导致叉车事故发生的主要原因之一。近年来&#xff0c;由于操作人员无证驾驶、违章作业等原因&#xff0c;国内发生了多起叉车安全事故&#xff0c;造…

【爬虫新手村】零基础入门到实战:解锁互联网数据收集的密钥,爬虫技术全攻略

文章目录 前言一、爬虫1.基本概念2.常用库3.基本步骤4.注意事项 二、爬虫示例代码1.案例一&#xff1a;requests 的基本使用2.案例二&#xff1a;爬取古诗文&#xff08;requestsBeautifulSoup&#xff09;3.案例三&#xff1a;爬取美食&#xff08;requestsBeautifulSoup&…

智能语音识别技术在无人驾驶领域的应用案例

随着无人驾驶技术的进步与发展&#xff0c;越来越多的企业、创业者注意到无人驾驶领域潜藏的巨大市场经济价值&#xff0c;越来越多的企业和创业者进入无人驾驶领域&#xff0c;以近期业内关注的萝卜快跑为例&#xff0c;其在武汉地区的成功推广与落地预示着无人驾驶在网约车领…

Linux的应用领域与历史发展

目录 linux应用领域 个人桌面领域的应用 服务器领域 嵌入式领域 linux概述 linux和unix的关系 linux应用领域 linux下开发项目&#xff0c;javaee&#xff0c;大数据&#xff0c;python&#xff0c;php&#xff0c;c&#xff0c;c&#xff0c;go等。 个人桌面领域的应…

DHU OJ 二维数组

思路及代码 #include<iostream> using namespace std; int main(){ //input 多组 //input M,N int 1< <20 //input M 行 N 列 数据 //initialize listint M, N;while (cin >> M >> N){int list[M][N];for (int i 0; i < M-1; i){for (int j 0; j…

基于价值流DevSecOps效能案例分享

背景 数字经济时代&#xff0c;企业数字化转型加速&#xff0c;软件业务收入目标设定&#xff0c;产业基础保障水平提升。DevSecOps: 作为解决交付能力挑战的方法&#xff0c;强调开发&#xff08;Dev&#xff09;、安全&#xff08;Sec&#xff09;、运维&#xff08;Ops&…

替代进程注入的新工具

目录 前言 Windows Session 的利用 Windows Session 介绍 跨会话激活技术 什么是跨会话激活机制&#xff1f; 常见的跨会话激活技术 结合利用 地址 前言 众所周知&#xff0c;常用的C2工具&#xff08;例如CobaltStrike&#xff09;在另一个进程上下文中执行代码经常使…

【安全科普】学完网络安全出去能做什么工作?

想要了解学完网络安全工程师就业班后&#xff0c;出去能做什么工作&#xff0c;这个时候会分甲方或是乙方&#xff0c;看个人更偏向哪个岗位。 甲方指的是政府、海关、税务机构、高校及其他国有银行、商业银行&#xff0c;以及移动运营商&#xff08;如中国移动、中国联通、中…