Load-Balanced-Online-OJ(负载均衡式在线OJ)

负载均衡式在线OJ

  • 前言
  • 1. 项目介绍
  • 2. 所用技术与环境
    • 所用技术栈
    • 开发环境
  • 3. 项目宏观结构
    • 3.1 项目核心模块
    • 3.2 项目的宏观结构
  • 4. comm公共模块
    • 4.1 日志(log.hpp )
      • 4.1.1 日志主要内容
      • 4.1.2 日志使用方式
      • 4.1.2 日志代码
    • 4.2 工具(util.hpp)
      • 工具类代码
  • 5. compiler_server设计
    • 第一个功能:编译功能(compiler.hpp)
    • 第二个功能:运行功能(runner.hpp)
    • 第三个功能:编译并运行功能(compile_run.hpp)
    • 第四个功能: 把编译并运行功能,形成网络服务(compile_server.cc)
  • 6. 基于MVC结构的OJ服务设计
      • MVC结构是什么?
    • 第一个功能:用户请求的服务路由功能(oj_server.cc)
    • 第二个功能:model功能,提供对数据的操作(oj_model.hpp)
    • 第三个功能:control,逻辑控制模块(oj_control.hpp)
    • 第四个功能:网页渲染功能(oj_view.hpp)
  • 7. 文件版题目设计
    • (1) 题目要求
    • (2) 两批文件构成
  • 8. 前端页面设计
    • (1) 首页(index.html)
    • (2) 所有题目的列表(all_questions.html)
    • (3) 指定题目的编写代码的页面+代码提交(one_questions.html)

前言

项目源代码:负载均衡式在线OJ源代码

手机端如果打不开,可以复制下面链接到浏览器中访问
https://gitee.com/hou-shanlin/linux/tree/master/load-balanced-online-OJ

1. 项目介绍

  • 本项目主要实现的是类似于 leetcode 的题目列表 + 在线编程功能。
  • 该项目采用负载均衡算法 (轮询检测) 使得多个服务器协同处理大量的提交请求和编译请求。
  • 可支持多用户在网站中同时选择题目、答题、提交代码、代码的编译与运行,以及查看题目的通过情况

2. 所用技术与环境

所用技术栈

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

开发环境

  • Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-113-generic x86_64)云服务器
  • VS Code

3. 项目宏观结构

3.1 项目核心模块

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

  1. comm : 公共模块(主要包含:httplib<网络服务>、log<日志信息>、util<项目中都需要使用到的工具类的集合>)

  2. compile_server : 编译与运行模块(主要包含:编译服务、运行服务、编译和运行服务)

  3. oj_server : 获取题目列表,查看题目编写题目界面,负载均衡

3.2 项目的宏观结构

在这里插入图片描述

  • B/S模式(Browser/Server):将应用程序分为两部分:客户端和服务器端。客户端通常是Web浏览器,用户通过浏览器与服务器进行交互。
  • compile_server 和 oj_server 会采用网络套接字的方式进行通信,这样就能将编译模块部署在服务器后端的多台主机上。
  • 而 oj_server 只有一台,这样子就会负载均衡的选择后台的编译服务。

4. comm公共模块

该模块主要为所有模块提供文件操作、字符串处理、网络请求、打印日志等公共功能

其中的 httplib.h 文件是第三方开源网络库 cpp-httplib 所提供的,因此之后不展示其代码。

在这里插入图片描述

文件名功能
httplib.h提供网络服务
log.hpp提供日志打印功能
util.hpp提供各种工具类

4.1 日志(log.hpp )

该模块主要是提供打印日志的功能,方便后续代码调试。

4.1.1 日志主要内容

  • 日志等级
  • 打印该日志的文件名
  • 对应日志所在的行号
  • 添加对应日志的时间
  • 日志信息

4.1.2 日志使用方式

LOG(日志等级) << "message" << "\n";	// 如: LOG(INFO) << "这是一条日志" << "\n";

4.1.2 日志代码

#pragma once
#include <iostream>
#include "util.hpp"
namespace ns_log
{using namespace ns_util;// 日志等级enum{INFO,DEBUG,WARNING,ERROR,FATAL};// level:等级inline std::ostream &Log(const std::string &level, const std::string &file_name, int line){// 添加日志等级std::string message = "[";message += level;message += "]";// 添加报错文件名称message += "[";message += file_name;message += "]";// 添加报错行message += "[";message += std::to_string(line);message += "]";// 日志时间message += "[";message += TimeUtil::TimeStampExLocalTime();message += "]";// cout内部包含缓冲区std::cout << message; // 刷新return std::cout;}// LOG(INFO)<<"message(信息)"#define LOG(level) Log(#level, __FILE__, __LINE__)
//编译时,__FILE__ 会被替换为包含当前代码的文件名,__LINE__会被替换为当前所在行数}

4.2 工具(util.hpp)

该模块主要提供的工具类及其说明如下:

类名说明提供的功能
TimeUtil时间工具获取当前时间
PathUtil路径工具根据文件名和路径构建 .cpp 后缀的文件完整名
根据文件名和路径构建 .exe 后缀的完整文件名
根据文件名和路径构建 .compile_error 后缀的完整文件名
根据文件名和路径构建 .stdin 后缀的完整文件名
根据文件名和路径构建 .stdout 后缀的完整文件名
根据文件名和路径构建 .stderr 后缀的完整文件名
FileUtil文件工具判断指定文件是否存在
用 毫秒级时间戳 + 原子性递增的唯一值 形成一个具有唯一性的文件名
将用户代码写到唯一的目标文件中, 形成临时的 .cpp 源文件
读取目标文件中的所有内容
StringUtil字符串工具根据指定的分隔符切割字符串,并将切分出的子串用数组存储返回

工具类代码

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <atomic> //原子的
#include <fstream>
#include <vector>
#include <boost/algorithm/string.hpp>namespace ns_util
{// 时间功能class TimeUtil{public:static std::string GetTimeStamp(){struct timeval _time;gettimeofday(&_time, nullptr);return std::to_string(_time.tv_sec);}// 毫秒static std::string GetTimeMs(){struct timeval _time;gettimeofday(&_time, nullptr);return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);}static std::string TimeStampExLocalTime(){time_t currtime = time(nullptr);struct tm *curr = localtime(&currtime);char time_buffer[128];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",curr->tm_year + 1900, curr->tm_mon + 1, curr->tm_mday,curr->tm_hour, curr->tm_min, curr->tm_sec);return time_buffer;}};const std::string temp_path = "./temp/";// 路径操作class PathUtil{public:// 添加后缀功能static std::string AddSuffix(const std::string &file_name, const std::string &suffix){std::string path_anme = temp_path;path_anme += file_name;path_anme += suffix;return path_anme;}// 编译时需要的临时文件//  构建源文件路径+后缀的完整文件名//  hsl -> ./temp/hsl.cppstatic std::string Src(const std::string &file_name){return AddSuffix(file_name, ".cpp");}// 构建可执行程序完整路径+后缀名static std::string Exe(const std::string &file_name){return AddSuffix(file_name, ".exe");}// 构建该程序对应的标准错误完整的路径+后缀名static std::string Stderr(const std::string &file_name){return AddSuffix(file_name, ".stderr");}// 运行时需要的临时文件//  构建编译时报错的文件路径static std::string CompilerError(const std::string &file_name){return AddSuffix(file_name, ".compilererror");}static std::string Stdin(const std::string &file_name){return AddSuffix(file_name, ".stdin");}static std::string Stdout(const std::string &file_name){return AddSuffix(file_name, ".stdout");}};// 文件操作功能class FileUtil{public:static bool IsFileExists(const std::string &path_name){struct stat st;if (stat(path_name.c_str(), &st) == 0){// 获取文件属性成功,表示已经存在return true;}return false;}static std::string UniqFileName(){std::atomic_uint id(0);id++;// 根据毫秒级时间戳+原子性递增唯一性:来保证唯一性std::string ms = TimeUtil::GetTimeMs();std::string uniq_id = std::to_string(id);return ms + "_" + uniq_id;}// content:内容static bool WriteFile(const std::string &target, const std::string &content){std::ofstream out(target);if (!out.is_open()){return false;}out.write(content.c_str(), content.size());out.close();return true;}// keep:是否保留"\n"static bool ReadFile(const std::string &target, std::string *content, bool keep = false /*可能需要其他参数*/){(*content).clear();std::ifstream in(target);if (!in.is_open()){return false;}std::string line;// getline不保留分割符("\n"),但是有些时候需要保留// getline内部重载了强制类型转换while (std::getline(in, line)){(*content) += line;(*content) += keep ? "\n" : "";}in.close();return true;}};class StringUtil{public:/************************************************************************ str: 传入的待切分的字符串* target: 输出型参数,传回分割完毕的结果* 分割标识符(空格,斜杠等等...)************************************************************************/static void SplitString(const std::string &str, std::vector<std::string> *target, std::string sep){boost::split(*target, str, boost::is_any_of(sep), boost::algorithm::token_compress_on);}};}

5. compiler_server设计

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

第一个功能:编译功能(compiler.hpp)

  • 当用户提交代码的时候,需要为提交的代码提供编译服务,可以将提交的代码打包,使用进程替换的方式进行 g++ 编译。
  • 为了防止远端代码是程序错误的代码或者恶意代码,需要 fork 出子进程去执行进程替换对用户提交的代码执行编译功能。
  • 编译服务只关心编译有没有出错,如果出错,则需要知道是什么原因导致的错误。
    • 需要形成一个临时文件,保存编译出错的结果。
#pragma once
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>#include "../comm/log.hpp"
#include "../comm/util.hpp"// 只负责代码编译
namespace ns_compiler
{using namespace ns_util;using namespace ns_log;// 引入路径拼接功能class Compiler{public:Compiler(){}// 返回值:编译成功:true,编译失败:false// file_name: hsl// hsl ->./temp/hsl.cpp// hsl ->./temp/hsl.exe// hsl ->./temp/hsl.stderrstatic bool Compile(const std::string &file_name){pid_t res = fork();if (res < 0){LOG(ERROR) << "内部错误,创建子进程失败" << "\n";return false;}else if (res == 0) // 子进程{umask(0);// 创建一个stderr文件,将编译错误信息重定向到该文件中int _stderr = open(PathUtil::CompilerError(file_name).c_str(), O_CREAT | O_WRONLY, 0644);if (_stderr < 0){LOG(WARNING) << "没有成功形成compilererror文件" << "\n";exit(1);}// 重定向错误到_stderrdup2(_stderr, 2);// 子进程:调用编译器,完成对代码的编译工作// execlp :用于替换当前进程为一个新程序(不会创建子进程,所以要配合fork使用)// g++ -o target(目标) src(源文件) -std=c++11execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(), PathUtil::Src(file_name).c_str(), "-std=c++11","-D","COMPILER_ONLONE", nullptr);LOG(ERROR) << "启动g++编译器失败,可能是参数错误" << "\n";exit(2);}else{// 父进程waitpid(res, nullptr, 0);// 编译是否成功: 判断有没有生成可执行程序if (FileUtil::IsFileExists(PathUtil::Exe(file_name))){LOG(INFO) << PathUtil::Src(file_name) << " 编译成功" << "\n";return true;}}LOG(ERROR) << "程序编译失败,未形成可执行程序" << "\n";return false;}~Compiler(){}};}

第二个功能:运行功能(runner.hpp)

  • 编译完成也要能将代码运行起来才能知道代码的结果是否正确,因此还需要体提供运行服务。运行服务也是需要 fork 出子进程执行运行服务。
  • 运行服务需要有的临时文件分别有 4 个:
    • .exe 可执行程序,没有这个代码可没法运行,在编译时已经创建好了该文件,直接用就行。
    • .stdin 标准输入文件,用来重定向保存用户的输入。
    • .stdout 标准输出文件,只用来保存程序运行完成后的结果。
    • .stderr 标准错误文件,如果用户代码在运行时发生错误了,需要用该文件保存运行时的错误信息。
  • 运行服务只关心程序是否正常运行完成,有没有收到信号 (使用进程等待的方式查看) 即可。运行结果是否正确由测试用例决定。
  • 同时还需要限制用户代码所占用的资源,不能让用户无限制的占用 CPU 资源以及内存资源。这就是平时刷题时最常见的资源限制。
    • 可以借助 setrlimit() 函数去限制用户代码所占用的时空资源。
#pragma once#include <iostream>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>#include "../comm/log.hpp"
#include "../comm/util.hpp"namespace ns_runner
{using namespace ns_log;using namespace ns_util;class Runner{public:Runner() {}~Runner() {}public://提供设备进程占用资源大小的接口(限制一下使用的资源,防止死循环,内存消耗过大....)static void SetProcLimit(int _cpu_limit,int _mem_limit){//设置CPU时长struct rlimit cpu_limit;cpu_limit.rlim_cur=_cpu_limit;cpu_limit.rlim_max=RLIM_INFINITY;setrlimit(RLIMIT_CPU,&cpu_limit);//设置内存大小struct rlimit mem_limit;mem_limit.rlim_cur=_mem_limit*1024;mem_limit.rlim_max=RLIM_INFINITY;setrlimit(RLIMIT_AS,&mem_limit);}// 指明文件名即可/*************************************************************** 返回值 > 0:程序异常了,退出时收到了信号,返回值就是对应的信号编号* 返回值 == 0:正常运行完毕,结果保存到了对应的临时文件* 返回值 < 0:内部错误** cpu_limit: 该程序运行时,可以使用的最大CPU上限(运行时间【秒】)* mem_limit: 该程序运行时,可以使用的最大内存上限【KB】***************************************************************/static int Run(const std::string &file_name, int cpu_limit, int mem_limit){/*************************************************************** 运行有三种情况:* 1.运行成功,通过* 2.运行成功,不通过* 3.编译出错,运行不了* 我们只管编译运行成功,具体代码有没有通过题目要求,是另外模块的事情* 需要创建stdin,stdout,stderr三个文件来保存运行信息***************************************************************/// execute:执行std::string execute = PathUtil::Exe(file_name);std::string _stdin = PathUtil::Stdin(file_name);std::string _stdout = PathUtil::Stdout(file_name);std::string _stderr = PathUtil::Stderr(file_name);umask(0);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 (_stderr_fd < 0 || _stdin_fd < 0 || _stdout_fd < 0){LOG(ERROR) << "运行时打开标准文件失败" << std::endl;return -1; // 文件打开失败}pid_t pid = fork();if (pid < 0){close(_stderr_fd);close(_stdout_fd);close(_stdin_fd);return -2; // 创建子进程失败}else if (pid == 0) // child{dup2(_stdin_fd, 0);dup2(_stdout_fd, 1);dup2(_stderr_fd, 2);SetProcLimit(cpu_limit, mem_limit);execl(execute.c_str() /*我要执行谁*/, execute.c_str() /*怎么执行*/, nullptr);exit(1);}else{close(_stderr_fd);close(_stdout_fd);close(_stdin_fd);int status = 0;waitpid(pid, &status, 0);LOG(INFO) << "运行完毕,退出码: info: " << (status & 0x7F) << std::endl;return status & 0x7F;}}};
}

第三个功能:编译并运行功能(compile_run.hpp)

该模块需要整合编译和运行功能、适配用户请求,定制通信协议字段并正确的调用 compile 和 run 模块。
在这里插入图片描述

#pragma once#include <jsoncpp/json/json.h>
#include <signal.h>
#include <unistd.h>#include "runner.hpp"
#include "compiler.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"namespace ns_compile_and_run
{using namespace ns_compiler;using namespace ns_runner;using namespace ns_log;using namespace ns_util;// 考虑的都是整体class CompileAndRun{public:// 清理临时文件(unlink函数)static void RemoveTempFile(const std::string &file_name){// 清理文件个数不确定,但是有哪些文件我们清楚std::string _src = PathUtil::Src(file_name);if (FileUtil::IsFileExists(_src))unlink(_src.c_str());std::string _execute = PathUtil::Exe(file_name);if (FileUtil::IsFileExists(_execute))unlink(_execute.c_str());std::string _compiler_error = PathUtil::CompilerError(file_name);if (FileUtil::IsFileExists(_compiler_error))unlink(_compiler_error.c_str());std::string _stdin = PathUtil::Stdin(file_name);if (FileUtil::IsFileExists(_stdin))unlink(_stdin.c_str());std::string _stdout = PathUtil::Stdout(file_name);if (FileUtil::IsFileExists(_stdout))unlink(_stdout.c_str());std::string _stderr = PathUtil::Stderr(file_name);if (FileUtil::IsFileExists(_stderr))unlink(_stderr.c_str());}/********************************************************************** code > 0 : 进程收到了信号导异常崩溃* code < 0 : 整个过程运行报错(代码为空,编译报错等)* code==0 : 整个过程运行完成**********************************************************************/static std::string CodeToDesc(int code, const std::string &file_name){std::string desc;switch (code){case 0:desc = "编译运行成功";break;case -1:desc = "提交的代码为空";break;case -2:desc = "发生未知错误";break;case -3:// desc = "编译时发生错误";FileUtil::ReadFile(PathUtil::CompilerError(file_name), &desc, true);break;case SIGABRT: // 6desc = "内存超过范围";break;case SIGXCPU: // 24desc = "CPU超时";break;case SIGFPE: // 8desc = "浮点数溢出";break;default:desc = "未知:" + std::to_string(code);break;}return desc;}/***************************************************************** 输入:* code:用户提交的代码* input:用户提交的代码对应的输入,不做处理* cpu_limit:时间要求* mem_limit:空间要求(内存)** 输出:* 必填* status:状态码* reason:请求结果(出错原因)** 选填* stdout:我的程序运行完的结果* stderr:我的程序运行完的错误结果* 参数:* in_json: {"code": "#include <iostream>...","input": "","cpu_limit": "1","mem_limit":"10240"}* out_json:{"status":"0","reason":"","stdout":"","stderr":""}****************************************************************/static void Start(const std::string &in_json, std::string *out_json){Json::Value in_value;Json::Reader reader;reader.parse(in_json, in_value); // 反序列化(差错处理)std::string code = in_value["code"].asString();std::string input = in_value["input"].asString();int cpu_limit = in_value["cpu_limit"].asInt();int mem_limit = in_value["mem_limit"].asInt();int status_code = 0;Json::Value out_value;int run_result = 0;std::string file_name;if (code.size() == 0){// 代码为空status_code = -1;goto END;}//编译服务可能随时被多人请求,必须保证传递上去的code,形成源文件名称的时候,要具有唯一性,不然多个用户之间会相互影响// 根据毫秒级时间戳+原子性递增唯一性:来保证唯一性file_name = FileUtil::UniqFileName(); // 唯一的文件名(只有文件ming)// 形成临时源文件(.cpp)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);if (status_code == 0){// 整个过程全部完成std::string _stdout;FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);out_value["stdout"] = _stdout;std::string _stderr;FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr, true);out_value["stderr"] = _stderr;}// out_value["emitUTF8"] = true;Json::StyledWriter writer;*out_json = writer.write(out_value); // 序列化// 清理临时文件RemoveTempFile(file_name);}};
}

第四个功能: 把编译并运行功能,形成网络服务(compile_server.cc)

#include "compile_run.hpp"
#include "../comm/httplib.h"using namespace ns_compile_and_run;
using namespace httplib;// 编译服务可能随时被多人请求,必须保证传递上来的code,形成源文件名称的时候,
// 要具有唯一性,不然多个用户之间会相互影响void Usage(std::string proc)
{std::cerr << "Usage: " << "\n\t" << proc << std::endl;
}//./compile_server port
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return 1;}Server svr; // 创建一个 Server 对象 svr// 注册 GET 请求处理器(测试)// svr.Get("/hello",[](const Request &req, Response &resp){//     // 当收到 GET 请求到 "/hello" 时,设置响应内容//     resp.set_content("hello http!!你好","text/plain;charset=utf-8");// });// 注册 POST 请求处理器(只是设置好回调函数,当有请求来的时候才会执行)svr.Post("/compile_and_run", [](const Request &req, Response &resp){// 用户请求的服务正文是我们想要的 JSON 字符串std::string in_json = req.body; // 获取请求体中的 JSON 字符串(表示请求的主体内容)std::string out_json; // 用于存储响应的 JSON 字符串// 如果输入 JSON 字符串不为空if (!in_json.empty()){// 调用 CompileAndRun::Start 函数进行编译和运行CompileAndRun::Start(in_json, &out_json);// 设置响应内容为输出的 JSON 字符串resp.set_content(out_json, "application/json;charset=utf-8");} });// 启动 HTTP 服务,监听所有地址的argv[1]端口svr.listen("0.0.0.0", atoi(argv[1])); // 启动 HTTP 服务// listen// 用于启动服务器并开始监听指定地址和端口的客户端请求。// 它使得服务器能够接收和处理来自客户端的连接。return 0; // 返回 0,表示程序正常结束
}

6. 基于MVC结构的OJ服务设计

本质:建立一个小型网站

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

MVC结构是什么?

MVC(Model-View-Controller)结构是一种广泛使用的软件设计模式,主要用于构建用户界面。它将应用程序分为三个主要组成部分,以实现关注点分离,增强可维护性和可扩展性

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

第一个功能:用户请求的服务路由功能(oj_server.cc)

#include <iostream>
#include <signal.h>
#include <ctemplate/template.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 + C全部上线ctrl_ptr->RecoveryMachine();
}int main()
{ // 1.用户请求的服务器路由功能signal(SIGQUIT, Recovery);//用户请求的服务路由功能Server svr;Control ctrl;ctrl_ptr = &ctrl;// 获取所有的题目列表(返回一张包含所有题目的html网页)svr.Get("/all_questions", [&ctrl](const Request &req, Response &resp){std::string html;ctrl.AllQuestions(&html);resp.set_content(html, "text/html;charset=utf-8");});// question:问题,// 2.用户根据题目编号,获取题目内容(返回包含题目具体内容的网页)// questions/100(100是题目编号)// R"()" :原始字符串,保持字符串的原貌,不用做相关的转义svr.Get(R"(/questions/(\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"); });// 3.用户提交代码,使用我们的判题功能(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"); });// 设置默认根目录svr.set_base_dir("./wwwroot");svr.listen("0.0.0.0", 8888);return 0;
}

第二个功能:model功能,提供对数据的操作(oj_model.hpp)

#pragma once#include "../comm/log.hpp"
#include "../comm/log.hpp"#include <iostream>
#include <string>
#include <unordered_map>
#include <cassert>
#include <vector>
#include <stdlib.h>// 根据list文件,加载所有的题目信息到内存中
// 和数据交互的模块,对外提供数据访问的接口,⽐如,对题库进⾏增删改查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拼接,形成完整代码};const string question_list = "./questions/questions.list";const string questions_path = "./questions/";class Model{public:Model(){assert(LoadQuestionList(question_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 = questions_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();return true;}// 获取全部题目bool GetAllQuestions(vector<Question> *out){if (questions.size() == 0){LOG(ERROR) << "获取题库失败" << "\n";return false;}for (auto &q : questions){out->push_back(q.second);}return true;}// 获取指定一个题目bool GetOneQuestions(const 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(){}private:// 题号 : 题目细节unordered_map<string, Question> questions;};
}

第三个功能:control,逻辑控制模块(oj_control.hpp)

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <cassert>
#include <fstream>
#include <jsoncpp/json/json.h>
#include <algorithm>#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"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;// 提供服务的主机【Machine(机器)】class Machine{public:Machine() : ip(""), port(0), load(0), mtx(nullptr){}// 提升主机负载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;}~Machine(){}public:std::string ip;  // 编译服务的ipint port;        // 编译服务的portuint64_t load;   // 编译服务的负载(加载)std::mutex *mtx; // mutex禁止拷贝};// 【service(服务)】const std::string service_machine = "./conf/service_machine.conf";// 【LoadBlancer(负载均衡)】//  负载均衡模块class LoadBlancer{public:LoadBlancer(){assert(LoadConf(service_machine));LOG(INFO) << "加载" << service_machine << "成功" << "\n";}// 加载配置bool LoadConf(const std::string &machine_list){std::ifstream in(machine_list);if (!in.is_open()){LOG(FATAL) << "加载: " << machine_list << "失败" << "\n";return false;}std::string line;while (std::getline(in, line)){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 mutex();online.push_back(machines.size());machines.push_back(m);}in.close();return true;}// 智能选择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 testvoid ShowMachines(){mtx.lock();cout << "当前在线主机列表:";for (auto &iter : online){cout << iter << " ";}cout << endl;cout << "当前离线主机列表:";for (auto &iter : offline){cout << iter << " ";}cout << endl;mtx.unlock();}~LoadBlancer(){}private:// 可以给我们提供服务的所有主机// 每台主机都有自己的下标。充当当前主机的idstd::vector<Machine> machines;// 所有在线的主机std::vector<int> online;// 所有离线的主机std::vector<int> offline;// 给保证LoadBlance它的数据安全std::mutex mtx;};// 业务核心逻辑的控制器class Control{public:Control(){}~Control(){}public:// 根据题目数据构建网页void RecoveryMachine(){_load_blance.OnlineMachine();}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{*html = "获取题目失败, 形成题目列表失败";ret = false;}return ret;}// 根据题目内容构建网页bool Question(const string &number, string *html){bool ret = true;struct Question q;if (_model.GetOneQuestions(number, &q)){// 获取题目信息成功,将指定的题目数据构建成网页_view.OneExpandHtml(q, html);}else{*html = "指定题目不存在";ret = false;}return ret;}// 把判题内容构建成网页// id : 100// code: include....// input: "..."void Judge(const std::string &number, const std::string in_json, std::string *out_json){// 0. 根据题目编号直接拿到对应的题目细节struct Question q;_model.GetOneQuestions(number, &q);// 1. in_json进行反序列化,得到题目id,得到用户提交的题目代码,输出Json::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;std::string compile_string = writer.write(compile_value);// 3. 选择负载最低的主机(差错处理)// 规则:一直选择,直到主机可用,否则就是全部挂掉while (true){int id = 0;Machine *m = nullptr;if (!_load_blance.SmartChoice(&id, &m)){break;}// 4. 然后发起http请求,得到结果Client cli(m->ip, m->port);m->IncLoad();LOG(INFO) << "选择主机成功,主机id: " << id << ",详情: " << m->ip << ":" << m->port << "," << "当前主机的负载是:" << m->Load() << "\n";if (auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8")){// 5. 将结果赋值给out_jsonif (res->status == 200){*out_json = res->body;m->DecLoad();LOG(INFO) << "请求编译和运行服务成功" << "\n";break;}m->DecLoad();}else{// 请求失败LOG(ERROR) << "请求主机失败, 主机id:" << id << "详情: " << m->ip << ":" << m->port << "可能已经离线" << "\n";_load_blance.OfflineMachine(id);_load_blance.ShowMachines(); // 调试}}}private:Model _model;             // 提供后台数据View _view;               // 提供html网页渲染功能LoadBlancer _load_blance; // 核心负载均衡器};
}

第四个功能:网页渲染功能(oj_view.hpp)

#pragma once
#include <iostream>
#include <string>
#include <ctemplate/template.h>#include "oj_model.hpp"namespace ns_view
{using namespace std;using namespace ns_model;const std::string template_path = "./template_html/";class View{public:View() {}//构建全部题目显示的网页void AllExpandHtml(const vector<struct Question>& questions, string *html){//题目编号,标题,难度//1.形成路径std::string src_html  = template_path +"all_questions.html";//2.形成数字典ctemplate::TemplateDictionary root("all_questions");for(const auto& q:questions){ctemplate::TemplateDictionary *sub =root.AddSectionDictionary("question_list");sub->SetValue("number",q.number);sub->SetValue("title",q.title);sub->SetValue("star",q.star);}//3.获取被渲染的网页ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);//4.完成渲染功能(开始渲染)tpl->Expand(html,&root);}//构建指定题目内容的网页void OneExpandHtml(const struct Question &q,string *html){//1.形成路径std::string src_html  = template_path +"one_questions.html";//2.形成数字典ctemplate::TemplateDictionary root("one_questions");root.SetValue("number",q.number);root.SetValue("title",q.title);root.SetValue("star",q.star);root.SetValue("desc",q.desc);root.SetValue("pre_code",q.header);//3.获取被渲染的网页ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);//4.完成渲染功能(开始渲染)tpl->Expand(html,&root);}~View() {}private:};
}

7. 文件版题目设计

(1) 题目要求

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

(2) 两批文件构成

  • 第一个:questions.list : 题目列表(不需要题目的内容)
  • 第二个:题目的描述,题目的预设置代码(header.cpp), 测试用例代码(tail.cpp)

这两个内容是通过题目的编号,产生关联的

当用户提交自己代码的时候,看到的代码是这样的(header.cpp):

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>using namespace std;class Solution
{
public:int search(vector<int>& nums, int target) {//将你的代码写在此处}
};

而OJ不只是把header.cpp交给compile_and_run,而是会把测试用例文件(tail.cpp)和header.cpp整合到一起打包给compile_and_run

tail.cpp:

#ifndef COMPILER_ONLONE
#include "header.cpp"
#endifvoid Test1()
{vector<int> nums={-1,0,3,5,9,12};int target =9;int ret = Solution().search(nums,target);if(ret==4){std::cout<<"通过测试用例1, 测试(nums = [-1,0,3,5,9,12], target = 9)通过 OK!"<<std::endl;}else{std::cout<<"未通过测试用例1, 测试的值为: (nums = [-1,0,3,5,9,12], target = 9)"<<std::endl;}
}void Test2()
{vector<int> nums={-1,0,3,5,9,12};int target =2;int ret = Solution().search(nums,target);if(ret==-1){std::cout<<"通过测试用例2, 测试(nums = [-1,0,3,5,9,12], target = 2)通过 OK!"<<std::endl;}else{std::cout<<"未通过测试用例2, 测试的值为: (nums = [-1,0,3,5,9,12], target = 2)"<<std::endl;}
}int main()
{Test1();Test2();
}

最终提交给后台编译运行服务的代码是:

//header.cpp
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>using namespace std;class Solution
{
public:int search(vector<int>& nums, int target) {//用户(你)提交的代码在这儿}
};//tail.cpp
#ifndef COMPILER_ONLONE
#include "header.cpp"
#endifvoid Test1()
{vector<int> nums={-1,0,3,5,9,12};int target =9;int ret = Solution().search(nums,target);if(ret==4){std::cout<<"通过测试用例1, 测试(nums = [-1,0,3,5,9,12], target = 9)通过 OK!"<<std::endl;}else{std::cout<<"未通过测试用例1, 测试的值为: (nums = [-1,0,3,5,9,12], target = 9)"<<std::endl;}
}void Test2()
{vector<int> nums={-1,0,3,5,9,12};int target =2;int ret = Solution().search(nums,target);if(ret==-1){std::cout<<"通过测试用例2, 测试(nums = [-1,0,3,5,9,12], target = 2)通过 OK!"<<std::endl;}else{std::cout<<"未通过测试用例2, 测试的值为: (nums = [-1,0,3,5,9,12], target = 2)"<<std::endl;}
}int main()
{Test1();Test2();
}

8. 前端页面设计

编写页面的时候,需要三剑客: html/css/js

(1) 首页(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%;background-image: url('hsl.jpg'); /* 添加背景图 */background-size: cover; /* 使背景图覆盖整个元素 */background-position: center; /* 背景图居中显示 */}.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>

(2) 所有题目的列表(all_questions.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 .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="/questions/{{number}}">{{title}}</a></td><td class="item">{{star}}</td></tr>{{/question_list}}</table></div><div class="footer"><!-- <hr> --><h4>@HSL</h4></div></div></body></html>

(3) 指定题目的编写代码的页面+代码提交(one_questions.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><!-- 引入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>

(优化持续更新中…)

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

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

相关文章

微信小程序上传微信官方审核流程(1)

1&#xff0c;打开微信开发者工具 2&#xff0c;微信开发者工具右上角有一个上传按钮&#xff0c;点击上传按钮 3&#xff0c;点击完上传按钮会弹出一个上传成功的提示&#xff0c;点击提示框中的确定按钮 4&#xff0c;点击完确定按钮后会显示填写版本好和项目备注 5&#x…

数据结构(Java版)第一期:时间复杂度和空间复杂度

目录 一、数据结构的概念 1.1. 什么是数据结构 1.2. 算法与数据结构的关系 二、算法效率 三、时间复杂度 3.1. 大O的渐进表⽰法 3.2. 计算冒泡排序的时间复杂度 3.3. 计算二分查找的时间复杂度 四、空间复杂度 4.1. 空间复杂度 4.2. 冒泡排序的空间复杂度 4.3.…

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程 引言 微信小程序作为一种新兴的轻量级应用,凭借其便捷性和丰富的功能受到了广泛的欢迎。在开发小程序的过程中,合理配置全局属性是提升用户体验的关键。本文将深入探讨小程序的全局配置中的window选项,重点介绍导…

语言模型中的多模态链式推理

神经网络的公式推导 简介摘要引言多模态思维链推理的挑战多模态CoT框架多模态CoT模型架构细节编码模块融合模块解码模块 实验结果运行代码补充细节安装包下载Flan-T5数据集准备rougenltkall-MiniLM-L6-v2运行 简介 本文主要对2023一篇论文《Multimodal Chain-of-Thought Reason…

LLaMA-Mesh: Unifying 3D Mesh Generation with Language Models 论文解读

目录 一、概述 二、相关工作 1、LLMs到多模态 2、3D对象生成 3、自回归的Mesh生成 三、LLaMA-Mesh 1、3D表示 2、预训练模型 3、有监督的微调数据集 4、数据集演示 四、实验 1、生成的多样性 2、不同模型text-to-Mesh的比较 3、通用语境的评估 一、概述 该论文首…

【Go】-go中的锁机制

目录 一、锁的基础知识 1. 互斥量/互斥锁 2. CAS&#xff08;compare and swap&#xff09; 3. 自旋锁 4. 读写锁 5. 乐观锁 & 悲观锁 6. 死锁 二、go中锁机制 1. Mutex-互斥锁 2. RWMutex-读写锁 2.1 RWMutex流程概览 2.2 写锁饥饿问题 2.3. golang的读写锁源…

.NET9 - 新功能体验(二)

书接上回&#xff0c;我们继续来聊聊.NET9和C#13带来的新变化。 01、新的泛型约束 allows ref struct 这是在 C# 13 中&#xff0c;引入的一项新的泛型约束功能&#xff0c;允许对泛型类型参数应用 ref struct 约束。 可能这样说不够直观&#xff0c;简单来说就是Span、ReadO…

抗癌药物“曲妥珠单抗”,或将纳入2025版《中国药典》!

在抗癌药物的浩瀚星空中&#xff0c;曲妥珠单抗如同一颗璀璨的星辰&#xff0c;以其卓越的治疗效果和广泛的应用前景&#xff0c;照亮了无数HER2阳性癌症患者的生命之路。近日&#xff0c;从国家药典委员会传来振奋人心的消息——注射用曲妥珠单抗正式进入《中国药典》2025版国…

JavaParser 的全面介绍

JavaParser 是什么&#xff1f; JavaParser 的快速介绍可以参考&#xff1a; # JavaParser的快速介绍 JavaParser是一个用于解析Java源码的开源工具&#xff0c;它提供了一种简单而有效的方式来解析和操作Java代码。JavaParser解析源码的方式主要基于其将Java代码转换为抽象语…

图形化界面MySQL(MySQL)(超级详细)

1.官网地址 MySQL :: Download MySQL Workbench 1.1在Linux直接点击NO thanks..... 下载完后是这个页面 1.2任何远端登录&#xff0c;再把jj数据库给授权 1.3建立新用户 进行连接 点击这个就运行了 只执行show tables&#xff1b;要先选中 圆圈处支持自己输入 点击这个就执…

【夹板涨停战法】技术形态,原理和操盘技术图文教程

夹板涨停战法的基本形态和原理 夹板涨停是指两股强大的做多力量以夹击的方式紧紧的封夹空头&#xff0c;把空头力量打趴下&#xff0c;让空头的做空希望被破灭。 两股强大的多头力量对付空头&#xff0c;多头战胜&#xff0c;轻易灭掉空头&#xff0c;一切在强大做多力量的把…

【2024 Optimal Control 16-745】Julia语法

Lecture 2 θ和它的导数符号是通过 Julia 中的变量命名方式实现的 变量 θ 的输入&#xff1a; 在 Julia 中&#xff0c;θ 是一个合法的变量名&#xff0c;就像普通的字母 x 或 y 一样。要输入 θ&#xff0c;可以使用以下方法&#xff1a; 在 Jupyter Notebook 或 Julia REP…

Java项目实战II基于SPringBoot的玩具销售商城管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着儿童娱乐与教育需求的…

Linux系统使用valgrind分析C++程序内存资源使用情况

内存占用是我们开发的时候需要重点关注的一个问题&#xff0c;我们可以人工根据代码推理出一个消耗内存较大的函数&#xff0c;也可以推理出大概会消耗多少内存&#xff0c;但是这种方法不仅麻烦&#xff0c;而且得到的只是推理的数据&#xff0c;而不是实际的数据。 我们可以…

stm32如何接收舵机的控制信号(而不是控制舵机)

看到很多如何stm32用pwm信号控制舵机的文章,老生常谈了 我来写一个stm32接收pwm信号的例子 ,这个pwm信号是用来控制舵机的 背景: 我需要接收航模接收机的,用来控制舵机的pwm信号, 得到这个信号后,做其他事情. 初版代码 pwm.h#ifndef _pwm_H #define _pwm_H#include "s…

RK3588 HDMI2.1电路参考设计原理

RK3588是瑞芯微电子&#xff08;Rockchip&#xff09;旗下的一款高性能应用处理器芯片&#xff0c;采用了ARM的big.LITTLE架构&#xff0c;结合了四个高性能的Cortex-A76核心和四个高效能的Cortex-A55核心。 big.LITTLE技术通过结合高性能的“大核”&#xff08;big cores&…

【大数据学习 | Spark-Core】详解分区个数

RDD默认带有分区的&#xff0c;那么创建完毕rdd以后他的分区数量是多少&#xff1f; 从hdfs读取文件的方式是最正规的方式&#xff0c;我们通过计算原理可以推出blk的个数和分区数量是一致的&#xff0c;本地化计算。 我们可以发现数据的读取使用的是textInputFormat&#xff…

艾体宝干货丨差异解读:IT 和 OT 网络的数据包和网络分析

IT 网络&#xff08;传统网络&#xff09; IT 网络是现代计算的支柱&#xff0c;为数据交换、通信和处理提供了基础设施。典型应用包括 办公网络数据中心云服务互联网连接 这些网络依靠 TCP/IP、DNS 和 HTTP 等标准协议来促进设备之间的通信。通信路径可能随时发生变化&…

AOC显示器915Sw按键失灵维修记

大家好&#xff0c;我是 程序员码递夫 今天给大家分享的是自己维修老古董AOC液晶显示器按键失灵的的过程&#xff0c;实属DIY记录。 1、引子 家里有台老古董的19寸AOC液晶显示器&#xff08;型号915Sw&#xff09;, 一直作为我的副显示器陪伴着左右&#xff0c;显示还正常&a…

Vscode进行Java开发环境搭建

Vscode进行Java开发环境搭建 搭建Java开发环境(Windows)1.Jdk安装2.VsCode安装3.Java插件4.安装 Spring 插件5.安装 Mybatis 插件5.安装Maven环境6.Jrebel插件7.IntelliJ IDEA Keybindings8. 收尾 VS Code&#xff08;Visual Studio Code&#xff09;是由微软开发的一款免费、开…