当前位置: 首页> 教育> 大学 > C++ -- 负载均衡式在线OJ (三)

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

时间:2025/7/18 9:59:12来源:https://blog.csdn.net/weixin_74461263/article/details/141134121 浏览次数:0次

文章目录

  • 四、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"
#endifvoid 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 30000if (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;  // 编译服务的ipint port;        // 编译服务的portuint64_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:// 可以给我们提供编译服务的所有的主机// 每一台主机都有自己的下标,充当当前主机的idstd::vector<Machine> machines;// 所有在线的主机idstd::vector<int> online;// 所有离线的主机idstd::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. 轮询+hashint 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 testvoid 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.htmleditor.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

关键字:C++ -- 负载均衡式在线OJ (三)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: