实战项目:负载均衡式在线OJ

1技术栈和项目环境

技术栈:
C++ STL 标准库,Boost 准标准库(字符串切割),cpp-httplib (第三方开源网络库),ctemplate (第三方开源前端网页渲染库),jsoncpp (第三方开源序列化、反序列化库),负载均衡设计,多进程、多线程,MySQL C connect()
前端:
vscode,html/css/js/jquery/ajax
项目环境:
Centos 7 云服务器,vscode,Mysql Workbench(图形化MySQL)

2项目宏观结构

结构:
在这里插入图片描述
在这里插入图片描述

编写思路:
1.compile_server
2.oj_server
3.版本一:基于文件版本的OJ
4.前端页面设计
5.版本二:基于mysql版本的OJ

关于leetcode:
题目列表+在线编程功能

3编译与运行服务compile_server

**need:**编译代码,运行代码,得到格式化的相关的结果

3.1编译功能–compiler.hpp

在这里插入图片描述

compiler.hpp

#pragma once

#include <iostream>
#include <unistd.h> //fork
#include "../comm/util.hpp"

#include<sys/wait.h>//waitpid
#include<sys/types.h>
#include <sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include"../comm/log.hpp"//日志

namespace ns_compiler
{
    //引入ns_util工具类(路径拼接)
    using namespace ns_util;
    using namespace ns_log;

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

        //file_name:编译文件名称(无后缀),返回值:编译是否成功!
        //目标:文件名称->./temp/文件名称.cpp
        //目标:文件名称->./temp/文件名称.exe
        //目标:文件名称->./temp/文件名称.stderr
        static bool Compile(const std::string &file_name)
        {
            pid_t child=fork();
            if(child<0) {
                LOG(ERROR)<<"内部错误,创建子进程失败!"<<std::endl;
                return false; //创建子进程失败 
            }
            else if (child==0)//子进程:调用编译器,完成对代码的编译工作
            {
            	umask(0);//将权限清零,这样设置的权限比较安全
                int _stderr = open(PathUtil::CompilerError(file_name).c_str(),O_CREAT|O_WRONLY,0644);
                if(_stderr<0)
                {
                    LOG(WARNING)<<"没有成功形成.stderr文件"<<std::endl;
                    //打开文件失败
                    exit(1);
                }
                //重定向标准错误到_stderr
                dup2(_stderr,2);
                //程序替换,并不影响进程的文件描述符表

                // g++ -o target src -std=c++11
                execlp("g++","g++","-o",PathUtil::Exe(file_name).c_str(),\
                    PathUtil::Src(file_name).c_str(),nullptr);//要以nullptr结束
                LOG(ERROR)<<"启动编译器g++失败"<<std::endl;
                exit(2);
            }
            else//父进程
            {
                waitpid(child,nullptr,0);

                //编译是否成功,判断可执行程序是否存在
                if(FileUtil::IsFileExists(PathUtil::Exe(file_name)))
                {
                    LOG(INFO)<<PathUtil::Src(file_name)<<"编译成功!"<<std::endl;
                    return true;
                }
            }

            LOG(ERROR)<<"编译失败,没有形成可执行程序"<<std::endl;
            return false;
        }
    };
}

日志

#pragma once
//日志

#include <iostream>
#include<string>

#include"util.hpp"

namespace ns_log
{
    using namespace ns_util;
    //日志等级,枚举从零开始
    enum{
        INFO,//常规的,没有任何错误信息,只是一些提示信息
        DEBUG,//调试时的调试日志
        WARNING,//告警,不影响后续使用
        ERROR,//错误,这个用户的请求不能继续了
        FATAL,//不光这个用户,整个系统都无法使用,引起系统整个出错
        //补充:如果正常工作中出现ERROR,FATAL那么就需要运维来解决
    };

    //level:错误等级;file_name:错误文件;line:错误行数
    //日志非常常用,所以建议设置为inline 
    inline std::ostream &Log(const std::string &level,const std::string &file_name,int line)
    {
        std::string message ="[" + level+ "]["+ file_name+"][" + std::to_string(line)+"]";
        //日志时间戳
        message=message+ "[" + TimeUtil::GetTimeStamp()+"]";

        //cout本质内部是包含缓冲区的
        std::cout<<message;//不用endl进行刷新,此时message就会暂存在cout中
        return std::cout;
    }

    #define LOG(level) Log(#level,__FILE__,__LINE__)
}

工具util.hpp

在这里插入图片描述

#pragma once
#include <iostream>
#include <string>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> //stat
#include <sys/time.h>//获取时间

namespace ns_util
{
    //全局路径
    const std::string temp_path = "./temp/";

    //对路径的操作
    class PathUtil
    {
    public:
        //构架源文件路径+后缀的完整文件名
        //目标:文件名称->./temp/文件名称.cpp
        static std::string Src(const std::string &file_name)
        {
            return temp_path + file_name + ".cpp";
        }

        //构建可执行程序的完整路径+后缀名成
        //目标:文件名称->./temp/文件名称.exe
        static std::string Exe(const std::string &file_name)
        {
            return temp_path + file_name + ".exe";
        }
        //构建该程序对应的标准错误完整的路径+后缀名
        //目标:文件名称->./temp/文件名称.compile_error        
        static std::string CompilerError(const std::string &file_name)
        {
            return temp_path + file_name + ".compile_error";
        }
    };


    //对文件(path)的操作方法
    class FileUtil
    {
    public:
        static bool IsFileExists(std::string path_name)
        {
            //方法一:查看文件是否能够正常打开
            //方法二:stat(文件路径,文件属性(可以自己选择自己需要的属性));
            struct stat st;
            if (stat(path_name.c_str(), &st) == 0)
                return true; //获取文件成功->文件存在
            return false;
        }
    };

    //时间相关工具
    class TimeUtil{
    public:
        //获取系统时间
        static std::string GetTimeStamp()
        {
            struct timeval _time;
            gettimeofday(&_time,nullptr);
            return std::to_string(_time.tv_sec);
        }
    };
}

Makefile

Compile_server:compile_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf Compile_server
	rm -rf ./temp/code.exe
	rm -rf ./temp/code.compiler_error

编译功能测试

1.在temp文件下创建一个code.cpp
2.在code.cpp内写下一段代码(正确的)
3.在compile_server.cc内调用

#include "compiler.hpp"

using namespace ns_compiler;

int main(int argc, char const *argv[])
{
    std::string code="code";
    Compiler::Compile(code);
    return 0;
}

4.make
5.执行Compile_server
6.在temp内执行code.exe
7.在code.cpp内写下一段代码(错误的)
8.执行3456,观察结果
9.查看code.compile_error(错误时,里面会包含错误信息,正确时,里面没有任何信息)

3.2运行功能–runner.hpp

Run

程序运行
1.代码跑完,结果正确
2.代码跑完,结果不正确
3.代码没跑完,异常了
Run不需要考虑代码跑完,结果是否正确,测试用例决定的;我们只考虑:是否正确运行完毕

问题:可执行程序是谁?
一个程序在默认启动的时候
标准输入: 不考虑用户自测
标准输出:程序运行完成,输出结果是什么
标准错误:运行时错误信息

代码:

runner.hpp

#pragma once

#include <iostream>
#include <string>
#include <unistd.h> //fork
#include "../comm/log.hpp"
#include "../comm/util.hpp"

//open
#include<sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>

#include<sys/wait.h>//waitpid

namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;

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

        //指名文件名即可,不需要带路径,带后缀
        //返回值>0:程序异常了,退出时收到了信号,返回值就是对应的信号编号
        //返回值==0:正常运行完毕,结果保存至对应的临时文件中
        //返回值<0:内部错误(打开文件失败,创建子进程失败)
        static int Run(const std::string &file_name)
        {
            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(_stdin_fd<0 || _stdout_fd<0 || _stderr_fd<0)
            {
                LOG(ERROR)<<"运行时打开标准文件失败!"<<std::endl;
                return -1;//打开文件失败 
            }

            pid_t pid = fork();
            if (pid < 0) //创建失败
            {
                LOG(ERROR)<<"运行时创建子进程失败!"<<std::endl;
                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);

                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)<<"运行完毕,info:"<<(status&0x7F)<<std::endl;
                return status&0x7F;
            }
        }
    };
}

ns_util::PathUtil新增

        //运行时需要拼接的文件
        static std::string Stdin(const std::string &file_name)
        {
            return temp_path + file_name + ".stdin";
        }
        static std::string Stdout(const std::string &file_name)
        {
            return temp_path + file_name + ".stdout";
        }
        //构建该程序对应的标准错误完整的路径+后缀名
        //目标:文件名称->./temp/文件名称.stderr
        static std::string Stderr(const std::string &file_name)
        {
            return temp_path + file_name + ".stderr";
        }

测试:

Makefile

Compile_server:compile_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf Compile_server
	
	rm -rf ./temp/code.exe
	rm -rf ./temp/code.compiler_error
	rm -rf ./temp/code.stdin
	rm -rf ./temp/code.stdout

在compiler_server路径下
1.make
2…/Compile_server
3.查看是否包含
在这里插入图片描述
并查看这些文件内时候有对应内容
例如:code.stdout内包含了输出内容

运行新问题 和 解决

当用户提交的代码是恶意代码:占用大量空间,时间复杂度极高,对程序不友好

引入:setrlimit();
对某个程序进行约束,一旦程序违反这个约束,程序就会直接返回,并发送信号量

runner.hpp

#pragma once

#include <iostream>
#include <string>
#include <unistd.h> //fork
#include "../comm/log.hpp"
#include "../comm/util.hpp"

//open
#include<sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>

#include<sys/wait.h>//waitpid

//setrlimit();
#include<sys/time.h>
#include<sys/resource.h>>


namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;

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

        //设置进程所占用的资源
        static void SetProcLimit(int _cpu,int _mem)
        {
            //设置cpu时长
            struct rlimit cpu_rlimit;
            cpu_rlimit.rlim_max=RLIM_INFINITY;
            cpu_rlimit.rlim_cur=_cpu;
            setrlimit(RLIMIT_CPU,&cpu_rlimit);

            //设置内存大小
            struct rlimit mem_rlimit;
            mem_rlimit.rlim_max=RLIM_INFINITY;
            mem_rlimit.rlim_cur=_mem*1024;
            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)
        {
            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(_stdin_fd<0 || _stdout_fd<0 || _stderr_fd<0)
            {
                LOG(ERROR)<<"运行时打开标准文件失败!"<<std::endl;
                return -1;//打开文件失败 
            }

            pid_t pid = fork();
            if (pid < 0) //创建失败
            {
                LOG(ERROR)<<"运行时创建子进程失败!"<<std::endl;
                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)<<"运行完毕,info:"<<(status&0x7F)<<std::endl;
                return status&0x7F;
            }
        }
    };
}

3.3编译并运行功能–compile_run.hpp

needs

1.适配用户请求,定制通信协议
2.正确的调用compile and run
3.形成唯一文件名

编译服务随时可能被多人请求,必须保证传递上来的code,形成源文件名称的时候,要具有唯一性,要不然多个用户之间会互相影响

compile_run.hpp

#pragma once

#include "compiler.hpp"
#include "runner.hpp"

#include <jsoncpp/json/json.h>

#include "../comm/log.hpp"
#include "../comm/util.hpp"

#include <signal.h>

namespace ns_compile_and_run
{
    using namespace ns_compiler;
    using namespace ns_runner;

    using namespace ns_log;
    using namespace ns_util;

    class CompileAndRun
    {
    public:
        // sign>0:进程收到了信号导致异常崩溃
        // sign<0:整个过程非运行报错
        // sign=0:整个过程全部完成
        static std::string CodeToDesc(int sign,std::string &file_name)
        {
            std::string desc;
            switch (sign)
            {
            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:
                desc = "内存超过范围";
                break;
            case SIGXCPU:
                desc = "cpu使用超时!";
                break;
            case SIGFPE:
                desc = "浮点数溢出!";
                break;
            default:
                desc = "未知错误:" + std::to_string(sign);
                break;
            }
            return desc;
        }

        // in_json:code用户提交的代码和input用户给自己提交的代码对应的输入
        //               cpu_limit时间要求,mem_limit空间要求
        // out_json:status状态码和reason请求结果
        //                 stdout:程序运行结果 stderr:程序运行错误结果
        static void Start(const std::string &in_json, std::string *out_json)
        {
            //解析in_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();

            // out_json
            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 = FileUtil::UniqFileName();

            //将code写到file_name.cpp中,形成临时src文件
            if (!FileUtil::writeFile(PathUtil::Src(file_name), code))
            {
                status_code = -2;
                goto END;
                //没有成功写入到文件中
            }

            if (!Compiler::Compile(file_name))
            {
                status_code = -3;
                goto END;
                //代码编译失败
            }

            run_result = Runner::Run(file_name, cpu_limit, mem_limit);
            if (run_result < 0)
            {
                status_code = -2;
                //没有成功写入到文件中
            }
            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;
            }

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

util.hpp—>FileUtil(对文件(path)的操作方法)


    //对文件(path)的操作方法
    class FileUtil
    {
    public:
        //查看文件是否存在
        static bool IsFileExists(const std::string &path_name)
        {
            //方法一:查看文件是否能够正常打开
            //方法二:stat(文件路径,文件属性(可以自己选择自己需要的属性));
            struct stat st;
            if (stat(path_name.c_str(), &st) == 0)
                return true; //获取文件成功->文件存在
            return false;
        }

        //形成一个唯一的文件名(形成的文件名没有目录没有后缀)
        //唯一性:毫秒级别的时间戳+原子性递增的唯一值
        static std::string UniqFileName()
        {
            static std::atomic_uint id(0);
            id++;
            std::string ms = TimeUtil::GetTimeMS();
            std::string uniq_id = std::to_string(id);
            return ms + uniq_id;
        }

        //将code写到target中,形成临时src文件
        static bool writeFile(const std::string &target, const std::string &code)
        {
            std::ofstream out(target);
            if (!out.is_open())
            {
                return false;
            }
            out.write(code.c_str(), code.size());
            out.close();
            return true;
        }

        //将文件内容读取
        // target文件名,content内容保存地址,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不保存行分隔符
            // getline内部重载了强制类型转化
            while (std::getline(in, line))
            {
                (*content) += line;
                (*content) += (keep ? "\n" : "");
            }
            in.close();
            return true;
        }
    };

测试:

测试代码1

#include"compile_run.hpp"
using namespace ns_compile_and_run;

int main(int argc, char const *argv[])
{
    //通过http让client给我们上传一个json string
    
    //模拟客户端请求的json串
    std::string in_json;
    Json::Value in_value;
    in_value["code"]=R"(#include<iostream>
    int main(){
        std::cout<<"ceshi!"<<std::endl;
        return 0;
    })";
    in_value["input"]="";
    in_value["cpu_limit"]=1;
    in_value["mem_limit"]=1024*30;

    Json::FastWriter writer;
    in_json=writer.write(in_value);

    std::string out_json;

    CompileAndRun::Start(in_json,&out_json);;
    std::cout<<out_json<<std::endl;
    return 0;
}

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

测试代码二
在这里插入图片描述

测试代码三

在这里插入图片描述
测试代码四
在这里插入图片描述

新问题–大量临时文件

以前我们的解决方法:
Makefile

Compile_server:compile_server.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp

.PHONY:clean
clean:
	rm -rf Compile_server
	
	rm -rf ./temp/*

在CompileAndRun末尾调用这个即可

       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 _compiler_error = PathUtil::CompilerError(file_name);
            if(FileUtil::IsFileExists(_compiler_error)) unlink(_compiler_error.c_str());

            std::string _execute = PathUtil::Exe(file_name);
            if(FileUtil::IsFileExists(_execute)) unlink(_execute.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());
        }

3.4形成网络服务

关于httplib的安装,使用和注意事项,我在我的上一个项目:基于boost的搜索引擎中的第八个模块
这个项目只需要将httplib.h移动到comm中
关于使用:百度即可

遇见问题

问题一:
如果遇见下面错误,在makefile中添加 -lpthread 即可
/tmp/ccMFzHXI.o: In function std::thread::thread<httplib::ThreadPool::worker>(httplib::ThreadPool::worker&&)': compile_server.cc:(.text._ZNSt6threadC2IN7httplib10ThreadPool6workerEJEEEOT_DpOT0_[_ZNSt6threadC5IN7httplib10ThreadPool6workerEJEEEOT_DpOT0_]+0x21): undefined reference to pthread_create’
/opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7/libstdc++_nonshared.a(thread48.o): In function std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())': (.text._ZNSt6thread15_M_start_threadESt10unique_ptrINS_6_StateESt14default_deleteIS1_EEPFvvE+0x11): undefined reference to pthread_create’
/opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7/libstdc++_nonshared.a(thread48.o): In function std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)())': (.text._ZNSt6thread15_M_start_threadESt10shared_ptrINS_10_Impl_baseEEPFvvE+0x5f): undefined reference to pthread_create’
collect2: error: ld returned 1 exit status
make: *** [Compile_server] Error 1

问题二:
因为我们的代码比较复杂(还行)
主要是这个httplib所占用空间太多,从而导致系统运行不成功
解决:重启vscode,即可
重启vscode的时候,如果gcc不是默认设置高版本的,就需要重新恢复到原来的版本的,(如果不在vscode上进行编译服务,那就需要更新gcc版本)

问题三:
网络ip加端口号访问不了网站
解决:
1.在云服务器上重新打开端口号
2.在Linux上查看端口号是否被打开,查看那些被打开,重启防火墙
开放8080端口
firewall-cmd --permanent --zone=public --add-port=8080/tcp
查询8080端口开放情况,若返回success,则为开放成功
firewall-cmd --zone=public --query-port=8080/tcp
重启防火墙
firewall-cmd --reload

代码

#include "compile_run.hpp"
using namespace ns_compile_and_run;

#include "../comm/httplib.h"
using namespace httplib;

void Usage(std::string proc)
{
    std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
}

//  ./compile_server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    Server svr;
    svr.Post("/compile_and_run", [](const Request &req, Response &resp)
             {
        // 用户请求的服务正文:json string
        std::string in_json = req.body;
        std::string out_json;
        if(!in_json.empty()){
            CompileAndRun::Start(in_json, &out_json);
            resp.set_content(out_json, "application/json;charset=utf-8");
        } });

    svr.listen("0.0.0.0", atoi(argv[1])); //启动http服务
}

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

即:建立一个小型网站

needs

  1. 首页:题目列表
  2. 编辑区域页面
  3. 提交判题功能

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

oj_server.cc–用户请求的服务路由功能

#include <iostream>
#include "../comm/httplib.h"
using namespace httplib;

int main()
{
    //用户请求的服务路由功能
    Server svr;
    // 获取所有的题目列表(首页+题目列表)
    svr.Get("/all_questions", [](const Request &req, Response &resp)
            { resp.set_content("首页", "text/plain;charset=utf-8"); });
    svr.set_base_dir("./wwwroot");


    // 用户要根据题目编号,获取题目内容
    svr.Get(R"(/questions/(\d+))", [](const Request &req, Response &resp)
            {
        std::string number=req.matches[1];
        resp.set_content("这是:"+number, "text/plain;charset=utf-8"); });


    // 用户提交代码,使用我们的判题功能:(1.每道题的测试用例,2.compile_and_run)
    svr.Get(R"(/judge/(\d+))", [](const Request &req, Response &resp)
            {
        std::string number=req.matches[1];
        resp.set_content("这是:"+number+" 的判题", "text/plain;charset=utf-8"); });


    svr.listen("0.0.0.0", 8080);
    return 0;
}

设计题库版本一:文件版本–questions

needs

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

两批文件构成:
第一个:questions.list:题目列表(不需要出现题目的内容)
第二个:题目的描述,预设值的代码(hander.cpp),测试用例代码(tail.cpp)
通过文件的编号,产生关联的

在这里插入图片描述

题库代码举例:

desc.txt

求一个数组中最大的值

示例 1:
输入: [1,2,3,4,5,6,7]
输出: 7

示例 2:
输入: [-1,1,2,3,4,5,6,7,9]
输出: 9

header.hpp

#include <iostream>
#include <vector>
using namespace std;

class Solution
{
public:
    int FindMax(vector<int>& v)
    {
        
        return true;
    }
};

tail.hpp

#ifndef CompileOnline
// 这是为了编写用例的时候有语法提示. 实际线上编译的过程中这个操作是不生效的.
#include "header.cpp"
#endif

void Test1()
{
    vector<int> v={1,2,3,4,5,6,7};
    int ret = Solution().FindMax(v);
    if (ret==7)
    {
        std::cout << "Test1 ok!" << std::endl;
    }
    else
    {
        std::cout << "测试用例: {1,2,3,4,5,6,7} 未通过" << std::endl;
    }
}
void Test2()
{
    vector<int> v={-1,1,2,3,4,5,6,7,9};

    int ret = Solution().FindMax(v);
    if (ret==9)
    {
        std::cout << "Test2 ok!" << std::endl;
    }
    else
    {
        std::cout << "测试用例: {-1,1,2,3,4,5,6,7,9} 未通过" << std::endl;
    }
}
int main()
{
    Test1();
    Test2();
    return 0;
}

oj_model.hpp-逻辑控制模块

和数据进行交互,对外提供访问数据的接口
根据题目.list文件,加载所有的题目信息到内存中
OJ需要的是 header.hpp+用户写的内容 + tail.cpp

#pragma once
//根据题目.list文件,加载所有的题目信息到内存中

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>

#include "../comm/log.hpp" //日志
#include <cassert> //assert
#include <fstream>
#include "../comm/util.hpp"//字符串切分
#include<cstdlib>//atoi


namespace ns_model
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;

    //题目信息
    struct Question
    {
        string number; //题目编号
        string title;  //题目标题
        string star;   //难度
        int cpu_limit; //时间要求
        int mem_limit; //空间要求
        string desc;   //题目描述
        string header; //预设代码
        string tail;   //测试用例
    };

    const std::string question_list = "./questions/questions.list";
    const std::string question_path="./questions/";
    
    class Model
    {
    private:
        unordered_map<string, Question> questions;

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

        //加载配置文件:questions/question.list+题目编号
        bool LoadQuestionList(const string &question_list)
        {
            ifstream in(question_list);
            if(!in.is_open())
            {
                LOG(FATAL)<<"加载题库失败!"<<std::endl;
                return false;
            }
            string line;
            while(getline(in,line))
            {
                vector<string> v;
                StringUtil::SplitString(line,&v," ");
                if(v.size()!=5)
                {
                    //切分失败
                    LOG(WARNING)<<"加载部分题目失败!"<<std::endl;
                    continue;
                }
                Question q;
                q.number=v[0];
                q.title=v[1];
                q.star=v[2];
                q.cpu_limit=atoi(v[3].c_str());
                q.mem_limit=atoi(v[4].c_str());

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

                FileUtil::ReadFile(path+"desc.txt",&(q.desc),true);
                FileUtil::ReadFile(path+"header.cpp",&(q.header),true);
                FileUtil::ReadFile(path+"tail.cpp",&(q.tail),true);
                
                questions.insert({q.number,q});
            }
            LOG(INFO)<<"加载题库成功!"<<std::endl;
            in.close();
            return true;

        }

        //获取所有题目
        bool GetALLQuestions(vector<Question> *out)
        {
            if (questions.size() == 0)
            {
                LOG(ERROR) << "用户获取题库失败" << std::endl;
                return false;
            }
            for(const auto &q : questions){
                out->push_back(q.second); 
            }
            return true;
        }

        //获取一个题目
        bool GetOneQuestion(const string &number, Question *q)
        {
            const auto &it = questions.find(number);
            if (it == questions.end())
            {
                //没有找到
                return false;
            }
            (*q) = it->second;
            return true;
        }

        ~Model() {}
    };
}

oj_view.hpp–渲染网页

ctemplate安装引入

ctemplate最初被称为谷歌模板,因为它起源于用于谷歌搜索结果页面的模板系统,

功能简单介绍:
in_html:初始网页,
out_html:最终网页
我们有很多的题库,每个题的网页相差无几,只有里面的内容是不一样的,而整体的网页结构是一模一样的
我们在in_html写下网页大纲,在网页不同部分写下{{key}},经过ctemplate,就可以根据每个题将网页设置为自己所需要的网页
(具体请看测试)

ctemplate安装

温馨提醒:这个是2022-8-19日可以安装的

git clone https://hub.fastgit.xyz/OlafvdSpek/ctemplate.git

./autogen.sh---->ll查看是否生成了./configure
./configure ----->ll查看是否生成了 makefile
sudo make---->sudo建议加上,尽量保证 make时,不要出现报错信息
如果make有报错信息,那么重复的 ./autogen.sh ./configure 然后make
sudo make install—> sudo建议加上,尽量保证 make时,不要出现报错信息
如果make install有报错信息,那么重复的 ./autogen.sh ./configure 然后make

无论你重复多少次./autogen.sh ./configure make sudo make install,
只要你的执行过程中没有报错即可

这个时候进行测试
如果出现
在这里插入图片描述
那么就是libctemolate.so.3这个库找不到

解决
跳转至这个地方,查看是否有这些文件(正常情况下是有的)
cd
将这些文件cp到
在这里插入图片描述

测试

<!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>
</body>
</html>

#include <iostream>
#include <string>
#include<ctemplate/template.h>
int main()
{
    std::string in_html = "./test.html";
    std::string value = "sakeww!";

    // 形成数据字典
    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;
}

有如下结果则为正确
在这里插入图片描述

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

oj_control.hpp–负载均衡+核心业务逻辑控制

核心业务逻辑控制 大纲ns_control::Control

    //核心业务逻辑控制器
    class Control
    {
    private:
        Model _model;//后台数据
        View _view;//html功能

    public:
        Control(){}
        ~Control(){}
    
    public:
        //根据题目数据构建网页
        bool AllQuestions(string *html)
        {
            //Question是一个结构体
            vector<struct Question> all;
            if(_model.GetALLQuestions(&all))
            {
                //将拿到的所有题目数据构建成网页
                _view.AllExpandHtml(all,html);
            }
            else
            {
                *html="获取题目失败,形成题目列表失败!";
                return false;
            }
            return true;
        }

        //拼接网页
        bool Question(const string &number,string *html)
        {
            struct Question q;
            if(_model.GetOneQuestion(number,&q))
            {
                // 构建指定题目信息成功,将所有的题目数据构建成网页
                _view.OneExpandHtml(q,html);
            }
            else{
                *html="获取题目:"+number+"失败!";
                return false;
            }
            return true;
        }
        

        void Judge(const std::string in_json,std::string *out_json)
        {
            // 对in_json进行反序列化,得到用户提交的代码-》input
            // 重新拼接用户代码+测试用例代码=新的代码
            // 选择负载最低的主机,要进行差错处理
            // 发起http请求,得到结果
            // 将结果赋值给out_json
        }
    };

负载均衡 大纲–ns_control::Machine

    //服务的主机
    class Machine
    {
    public:
        string ip;//编译服务的ip
        int port;//编译服务的端口
        uint64_t load;//编译服务的负载

        //保护负载
        mutex *mtx;
    
    public:
        Machine():ip(""),port(0),load(0),mtx(nullptr)
        {}
        ~Machine(){}
    };


    //主机所在位置
    const string service_machine="./conf/service_machine.conf";
    //负载均衡
    class LoadBlance
    {
    private:
        //每一台主机都有自己的下标-》id
        std::vector<Machine> machines;//能够给我们提供编译服务的所有主机
        std::vector<int> online;//所有在线的主机id
        std::vector<int> offline;//所有离线的主机id

    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
        }
    	
    	//加载配置文件
        bool LoadConf(const std::string &machine_list)
        {

        }

        //智能选择
        bool SmartChoice()
        {

        }

        //离线主机
        void OfflineMachine()
        {

        }

        //上线主机
        void OnlineMachine()
        {
            
        }

        ~LoadBlance()
        {}
    };

control.hpp–增加负载均衡

#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
#include <mutex>
#include <cassert>
#include <jsoncpp/json/json.h>
#include "oj_model.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"

#include "oj_view.hpp"

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

    //服务的主机
    class Machine
    {
    public:
        string ip;     //编译服务的ip
        int port;      //编译服务的端口
        uint64_t load; //编译服务的负载

        //保护负载
        mutex *mtx;

    public:
        Machine() : ip(""), port(0), load(0), mtx(nullptr)
        {}
        ~Machine() {}

        // 提升主机负载
        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 string service_machine = "./conf/service_machine.conf";
    //负载均衡
    class LoadBlance
    {
    private:
        //每一台主机都有自己的下标-》id
        std::vector<Machine> machines; //能够给我们提供编译服务的所有主机
        std::vector<int> online;       //所有在线的主机id
        std::vector<int> offline;      //所有离线的主机id
        mutex mtx;                     //保证LoadBlance 主机数据安全
    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
            LOG(INFO) << "加载:" << service_machine << "成功!" << std::endl;
        }

        //加载配置文件
        bool LoadConf(const std::string &machine_list)
        {
            std::ifstream in(machine_list);
            if (!in.is_open())
            {
                LOG(FATAL) << "加载配置文件:" << machine_list << "失败!" << std::endl;
                return false;
            }

            std::string line;
            while (std::getline(in, line))
            {
                vector<string> v;
                StringUtil::SplitString(line, &v, ":");
                if (v.size() != 2)
                {
                    //我们只设置了三个端口,所以切分没有什么问题
                    //但是,一般情况下,这里可能会写一些注释等别的语句,这些是不需要切分的
                    LOG(WARNING) << "切分" << line << "失败!" << std::endl;
                    continue;
                }
                Machine m;
                m.ip = v[0];
                m.port = atoi(v[1].c_str());
                m.load=0;
                m.mtx = new mutex();

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

            in.close();
            return true;
        }

        //智能选择
        // id:主机id m:需要获取的详细信息
        bool SmartChoice(int *id, Machine **m)
        {
            mtx.lock();
            int online_num = online.size(); //当前主机在线个数
            if (online_num == 0)
            {
                //所有主机均已经离线
                mtx.unlock();
                LOG(FATAL) << "所有的后端编译主机已经离线" << std::endl;
                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++)
            {
                if (min_load > machines[online[i]].Load())
                {
                    min_load = machines[online[i]].Load();
                    *id = online[i];
                    *m = &machines[online[i]];
                }
            }
            mtx.unlock();
            return true;
        }

        //离线主机
        void OfflineMachine(int id)
        {
            mtx.lock();
            for (auto iter = online.begin(); iter != online.end(); iter++)
            {
                if (*iter == id)
                {
                    machines[id].ResetLoad();
                    //要离线的主机已经找到啦
                    online.erase(iter);
                    offline.push_back(id);
                    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";
        }

        //显示在线和离线主机
        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:
        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)
        {
            struct Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 构建指定题目信息成功,将所有的题目数据构建成网页
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "获取题目:" + number + "失败!";
                return false;
            }
            return true;
        }

        // number:题号 in_json:客户提交的代码 out_json:返回结果
        void Judge(const std::string number, const std::string in_json, std::string *out_json)
        {
            // 根据题号,获取题目细节
            struct Question q;
            _model.GetOneQuestion(number, &q);

            // 对in_json进行反序列化,得到用户提交的代码-》input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString(); //用户提交的代码

            // 重新拼接用户代码+测试用例代码=新的代码
            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);

            // 选择负载最低的主机,要进行差错处理
            // 规则:一直选择,直到主机可用,否则就是,全部挂掉(不需要客户知道)
            while (true)
            {
                int id = 0;
                Machine *m = nullptr;
                if (!_load_blance.SmartChoice(&id, &m))
                {
                    break;
                }
                LOG(INFO) << "选择主机:" << id << "成功,主机详情:" << m->ip << ":" << m->port << std::endl;

                // 发起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"))
                {
                    // 将结果赋值给out_json
                    if (res->status == 200)
                    {
                        *out_json = res->body;
                        m->DecLoad();
                        LOG(INFO) << "请求编译和运行服务成功!" << std::endl;
                        break;
                    }
                }
                else
                {
                    //请求失败
                    LOG(ERROR) << "当前请求的主机id:" << id << "详情:" << m->ip << ":" << m->port << "可能已经离线" << std::endl;
                    _load_blance.OfflineMachine(id); //离线之后,会将负载清零
                    _load_blance.ShowMachines();     //显示所有在线和离线主机列表
                }
            }
        }
    };
}

最终测试:oj_server.cc

#include <iostream>
#include <signal.h>

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

using namespace httplib;
using namespace ns_control;

static Control *ctrl_ptr = nullptr;

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

int main()
{
    //使用信号进行主机上线
    signal(SIGQUIT, Recovery);

    Server svr;

    Control ctrl;
    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");
    });

    // 用户要根据题目编号,获取题目的内容
    svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp){
        std::string number = req.matches[1];
        std::string html;
        ctrl.Question(number, &html);
        resp.set_content(html, "text/html; charset=utf-8");
    });

    // 用户提交代码,进行判题
    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", 8080);
    return 0;
}

5项目延伸:使用数据库

建表相关操作

使用workbench建立表格和插入数据

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

其中number 和 desc 不能直接写入创建语句中,可以先这样写
workbench支持更改列名
在这里插入图片描述
在这里插入图片描述

更改oj_model_db.hpp内容

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>

#include "../comm/log.hpp" //日志
#include <cassert>         //assert
#include <fstream>
#include "../comm/util.hpp" //字符串切分
#include <cstdlib>          //atoi

#include "include/mysql.h"

namespace ns_model
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;

    //题目信息
    struct Question
    {
        string number; //题目编号
        string title;  //题目标题
        string star;   //难度
        string desc;   //题目描述
        string header; //预设代码
        string tail;   //测试用例
        int cpu_limit; //时间要求
        int mem_limit; //空间要求
    };

    const std::string host = "127.0.0.1";
    const std::string user = "fengyin";
    co\nst std::s\tring pas\swd = "XXX";//这里输入你的密码即可,忽略\
    //CSDN会进行password检测
    const std::string db = "OJ";//数据库名
    const int port = 3306;

    class Model
    {
    public:
        Model()
        {}

        //根据sql语句,返回需要的Question
        bool QueryMySql(const std::string &sql, vector<Question> *out)
        {
            // 创建mysql句柄
            MYSQL *my = mysql_init(nullptr);

            // 连接数据库
            if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0))
            {
                LOG(FATAL) << "连接数据库失败!"<<std::endl;
                return false;
            }

            // 设置该链接的编码格式
            mysql_set_character_set(my, "utf8");

            LOG(INFO) << "连接数据库成功!"<<std::endl;

            // 执行sql语句
            if (0 != mysql_query(my, sql.c_str()))
            {
                LOG(WARNING) << sql << " 执行失败!" <<std::endl;
                return false;
            }

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

            // 分析结果
            int rows = mysql_num_rows(res);
            int cols = mysql_num_fields(res);

            struct Question q;

            for (int i = 0; i < rows; i++)
            {
                MYSQL_ROW row = mysql_fetch_row(res);//返回一个数组
                q.number = row[0];
                q.title = row[1];
                q.star = row[2];
                q.desc = row[3];
                q.header = row[4];
                q.tail = row[5];
                q.cpu_limit = atoi(row[6]);
                q.mem_limit = atoi(row[7]);

                out->push_back(q);
            }
            
            // 释放结果空间
            free(res);
            // 关闭mysql连接
            mysql_close(my);

            return true;
        }

        bool GetAllQuestions(vector<Question> *out)
        {
            std::string sql = "select * from oj_questions";
            return QueryMySql(sql, out);
        }

        bool GetOneQuestion(const std::string &number, Question *q)
        {
            std::string sql = "select * from oj_questions where number=";
            sql += number;
            vector<Question> result;
            if (QueryMySql(sql, &result))
            {
                if (result.size() == 1)
                {
                    *q = result[0];
                    return true;
                }
            }
            return false;
        }

        ~Model() {}
    };
}

6.补充:

所有代码文件图解

在这里插入图片描述

顶层makefile与创建发布版本output

# 编译
.PHONY: all
all:
	@cd compile_server;\
	make;\
	cd -;\
	cd oj_server;\
	make;\
	cd -;

# 创建output文件,存放发布版本
.PHONY:output
output:
	@mkdir -p output/compile_server;\
	mkdir -p output/oj_server;\
	cp -rf compile_server/Compile_server output/compile_server;\
	cp -rf compile_server/temp output/compile_server;\
	cp -rf oj_server/conf output/oj_server;\
	cp -rf oj_server/lib output/oj_server;\
	cp -rf oj_server/questions output/oj_server;\
	cp -rf oj_server/template_html output/oj_server;\
	cp -rf oj_server/wwwroot output/oj_server;\
	cp -rf oj_server/OJ_server output/oj_server;

# 删除所有可执行程序
.PHONY:clean
clean:
	@cd compile_server;\
	make clean;\
	cd -;\
	cd oj_server;\
	make clean;\
	cd -;\
	rm -rf output;

gitee

https://gitee.com/sakeww/linux_code/tree/master/item_oj
  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 19
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值