OnlineJudge平台(负载均衡)


项目链接,可以作为参考该代码阅读里面的文档路径
项目gitee链接https://gitee.com/yuyka/online-bridge-platform/tree/master

1.所用技术与开发环境

所用技术:

  • C++ STL 标准库

  • Boost 准标准库(字符串切割)

  • cpp-httplib 第三方开源网络库

  • ctemplate 第三方开源前端网页渲染库

  • jsoncpp 第三方开源序列化、反序列化库

  • 负载均衡设计

  • 多进程、多线程

  • MySQL C connect

  • Ace前端在线编辑器(了解)

  • html/css/js/jquery/ajax (了解)

开发环境:

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

2.项目宏观结构

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

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

I. leetcode 结构

只实现类似 leetcode 的题目列表+在线编程功能

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

II. 我们的项目宏观结构

在这里插入图片描述

III. 编写思路

  1. 先编写 compile_server - - 编译服务器
  2. oj_server - - 处理客户端的请求
  3. version1 - - 基于文件版的在线OJ
  4. 前端的页面设计
  5. version2 基于 MySQL 版的在线O

3.compiler服务设计

提供的服务:编译并运行代码,得到格式化的相关的结果

在这里插入图片描述

功能一、编写compile模块 (编译功能)

编写过程:

  • 第一步,创建子进程完成文件编译
  • 第二步,父进程检查是否编译成功
  • temp目录下存放所有的临时文件,包含.cpp,.exe,.stderro ,.compile_erro后缀的文件。
  • 第三步,重定向子进程错误流,子进程编译错误后,将错误信息存放到重定向文件里,我们把该文件存放到temp目录以.compile_erro后缀命名
#pragma once

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

namespace ns_compiler
{
    using namespace ns_log;
    using namespace ns_util;

    class compiler
    {
    public:
        compiler() {}
        ~compiler() {}

        // 1.获取要编译的临时文件(.cpp)
        // 2.我们把生成的临时文件都存放在temp目录下
        //
        static bool compile(const std::string &file_name)
        {
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(ERROR) << "内部错误,创建子进程失败"
                           << "\n";
                return false;
            }
            else if (pid == 0)
            {
                // child
                // 1.错误重定向,如果编译失败,错误信息可以在temp目录下以stderr后缀文件名保存
                int stderr_fd = open(ns_util::util_path::Stderr(file_name).c_str(), O_CREAT | O_WRONLY, 0666);
                if (stderr_fd < 0)
                {
                    LOG(WARNING) << "没有成功形成stderr文件"
                                 << "\n";
                    exit(1);
                }
               dup2(stderr_fd, 2);
                //dup2(2, stderr_fd);

                // 2.程序替换,完成编译工作,
                LOG(INFO)<<"程序替换,编译工作,开始\n";
                execlp("g++", "g++", "-o", /*生成文件*/ ns_util::util_path::Exe(file_name).c_str(),
                       ns_util::util_path::Src(file_name).c_str(),"-std=c++11",nullptr);

                LOG(ERROR) << "启动编译器g++失败,可能是参数错误"
                           << "\n";

                exit(2);
            }
            else
            {
                // father
                waitpid(pid, nullptr, 0); //阻塞等待子进程
                //判断编译是否成功
                if (ns_util::util_file::IsCompile(ns_util::util_path::Exe(file_name)))
                {
                    LOG(INFO) << util_path::Src(file_name) << " 编译成功!"
                              << "\n";
                    return true;
                }
                //下面是编译失败
                //错误信息保存到了temp目录下 file_name.stderr文件里
                LOG(ERROR) << "编译失败,没有形成可执行程序"
                           << "\n";
            }
            return false;
        }
    };
} // namespace ns_compiler

日志信息的编写(其他)

设计的需要:

LOG(INFo) << "message(日志信息)" << "\n";
打印文件名,错误行,错误等级,并且返回标准输出流,方便日志信息输出。

函数模型:

inline std::ostream &Log(const std::string &level /*错误登记*/, const std::string &file_name /*错误文件*/, int line /*错误行*/)

函数宏替换(方便后期调用):

// LOG(INFo) << "message(日志信息)" << "\n";
// 开放式日志
// __FILE__ ,__LINE__ 为宏定义,用来打印当前文件名和当前执行行
#define LOG(level) Log(#level/*宏参数,必须要有'#'*/, __FILE__, __LINE__)

日志信息的实现代码(其他)

#pragma once

#include <iostream>
#include <string>
#include "util.hpp"


namespace ns_log
{
    using namespace ns_util;

    // 日志等级
    enum
    {
        INFO,    // 普通的信息
        DEBUG,   // 调试信息
        WARNING, // 警告信息
        ERROR,   // 用户的请求错误,此时不能继续了
        FATAL    // 引起系统错误的时候,使整个系统都不能运行了
    };
    //     设计的需要:LOG(INFo) << "message(日志信息)" << "\n";
    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 += ns_util::Util_Time::GetTimeStamp();
        message += "]";

        // cout 本质 内部是包含缓冲区的
        std::cout << message; //不要endl进行刷新

        return std::cout;
    }
// LOG(INFo) << "message(日志信息)" << "\n";
// 开放式日志
// __FILE__ ,__LINE__ 为宏定义,用来打印当前文件名和当前执行行
#define LOG(level) Log(#level/*宏参数,必须要有'#'*/, __FILE__, __LINE__)
}

获取时间戳代码 (其他)

 class Util_Time
    {
    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);
        }
    };

运行效果图:

  • compile编译code.cpp文件。
  • 错误信息流向错误文件。
  • 向错误流打印错误信息。

在这里插入图片描述

功能二 、编写 Runner 模块 (运行功能)

  • 该功能负责运行编译后的可执行文件,只关心运行错误,不关心运行结果。
  • 第一步、创建子进程运行可执行程序,
  • 重定向子进程三个标准流,重定向到以.stdin,.stdout,.stderro 后缀的文件里。
  • 第二步、父进程检查,子进程运行是否异常,如果异常则会收到信号
  • 第三步、给子进程添加资源限制,例如:运行超时,内存超限,

资源限制接口讲解

接口测试:

#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
  std::cout << "signo : " << signo << std::endl;
  exit(1);
}
int main()
{
  //资源不足,导致OS终止进程,是通过信号终止的
  for(int i =1; i <= 31; i++){
    signal(i, handler);
 }
  // 限制累计运行时长
  // struct rlimit r;
  // r.rlim_cur = 1;
  // r.rlim_max = RLIM_INFINITY;
  // setrlimit(RLIMIT_CPU, &r);
  //while(1);
  struct rlimit r;
  r.rlim_cur = 1024 * 1024 * 40; //20M
  r.rlim_max = RLIM_INFINITY;
  setrlimit(RLIMIT_AS, &r);
  int count = 0;
  while(true)
 {
    int *p = new int[1024*1024];
    count++;
    std::cout << "size: " << count << std::endl;
    sleep(1);
 }
  return 0;
}
//内存申请失败
terminate called after throwing an instance of 'std::bad_alloc'
 what():  std::bad_alloc
signo : 6
[whb@bite-alicloud OnlineJudge]$ kill -l
1) SIGHUP    2) SIGINT    3) SIGQUIT    4) SIGILL    5) SIGTRAP
6) SIGABRT    7) SIGBUS    8) SIGFPE    9) SIGKILL   10) SIGUSR1
11) SIGSEGV   12) SIGUSR2   13) SIGPIPE   14) SIGALRM   15) SIGTERM
16) SIGSTKFLT  17) SIGCHLD   18) SIGCONT   19) SIGSTOP   20) SIGTSTP
...
 
 
//CPU使用超时
[whb@bite-alicloud OnlineJudge]$ ./a.out
signo : 24
[whb@bite-alicloud OnlineJudge]$ kill -l
1) SIGHUP    2) SIGINT    3) SIGQUIT    4) SIGILL    5) SIGTRAP
6) SIGABRT    7) SIGBUS    8) SIGFPE    9) SIGKILL   10) SIGUSR1
11) SIGSEGV   12) SIGUSR2   13) SIGPIPE   14) SIGALRM   15) SIGTERM
16) SIGSTKFLT  17) SIGCHLD   18) SIGCONT   19) SIGSTOP   20) SIGTSTP
21) SIGTTIN   22) SIGTTOU   23) SIGURG    24) SIGXCPU   25) SIGXFSZ
26) SIGVTALRM  27) SIGPROF   28) SIGWINCH   29) SIGIO    30) SIGPWR
....

编写 Runner 模块:

#pragma once

#include <iostream>
#include <string>
#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 "../common/log.hpp"
#include "../common/util.hpp"

namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;

    class Runner
    {
    public:
        Runner() {}
        ~Runner() {}

    public:
        //提供设置进程占用资源大小的接口
        static void SetProcLimit(int _cpu_limit /*以秒为单位*/, int _mem_limit /*以KB为单位*/)
        {
            // 设置CPU时长
            struct rlimit cpu_rlimit;
            cpu_rlimit.rlim_max = RLIM_INFINITY; //硬限制设置为无穷即最大值
            cpu_rlimit.rlim_cur = _cpu_limit;    // 软限制设置为 特定值(不超过硬限制值)
            setrlimit(RLIMIT_CPU, &cpu_rlimit);  // 以秒为单位

            // 设置内存大小
            struct rlimit mem_rlimit;
            mem_rlimit.rlim_max = RLIM_INFINITY;     //硬限制设置为无穷即最大值
            mem_rlimit.rlim_cur = _mem_limit * 1024; //转化成为KB,// 软限制设置为 特定值(不超过硬限制值)
            setrlimit(RLIMIT_AS, &mem_rlimit);
        }
        // 指明文件名即可,不需要代理路径,不需要带后缀
        /*******************************************
         * 返回值 > 0: 程序异常了,退出时收到了信号,返回值就是对应的信号编号
         * 返回值 == 0: 正常运行完毕的,结果保存到了对应的临时文件中
         * 返回值 < 0: 内部错误
         *
         * cpu_limit: 该程序运行的时候,可以使用的最大cpu资源上限
         * mem_limit: 改程序运行的时候,可以使用的最大的内存大小(KB)
         * *****************************************/
        // static int Run(const std::string &file_name, int cpu_limit, int mem_limit)
        static int Run(const std::string &file_name)
        {
            /*********************************************
             * 程序运行:
             * 1. 代码跑完,结果正确
             * 2. 代码跑完,结果不正确
             * 3. 代码没跑完,异常了
             * Run需要考虑代码跑完,结果正确与否吗??不考虑!
             * 结果正确与否:是由我们的测试用例决定的!
             * 我们只考虑:是否正确运行完毕
             *
             * 我们必须知道可执行程序是谁?
             * 一个程序在默认启动的时候
             * 标准输入: 不处理
             * 标准输出: 程序运行完成,输出结果是什么
             * 标准错误: 运行时错误信息
             * *******************************************/
            std::string _execute = ns_util::util_path::Exe(file_name);
            std::string _stdin = ns_util::util_path::Stdin(file_name);
            std::string _stdout = ns_util::util_path::Stdout(file_name);
            std::string _stderr = ns_util::util_path::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 (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
            {
                LOG(ERROR) << "运行时打开标准文件失败"
                           << "\n";
                return -1; //代表打开文件失败
            }

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

                SetProcLimit(cpu_limit, mem_limit);

						/******************************************
						* 运行 程序
						* ******************************************/
                execl(_execute.c_str() /*我要执行谁*/, _execute.c_str() /*我想在命令行上如何执行该程序*/, nullptr);
                exit(1);
            }
            else
            {
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                int status = 0;
                waitpid(pid, &status, 0);
                // 程序运行异常,一定是因为因为收到了信号!
                LOG(INFO) << "运行完毕, 子进程退出signal: " << (status & 0x7F) << "\n";

                /*************************************************************************************
                 * 该函数只关心异常,,status的低7位表示信号标识,默认情况下为0,当收到异常信号时,会被设置
                 ****************************************************************************************/
                return status & 0x7F;
            }
        }
    };
}



功能三、compile_run (编译并运行功能)

在这里插入图片描述
如上图分析我们compile_run 大概要实现的功能:

  • 该模块用于完成用户数据到compile和runner模块再到用户的过程。
  • 适配用户请求, 定制通信协议字段
  • 形成唯一的.cpp文件,可以让compile()运行
  • 正确的调用compile and run

Jsoncpp的使用 (其他)

  • 适配用户请求, 定制通信协议字段
  • Jsoncpp的安装: sudo yum install -y jsoncpp-devel

序列化:

#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <signal.h>
#include <jsoncpp/json/json.h>
int main()
{

  //序列化的工作
  //将结构化数据转化成为一个字符串
  //Value是一个Json的中间类,可以填充KV值
  Json::Value root;
  root["code"] = "mycode";
  root["user"] = "whb";
  root["age"] = "19";
  /***************************************
  * Json::StyledWriter writer;   
  * Json::FastWriter writer;
  * FastWriter 处理起来更快,StyledWriter 方便调试
  * ***************************************/
  Json::FastWriter writer;
  std::string str = writer.write(root);
  std::cout << str << std::endl;
}

在这里插入图片描述
反序列化:

#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <signal.h>
#include <jsoncpp/json/json.h>
int main()
{
  std::string json_str = "{\"age\":19,\"code\":\"mycode\",\"user\":\"YYK\"}";
  /**********************************
   * 反序列化的过程
   * *********************************/
  Json::Reader read;
  Json::Value val;
  read.parse(json_str, val);

  std::cout << "code:" << val["code"].asCString() << std::endl;
  std::cout << "user:" << val["user"].asCString() << std::endl;
  std::cout << "age:" << val["age"].asInt()<< std::endl;
}

在这里插入图片描述

编译和运行模块 (功能实现)1

#pragma once

#include "compiler.hpp"
#include "runner.hpp"
#include "../common/log.hpp"
#include "../common/util.hpp"

#include <signal.h>
#include <unistd.h>
#include <jsoncpp/json/json.h>

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

    class CompileAndRun
    {
    public:
        /***************************************
         * 输入:
         * code: 用户提交的代码
         * input: 用户给自己提交的代码对应的输入,不做处理
         * cpu_limit: 时间要求
         * mem_limit: 空间要求
         *
         * 输出:
         * 必填
         * status: 状态码
         * reason: 请求结果
         * 选填:
         * stdout: 我的程序运行完的结果
         * stderr: 我的程序运行完的错误结果
         *
         * 参数:
         * in_json: {"code": "#include...", "input": "","cpu_limit":1, "mem_limit":10240}
         * out_json: {"status":"0", "reason":"","stdout":"","stderr":"",}
         * ************************************/
        static void Start(const std::string &in_json, std::string *out_json)
        {
             std::string temp_file_name("test");
            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;
            }
            // 形成的文件名只具有唯一性,没有目录没有后缀
            // 毫秒级时间戳+原子性递增唯一值: 来保证唯一性
            file_name = util_file::UniqFileName();
           
           // file_name = temp_file_name;

            //形成临时src文件
            if (!util_file::WriteFile(util_path::Src(file_name), code))
            {
                status_code = -2; //未知错误
                goto END;
            }

            if (!compiler::compile(file_name))
            {
                //编译失败
                status_code = -3; //代码编译的时候发生了错误
                goto END;
            }
            /******************************
             * Run()只关心异常问题,返回错误信号
            *******************************
            */
            run_result = Runner::Run(file_name, cpu_limit, mem_limit);
            if (run_result < 0)
            {
                status_code = -2; //未知错误
            }
            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;
                util_file::ReadFile(util_path::Stdout(file_name), &_stdout, true);
                out_value["stdout"] = _stdout;

                std::string _stderr;
                util_file::ReadFile(util_path::Stderr(file_name), &_stderr, true);
                out_value["stderr"] = _stderr;
            }

            Json::StyledWriter writer;
            *out_json = writer.write(out_value);

            RemoveTempFile(file_name);
        }
    };
}

  • 解析 in_json
  • 形成临时src文件【完成编译和运行的工作】
  • 编译和运行代码
  • 获取状态码,错误原因……运行成功则获取输出文件内容……
  • 状态码……生成 out_json
  • 清理生成的临时文件
  • 返回out_json

编译和运行模块 (功能实现)2

#pragma once

#include "compiler.hpp"
#include "runner.hpp"
#include "../common/log.hpp"
#include "../common/util.hpp"

#include <signal.h>
#include <unistd.h>
#include <jsoncpp/json/json.h>

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

    class CompileAndRun
    {
    public:
        static void RemoveTempFile(const std::string &file_name)
        {
            //清理文件的个数是不确定的,但是有哪些我们是知道的
            std::string _src = util_path::Src(file_name);
            if(util_file::IsFileExists(_src)) unlink(_src.c_str());

            std::string _compiler_error = util_path::CompileErro(file_name);
            if(util_file::IsFileExists(_compiler_error)) unlink(_compiler_error.c_str());

            std::string _execute = util_path::Exe(file_name);
            if(util_file::IsFileExists(_execute)) unlink(_execute.c_str());

            std::string _stdin = util_path::Stdin(file_name);
            if(util_file::IsFileExists(_stdin)) unlink(_stdin.c_str());

            std::string _stdout = util_path::Stdout(file_name);
            if(util_file::IsFileExists(_stdout)) unlink(_stdout.c_str());

            std::string _stderr = util_path::Stderr(file_name);
            if(util_file::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 = "代码编译的时候发生了错误";
                util_file::ReadFile(util_path::CompileErro(file_name), &desc, true);
                break;
            case SIGABRT: // 6
                desc = "内存超过范围";
                break;
            case SIGXCPU: // 24
                desc = "CPU使用超时";
                break;
            case SIGFPE: // 8
                desc = "浮点数溢出";
                break;
            default:
                desc = "未知: " + std::to_string(code);
                break;
            }

            return desc;
        }
    };
}

测试用例:

compiler 提供一个编译网络服务,通过http 让client 给我们 上传一个json string, 下面的工作,充当客户端请求的json串。


#include"compiler.hpp"
#include"runner.hpp"
#include"compile_run.hpp"
#include<jsoncpp/json/json.h>
using namespace ns_compiler;

int main()
{
    std::string in_json;
    Json::Value in_value;
    //R"()", raw string
    // 用例1:测试内存
    in_value["code"] = R"(#include<iostream>
    int main(){
        std::cout << "你可以看见我了" << std::endl;
        int * p=new int[1024*1024*1024];
        return 0;
    })";
   // 用例2:测试编译
    in_value["code"] = R"(#include<iostream>
    int main(){
        std::cout << "你可以看见我了" << std::endl;
        aaaaaaaaaaaa;
        return 0;
    })";
    // 用例1:测试运行正确
    in_value["code"] = R"(#include<iostream>
    int main(){
        std::cout << "你可以看见我了" << std::endl;
        return 0;
    })";
    in_value["input"] = "";
    in_value["cpu_limit"] = 1;
    in_value["mem_limit"] = 10240*3;//kb为单位,这里为30mb
    Json::FastWriter writer;
    in_json = writer.write(in_value);
    std::cout << in_json << std::endl;
    //这个是将来给客户端返回的json串
    std::string out_json;
    ns_compile_and_run::CompileAndRun::Start(in_json, &out_json);

    std::cout << out_json << std::endl;

}

4. 基于MVC结构的oj服务设计

本质:建立一个小型网站

  1. 获取首页,用题目列表充当
  2. 编辑区域页面
  3. 提交判题功能(编译并运行)
字母字母含义
M:Model,通常是和数据交互的模块,比如,对题库进行增删改查(文件版,MySQL)
V:view, 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的(浏览器)
C:control, 控制器,就是我们的核心业务逻辑

第一个功能:用户请求的服务路由功能 (oj_server.cpp)下面只是模版,需待补充

  • oj_server 主要是处理请求
  • 得到对方请求后调用用M,V,C模块来处理请求
#include "../common/httplib.h"
#include <string>
#include <fstream>
using namespace httplib;
int main(int argc, char *argv[])
{
    // http 服务
    Server svr;
    // 1.获取题目列表
    svr.Get(R"(/question_all)", [](const Request &req, Response &resp)
            { 
        std::string content="获取列表成功";
        resp.set_content(content,"test/html;charset=utf-8"); });
    // 2.获取对应题号的题目描述
    // /question/100 -> 正则匹配
    // R"()", 原始字符串raw string,保持字符串内容的原貌,不用做相关的转义

    svr.Get(R"(/question/(\d+))", [](const Request &req, Response &resp)
            {

        std::string content="题目获取成功\n";
        resp.set_content(content,"test/html;charset=utf-8"); });

    // 3.用户提交代码,使用我们的判题功能(1. 每道题的测试用例 2. compile_and_run)
    svr.Post(R"(/judge/(\d+))", [](const Request &req, Response &resp)
             {
        std::string number = req.matches[1];//获取(\d+)的内容
        std::string result_json;
        resp.set_content(result_json, "application/json;charset=utf-8"); });
    // resp.set_content("指定题目的判题: " + number, "text/plain; charset=utf-8"); });

    // 4.
    svr.set_base_dir("./wwwroot"); //对应根目录
    svr.listen("0.0.0.0", 3389);
}

设计题库(文件版的)

在这里插入图片描述

  • 在oj_server目录下创建题目列表目录名为questions,
  • 使用编号为命名的目录下创建题目,每个题目下包含三个文件,
  • desc.txt :描述题目
  • header.cpp : 用户编码区
  • tail.cpp : 测试用例,条件编译只是不让文件有报错展示,
  • 后续,tail.cpp文件将会与用户提交的代码进行拼接,使用 g++ -D COMPILER_ONLINE 就是给文件定义一个宏,可以去除 条件编译内容。

实现MVC模块

1.oj_model.hpp

  • 用于加载题目
  • 把结果给oj_view,由oj_view渲染模版页面
#pragma once
//文件版本
#include "../common/util.hpp"
#include "../common/log.hpp"

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <cstdlib>
#include <cassert>

// 根据题目list文件,加载所有的题目信息到内存中
// model: 主要用来和数据进行交互,对外提供访问数据的接口
using namespace std;
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 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;
                util_string::SplitString(line, &tokens, " ");
                // 1 判断回文数 简单 1 30000
                if(tokens.size() != 5)
                {
                    LOG(WARNING) << "加载部分题目失败, 请检查文件格式" << "\n";
                    continue;
                }
                Question q;
                q.number = tokens[0];
                q.title = tokens[1];
                q.star = tokens[2];
                q.cpu_limit = atoi(tokens[3].c_str());
                q.mem_limit = atoi(tokens[4].c_str());

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

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

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

            return true;
        }
        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()
        {}
    };
} // namespace ns_model

2.oj_view.hpp 渲染的页面

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>
</head>

<body>
    <div class="container">
        <!-- 导航栏, 功能不实现-->
        <div class="question_list">
            <h1>OnlineJuge题目列表</h1>
            <table>
                <tr>
                    <th class="item">编号</th>
                    <th class="item">标题</th>
                    <th class="item">难度</th>
                </tr>s
                {{#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>

</body>

</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=, initial-scale=1.0">
    <title>{{number}}.{{title}}</title>
</head>
<body>
    <h4>{{number}}.{{title}}.{{star}}</h4>
    <P>{{desc}}</P>
    <textarea name="code" cols="300" rows="100">{{pre_code}}</textarea>
</body>
</html>

渲染之后的效果:

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

进行网页渲染

在备注栏学习:

  • 安装ctemplate库
  • ctemplate库的基本使用
#pragma once

#include <iostream>
#include <string>
#include <ctemplate/template.h>

 #include "oj_model.hpp"


namespace ns_view
{
    using namespace ns_model;

    const std::string template_path = "./template_html/";

    class View
    {
    public:
        View(){}
        ~View(){}
    public:
        void AllExpandHtml(const vector<struct Question> &questions, std::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. 获取被渲染的html
            ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);

            //4. 开始完成渲染功能
            tpl->Expand(html, &root);
        }
        void OneExpandHtml(const struct Question &q, std::string *html)
        {
            // 1. 形成路径
            std::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("desc", q.desc);
            root.SetValue("pre_code", q.header);

            //3. 获取被渲染的html
            ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);
           
            //4. 开始完成渲染功能
            tpl->Expand(html, &root);
        }
    };
}

3.oj_control.hpp

oj_control模块的核心工作:

  • 处理核心业务逻辑
  • 用户需求:
  • 1.题目列表html网页
  • 2.特定题目网页
  • 3.判题服务
  • control将view,model,compile_server,组合使用解决业务逻辑。

下面代码只实现了一部分,处理判题功能没有实现,原因是Judge需要有一个负载均衡的算法,有了负载均衡我们能够均衡的发送请求给每一个compile_server,不至于出现极端情况。

class Control
    {
    private:
        Model model_; //提供后台数据
        View view_;   //提供html渲染功能

    public:
        Control()
        {
        }
        ~Control()
        {
        }

    public:
        //根据题目数据构建网页
        // 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
            {
                *html = "获取题目失败, 形成题目列表失败";
                ret = false;
            }
            return ret;
        }
        bool Question(const string &number, string *html)
        {
            bool ret = true;
            struct Question q;
            if (model_.GetOneQuestion(number, &q))
            {
                // 获取指定题目信息成功,将所有的题目数据构建成网页
                LOG(INFO)<<"获取指定题目信息成功,将所有的题目数据构建成网页\n";
                view_.OneExpandHtml(q, html);
            }
            else
            {
                *html = "指定题目: " + number + " 不存在!";
                ret = false;
            }
            return ret;
        }
        void Judge(const std::string& in_json,std::string*out_json)
        {
            //1.反序列化 in_json ,得到 id,code,input
            //2.code拼接测试用例
            //3.选择负载低的 conliler_server 
            //4.建立客户端,发送请求
            //5.结果返回out_json
        }
        // code: #include...
        // input: ""
    };
} // namespace name

4.oj_server.hpp

我们再次补充oj_server.hpp 模块。


#include "../common/httplib.h"
#include <string>
#include <fstream>
#include"oj_control.hpp"
using namespace httplib;
const std::string file_path = "./temp.7z";
using namespace ns_control;

int main(int argc, char *argv[])
{
    // http 服务
    Server svr;
    Control crl;
    // 1.获取题目列表
    svr.Get(R"(/question_all)", [&crl](const Request &req, Response &resp)
            { 
        std::string content;
        crl.AllQuestions(&content);
        resp.set_content(content,"text/html;charset=utf-8"); });
    // 2.获取对应题号的题目描述
    // /question/100 -> 正则匹配
    // R"()", 原始字符串raw string,保持字符串内容的原貌,不用做相关的转义

    svr.Get(R"(/question/(\d+))", [&crl](const Request &req, Response &resp)
            {
        std::string number = req.matches[1];//获取(\d+)的内容
        std::string content;
        crl.Question(number,&content);
        resp.set_content(content,"text/html;charset=utf-8"); });

    // 3.用户提交代码,使用我们的判题功能(1. 每道题的测试用例 2. compile_and_run)
    svr.Post(R"(/judge/(\d+))", [&crl](const Request &req, Response &resp)
             {
        std::string number = req.matches[1];//获取(\d+)的内容
        std::string in_json= req.body;
        std::string result_json;
        crl.Judge(in_json,&result_json);
        resp.set_content(result_json, "application/json;charset=utf-8"); });
    // resp.set_content("指定题目的判题: " + number, "text/plain; charset=utf-8"); });

    // 4.
    svr.set_base_dir("./wwwroot"); //对应根目录
    svr.listen("0.0.0.0", 3389);
}

5.实现负载均衡

实现Judge的功能核心步骤如下:

 class Control
    {
    public:
 
        void Judge(const std::string& in_json,std::string*out_json)
        {
            //1.反序列化 in_json ,得到 id,code,input
            //2.code拼接测试用例
            //3.选择负载低的 conliler_server 
            //4.建立客户端,发送请求
            //5.结果返回out_json
        }
    };
} // namespace name

除第三步外,我们现在都能实现,现在我们来设计负载均衡。

负载均衡设计

在这里插入图片描述

添加comile_server的配置文件,用来初始化主机对象使用,保存每台compile_server的ip 和 port

#### service_machine.conf文件
101.33.225.53 8080
101.33.225.53 8081
101.33.225.53 8082


负载均衡功能模块

 class Machine
    {
    public:
        std::string ip;  //编译服务的ip
        int port;        //编译服务的port
        uint64_t load;   //编译服务的负载
        std::mutex *mtx; // mutex禁止拷贝的,使用指针
    public:
    // 提升主机负载
        void IncLoad();
    		 // 减少主机负载
        void DecLoad();
        // 重启主机,需要使负载初始化
        void ResetLoad();
   };

  • Machine对象:描述主机信息,负载情况
class LoadBlance
    {
    private:
        // 可以给我们提供编译服务的所有的主机
        // 每一台主机都有自己的下标,充当当前主机的id
        std::vector<Machine> machines;
        // 所有在线的主机id
        std::vector<int> online;
        // 所有离线的主机id
        std::vector<int> offline;
        // 保证LoadBlance它的数据安全
        std::mutex mtx;

    public:
    		//初始化machines,即从文件或者数据里加载所有题目
        LoadBlance();
        // 根据全有主机的赋值情况,选择主机,id,m为输出型参数
			 bool SmartChoice(int *id, Machine **m);
			 //  有下线的主机,需要修改成员变量,即online里减少对应主机,offline里添加对应主机
			  void OfflineMachine(int which);
			  // 让全部的主机都上线
			   void OnlineMachine();
			   // for test
			   void ShowMachines();
		    };
  • LoadBlance对象:根据每台主机的负载情况,来选择主机
#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
#include <mutex>
#include <cassert>
#include <jsoncpp/json/json.h>

#include "../common/util.hpp"
#include "../common/log.hpp"
#include "../common/httplib.h"
//#include "oj_model.hpp"
#include "./oj_view.hpp"
#include "./oj_model.hpp"

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

    // 提供服务的主机
    class Machine
    {
    public:
        std::string ip;  //编译服务的ip
        int port;        //编译服务的port
        uint64_t load;   //编译服务的负载
        std::mutex *mtx; // mutex禁止拷贝的,使用指针
    public:
        Machine() : ip(""), port(0), load(0), mtx(nullptr)
        {
        }
        ~Machine()
        {
        }

    public:
        // 提升主机负载
        void IncLoad()
        {
            if (mtx)
                mtx->lock();
            ++load;
            if (mtx)
                mtx->unlock();
        }
        // 减少主机负载
        void DecLoad()
        {
            if (mtx)
                mtx->lock();
            --load;
            if (mtx)
                mtx->unlock();
        }
        // 重启主机
        void ResetLoad()
        {
            if (mtx)
                mtx->lock();
            load = 0;
            if (mtx)
                mtx->unlock();
        }
        // 获取主机负载,没有太大的意义,只是为了统一接口
        uint64_t Load()
        {
            uint64_t _load = 0;
            if (mtx)
                mtx->lock();
            _load = load;
            if (mtx)
                mtx->unlock();

            return _load;
        }
    };

    const std::string service_machine = "./conf/service_machine.conf";
    // 负载均衡模块
    class LoadBlance
    {
    private:
        // 可以给我们提供编译服务的所有的主机
        // 每一台主机都有自己的下标,充当当前主机的id
        std::vector<Machine> machines;
        // 所有在线的主机id
        std::vector<int> online;
        // 所有离线的主机id
        std::vector<int> offline;
        // 保证LoadBlance它的数据安全
        std::mutex mtx;

    public:
        LoadBlance()
        {
            std::cout<<"创建LoadBlance对象"<<std::endl;
            assert(LoadConf(service_machine));
            LOG(INFO) << "加载 " << service_machine << " 成功"
                      << "\n";
        }
        ~LoadBlance()
        {
        }

    public:
        bool LoadConf(const std::string &machine_conf)
        {
            std::cout<<"machine_conf:"<<machine_conf<<std::endl;
            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::cout<<"line:"<<line<<std::endl;
                std::vector<std::string> tokens;
                util_string::SplitString(line, &tokens, ":");
                if (tokens.size() != 2)
                {
                    LOG(WARNING) << " 切分 " << line << " 失败"
                                 << "\n";;
                              //   std::cout<<tokens[0]<<" "<<tokens[1]<<std::endl;
                    continue;
                }
                Machine m;

                m.ip = tokens[0];
                m.port = atoi(tokens[1].c_str());
                m.load = 0;
                m.mtx = new std::mutex();
                std::cout<<"创建Machine对象成功"<<std::endl;
                online.push_back(machines.size());
                machines.push_back(m);
            }

            in.close();
            return true;
        }
        // id: 输出型参数
        // m : 输出型参数
        bool SmartChoice(int *id, Machine **m)
        {
            // 1. 使用选择好的主机(更新该主机的负载)
            // 2. 我们需要可能离线该主机
            mtx.lock();
            // 负载均衡的算法
            // 1. 随机数+hash
            // 2. 轮询+hash
            int online_num = online.size();
            if (online_num == 0)
            {
                mtx.unlock();
                LOG(FATAL) << " 所有的后端编译主机已经离线, 请运维的同事尽快查看"
                           << "\n";
                return false;
            }
            // 通过遍历的方式,找到所有负载最小的机器
            *id = online[0];
            *m = &machines[online[0]];
            uint64_t min_load = machines[online[0]].Load();
            for (int i = 1; i < online_num; i++)
            {
                uint64_t curr_load = machines[online[i]].Load();
                if (min_load > curr_load)
                {
                    min_load = curr_load;
                    *id = online[i];
                    *m = &machines[online[i]];
                }
            }
            mtx.unlock();
            return true;
        }
        void OfflineMachine(int which)
        {
            mtx.lock();
            for (auto iter = online.begin(); iter != online.end(); iter++)
            {
                if (*iter == which)
                {
                    machines[which].ResetLoad();
                    //要离线的主机已经找到啦
                    online.erase(iter);
                    offline.push_back(which);
                    break; //因为break的存在,所有我们暂时不考虑迭代器失效的问题
                }
            }
            mtx.unlock();
        }
        void OnlineMachine()
        {
            //我们统一上线,后面统一解决
            mtx.lock();
            online.insert(online.end(), offline.begin(), offline.end());
            offline.erase(offline.begin(), offline.end());
            mtx.unlock();

            LOG(INFO) << "所有的主机有上线啦!"
                      << "\n";
        }
        // for test
        void ShowMachines()
        {
            mtx.lock();
            std::cout << "当前在线主机列表: ";
            for (auto &id : online)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            std::cout << "当前离线主机列表: ";
            for (auto &id : offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
            mtx.unlock();
        }
    };

    class Control
    {
    private:
        Model model_;            //提供后台数据
        View view_;              //提供html渲染功能
        LoadBlance load_blance_; //核心负载均衡
		/********************************
		* 中间代码隐藏
		* *********************************/
    public:
        // in_json : 题号+code+input
        void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            // LOG(DEBUG) << in_json << " \nnumber:" << number << "\n";

            // 0. 根据题目编号,直接拿到对应的题目细节
            struct Question q;
            model_.GetOneQuestion(number, &q);

            // 1. in_json进行反序列化,得到题目的id,得到用户提交源代码,input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::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_json
                    if (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(); //仅仅是为了用来调试
                }
            }
        }

        // code: #include...
        // input: ""
    };
} // namespace name

整个oj_sever的宏观图

在这里插入图片描述

5. 前端页面设计

丐版的首页

在这里插入图片描述




<!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="/question_all">题库</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="/question_all">点击我开始编程啦!</a>
        </div>
    </div>
</body>

</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="/question/{{number}}">{{title}}</a></td>
                    <td class="item">{{star}}</td>
                </tr>
                {{/question_list}}
            </table>
        </div>

    </div>

</body>

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

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

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

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

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

        function submit(){
            // alert("嘿嘿!");
            // 1. 收集当前页面的有关数据, 1. 题号 2.代码

            // console.log(judge_url);
            // 2. 构建json,并通过ajax向后台发起基于http的json请求
      
            // 3. 得到结果,解析并显示到 result中
          
       
    </script>
</body>

</html>


//代码提交
1.收集当前页面的有关数据, a. 题号 a.代码,我们采用JQuery来进行获取html中的内容
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({
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);
 }
});
// 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
       }
     }
// 4. 全部网页代码


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

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

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

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

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

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

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

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

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

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

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

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

</html>

6 version2MySQL版题目设计

  1. 在数据库中设计可以远程登陆的MySQL用户,并给他赋权
  • 创建oj_client用户
  1. 设计表结构
  • 创建 数据库:oj, 表:oj_questions
#1.创建oj数据库
create database oj;
#2. 创建oj_client 用户
create user 'oj_client'@'%' identified by 'oj_client!#%135';
#3. 给oj_client 赋予oj数据的所有权限
grant all on oj.* to 'oj_client'@'%' ;
#4. 测试远程登录是否成功
mysql -uoj_client -h43.139.99.192 -P5555 -p

3.MySQL 建表

use oj;
CREATE TABLE IF NOT EXISTS `questions`(
`number` int PRIMARY KEY AUTO_INCREMENT COMMENT '题目的ID',
`star` VARCHAR(8) NOT NULL COMMENT '题目的难度',
`desc` TEXT NOT NULL COMMENT '题目描述',
`title`VARCHAR(64) NOT NULL COMMENT '题目的标题',
`tail` TEXT NOT NULL COMMENT '题目尾部,包含我们的测试用例',
`header` TEXT NOT NULL COMMENT '题目头部,给用户看的代码',
`time_limit` int DEFAULT 1 COMMENT '题目的时间限制',
`mem_limit` int DEFAULT 5000000 COMMENT '题目的空间限制'
)ENGINE=INNODB DEFAULT CHARSET=utf8;

4.使用mysql workbench 插入数据

mysql workbench下载路径:

在这里插入图片描述

  1. 重新设计oj_model
  • 连接访问数据库

  • 有可能你的系统中,已经默认安装了mysql的开发包, 使用第三方引入的方式,不安装
    我们的oj_server基于MVC模式的,和数据打交道的只有一个oj_model模块,只需要更改该文件即可!!接口不变,只需要改变获取数据的方式即可。

  • 计划是一张表,结束我们的所有的需求!

  • 我们要注意,我们默认曾经安装的mysql可能已经默认具有了开发包,我们默认使用的就是系统自带的如果你没有 往下查找 安装方法。

如果页面乱码,我们需要考虑是否是连接的编码不正确!

编写代码

#pragma once
//文件版本
#include "../common/util.hpp"
#include "../common/log.hpp"
#include "./include/mysql.h"
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <cstdlib>
#include <cassert>

// 根据题目list文件,加载所有的题目信息到内存中
// model: 主要用来和数据进行交互,对外提供访问数据的接口
using namespace std;
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 std::string& questions ="questions ";
const std::string& db = "oj";
const std::string host = "127.0.0.1";
const std::string user = "oj_client";
const std::string passwd = "oj_client!#%135";
const int port = 5555;

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

        
        bool QueryMysql(const std::string& sql,std::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) << "连接数据库失败!" << "\n";
                return false;
            }
            // 一定要设置该链接的编码格式, 要不然会出现乱码问题
            mysql_set_character_set(my, "utf8");

            LOG(INFO) << "连接数据库成功!" << "\n";

            // 执行sql语句
            if(0 != mysql_query(my, sql.c_str()))
            {
                LOG(WARNING) << sql << " execute error!" << "\n";
                return false;
            }

            // 提取结果
            MYSQL_RES *res = mysql_store_result(my);

                        // 分析结果
            int rows = mysql_num_rows(res); //获得行数量
            int cols = mysql_num_fields(res); //获得列数量

            Question q;

            for(int i = 0; i < rows; i++)
            {
                MYSQL_ROW row = mysql_fetch_row(res);
                q.number = row[0];
                q.star = row[1];
                q.desc = row[2];
                q.title = row[3];
                q.tail = row[4];
                q.header = 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<Question> *out)
        {
            std::string sql="select * from ";
            sql+= questions;
            return QueryMysql(sql,out);
        }
        bool GetOneQuestion(const std::string &number, Question *q)
        {
            std::string sql="select * from ";
            sql+= questions;
            sql+="where number=";
            sql+=number;
            std::vector<Question> out;
            bool ret=false;
            if(QueryMysql(sql,&out))
            {
                if((out).size()==1)
                {
                    ret=true;
                    *q=out[0];
                }
            }
            return ret;

        }
        ~Model()
        {}
    };
} // namespace ns_model

7 安装指南

升级 gcc

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --
infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-
bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-
zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --
enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-
c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --
with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --
with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install -
-enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-
redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
cpp-httplib 用老的编译器,要么编译不通过,要么直接运行报错
百度搜索:scl gcc devsettool升级gcc
//安装scl
$ sudo yum install centos-release-scl scl-utils-build
//安装新版本gcc,这里也可以把7换成8或者9,我用的是9,也可以都安装
$ sudo yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++
$ ls /opt/rh/
//启动: 细节,命令行启动只能在本会话有效
$ scl enable devtoolset-7 bash
$ gcc -v
//可选:如果想每次登陆的时候,都是较新的gcc,需要把上面的命令添加到你的~/.bash_profile中
$ cat ~/.bash_profile
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH
#添加下面的命令,每次启动的时候,都会执行这个scl命令
scl enable devtoolset-7 bash
or
scl enable devtoolset-8 bash
or
scl enable devtoolset-9 bash

安装 jsoncpp

$ sudo yum install -y jsoncpp-devel

安装 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
把httplib.h拷贝到我们的项目中即可,就这么简单
使用样例:

#include "httplib.h"
int main()
{
  httplib::Server svr;
  svr.Get("/hi", [](const httplib::Request &req, httplib::Response &rsp){
      rsp.set_content("你好,世界!", "text/plain; charset=utf-8");
     });
  svr.listen("0.0.0.0", 8080);
  return 0;
}
更多的细节可以看gitee上面的使用手册,结合源码学习

安装boost库

 sudo yum install -y boost-devel //是boost 开发库

安装与测试 ctemplate

安装

## 如果连接挂了,可以去gitee上搜索ctemplate
$ git clone https://gitee.com/mirrors_svn2github/ctemplate.git
$ ./autogen.sh
$ ./configure
$ make         //编译
$ make install     //安装到系统中

测试用例:

// 1. 编写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>用来测试</title>
</head>
<body>
    <p>{{key}}</p>
    <p>{{key}}</p>
    <p>{{key}}</p>
    <p>{{key}}</p>
    <p>{{key}}</p>
</body>
</html>

//2. 编写ctemplate代码
#include <iostream>
#include <string>
#include <ctemplate/template.h>

int main()
{
    std::string in_html = "./test.html";
    std::string value = "渲染后的结果";

    // 形成数据字典,创建字典
    ctemplate::TemplateDictionary root("test"); //unordered_map<> test;
    root.SetValue("key", value);                //test.insert({});
    
    // 创建被渲染网页对象
    ctemplate::Template *tpl = ctemplate::Template::GetTemplate(in_html, ctemplate::DO_NOT_STRIP/*对渲染对象不做其他处理例如空格去除*/);

    // 添加字典数据到网页中
    std::string out_html;
    tpl->Expand(&out_html, &root);

    //完成了渲染
    std::cout << out_html << std::endl;
}

//4. 编译
$ g++ test.cc -o mytest -lctemplate -lpthread
$ ls
mytest  test.cc  test.html
$ ./mytest


//5. 对比结果 

<!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>用来测试</title>
</head>
<body>
    <p>渲染后的结果</p>
    <p>渲染后的结果</p>
    <p>渲染后的结果</p>
    <p>渲染后的结果</p>
    <p>渲染后的结果</p>
</body>
</html>

注意:调用时可能出现找不到动态库的情况,我们需要配置一下环境变量,可以先使用ldd 编译后的文件 查看是否有 not found 。如果出现解决方案:
1.find / -name 'ctemplate : 找到动态库路径
2.vim ~/.bashrc
3. 填入 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: 填入你的动态库路径

使用Ace在线编辑器(直接复制粘贴即可)

<!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>Ace测试</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/
-->
  <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>
  <style>
   * {
      margin: 0;
      padding: 0;
   }
    html,
    body {
      width: 100%;
      height: 100%;
   }
    div .ace_editor {
      height: 600px;
      width: 100%;
   }
  </style>
</head>
<body>
  <div>
    <pre id="code" class="ace_editor"><textarea class="ace_text-
input">#include<iostream>
int main()
{
 std::cout << "hello ace editor" << std::endl;
 return 0;
}</textarea></pre>
    <button class="bt" onclick="submit()">提交代码</button><br/>
  </div>
  <script>
    //初始化对象
    editor = ace.edit("code");
    //设置风格和语言(更多风格和语言,请到github上相应目录查看)
    // 主题大全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.html
    editor.setTheme("ace/theme/monokai");
    editor.session.setMode("ace/mode/c_cpp");
    // 字体大小
    editor.setFontSize(16);
    // 设置默认制表符的大小:
    editor.getSession().setTabSize(4);
    // 设置只读(true时只读,用于展示代码)
    editor.setReadOnly(false);
    // 启用提示菜单
    ace.require("ace/ext/language_tools");
    editor.setOptions({
      enableBasicAutocompletion: true,
      enableSnippets: true,
      enableLiveAutocompletion: true
   });
  </script>
</body>
</html>

在线OJ样例题目

//##questions.list: 题目编号 题目标题 题目难度 题目所在路径 题目时间限制(s) 题目空间限制(kb)##
1 回文数 简单 ./oj_questions/1 1 5000
//##题目描述##
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读,-121 。 从右向左读,121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读,01 。因此它不是一个回文数。
进阶:
你能不将整数转为字符串来解决这个问题吗?
//##题目头部,就是用户 写代码 时,所看到的的代码结构##
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
class Solution {
 public:
  bool isPalindrome(int x) {
   //请将你的代码写在这里
  
   return true;    
 }
};
//##题目尾部,就是测试用例,后台判定用户提交上来的代码过 还是不过 的参考依据##
#ifndef CompileOnline
// 这是为了编写用例的时候有语法提示. 实际线上编译的过程中这个操作是不生效的.
#include "header.cpp"
#endif
///

// 此处约定:
// 1. 每个用例是一个函数
// 2. 每个用例从标准输出输出一行日志
// 3. 如果用例通过, 统一打印 [TestName] ok!
// 4. 如果用例不通过, 统一打印 [TestName] failed! 并且给出合适的提示.
///
void Test1() {
 bool ret = Solution().isPalindrome(121);
 if (ret) {
  std::cout << "Test1 ok!" << std::endl;
} else {
  std::cout << "Test1 failed! input: 121, output expected true, actual false" <<
std::endl;
}
}
void Test2() {
 bool ret = Solution().isPalindrome(-10);
 if (!ret) {
  std::cout << "Test2 ok!" << std::endl;
} else {
  std::cout << "Test2 failed! input: -10, output expected false, actual true" <<
std::endl;
}
}
int main() {
 Test1();
 Test2();
 return 0;
}

安装连接mysql第三方库

【安装路径】

在这里插入图片描述

# 1.上传文件
rz
# 2.压缩文件
tar -xzf 压缩包
# 3. 在oj_server目录下建立软链接
ln -s 库路径 lib
ln -s 库头文件路径 include

mysql workbench

mysql workbench下载路径

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

2023框框

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

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

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

打赏作者

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

抵扣说明:

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

余额充值