「项目」负载均衡在线OJ(ONLINE_JUDGE)系统

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux

🔥座右铭:“不要等到什么都没有了,才下定决心去做”

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

一、项目介绍

二、项目技术与开发环境 

1.项目技术

2.开发环境 

三、项目的宏观结构 

1.项⽬核⼼是三个模块

2.项目的框架

3.项目实现流程

四、comm公共模块设计

1.日志模块开发

2.UTIL工具模块开发

1)TimeUtil模块

2)PathUtil模块

3)FileUtil模块

4)StringUtil模块

3.httplib.h模块

五、compile_server模块

1.compile编译模块

2.runneryu运行模块

3.compile_run模块

4.compile_server.cc

5.temp缓冲区

6.Makefile

六、oj_server模块

1.oj_model数据交互模块

2.oj_view视图模块

3.oj_control控制模块

4.oj_server.cc

5.questions文件版题库

1)1

[1]desc

[2]header.cpp

[3]tail.cpp

2)questions.list

6.wwwroot主页

7.template_html

1)all_questions.html

2)one_question.html

8.conf

9.Makefile

七、MySQL版本的实现

1.oj_model_m

2.include

3.lib

4.linux下载mysql服务

5.在Ubuntu上设置MySQL可以远程登录用户

6.创建表结构​​​​​​​

 八、综合

1.安装 jsoncpp

2.安装 cpp-httplib

3.安装boost库

4.安装ctemplate

5.结果展示


一、项目介绍

随着计算机技术的快速发展和互联网的普及,在线评测系统(Online Judge,简称 OJ 系统)在教育和竞赛领域中得到了广泛应用。OJ 系统是一个用于批量评测编程作业和竞赛题目的平台,通过自动化评测和实时反馈,提供了高效、公正和具有即时性的编程评测服务。

该项目旨在构建一个功能强大的 OJ 系统,为学生、教师和竞赛参与者提供一个便捷的在线学习和评测环境。下面将介绍该项目的历史、项目实例、市场需求和未来发展。

1)项目历史: 过去几年中,计算机编程的重要性逐渐被广大学生和教育机构所认识到。传统的教学方法已经不再能够满足快速发展的计算机科学领域的需求。为了更好地培养学生的编程能力和解决实际问题的能力,开发一个功能全面的 OJ 系统成为了迫切的需求。

2)项目实例:

LeetCode(https://leetcode.com/):LeetCode 是一个非常受欢迎的面向算法和数据结构练习的在线评测平台。它提供了一系列编程题目,并为用户提供了在线编码环境和自动评测功能。

Codeforces(https://codeforces.com/):Codeforces 是一个面向竞赛编程的 OJ 系统,其主要目标是提供高质量的编程竞赛题目和评测服务。它支持多种编程语言,并为用户提供实时排名和评测结果等功能。

HackerRank(https://www.hackerrank.com/):HackerRank 是一个面向编程技能测试和面试准备的 OJ 系统。它提供了丰富的编程题目和技术挑战,并为用户提供实时评测、学习资源和招聘服务等功能。

UVa Online Judge(https://onlinejudge.org/):UVa Online Judge 是一个老牌的 OJ 系统,拥有大量的编程题目,并为用户提供了在线编程环境和自动评测功能。它旨在提供一个开放的学习和竞赛平台。

3)市场需求: 在当今的教育和竞赛环境中,对于计算机编程的需求越来越高。越来越多的教育机构和竞赛组织需要一个可靠的 OJ 系统来支持他们的教学和竞赛活动。正因如此,开发一个功能完善且易于使用的 OJ 系统成为了市场的迫切需求。该系统不仅能够满足学习者的自主学习需求,还能够提供教师管理和评估的功能,能够适应不同层次和不同类型的编程题目。

4)未来发展: 随着人工智能、大数据、云计算等技术的不断发展,未来 OJ 系统还可以进一步提升和完善。例如,可以引入自动评测算法的优化和智能化,增加更多的编程题库和实例,支持多种编程语言的评测,提供个性化学习推荐等功能。此外,该系统还可以扩展到更广泛的应用领域,如软件工程测评、面试准备等。与此同时,与其他教育平台和在线学习资源的集成将是未来 OJ 系统的重要发展方向。通过不断创新和拓展,该项目将为学生和教师提供更加优质的编程学习和评测体验,并为教育领域的发展做出积极的贡献。

二、项目技术与开发环境 

1.项目技术

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

2.开发环境 

  • Ubuntu 22.04 64位 服务器
  • vscode
  • Navicat Premium

三、项目的宏观结构 

1.项⽬核⼼是三个模块

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

2.项目的框架

图1

3.项目实现流程

  1. 第一步:compile_server
  2. 第二步:oj_server
  3. 第三步:⽂件版的在线OJ
  4. 第四步:⽂件版的在线OJ
  5. 第五步:MySQL 版的在线OJ

四、comm公共模块设计

文件处理、日志、网络服务

1.日志模块开发

  • 日志等级
  • 打印日志的文件名称
  • 报错行
  • 添加日志的时间
  • 开放性输出

开放性输出就是说我们可以在后面输出自己想输出的东西,比如LOG(INFO)<<"功能正常运行"<<endl;

#pragma once
#include <iostream>
#include <string>
#include "util.hpp"using namespace std;namespace ns_log
{using namespace ns_util;//日志等级enum{INFO = 0, //正常运行DEBUG,    //调试信息WARNING,  //警告信息,但不影响程序运行ERROR,    //错误信息,程序运行错误FATAL     //严重错误,系统崩溃};//我们通过Log函数,其返回值是标准输出流对象,从而实现开放性日志inline std::ostream &Log(const string &level, const string &file_name, int line){// 添加日志等级string message = "[";message += level;message += "]";// 添加报错文件名称message += "[";message += file_name;message += "]";// 添加报错行号message += "[";message += to_string(line);message += "]";// 添加报错时间message += "[";message += TimeUtil::GetTimeStamp();//TimeUtil::GetTimeStamp(),将日志转化为字符串,XXX(秒)message += "]";// cout本质 内部是包含缓冲区cout << message; // 不要endl进行刷新return cout;}// LOG(INFO)<<"messge"<<"\n";// 开放式接口
#define LOG(level) Log(#level, __FILE__, __LINE__)//LOG是函数log的宏替换函数,这样提供了,两个默认的参数,__FILE__(自动获取文件名称), __LINE__(自动获取文件行数)
}

2.UTIL工具模块开发

1)TimeUtil模块

将时间戳转化为字符串形式

  • 毫秒
 class TimeUtil{public:static string GetTimeStamp(){struct timeval _time;gettimeofday(&_time, nullptr);return to_string(_time.tv_sec);//秒}static string GetTimeMs(){struct timeval _time;gettimeofday(&_time, nullptr);return to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);//毫秒}};
  • to_string:是C++中用于将数字或其他可转换为字符串的类型转换为字符串
  • gettimeofday是一个用于获取当前时间和日期的系统调用函数。它以微秒精度返回当前的UTC时间和日期。

2)PathUtil模块

形成一些文件,源文件,可执行文件,编译错误文件。

const string temp_path = "./temp/";
class PathUtil{public://添加后缀static string AddSuffix(const string &file_name, const string &Suffix)//需要提供文件名和文件名后缀{string path_name = temp_path; //./temp/path_name += file_name;       //./temp/codepath_name += Suffix;          //./temp/code.stderrreturn path_name;}// 编译时需要有的临时文件// 构建一个源文件+路径+后缀的完整文件名// 1234 -> ./temp/1234.cppstatic string Src(const string &file_name){return AddSuffix(file_name, ".cpp");}// 构建一个可执行程序的源文件+路径+后缀的完整文件名static string Exe(const string &file_name){return AddSuffix(file_name, ".exe");}// 构建一个可执行程序的标准错误完整的路径+后缀名static string Err(const string &file_name){return AddSuffix(file_name, ".stderr");}// 构建一个可执行程序的标准错误完整的路径+后缀名-编译该源文件时运行的错误static string CompilerError(const string &file_name){return AddSuffix(file_name, ".compile_error");}// 运行时所需要的临时文件//输入缓冲区文件static string Stdin(const string &file_name){return AddSuffix(file_name, ".stdin");}//输出缓冲区文件static string Stdout(const string &file_name){return AddSuffix(file_name, ".stdout");}//错误缓冲区文件static string Stderr(const string &file_name){return AddSuffix(file_name, ".stderr");}};

3)FileUtil模块

判断文件是否存在、生成唯一的文件名、读写文件

class FileUtil{public:static bool IsFileExists(const string &path_name){struct stat st;if (stat(path_name.c_str(), &st) == 0){// 获取属性成功,文件已经存在return true;}return false;}// 毫秒级时间戳+源子性递增唯一值:来保证唯一性static string UniqFileName(){static atomic_uint id(0);string ms = TimeUtil::GetTimeMs();string uniq_id = to_string(id++);return ms + "_" + uniq_id;}static bool WriteFile(const string &target, const string &code){ofstream out(target, ios::out);if (!out.is_open()){return false;}out.write(code.c_str(), code.size());out.close();return true;}static bool ReadFile(const string &target, string *content, bool keep = false){(*content).clear();ifstream in(target, ios::in);if (!in.is_open()){return false;}string line;// getline:不保存行分隔符// getline内部重载了强制类型转化while (getline(in, line)){(*content) += line;(*content) += (keep ? "\n" : "");}in.close();return true;}};

std::atomic_uint是C++标准库中的一个原子类型,用于支持并发编程中的原子操作。它提供了原子无锁访问的能力,用于保证多个线程之间对数据的操作不会发生竞争条件

4)StringUtil模块

   class StringUtil{public:/************************************************************************ str:输入型,目标要切分的字符串* target:输出型,保存切分完毕的结果* sep:输入型,切分符***********************************************************************/static void SplitString(const string &str, vector<string> *target, string sep){// boost库boost::split(*target, str, boost::is_any_of(sep), boost::token_compress_on);//用于将字符串str按照指定的分隔符sep进行分割,并将分割后的子字符串存储在target指针所指向的容器中}};

boost::split 是 Boost 库中的一个函数,用于将一个字符串拆分成多个子串,并将结果存储在目标容器中

split函数的第一个参数是一个迭代器,指向目标容器的起始位置。第二个参数是要分割的字符串str。第三个参数是分隔符sep,可以是一个字符串,也可以是一个字符数组。第四个参数是一个标志,用于指定是否压缩分割后的子字符串,默认为boost::token_compress_on,表示压缩分割后的子字符串

3.httplib.h模块

cpp-httplib第三方网络库,我们自己写网络套接字来进行通信也是可以的,不过太麻烦了,我们直接使用开源第三方库cpp-httplib

进行cpp-httplib的安装后,cpp-httplib是header独立分开的,只需要将它里面的httplib.h文件拷贝到项目中。如果你想的话,也可以拷贝到系统目录下/usr/include/,但是不推荐

需要注意的是

cpp-httplib的使用需要使用高版本的gcc/g++
cpp-httplib是阻塞式的多线程http网络库,因为里面使用了原生线程库,所以在编译的时候,需要带上选项-lpthread

五、compile_server模块

1.compile编译模块

负责将用户提交的源代码转换为目标代码的过程。其主要功能包括词法分析、语法分析(构建语法树)、语义分析(检查语义正确性)、代码优化(提高程序性能)、目标代码生成(生成目标机器代码或中间代码)、符号表管理(记录变量、函数等信息)以及错误处理(收集和报告错误信息

#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <fcntl.h>#include "../comm/util.hpp"
#include "../comm/log.hpp"
// 只进行代码的编译namespace ns_compiler
{// 引入路径拼接功能using namespace ns_util;// 引入日志功能using namespace ns_log;class Compiler{public:Compiler() {}~Compiler() {}// 返回值:// true:编译成功// false:编译失败// file_name:编译的文件名// 如果编译的文件是1234 我们需要处理成./temp/1234.cpp (temp专门用来存储生成的临时文件)// 我们还需要形成 ./temp/1234.exe文件// 我们还需要形成 ./temp/1234.stderr文件static bool Compile(string &file_name){pid_t pid = fork();if (pid < 0){LOG(ERROR) << "内部错误,创建子进程失败" << endl;return false;}else if (pid == 0){umask(0);int _stderr = open(PathUtil::CompilerError(file_name).c_str(), O_WRONLY | O_CREAT, 0644); //./temp/code.stderrif (_stderr < 0){LOG(WARNING) << "没有成功形成stderr文件" << endl;exit(1);}// 重定向:标准错误到_stderr中dup2(_stderr, 2);// 子进程:调用编译器,完成对代码的编译工作// g++ -o target src -std=c++11execlp("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++失败,可能是参数错误" << endl;exit(2);}else{waitpid(pid, nullptr, 0);// 编译是否成功,就看有没有形成对应的可执行程序if (FileUtil::IsFileExists(PathUtil::Exe(file_name))){LOG(INFO) << PathUtil::Src(file_name) << "编译成功" << endl;return true;}}//如果程序(g++)替换失败,才会输出下面的日志信息LOG(ERROR) << "编译失败,没有形成可执行程序" << endl;return false; //}private:};
}

2.runneryu运行模块

编译成功,我们需要运行可执行文件,其中会成功,也会失败,成功我们需要向用户返回运行结果,运行错误,我们需要向用户提供运行错误的信息

#pragma once#include <iostream>
#include <string>#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/time.h>#include "../comm/util.hpp"
#include "../comm/log.hpp"
using namespace std;namespace ns_runner{using namespace ns_util;using namespace ns_log;class Runner{public:Runner() {}~Runner() {}// 设置进程占用资源大小的接口static void SetProcLimit(int _cpu_limit, int _mem_limit){struct rlimit cpu_limit, mem_limit;cpu_limit.rlim_cur = _cpu_limit;cpu_limit.rlim_max = RLIM_INFINITY;mem_limit.rlim_cur = _mem_limit * 1024; // 设置最小内存资源占用的空间为以kb为单位mem_limit.rlim_max = RLIM_INFINITY;setrlimit(RLIMIT_CPU, &cpu_limit);setrlimit(RLIMIT_AS, &mem_limit);}public:// 指明路径即可,不需要代码路径,不需要带后缀/************************************************************************  返回值>0:程序异常了,退出时收到信号,返回值就是信号编号*  返回值<0:程序异常了,fork失败(内部失败)*  返回值=0:程序正常运行完毕,结果保存在对应的临时文件中** cpu_limit:cpu时间限制,单位是秒* mem_limit:内存限制,单位是kb************************************************************************/static int Run(const string &file_name, int cpu_limit, int mem_limit){// 程序运行:// 1.代码跑完,结果正确// 2.代码跑完,结果不正确// 3.代码没跑完,异常了// Run需要考虑代码跑完,结果正确与否??不考虑// 结果正确与否,是由我们测试用例决定的!// 我们只考虑:是否正确运行完毕// 我们必须知道可执行程序是谁?// 一个程序在默认启动的时候// 标准输入:暂时不处理// 标准输出:程序运行完成,输出结果是什么// 标准错误:运行时错误的信息string _execute = PathUtil::Exe(file_name);string _stdin = PathUtil::Stdin(file_name);string _stdout = PathUtil::Stdout(file_name);string _stderr = PathUtil::Stderr(file_name);int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, 0644);int _stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);int _stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);if (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0){LOG(ERROR) << "运行时打开标准文件失败" << endl;return -1; // 代表打开文件失败}pid_t pid = fork();if (pid < 0){LOG(ERROR) << "创建子进程失败" << endl;close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);return -2; // 代表fork失败}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() /*我想要在命令行上如何执行*/, NULL);exit(1);}else{close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);int status;waitpid(pid, &status, 0);// 程序运行异常,一定是因为收到了信号LOG(INFO) << "运行完毕,info:" << (status & 0x7f) << endl;return status & 0x7f;}}};
}

3.compile_run模块

compile_run模块主要完成compile模块和runner模块的调度,首先编译程序,然后运行程序,最后返回运行后的结果。编译错误和运行错误都会将错误原因返回用户

#pragma once// 适配用户请求#include "compiler.hpp"
#include "runner.hpp"#include "../comm/util.hpp"
#include "../comm/log.hpp"#include <signal.h>
#include <unistd.h>
#include <jsoncpp/json/json.h>using namespace std;
namespace ns_compile_and_run
{using namespace ns_compiler;using namespace ns_runner;using namespace ns_util;using namespace ns_log;class CompileAndRun{public:// code>0:进程收到了信号导致导致崩溃// code<0:整个过程非运行出错(代码为空,编译报错)// code=0:整个过程全部完成static string CodeToDesc(int code, const string &file_name){string desc;switch (code){case 0:desc = "编译运行成功";break;case -1:desc = "代码为空";break;case -2:desc = "未知错误";break;case -3:FileUtil::ReadFile(PathUtil::CompilerError(file_name), &desc, true);break;case SIGABRT: // 6desc = "内存超限";break;case SIGXCPU: // 24desc = "time out";break;case SIGFPE: // 8desc = "浮点数溢出";break;default:desc = "未知" + to_string(code);break;}return desc;}// 删除临时文件static void RemoveTempFile(const string &file_name){// 清理文件的个数是不确定的// 删除源文件string _src = PathUtil::Src(file_name);if (FileUtil::IsFileExists(_src))unlink(_src.c_str());// 删除编译报错文件string _compiler_error = PathUtil::CompilerError(file_name);if (FileUtil::IsFileExists(_compiler_error))unlink(_compiler_error.c_str());// 删除可执行文件string _execute = PathUtil::Exe(file_name);if (FileUtil::IsFileExists(_execute))unlink(_execute.c_str());// 删除标准输入文件string _stdin = PathUtil::Stdin(file_name);if (FileUtil::IsFileExists(_stdin))unlink(_stdin.c_str());// 删除标准输出文件string _stdout = PathUtil::Stdout(file_name);if (FileUtil::IsFileExists(_stdout))unlink(_stdout.c_str());// 删除标准错误文件string _stderr = PathUtil::Stderr(file_name);if (FileUtil::IsFileExists(_stderr))unlink(_stderr.c_str());}/***************************************************************** 输入:* code:用户提交的代码* input:用户给自己提交的代码对应的输入* cpu_limit:时间要求* mem_limit:空间要求* 选填:* time_limit:* ***************************************************************** 输出:* status:状态码* reason:请求的原因* 选填:* stdout:我的程序运行完的结果* stderr:我的程序运行完的错误结果* in_json:{"code","#include...","cpu_limit:1","mem_limit:1024"}* out_json:{"status":0,"reason":"ok","stdout":"","stderr":"error"}*****************************************************************/static void Start(const string &in_json, string *out_json){Json::Value in_value;Json::Reader reader;// 反序列化reader.parse(in_json, in_value); // 最后处理差错问题string code = in_value["code"].asString();string input = in_value["input"].asString();int cpu_limit = in_value["cpu_limit"].asInt();int mem_limit = in_value["mem_limit"].asInt();int status_code = 0;Json::Value out_value;int run_result;string file_name; // 需要内部形成的临时文件名if (code.size() == 0){status_code = -1; // 代码为空goto END;}// 形成一个唯一的文件名// 形成的文件名只具有唯一性,没有目录和后缀// 毫秒级时间戳+源子性递增唯一值:来保证唯一性file_name = FileUtil::UniqFileName();// 形成临时src源文件if (!FileUtil::WriteFile(PathUtil::Src(file_name), code)){status_code = -2; // 未知错误goto END;}if (!Compiler::Compile(file_name)){// 编译失败status_code = -3; // 编译时发生错误goto END;}run_result = Runner::Run(file_name, cpu_limit, mem_limit);if (run_result < 0){status_code = -2; // 未知错误goto END;}else if (run_result > 0){// 程序运行崩溃status_code = run_result; // 程序运行崩溃}else{// 运行成功status_code = 0;}END:out_value["status"] = status_code;out_value["reason"] = CodeToDesc(status_code, file_name);cout << out_value["reason"] << endl;if (status_code == 0){// 整个过程全部成功string _stdout;FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);out_value["stdout"] = _stdout;string _stderr;FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr, true);out_value["stderr"] = _stderr;}// 序列化Json::StyledWriter writer;*out_json = writer.write(out_value);// 删除临时文件RemoveTempFile(file_name);return;}};
}

在Linux系统中,JsonCpp 是一个流行的 C++ 库,用于处理 JSON(JavaScript Object Notation)数据格式。JsonCpp 提供了一组易于使用的 API,使得在 C++ 程序中解析、构建和操作 JSON 数据变得简单。

4.compile_server.cc

形成编译运行网络服务

#include "compile_run.hpp"
#include"../comm/httplib.h"using namespace std;using namespace ns_compile_and_run;using namespace httplib;void Usage(string proc)
{cerr<<"Usage: "<<"\n\t"<<proc<<" <port>"<<endl;
}// 编译服务随时可能被多个人请求,必须保证传递上来的code,形成源文件名称的时候,要具有唯一性
int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);return -1;}//提供的编译服务,打包形成一个网络服务//cpp-httplibServer svr;svr.Get("/hello",[](const Request &req,Response &resp){resp.set_content("hello world,hello 你好", "text/plain;charset=utf-8");});svr.Post("/compile_and_run",[](const Request &req,Response &resp){//用户请求的服务正文是我们想要的json stringstring in_json=req.body; string out_json;if(!in_json.empty()){CompileAndRun::Start(in_json,&out_json);resp.set_content(out_json,"application/json;charset=utf-8");}});//svr.set_base_dir("./wwwroot");//指明了根目录svr.listen("0.0.0.0",atoi(argv[1]));//启动http服务return 0;
}

5.temp缓冲区

主要用存放编译运行产生的临时文件

  • 编译错误文件
  • 编译源文件
  • 可执行文件
  • 运行错误文件
  • 交互输入文件
  • 输出运行结果文件

这些都是临时文件,在向用户响应之后,这些文件将会自动被清理,可以手动设置保留这些临时文件,在compile_run模块中取消RemoveTempFile方法的调用

6.Makefile

compile_server:compile_server.ccg++ -o $@ $^ -std=c++11 -ljsoncpp -lpthread.PHONY:clean
clean:rm -f compile_server

因为使用了jsconcpp库需要在编译的时候进行链接,还有使用了多线程需要链接pthread库


六、oj_server模块

oj_server主要是用于面向客户交互的部分,编译运行是compile_sever负责

oj_server的功能

  • 获取首页
  • 获取题目列表
  • 获取指定题目,并提供编辑功能
  • 提交判题功能(编译功能)

MVC模式

  • 这种模式可以减少各个模块的耦合性,保证整个系统运行的稳定性
  • M: model,通常是用和数据交互的模块,比如,对题库进行增删改查
  • V: view,通常是拿到数据之后,要构建网页,渲染网页内容,展示给用户(浏览器)
  • C: control,控制器,就是我们业务核心

1.oj_model数据交互模块

给用户提供题目题库,让用户可以自主决定在线解答指定的题

#pragma once
// 文件版
#include "../comm/log.hpp"
#include <iostream>
#include <string>
#include <unordered_map>
#include <cassert>
#include <vector>
#include <fstream>
#include "../comm/util.hpp"
#include <cstdlib>// 根据题目list文件,加载所有的题目信息到内存中
// model: 主要用来和数据进行交互,对外提供访问数据的接口
namespace ns_model
{using namespace std;using namespace ns_log;using namespace ns_util;struct Question{string number; // 题号string title;  // 题目标题string star;   // 难度int cpu_limit; // 时间限制int mem_limit; // 内存限制string desc;   // 题目描述string header; // 题目预设给用户在线编辑器的代码string tail;   // 题目的测试用例,需要和header拼接,形成完整的代码};const string questlines_list = "./questions/questions.list";const string question_path = "./questions/";class Model{private:// 题号: 题目细节unordered_map<string, Question> questions;public:Model(){assert(LoadquestionList(questlines_list));}bool LoadquestionList(const string &question_list){// 加载配置文件:questions/questions.list+题目编号文件ifstream in(question_list);if (!in.is_open()){LOG(FATAL) << "加载题库失败,请检查是否存在题库文件" << endl;return false; // 打开文件失败}string line;while (getline(in, line)){vector<string> tokens; // 保留好切分的字符串StringUtil::SplitString(line, &tokens, "  ");// 1 判断回文数 简单  1 30000if (tokens.size() != 5){LOG(WARNING) << "加载部分题库失败,请检查题库文件格式" << endl;continue;}struct 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 = question_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(make_pair(q.number, q));}LOG(INFO) << "加载题库...成功" << endl;in.close();return true;}bool GetAllQuestions(vector<struct Question> *out){if (questions.size() == 0){return false; // 题库没有题目}for (const auto &q : questions){out->push_back(q.second);}return true;}bool GetOneQuestion(const string &number, Question *q){const auto &iter = questions.find(number);if (iter == questions.end()){LOG(ERROR) << "用户获取题目失败" << endl;return false; // 题目不存在}(*q) = iter->second;return true;}~Model(){}};
}

Boost 是一个广泛使用的 C++ 库集合,为 C++ 开发人员提供了丰富的工具和功能增强。Boost 库通过提供高质量、可移植、可重用的开源组件,扩展了 C++ 标准库,涵盖了各个领域,包括算法、容器、字符串处理、多线程、网络编程、数学和并发编程等。

以下是 Boost 库的一些核心模块和功能:

  • 智能指针(Smart Pointers):包括 shared_ptrunique_ptrweak_ptr,提供了更安全和方便的动态内存管理。
  • 容器(Containers):包括 vectorlistmapset 等常用容器的增强版本,提供了更多功能和效率。
  • 字符串处理(String Handling):包括字符串分割、替代、查找、格式化等操作,提供了正则表达式库和较强大的字符串处理工具。
  • 算法(Algorithms):提供了很多算法模板,例如排序、查找、合并、计数等,扩展了 C++ 标准库中的算法。
  • 文件系统(Filesystem):提供了用于文件和目录操作的跨平台接口,比标准库的 std::filesystem 更早支持。
  • 多线程(Multithreading):提供了线程和互斥量、条件变量等同步和并发编程工具。
  • 正则表达式(Regex):提供了正则表达式库,支持 Perl 风格的正则表达式,方便进行文本处理。
  • 数学(Math):提供了数值计算、随机数生成、矩阵操作等数学相关的库。
  • 网络编程(Networking):提供了 TCP/IP 网络编程和网络通信的库,如套接字、HTTP 请求等。
  • 序列化(Serialization):提供了对象序列化和反序列化的支持,使对象可以在不同平台和语言间进行通信和持久化。

我们在这里使用boost库字符串处理功能中的字符串分割

2.oj_view视图模块

主要是完成网页的渲染功能,负责对题库和单个题目的渲染

#pragma onece#include <iostream>
#include <string>
#include <ctemplate/template.h>//#include "oj_model.hpp"
#include "oj_model_m.hpp"namespace ns_view
{using namespace std;using namespace ns_model;// string number;//题号//     string title;//题目标题//     string star;//难度//     int cpu_limit;//时间限制//     int mem_limit;//内存限制//     string desc;//题目描述//     string header;//题目预设给用户在线编辑器的代码//     string tail;//题目的测试用例,需要和header拼接,形成完整的代码const string template_path = "./template_html/";class View{public:View() {}~View() {}public:void AllExpandHtml(const vector<struct Question> &questions, string *html){// 题目编号 题目的难度 题目的难度// 推荐使用表格显示// 1.形成路径string src_html = template_path + "all_questions.html";// 2.形成字典ctemplate::TemplateDictionary root("all_questions");for (const auto &q : questions){ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");sub->SetValue("number", q.number);sub->SetValue("title", q.title);sub->SetValue("star", q.star);}// 3.获取被渲染的htmlctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);// 4.开始完成渲染功能tpl->Expand(html, &root);}void OneExpandHtml(struct Question &q, string *html){// 1.形成路径string src_html = template_path + "one_question.html";// 2.形成字典ctemplate::TemplateDictionary root("one_question");root.SetValue("number", q.number);root.SetValue("title", q.title);root.SetValue("star", q.star);//    root.SetValue("cpu_limit",to_string(q.cpu_limit));//    root.SetValue("mem_limit",to_string(q.mem_limit));root.SetValue("desc", q.desc);root.SetValue("pre_code", q.header);// 3.获取被渲染的htmlctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);// 4.开始完成渲染功能tpl->Expand(html, &root);}};
}

ctemplate 是一个基于 C++ 的模板库,用于生成动态内容或静态文本。它提供了一种简单且强大的方式来将数据与模板进行结合,生成输出结果。ctemplate 的设计目标是快速且易于使用,尤其适用于 Web 开发中的动态页面生成。

3.oj_control控制模块

完成对oj_model和oj_view模块调度控制

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <cassert>
#include <fstream>
#include <algorithm>
#include <jsoncpp/json/json.h>#include "../comm/log.hpp"
#include "../comm/util.hpp"
//#include "oj_model.hpp"
#include "oj_model_m.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"namespace ns_control
{using namespace ns_model;using namespace ns_view;using namespace ns_log;using namespace ns_util;using namespace std;using namespace httplib;const string service_machine = "./conf/service_machine.conf";// 负载均衡模块class Machine{public:string ip;     // 编译服务的ipint port;      // 编译服务的portuint64_t load; // 编译服务的负载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 GetLoad(){if (mtx)mtx->lock();uint64_t _load = load;if (mtx)mtx->unlock();return _load;}};class LoadBlance{private:vector<Machine> _machines; // 可以给我们提供编译服务的所有的主机// 每一台主机都有自己的下标,充当当前主机的id// 所有在线的主机idvector<int> online;// 所有离线的主机idvector<int> offline;// 保证LoadBlance的线程安全mutex mtx;public:LoadBlance(){assert(LoadConf(service_machine));LOG(INFO) << "加载" << service_machine << "成功" << endl;}~LoadBlance() {}public:bool LoadConf(const string &machine_conf){ifstream in(machine_conf, ios::in);if (!in.is_open()){LOG(FATAL) << "加载:" << machine_conf << "失败" << endl;return false;}string line;while (getline(in, line)){vector<string> tokens;StringUtil::SplitString(line, &tokens, ":");if (tokens.size() != 2){LOG(WARNING) << "切分" << line << "失败" << endl;continue;}Machine m;m.ip = tokens[0];m.port = atoi(tokens[1].c_str());m.load = 0;m.mtx = new mutex();online.push_back(_machines.size()); // 默认初始化主机的时候都在online_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) << "所有的后端编译主机已经离线,请运维的同事查看" << endl;return false;}// 通过遍历的方式,找到所有负载最小的机器*id = online[0];*m = &_machines[online[0]]; // 拿到负载最小主机的地址uint64_t min_load = _machines[online[0]].GetLoad();for (int i = 0; i < online_num; ++i){uint64_t curload = _machines[online[i]].GetLoad();if (min_load > curload){min_load = curload;*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) << "所有的主机上线啦" << endl;}// for testvoid ShowMachinees(){mtx.lock();cout << "当前主机列表:    ";for (auto &id : online){cout << id << " ";}cout << endl;cout << "当前离线主机列表:";for (auto &id : offline){cout << id << " ";}cout << endl;mtx.unlock();}};// 这是我们的核心业务逻辑的控制器class Control{private:Model _model;View _view;LoadBlance _load_balance; // 核心负载均衡public:Control() {}~Control() {}public:void Recovery(){_load_balance.OnlineMachine();}// 根据题目数据构建网页// html:输出型参数bool AllQuestions(string *html){bool ret = true;vector<struct Question> all;if (_model.GetAllQuestions(&all)){sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2){ return atoi(q1.number.c_str()) < atoi(q2.number.c_str()); });// 获取题目信息成功,将所有的题目数据构建成网页_view.AllExpandHtml(all, html);}else{ret = false;*html = "获取题目失败,形成题目列表失败";}return ret;}bool Question(const string &number, string *html){bool ret = true;struct Question q;if (_model.GetOneQuestion(number, &q)){// 获取指定题目信息成功,将所有的题目数据构建成网页_view.OneExpandHtml(q, html);}else{ret = false;*html = "指定题目" + number + "不存在";}return ret;}// code:"#include"//void Judge(const string &number, const string in_json, string *out_json){// LOG(DEBUG)<<in_json<<"\nnumber:"<<number<<endl;// 0.根据题目编号,直接拿到对应的题目细节struct Question q;_model.GetOneQuestion(number, &q);// 1.in_json进行反序列化,得到题目id,得到用户提交的源代码,inputJson::Reader reader;Json::Value in_value;reader.parse(in_json, in_value);string code = in_value["code"].asString();// 2.重新拼接用户代码+测试代码用例,形成新代码Json::Value compile_value;compile_value["input"] = in_value["input"].asString();compile_value["code"] = code + "\n" + q.tail; // 用户代码+测试用例compile_value["cpu_limit"] = q.cpu_limit;compile_value["mem_limit"] = q.mem_limit;Json::FastWriter writer;string compile_string = writer.write(compile_value);// 3.选择负载最低的主机// 规则:// 一直选择,直到主机可用,否则,就是全部挂掉while (true){int id = 0;Machine *m = nullptr;if (!_load_balance.SmartChoice(&id, &m)){break;}// 4.然后发起http请求,得到结果m->IncLoad();Client client(m->ip, m->port);LOG(INFO) << "选择主机成功,主机id:" << id << "   详情" << m->ip << ":" << m->port << "当前主机的负载为:" << m->GetLoad() << endl;if (auto resp = client.Post("/compile_and_run", compile_string, "application/json;charset=utf-8")){// 5.将结果赋值给out_jsonif (resp->status == 200){*out_json = resp->body;m->DecLoad();LOG(INFO) << "请求编译运行服务成功...." << endl;break;}m->DecLoad(); // 请求失败(状态码不等于200),负载}else{// 请求失败LOG(ERROR) << "当前请求的主机id:" << id << " 详情:" << m->ip << ":" << m->port << "可能已经离线" << endl;// m->IncLoad();//可以没有必要,因为离线后,我们会将负载清零_load_balance.OfflineMachine(id);_load_balance.ShowMachinees(); // 仅仅是用来调试的}}}};
}

4.oj_server.cc

形成在线OJ的网络服务

#include <iostream>#include <signal.h>#include "../comm/httplib.h"
#include "oj_control.hpp"using namespace httplib;
using namespace ns_control;
using namespace std;static Control *ctrl_ptr = nullptr;void Recovery(int signno)
{ctrl_ptr->Recovery();
}int main()
{//这个信号用于将所有的主机上线signal(SIGQUIT, Recovery);// 用户请求的服务路由功能Server svr;Control ctrl;ctrl_ptr = &ctrl;// 获取所有的题目列表svr.Get("/all_questions", [&ctrl](const Request &req, Response &resp){//resp.set_content("这是所有的题目列表", "text/plain;charset=utf-8");//返回一张含有所有题目的html网页string html;ctrl.AllQuestions(&html);resp.set_content(html, "text/html;charset=utf-8"); });// 用户要根据题目编号,获取题目类容/// questions/100->正则匹配(/d+是正则表达式)// R"()",原始字符串,保持字符串内容的原貌,不用做相关的转义svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp){//resp.set_content("这是指定的一道题"+number, "text/plain;charset=utf-8");string number=req.matches[1];//???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){string number = req.matches[1]; //???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");});// 设置首页的内容svr.set_base_dir("./wwwroot"); // 设置静态资源的根目录svr.listen("0.0.0.0", 8080);return 0;
}

R"()"作用就是保持字符串的原貌,在 C++11 中引入了一种新的字符串字面量语法,称为原始字符串字面量(raw string literal)。原始字符串字面量使用 R"() 和 )" 作为界定符来定义字符串,其中 ) 和 ( 之间的任何字符都作为字符串内容。

5.questions文件版题库

1)1

存储1号题详细描述,头部代码,测试代码

[1]desc
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
[2]header.cpp
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
#include <unordered_map>using namespace std;
class Solution{public:bool isPalindrome(int x){//将你的代码写在下面return true;}
};
[3]tail.cpp

#ifndef COMPILER_ONLINE
#include"header.cpp"
#endifvoid Test1()
{//通过定义临时对象,来完成方法的调用bool ret=Solution().isPalindrome(121);if(ret){cout<<"通过了用例1,测试121通过....OK!"<<endl;}else{cout<<"没有通过用例1,测试121失败....ERROR!"<<endl;}
}
void Test2()
{//通过定义临时对象,来完成方法的调用bool ret=Solution().isPalindrome(-10);if(!ret){cout<<"通过了用例2,测试-10通过....OK!"<<endl;}else{cout<<"没有通过用例2,测试-10失败....ERROR!"<<endl;}
}
int main()
{Test1();Test2();return 0;
}

2)questions.list

用于存储题目详情,包含

  • 题号
  • 难度
  • 题目简单描述
  • 时间限制
  • 空间限制
1 判断回文数 简单  1 30000
2 两数之和  简单   1 30000

6.wwwroot主页

在线OJ系统的网页首页,index.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>这是我的个人oj系统</title><style>/* 保证我们的样式设置可以不受默认影响 *//* "*"是选中所有的标签 */* {/* 消除网页的默认外边距 */margin: 0px;/*消除网页的默认内边距 */padding: 0;}html,body {/* 设置网页的宽高 */width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 给父级标签设置overflow属性,可以防止子级标签溢出 */overflow: hidden;}.container .navbar a {/* 设置a标签为行内块元素,可以设置宽度和高度等属性 */display: inline-block;/* 设置a标签的宽度 a标签是一个行内元素,无法设置宽度*/width: 100px;/* 设置颜色 */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: 0 auto;/* 设置文字居中 */text-align: center;/* 设置上外边距 */margin-top: 200px;}.container .content .font_ {/* 设置标签为块级元素,独占一行,可以设置高度宽度等属性 */display: block;/* 设置每个文字的上外边距 */margin: 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.template_html

1)all_questions.html

题库网络页面

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>在线OJ-题目列表</title><style>/* 保证我们的样式设置可以不受默认影响 *//* "*"是选中所有的标签 */* {/* 消除网页的默认外边距 */margin: 0px;/*消除网页的默认内边距 */padding: 0;}html,body {/* 设置网页的宽高 */width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 给父级标签设置overflow属性,可以防止子级标签溢出 */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: 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: rgb(31, 31, 190);font-size: larger;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>OnlineJudge题目列表</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"><h4>@毕财华</h4></div></div>
</body></html>

2)one_question.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>{{number}}.{{title}}</title><!-- 引入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%;}/* div .ace_editor {width: 100%;} */.container .navbar {width: 100%;height: 50px;background-color: black;/* 给父级标签设置overflow属性,可以防止子级标签溢出 */overflow: hidden;}.container .navbar a {/* 设置a标签为行内块元素,可以设置宽度和高度等属性 */display: inline-block;/* 设置a标签的宽度 a标签是一个行内元素,无法设置宽度*/width: 100px;/* 设置颜色 */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 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: #26bb9c;color: white;/* 给按钮带上圆角 *//* border-radius:1ch; */border: 0px;margin-top: 10px;margin-right: 10px;}.container .part2 button:hover {color: red;}.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"><!-- 1.题目描述2.代码编辑区 --><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"><p>运行结果</p></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();var judge_url = "/judge/" + number;//2.构建json 并通过ajax向后台发送请求$.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) {//成功的结果放在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.stderrvar stdout_lable = $("<pre>", {text: _stdout});var stderr_lable = $("<pre>", {text: _stderr});stdout_lable.appendTo(result_div);stderr_lable.appendTo(result_div)}else {//编译运行错误}}}</script>
</body></html>

8.conf

service_machine.conf中用于存储端口号信息,决定有几个编译服务

127.0.0.1:8081
127.0.0.1:8082
127.0.0.1:8083

这里只有三个编译服务,且都在本地,根据自己的需求进行更改

9.Makefile

oj_server:oj_server.ccg++ -o $@ $^ -std=c++11 -lpthread -lctemplate -ljsoncpp .PHONY:clean
clean:rm -f oj_server

因为使用了jsconcpp库需要在编译的时候进行链接,还有httplib库中使用了多线程需要链接pthread库,使用渲染模块需要链接ctemplate

七、MySQL版本的实现

文件版的在线OJ系统,日常维护难度较高,我们建议使用数据库的版本,由文件版本转化为数据库版本,只需用oj_model_m.hpp要替换掉oj_model.hpp,更改一下oj_view.hpp中的头文件,删除#include"oj_model.hpp",添加#include"oj_model_m.hpp",更改一下oj_control.hpp中的头文件,和oj_view.hpp的操作即可实现MySQL的切换

注意:还需要添加mysql的库和头文件

1.oj_model_m

#pragma once
// mysql版
#include "../comm/log.hpp"
#include <iostream>
#include <string>
#include <unordered_map>
#include <cassert>
#include <vector>
#include <fstream>
#include "../comm/util.hpp"
#include <cstdlib>#include "include/mysql.h" //连接mysql库// 根据题目list文件,加载所有的题目信息到内存中
// model: 主要用来和数据进行交互,对外提供访问数据的接口
namespace ns_model
{using namespace std;using namespace ns_log;using namespace ns_util;struct Question{string number; // 题号string title;  // 题目标题string star;   // 难度string desc;   // 题目描述string header; // 题目预设给用户在线编辑器的代码string tail;   // 题目的测试用例,需要和header拼接,形成完整的代码int cpu_limit; // 时间限制int mem_limit; // 内存限制};const string oj_questions = "oj_questions";const string host = "127.0.0.1";const string user = "oj_client";const string passwd = "123456";const string db = "oj";const int port = 3306;class Model{public:Model(){}bool QueryMysql(const string &sql, vector<Question> *out){// 创建mysql句柄MYSQL *my = mysql_init(nullptr);// 连接数据库if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0)){LOG(FATAL) << " " << mysql_error(my) << endl;return false;}LOG(INFO) << "连接数据库成功" << endl;// 一定要设置连接编码格式mysql_set_character_set(my, "utf8");// 执行sql语句if (0 != mysql_query(my, sql.c_str())){LOG(WARNING) << sql << "execute error!" << endl;return false;}// 提取结果MYSQL_RES *res = mysql_store_result(my);// 分析结果int rows = mysql_num_rows(res);   // 获取行数量int cols = mysql_num_fields(res); // 获取列数量for (int i = 0; i < rows; i++){MYSQL_ROW row = mysql_fetch_row(res);struct Question q;q.number = row[0];q.title = row[1];q.star = row[2];q.desc = row[3];q.header = row[4];q.tail = row[5];q.cpu_limit = atoi(row[6]);q.mem_limit = atoi(row[7]);out->push_back(q);}// 释放结果空间free(res);// 关闭mysql连接mysql_close(my);return true;}bool GetAllQuestions(vector<struct Question> *out){string sql = "select * from ";sql += oj_questions;return QueryMysql(sql, out);}bool GetOneQuestion(const string &number, Question *q){bool ret = false;string sql = "select * from ";sql += oj_questions;sql += " where number=";sql += number;vector<Question> result;if (QueryMysql(sql, &result)){if (result.size() == 1){*q = result[0];ret = true;}}return ret;}~Model(){}};
}

2.include

用于存放mysql所需要使用的头文件,需要在MySQL官网下载mysql安装包

3.lib

用于存放mysql所需要使用的库,需要在MySQL官网下载mysql安装包

4.linux下载mysql服务

linux服务器下载的mysql操作和上面在官网下载mysql不一样,上面下载的是mysql库,实现C/C++语言和sql进行交互,而现在下载的mysql服务是对一个mysql数据库进行操作

1)安装MySQL:使用 apt 包管理器安装MySQL服务器。如果您还没有安装MySQL,请使用以下命令进行安装:
​​​​​​​

sudo apt update
sudo apt install mysql-server

2)启动MySQL服务:安装完成后,MySQL服务通常会自动启动。您可以使用以下命令检查其状态:

sudo systemctl status mysql

3)如果MySQL未运行,您可以使用以下命令启动它:

sudo systemctl start mysql

4)安全设置:MySQL安装后,执行以下命令以提高安全性并设置root用户的密码:
 

sudo mysql_secure_installation

这个命令将引导您完成一系列安全设置步骤,包括设置root密码、删除匿名用户、禁止root远程登录等。按照提示操作即可。

更改root密码:登录后,您可以使用以下命令来更改root用户的密码:
 

ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

将 new_password 替换为您想要设置的新密码。

刷新权限:密码更改后,您需要刷新MySQL的权限以使更改生效:

FLUSH PRIVILEGES;

5)第一步:
登录MySQL:安装完成并设置好密码后,您可以使用以下命令登录到MySQL服务器:

 

sudo mysql -u root -p

然后输入您设置的root密码。(第一次登录的时候没有密码)

第二步:
进入MySQL后,对root的密码进行修改

更改root密码:连接到MySQL后,使用以下命令更改root密码。替换 new_password 为您要设置的新密码:

ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

刷新权限:密码更改后,刷新MySQL的权限:

FLUSH PRIVILEGES;

6)创建新用户(可选):出于安全考虑,建议不要总是使用root用户来管理数据库。您可以创建一个新的MySQL用户,并为其分配适当的权限。例如,您可以使用以下命令创建一个新用户,并允许他从本地主机登录:

CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'newuser'@'localhost' WITH GRANT OPTION;

将 newuser 替换为您要创建的用户名,password 替换为密码。

退出MySQL:完成操作后,您可以键入 exit 或 \q 来退出MySQL命令行界面。

5.在Ubuntu上设置MySQL可以远程登录用户

1)编辑mydqld.cnf文件

sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
找到bind-address字段,将其设置:bind-address = 0.0.0.0

2)创建用户

create user oj_client@'%' identified by '123456';
数据库:oj

3)给用户赋权(只让用户操作oj这个数据库)

grant all on oj.* to oj_client@'%';

4)完成权限更改后,必须刷新MySQL以使更改生效:

FLUSH PRIVILEGES;

6.创建表结构​​​​​​​

CREATE TABLE if not EXISTS `oj_questions`(
`number` int PRIMARY key auto_increment COMMENT '题目编号',
`title` varchar(128) NOT NULL COMMENT '题目的标题',
`star` VARCHAR(8) NOT NULL COMMENT'题目难度',
`desc` text NOT NULL COMMENT'题目的描述',
`header` text NOT NULL COMMENT'预设给用户的代码',
`tail` text NOT NULL COMMENT'测试用例的代码',
`cpu_limit` int default 1 COMMENT '题目的cpu超时时间',
`mem_limit` int default 50000 COMMENT '题目的memery超限空间'
)engine=INNODB default charset=utf8;

 八、综合

1.安装 jsoncpp

sudo apt update
sudo apt install libjsoncpp-dev

2.安装 cpp-httplib

最新的cpp-httplib在使⽤的时候,如果gcc不是特别新的话有可能会有运⾏时错误的问题
建议:cpp-httplib 0.7.15
下载zip安装包,上传到服务器即可
cpp-httplib gitee链接:https://gitee.com/yuanfeng1897/cpp-httplib?
_from=gitee_search
v0.7.15版本链接: https://gitee.com/yuanfeng1897/cpp-httplib/tree/v0.7.15
把httplib.h拷⻉到我们的项⽬中即可,就这么简单

3.安装boost库

sudo apt update
sudo apt install libboost-all-dev

4.安装ctemplate

https://gitee.com/src-oepkgs/ctemplate
$ git clone https:https://gitee.com/src-oepkgs/ctemplate
$ ./autogen.sh
$ ./configure
$ make //编译
$ make install //安装到系统中

5.结果展示

​​​​​​​

  🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸

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

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

相关文章

技术驱动未来,全面揭秘 Sui 的生态发展和布局

在不到一年的时间里&#xff0c;由 Mysten Labs 团队创立的 Layer1 区块链 Sui 迅速崛起&#xff0c;成功跃升至去中心化金融&#xff08;DeFi&#xff09;的前十名。根据 DeFi Llama 的数据&#xff0c;Sui的总锁定价值&#xff08;TVL&#xff09;在短短四个月内增长超过 100…

深度学习之基于Tensorflow+Flask框架Web手写数字识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手写数字识别是深度学习领域中的一个经典问题&#xff0c;也是计算机视觉领域的重要应用之一。…

BFT Robotics - 您的智能自动化伙伴

“买机器人&#xff0c;上BFT” 自动化和机器人技术是推动现代工业发展的重要力量。BFT Robotics以其创新的产品系列和定制化解决方案&#xff0c;为企业提供了一条通往高效、智能生产环境的道路。通过采用BFT Robotics的产品和服务&#xff0c;企业不仅能够提高生产效率&#…

iView实现表格拖拽列宽度,列显示顺序及显示字段

需要实现表格列宽度调整,选择展示表格字段,以及显示顺序,先看效果,每次变动后保存到本地缓存中,也可以与后台配合保存到数据库,实现用户自定义表格. 1.安装vuedraggable实现拖拽 npm i vuedraggable2.新建组件 FilterColumns.vue 我这里默认把操作列放到最后一个并且不允许…

机器学习(五) -- 监督学习(3) -- 决策树

系列文章目录及链接 上篇&#xff1a;机器学习&#xff08;五&#xff09; -- 监督学习&#xff08;2&#xff09; -- 朴素贝叶斯 下篇&#xff1a;机器学习&#xff08;五&#xff09; -- 监督学习&#xff08;4&#xff09; -- 集成学习方法-随机森林 前言 tips&#xff1a…

学习Uni-app开发小程序Day23

今天学习了将上一章的所有核算的js&#xff0c;抽离出去&#xff0c;让在其他地方可以直接调用&#xff0c;然后和适配抖音的办法&#xff0c;封装网络请求&#xff1b; 抽离公共方法 如何将公共方法抽离&#xff1f; 1、在根目录创建一个目录&#xff0c;一般起名是:utils 2…

物联网网关在电梯按需维保方案中起到什么作用?梯联网网关

为减少电梯故障和预防电梯事故&#xff0c;保障人身和财产安全&#xff0c;基于物联网技术的电梯按需维保已在全国多地陆续推行&#xff0c;做到了电梯安全隐患预测式排查&#xff0c;处理问题更具科学性、针对性和精准性&#xff0c;有效提升了电梯运行的安全性。那么&#xf…

深度学习之基于YoloV5入侵检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显。入侵检测系统&#xff08;IDS&#xff0…

IC设计运营管理ERP适合中小型芯片公司

在数字化、智能化的今天&#xff0c;企业资源计划(ERP)系统已成为企业管理的重要工具。特别是在中小型芯片公司中&#xff0c;IC设计运营管理ERP更是发挥着举足轻重的作用。 首先&#xff0c;ERP系统能够实现对企业内各种资源的集成管理&#xff0c;包括资金、人力资源、设备和…

我的第一个JAVA程序IDEA版

目录 第一步 新建一个空项目第二步 新建模块第三步 新建包第四步 新建类第五步 新建main方法 第一步 新建一个空项目 第二步 新建模块 第三步 新建包 第四步 新建类 然后在包文件夹下新建类 第五步 新建main方法

线程池(C++)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 线程池 实现线程类 #pragma once#include <pthread.h> #include <iostream> #include <vector> #include <string> #include <cstdlib> #include <cstring> #include <functional&…

Python深度学习基于Tensorflow(12)实战生成式模型

文章目录 Deep Dream风格迁移参考资料 Deep Dream DeepDream 是一项将神经网络学习模式予以可视化展现的实验。与孩子们观察云朵并尝试解释随机形状相类似&#xff0c;DeepDream 会过度解释并增强其在图像中看到的图案。 DeepDream为了说明CNN学习到的各特征的意义&#xff0c…

「51媒体」线下活动媒体同步直播,云分发,分流直播

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 线下活动除了邀请嘉宾&#xff0c;邀请媒体&#xff0c;邀请行业大咖KOL&#xff0c;来为活动站台&#xff0c;背书外&#xff0c;我们也可以将线下的活动同步在线上进行直播&#xff0c…

react【框架原理详解】JSX 的本质、SyntheticEvent 合成事件机制、组件渲染过程、组件更新过程

JSX 的本质 JSX 代码本身并不是 HTML&#xff0c;也不是 Javascript&#xff0c;在渲染页面前&#xff0c;需先通过解析工具&#xff08;如babel&#xff09;解析之后才能在浏览器中运行。 babel官网可查看 JSX 解析后的效果 更早之前&#xff0c;Babel 会把 JSX 转译成一个 R…

AI大模型探索之路-实战篇4:DB-GPT数据应用开发框架调研实践

目录 前言一、DB-GPT总体概述二、DB-GPT关键特性1、私域问答&数据处理&RAG2、多数据源&GBI3、多模型管理4、自动化微调5、Data-Driven Multi-Agents&Plugins6、隐私安全 三、服务器资源准备1、创建实例2、打开jupyterLab 四、DB-GPT启动1、激活 conda 环境2、切…

区块链fisco联盟链搭建(二)搭建多群组联盟链

本文章只讲搭建的命令方法 以单机、四机构、三群组、八节点的星形组网拓扑为例 第一步创建并进入工作目录&#xff08;继续以fisco为例&#xff09; mkdir /fisco cd /fisco 获取搭链脚本上一篇文章区块链fisco联盟链搭建 (一)搭建单群组四节点联盟链中有 第二步生成多群组…

抖音小店没有流量不出单?归根到底,就是转化率不行!

哈喽~我是电商月月 新手做抖音小店&#xff0c;最忧愁的就是&#xff1a;店铺不出单怎么办&#xff1f; 商家通常会把没有销量的原因&#xff0c;都推向于“店铺没有流量” 但在抖音&#xff0c;这个日活量高达9亿的平台来说&#xff0c;任何商铺最不缺的应该就是流量了 但…

dll文件是什么?电脑丢失某个dll文件有什么解决办法

Dll文件是什么&#xff1f;这个文件在电脑中是什么样的地位&#xff1f;如果电脑提示丢失了某个dll文件那么有什么办的解决这个问题呢&#xff1f;如何将丢失的dll文件进行修复呢&#xff1f;今天这篇文章将按就来教大家几种修复丢失dll文件问题的方法。 DLL 文件&#xff0c;全…

[Redis]基本全局命令

Redis存储方式介绍 在 Redis 中数据是以键值对的凡事存储的&#xff0c;键&#xff08;Key&#xff09;和值&#xff08;Value&#xff09;是基本的数据存储单元。以下是对 Redis 键值对的详细讲解&#xff1a; 键&#xff08;Key&#xff09;&#xff1a; 类型&#xff1a;…

全局配置路径无法识别的解决——后端

在全局配置路径reggie.path的时候&#xff0c;无法正常启动SpringBoot项目 Value("${reggie.path}")private String basePath; 查看application.yml的配置情况: 发现path没有起作用&#xff0c;推测是格式问题&#xff0c;冒号后面空格后即可