• 实战项目:负载均衡式在线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 
    #include  //fork
    #include "../comm/util.hpp"
    
    #include//waitpid
    #include
    #include 
    #include
    #include
    #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;
            }
        };
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    日志

    #pragma once
    //日志
    
    #include 
    #include
    
    #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__)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    工具util.hpp

    在这里插入图片描述

    #pragma once
    #include 
    #include 
    
    #include 
    #include 
    #include  //stat
    #include //获取时间
    
    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);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    编译功能测试

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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 
    #include 
    #include  //fork
    #include "../comm/log.hpp"
    #include "../comm/util.hpp"
    
    //open
    #include
    #include 
    #include
    
    #include//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;
                }
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    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";
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    测试:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    运行新问题 和 解决

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

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

    runner.hpp

    #pragma once
    
    #include 
    #include 
    #include  //fork
    #include "../comm/log.hpp"
    #include "../comm/util.hpp"
    
    //open
    #include
    #include 
    #include
    
    #include//waitpid
    
    //setrlimit();
    #include
    #include>
    
    
    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;
                }
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    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 
    
    #include "../comm/log.hpp"
    #include "../comm/util.hpp"
    
    #include 
    
    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);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    测试:

    测试代码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
        int main(){
            std::cout<<"ceshi!"<;
        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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

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

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

    测试代码三

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

    新问题–大量临时文件

    以前我们的解决方法:
    Makefile

    Compile_server:compile_server.cc
    	g++ -o $@ $^ -std=c++11 -ljsoncpp
    
    .PHONY:clean
    clean:
    	rm -rf Compile_server
    	
    	rm -rf ./temp/*
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在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());
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.4形成网络服务

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

    遇见问题

    问题一:
    如果遇见下面错误,在makefile中添加 -lpthread 即可
    /tmp/ccMFzHXI.o: In function std::thread::thread(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 >, 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, 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服务
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

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

    即:建立一个小型网站

    needs

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

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

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

    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    设计题库版本一:文件版本–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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    header.hpp

    #include 
    #include 
    using namespace std;
    
    class Solution
    {
    public:
        int FindMax(vector<int>& v)
        {
            
            return true;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    oj_model.hpp-逻辑控制模块

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

    #pragma once
    //根据题目.list文件,加载所有的题目信息到内存中
    
    #include 
    #include 
    #include 
    #include 
    
    #include "../comm/log.hpp" //日志
    #include  //assert
    #include 
    #include "../comm/util.hpp"//字符串切分
    #include//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() {}
        };
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

    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
    
    • 1

    ./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到
    在这里插入图片描述

    测试

    
    
    
        
        
        
        测试标题
    
    
        

    {{key}}

    {{key}}

    {{key}}

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    #include 
    #include 
    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

    #pragma once
    
    #include 
    #include 
    #include 
    
    #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);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    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
            }
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    负载均衡 大纲–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()
            {}
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    control.hpp–增加负载均衡

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #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();     //显示所有在线和离线主机列表
                    }
                }
            }
        };
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334

    最终测试:oj_server.cc

    #include 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    更改oj_model_db.hpp内容

    #pragma once
    
    #include 
    #include 
    #include 
    #include 
    
    #include "../comm/log.hpp" //日志
    #include          //assert
    #include 
    #include "../comm/util.hpp" //字符串切分
    #include           //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() {}
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    gitee

    https://gitee.com/sakeww/linux_code/tree/master/item_oj
    
    • 1
  • 相关阅读:
    Zookeeper整理
    Axure RP9 引入eCharts图表
    8、信息打点——系统篇&端口扫描&CDN服务&负载均衡&WAF
    java版Spring Cloud+Mybatis+Oauth2+分布式+微服务+实现工程管理系统
    [C#]JCoder.Db4Net,支持Net Fx\Net Standard\Net5\Net6的数据库类库
    用R语言模拟混合制排队随机服务排队系统
    go微服务框架Kratos笔记「链路追踪实战」
    轻松了解JVM
    SpringBoot
    啥?13行python代码实现微信推送消息?
  • 原文地址:https://blog.csdn.net/sakeww/article/details/126263313