【C++项目】负载均衡式在线OJ

文章目录

一、演⽰项⽬

项⽬源码链接:https://gitee.com/zhang-shiyang-h/l_code/tree/master/OnlineOJ



二、所⽤技术与开发环境

1、核心技术栈

后端开发

  • C++基础 + STL标准库
  • 多进程、多线程并发编程
  • 负载均衡架构设计

第三方库

  • Boost库(字符串切割等工具)
  • cpp-httplib HTTP 网络库
  • ctemplate 前端模板渲染库
  • jsoncpp JSON 序列化 / 反序列库

数据库

  • MySQL + C API 数据库连接

前端基础

  • HTML/CSS/JS/jQuery/AJAX
  • Ace 在线代码编辑器

2、开发环境

  • 服务器:Ubuntu 20.04 云服务器
  • 开发工具:VS Code
  • 数据库管理:MySQL Workbench

三、项⽬宏观结构

我的项⽬核⼼由三个模块组成:

  1. comm: 公共模块
  2. compile_server: 编译运行模块
  3. oj_server: 题目管理、负载均衡及业务功能

代码结构:

c 复制代码
comm:
	httplib.h
	Mutex.hpp
	Log.hpp
	Util.hpp
	
compile_server:
	compile.hpp
	runner.hpp
	compile_run.hpp
	compile_server.cc
	temp 存放临时文件

oj_server
	oj_model.hpp 文件版
	oj_model2.hpp MySQL版
	oj_view.hpp
	oj_control.hpp
	oj_server.cc
	conf:
		service_machine.conf
	questions:
		questions.list
		1:
			desc.txt
			header.cpp
			tail.hpp
	template_html:
		all_questions.html
		one_question.html
	wwwroot
		index.html

makefile1

c 复制代码
compile_server:compile_server.cc
	g++ -o $@ $^ -std=c++17 -ljsoncpp -lpthread
.PHONY:clean
clean:
	rm -rf compile_server

makefile2

c 复制代码
oj_server:oj_server.cc
	g++ -o $@ $^ -I./include -L./lib -std=c++17 -lpthread -lctemplate -ljsoncpp -lmysqlclient
.PHONY:clean
clean:
	rm -rf oj_server 	

makefile3

c 复制代码
.PHONY:all
all:
	@cd compile_server; \
	make;\
	cd -;\
	cd oj_server;\
	make;\
	cd -;

.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/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;

四、公共模块comm

1、Mutex.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    // 互斥锁封装类
    class Mutex
    {
    public:
        // 初始化锁
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }

        // 加锁
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
        }

        // 解锁
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
        }

        // 获取锁指针
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }

        // 销毁锁
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex; // 原生互斥锁对象
    };

    // RAII 自动锁
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex)
            : _mutex(mutex)
        {
            _mutex.Lock();
        }

        ~LockGuard()
        {
            _mutex.Unlock();
        }

    private:
        Mutex &_mutex;
    };
}

2、Log.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <memory>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;

    const std::string gsep = "\r\n";

    // 日志策略基类(接口)
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 控制台日志输出(线程安全)
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy() {}

    private:
        Mutex _mutex;
    };

    const std::string defaultpath = "/var/log";
    const std::string defaultfile = "my.log";

    // 文件日志输出(自动建目录、线程安全)
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path))
                return;

            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << std::endl;
            }
        }

        // 追加写入日志文件
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app);
            if (!out.is_open())
                return;

            out << message << gsep;
            out.close();
        }

    private:
        std::string _path;
        std::string _file;
        Mutex _mutex;
    };

    // 日志等级类
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    // 日志等级转字符串
    std::string LevelStr(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    // 获取格式化时间字符串(线程安全)
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm; // 出参
        localtime_r(&curr, &curr_tm);
        char buf[128]; // 出参
        snprintf(buf, sizeof(buf), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr_tm.tm_year + 1900,
                 curr_tm.tm_mon + 1,
                 curr_tm.tm_mday,
                 curr_tm.tm_hour,
                 curr_tm.tm_min,
                 curr_tm.tm_sec);
        return buf;
    }

    // 日志核心管理类
    class Logger 
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }

        // 切换为文件输出
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

        // 切换为控制台输出
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 日志消息构造: 负责拼接内容, 析构时自动输出
        class LogMessage
        {
        public:
            // 构造日志头部(时间、等级、进程ID、文件名、行号)
            LogMessage(LogLevel level, std::string src_name, int line_number, Logger &logger)
                :  _logger(logger) 
            {
                std::stringstream ss;
                ss << "[" << GetTimeStamp() << "] "
                   << "[" << LevelStr(level) << "] "
                   << "[" << getpid() << "] "
                   << "[" << src_name << "] "
                   << "[" << line_number << "] - ";
                _loginfo = ss.str(); 
            }

            // 流方式拼接日志内容
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;           
                _loginfo += ss.str(); 
                return *this;         
            }

            // 析构自动输出日志
            ~LogMessage()
            {
                if (_logger._fflush_strategy) 
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:     
            std::string _loginfo;   
            Logger &_logger;        
        };

        // 仿函数接口, 创建日志消息
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }

    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;
    };

    Logger logger; 

// 简化调用宏
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}

3、Util.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <atomic>
#include <fstream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <boost/algorithm/string.hpp>

const std::string temp_path = "./temp/";

namespace UtilModule
{
    // 时间工具类
    class TimeUtil
    {
    public:
        // 获取秒级时间戳
        static std::string GetTimeStamp()
        {
            struct timeval _time; // 出参
            gettimeofday(&_time, nullptr);
            return std::to_string(_time.tv_sec);
        }

        // 获取毫秒级时间戳
        static std::string GetTimeMs()
        {
            struct timeval _time; // 出参
            gettimeofday(&_time, nullptr);
            return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
        }
    };

    // 路径工具类: 拼接编译/运行所需临时文件路径
    class PathUtil
    {
    public:
        // 给文件名添加指定后缀
        static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
        {
            std::string path_name = temp_path;
            path_name += file_name;
            path_name += suffix;
            return path_name;
        }

        // 源文件
        static std::string Src(const std::string &file_name)
        {
            return AddSuffix(file_name, ".cpp");
        }

        // 可执行文件
        static std::string Exe(const std::string &file_name)
        {
            return AddSuffix(file_name, ".exe");
        }

        // 编译错误文件
        static std::string CompileError(const std::string &file_name)
        {
            return AddSuffix(file_name, ".compile_error");
        }

        // 标准输入输出错误文件
        static std::string Stdin(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdin");
        }

        static std::string Stdout(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdout");
        }

        static std::string Stderr(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stderr");
        }
    };

    // 文件工具类: 文件判断、读写、唯一文件名生成
    class FileUtil
    {
    public:
        // 判断文件是否存在
        static bool IsFileExists(const std::string &path_name)
        {
            struct stat st;
            if (stat(path_name.c_str(), &st) == 0)
            {
                return true;
            }
            return false;
        }

        // 生成唯一文件名(时间戳+原子自增ID)
        static std::string UniqFileName()
        {
            static std::atomic_uint id(0);
            id++;
            std::string ms = TimeUtil::GetTimeMs();
            return ms + "_" + std::to_string(id);
        }

        // 写入文件
        static bool WriteFile(const std::string &target, const std::string &content)
        {
            std::ofstream out(target);
            if (!out.is_open())
                return false;

            out.write(content.c_str(), content.size());
            out.close();
            return true;
        }

        // 读取文件, keep控制是否保留换行符
        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;              // 出参
            while (std::getline(in, line)) // 读取一行
            {
                (*content) += line;
                (*content) += (keep ? "\n" : "");
            }
            in.close();
            return true;
        }
    };

    // 字符串工具类: 字符串分割
    class StringUtil
    {
    public:
        // 分隔字符串,合并连续分隔符    target出参
        static void SplitString(const std::string &src, std::vector<std::string> *target, const std::string &sep)
        {
            boost::split(*target, src, boost::is_any_of(sep), boost::algorithm::token_compress_on);
        }
    };
}

五、编译运行模块compile_server

提供服务:代码编译运行,输出格式化执行结果

1、compiler.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include "../comm/Log.hpp"
#include "../comm/Util.hpp"

namespace ns_compiler
{
    using namespace LogModule;
    using namespace UtilModule;

    // 编译模块: 负责调用g++编译用户代码
    class Compiler
    {
    public:
        Compiler() = default;
        ~Compiler() = default;

        // 编译指定源文件, 生成可执行程序
        static bool Compile(const std::string &file_name)
        {
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(LogLevel::ERROR) << "创建子进程失败";
                return false;
            }
            else if (pid == 0)
            {
                // 子进程: 执行编译逻辑
                umask(0);
                int _stderr = open(PathUtil::CompileError(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
                if (_stderr < 0)
                {
                    LOG(LogLevel::WARNING) << "打开stderr文件失败";
                    exit(1);
                }
                
                // 标准错误重定向到文件
                dup2(_stderr, 2);

                // 替换为g++执行编译
                execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(), PathUtil::Src(file_name).c_str(),
                       "-D", "COMPILER_ONLINE", "-std=c++11", nullptr);

                LOG(LogLevel::ERROR) << "启动编译器g++失败";
                exit(2);
            }
            else
            {
                // 父进程: 等待子进程完成
                waitpid(pid, nullptr, 0);
                if (FileUtil::IsFileExists(PathUtil::Exe(file_name)))
                {
                    LOG(LogLevel::INFO) << PathUtil::Src(file_name) << "编译成功";
                    return true;
                }
            }
            LOG(LogLevel::ERROR) << "编译失败, 未生成可执行程序";
            return false;
        }
    };
}

2、runner.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>
#include "../comm/Log.hpp"
#include "../comm/Util.hpp"

namespace ns_runner
{
    using namespace LogModule;
    using namespace UtilModule;

    // 运行模块: 执行用户程序, 限制资源, 重定向输入输出
    class Runner
    {
    public:
        Runner() = default;
        ~Runner() = default;

        // 设置进程资源限制(CPU时间、内存大小)
        static void SetProcLimit(int _cpu_limit, int _mem_limit)
        {
            // 设置CPU时长限制(秒)
            struct rlimit cpu_rlimit;
            cpu_rlimit.rlim_max = RLIM_INFINITY;
            cpu_rlimit.rlim_cur = _cpu_limit;
            setrlimit(RLIMIT_CPU, &cpu_rlimit);

            // 设置内存大小限制(KB)
            struct rlimit mem_rlimit;
            mem_rlimit.rlim_max = RLIM_INFINITY;
            mem_rlimit.rlim_cur = _mem_limit * 1024;
            setrlimit(RLIMIT_AS, &mem_rlimit);
        }

        // 运行可执行程序, 返回运行状态码
        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(LogLevel::ERROR) << "运行时打开标准文件失败";
                return -1;
            }

            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(LogLevel::ERROR) << "运行时创建子进程失败";
                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);
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);

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

测试资源限制:

cpp 复制代码
#include <iostream>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <signal.h>

void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
    exit(1);
}

int main()
{
    for (int i = 1; i < 31; i++)
    {
        signal(i, handler);
    }

    // 测试CPU时长限制
    struct rlimit r;
    r.rlim_max = RLIM_INFINITY;
    r.rlim_cur = 1;
    setrlimit(RLIMIT_CPU, &r);
    
    while (1)
        ;

    // // 测试内存限制
    // struct rlimit r;
    // r.rlim_max = RLIM_INFINITY;
    // r.rlim_cur = 1024 * 1024 * 10; // 10M
    // setrlimit(RLIMIT_AS, &r);
    // int count = 0;

    // while (true)
    // {
    //     int *p = new int[1024 * 1024];
    //     count++;
    //     std::cout << "size: " << count << std::endl;
    //     sleep(1);
    // }

    return 0;
}

运行结果:


3、compile_run.hpp

cpp 复制代码
#pragma once

#include <signal.h>
#include <jsoncpp/json/json.h>
#include "../comm/Log.hpp"
#include "../comm/Util.hpp"
#include "compiler.hpp"
#include "runner.hpp"

namespace ns_compile_and_run
{
    using namespace LogModule;
    using namespace UtilModule;
    using namespace ns_compiler;
    using namespace ns_runner;

    // 编译运行模块: 整合编译、运行、结果返回、临时文件清理
    class CompileAndRun
    {
    public:
        // 清理本次请求产生的所有临时文件
        static void RemoveTempFile(const std::string &file_name)
        {
            unlink(PathUtil::Src(file_name).c_str());
            unlink(PathUtil::Exe(file_name).c_str());
            unlink(PathUtil::CompileError(file_name).c_str());
            unlink(PathUtil::Stdin(file_name).c_str());
            unlink(PathUtil::Stdout(file_name).c_str());
            unlink(PathUtil::Stderr(file_name).c_str());
        }

        // 状态码转描述信息
        static std::string CodeToDesc(int code, const std::string &file_name)
        {
            std::string desc; // 出参
            switch (code)
            {
            case 0:
                desc = "编译运行成功";
                break;
            case -1:
                desc = "提交的代码为空";
                break;
            case -2:
                desc = "未知错误";
                break;
            case -3:
                FileUtil::ReadFile(PathUtil::CompileError(file_name), &desc, true);
                break;
            case SIGABRT:
                desc = "内存超过限制";
                break;
            case SIGXCPU:
                desc = "CPU使用超时";
                break;
            case SIGFPE:
                desc = "浮点数溢出";
                break;
            default:
                desc = "未知错误: " + std::to_string(code);
                break;
            }
            return desc;
        }

        // 编译运行服务入口
        // 输入: JSON字符串(代码、输入、资源限制)
        // 输出: JSON字符串(状态、描述、运行结果)
        static void Start(const std::string &in_json, std::string *out_json)
        {
            Json::Value in_value; // 出参
            Json::Reader reader;
            reader.parse(in_json, in_value);

            std::string code = in_value["code"].asString();
            std::string input = in_value["input"].asString();
            int cpu_limit = in_value["cpu_limit"].asInt();
            int mem_limit = in_value["mem_limit"].asInt();

            int status_code = 0;
            int run_result = 0;
            Json::Value out_value; // 出参
            std::string file_name;

            // 代码为空
            if (code.size() == 0)
            {
                status_code = -1;
                goto END;
            }

            // 生成唯一文件名
            file_name = FileUtil::UniqFileName();

            // 写入源文件
            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_str; // 出参
                FileUtil::ReadFile(PathUtil::Stdout(file_name), &stdout_str, true);
                out_value["stdout"] = stdout_str; 

                std::string stderr_str;
                FileUtil::ReadFile(PathUtil::Stderr(file_name), &stderr_str, true);
                out_value["stderr"] = stderr_str; 
            }

            // 构造返回JSON
            Json::StyledWriter writer;
            *out_json = writer.write(out_value);

            // 清理临时文件
            RemoveTempFile(file_name);
        }
    };
}

测试用例:

cpp 复制代码
#include"compile_run.hpp"

using namespace ns_compile_and_run;

int main()
{
    std::string in_json; // 出参
    Json::Value in_value;

    in_value["code"] = R"(
    #include<iostream> 
    int main()
    {
        std::cout<<"你可以看见我了"<<std::endl;
        return 0;
    })";
    in_value["input"] = "";
    in_value["cpu_limit"] = 1;
    in_value["mem_limit"] = 1024 * 30;
    Json::FastWriter writer;
    in_json = writer.write(in_value);
    std::cout << in_json << std::endl;

    std::string out_json; // 出参
    CompileAndRun::Start(in_json, &out_json);

    std::cout << out_json << std::endl;
    return 0;
}

运行结果:

4、compile_server.cc

cpp 复制代码
#include"compile_run.hpp"
#include"../comm/httplib.h"

using namespace ns_compile_and_run;
using namespace httplib;

// 帮助提示: 参数错误时打印用法
void Usage(std::string proc)
{
    std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
}

// HTTP服务主入口: 提供在线编译运行接口
int main(int argc,char* argv[])
{
    // 校验启动参数
    if(argc!=2)
    {
        Usage(argv[0]);
        return 1;
    }
    
    Server svr;

    // 注册服务接口: 接收JSON, 处理后返回JSON
    svr.Post("/compile_and_run",[](const Request& req, Response& resp)
    {
        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]));
    return 0;
}

六、基于 MVC 结构的 OJ 模块

核心:搭建在线判题小型网站

功能

  1. 首页: 展示题目列表
  2. 编辑页: 在线编写代码
  3. **判题功能:**编译 + 运行

MVC 职责

  • M(模型): 处理题库数据(文件 / MySQL 增删改查)
  • V(视图): 渲染网页,展示给用户
  • C(控制器): 处理核心业务逻辑

1、oj_model.hpp 文件版

cpp 复制代码
#pragma once

// 文件版本
#include <iostream>
#include <unordered_map>
#include <fstream>
#include <cassert>
#include <fstream>
#include <vector>
#include <string>

#include "../comm/Log.hpp"
#include "../comm/Util.hpp"

namespace ns_model
{
    using namespace LogModule;
    using namespace UtilModule;

    // 题目结构体: 单题完整信息
    struct Question
    {
        std::string number; // 题目编号
        std::string title;  // 题目标题
        std::string star;   // 难度
        int cpu_limit;      // CPU时间限制
        int mem_limit;      // 内存限制
        std::string desc;   // 题目描述
        std::string header; // 代码头模板
        std::string tail;   // 测试用例尾
    };

    const std::string questions_list = "./questions/questions.list";
    const std::string questions_path = "./questions/";

    // 数据模型层: 加载、管理、提供题目数据
    class Model
    {
    private:
        std::unordered_map<std::string, Question> questions;

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

        // 加载题库配置文件
        bool LoadQuestionList(const std::string &questions_list)
        {
            std::ifstream in(questions_list);
            if (!in.is_open())
            {
                LOG(LogLevel::FATAL) << "加载题库失败, 请检查题库文件";
                return false;
            }

            std::string line; // 出参
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens; // 出参
                StringUtil::SplitString(line, &tokens, " ");
                if (tokens.size() != 5)
                {
                    LOG(LogLevel::WARNING) << "加载题目格式错误";
                    continue;
                }

                Question q;
                q.number = tokens[0];
                q.title = tokens[1];
                q.star = tokens[2];
                q.cpu_limit = atoi(tokens[3].c_str());
                q.mem_limit = atoi(tokens[4].c_str());

                std::string path = questions_path + q.number + "/";
                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[q.number] = q;
            }

            LOG(LogLevel::INFO) << "加载题库成功";
            in.close();
            return true;
        }

        // 获取全部题目   出参out
        bool GetAllQuestions(std::vector<Question> *out)
        {
            if (questions.empty())
            {
                LOG(LogLevel::ERROR) << "获取题库失败";
                return false;
            }
            for (const auto &kv : questions)
            {
                out->push_back(kv.second);
            }

            return true;
        }

        // 根据题号获取单题   出参q
        bool GetOneQuestion(const std::string &number, Question *q)
        {
            auto it = questions.find(number);
            if (it == questions.end())
            {
                LOG(LogLevel::ERROR) << "题目不存在: " << number;
                return false;
            }

            (*q) = it->second;
            return true;
        }

        ~Model() {}
    };
}

2、oj_model2.hpp MySQL版

cpp 复制代码
#pragma once

// MySQL版本
#include <iostream>
#include <unordered_map>
#include <fstream>
#include <cassert>
#include <fstream>
#include <vector>
#include <string>
#include <mysql/mysql.h>

#include "../comm/Log.hpp"
#include "../comm/Util.hpp"

namespace ns_model
{
    using namespace LogModule;
    using namespace UtilModule;

    // 题目结构体:单题完整信息
    struct Question
    {
        std::string number; // 题目编号
        std::string title;  // 题目标题
        std::string star;   // 难度
        int cpu_limit;      // CPU时间限制
        int mem_limit;      // 内存限制
        std::string desc;   // 题目描述
        std::string header; // 代码模板
        std::string tail;   // 测试用例
    };

    // MySQL 配置
    const std::string oj_questions = "oj_questions";
    const std::string host = "127.0.0.1";
    const std::string user = "oj_client";
    const std::string passwd = "123456";
    const std::string db = "oj";
    const int port = 3306;

    // MySQL 数据模型层
    class Model
    {
    public:
        Model() = default;
        ~Model() = default;

        // 执行SQL查询, 返回题目列表   出参out
        bool QueryMySql(const std::string &sql, std::vector<Question> *out)
        {
            // 初始化MySQL句柄
            MYSQL *my = mysql_init(nullptr);
            if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0))
            {
                LOG(LogLevel::FATAL) << "连接数据库失败!";
                return false;
            }

            // 设置编码
            mysql_set_character_set(my, "utf8mb4");
            LOG(LogLevel::INFO) << "连接数据库成功!";

            // 执行SQL
            if (0 != mysql_query(my, sql.c_str()))
            {
                LOG(LogLevel::WARNING) << sql << " execute error!";
                return false;
            }

            // 获取结果集
            MYSQL_RES *res = mysql_store_result(my);
            if (res == nullptr)
            {
                LOG(LogLevel::WARNING) << "结果集为空";
                mysql_close(my);
                return false;
            }

            // 解析结果
            int rows = mysql_num_rows(res);
            for (int i = 0; i < rows; i++)
            {
                // 获取一行数据
                MYSQL_ROW row = mysql_fetch_row(res); 
                Question q;
                q.number = row[0];
                q.title = row[1];
                q.star = row[2];
                q.desc = row[3];
                q.header = row[4];
                q.tail = row[5];
                q.cpu_limit = atoi(row[6]);
                q.mem_limit = atoi(row[7]);

                // 加入结果列表
                out->push_back(q);
            }

            // 释放资源
            mysql_free_result(res);
            mysql_close(my);
            return true;
        }

        // 获取所有题目
        bool GetAllQuestions(std::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='" + number + "'";
            std::vector<Question> result;

            if (QueryMySql(sql, &result) && result.size() == 1)
            {
                *q = result[0];
                return true;
            }
            return false;
        }
    };
}

3、oj_view.hpp

cpp 复制代码
#pragma once

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

#include"oj_model2.hpp"

namespace ns_view
{
    using namespace ns_model;

    // HTML模版文件所在目录
    const std::string template_path = "./template_html/";

    // 视图层: 将数据渲染成HTML页面
    class View
    {
    public:
        View() = default;
        ~View() = default;

        // 渲染题目列表页面
        void AllExpandHtml(const std::vector<ns_model::Question> &questions, std::string *html)
        {
            // 模板文件路径
            std::string src_html = template_path + "all_questions.html";
            // 渲染数据字典
            ctemplate::TemplateDictionary root("all_questions");

            // 循环填充题目数据
            for (const auto &q : questions)
            {
                ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("questions_list");
                sub->SetValue("number", q.number); 
                sub->SetValue("title", q.title);   
                sub->SetValue("star", q.star);     
            }

            // 加载模板并渲染
            ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP); 
            tpl->Expand(html, &root);
        }

        // 渲染单题详情页面
        void OneExpandHtml(const ns_model::Question &q, std::string *html)
        {
            // 模板文件路径
            std::string src_html = template_path + "one_question.html";
            // 渲染数据字典
            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); 
            
            // 加载模板并渲染
            ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);
            tpl->Expand(html, &root);
        }
    };
}

4、oj_control.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <mutex>
#include <vector>
#include <fstream>
#include <jsoncpp/json/json.h>
#include <algorithm>
#include "../comm/Log.hpp"
#include "../comm/Util.hpp"
#include "../comm/httplib.h"
#include "oj_model2.hpp"
#include "oj_view.hpp"

namespace ns_control
{
    using namespace LogModule;
    using namespace UtilModule;
    using namespace httplib;
    using namespace ns_model;
    using namespace ns_view;

    // 编译服务器节点
    class Machine
    {
    public:
        std::string ip;
        int port;
        uint64_t load; // 负载
        std::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 std::string service_machine = "./conf/service_machine.conf";

    // 负载均衡管理器
    class LoadBalance
    {
    private:
        std::vector<Machine> machines;
        std::vector<int> online;
        std::vector<int> offline;
        std::mutex mtx;

    public:
        LoadBalance()
        {
            assert(LoadConf(service_machine));
            LOG(LogLevel::INFO) << "加载 " << service_machine << " 成功";
        }

        // 加载服务器列表
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if (!in.is_open())
            {
                LOG(LogLevel::FATAL) << "加载配置失败";
                return false;
            }

            std::string line; // 出参
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens; // 出参
                StringUtil::SplitString(line, &tokens, ":");
                if (tokens.size() != 2)
                    continue;
 
                Machine m;
                m.ip = tokens[0];
                m.port = atoi(tokens[1].c_str());
                m.load = 0;
                m.mtx = new std::mutex();

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

            in.close();
            return true;
        }

        // 选择负载最低的机器   轮询+hash
        bool SmartChoice(int *id, Machine **m)
        {
            std::lock_guard<std::mutex> lock(mtx);
            int online_num = online.size();
            if (online_num == 0)
            {
                LOG(LogLevel::FATAL) << " 所有的后端编译主机已经离线, 请事尽快查看";
                return false;
            }

            *id = online[0];
            *m = &machines[online[0]];
            uint64_t min_load = (*m)->Load();

            for (int i = 1; i < online_num; i++)
            {
                int curr_id = online[i];
                uint64_t curr_load = machines[curr_id].Load();
                if (curr_load<min_load)
                {
                    min_load = curr_load;
                    *id = curr_id;
                    *m = &machines[curr_id];
                }
            }
            return true;
        }

        // 下线机器
        void OfflineMachine(int which)
        {
            std::lock_guard<std::mutex> lock(mtx);
            for (auto it = online.begin(); it != online.end(); it++)
            {
                // 找到要下线的机器id
                if (*it == which)
                {
                    machines[which].ResetLoad(); 
                    online.erase(it);          
                    offline.push_back(which);   
                    break;
                }
            }
        }

        // 全部上线
        void OnlineMachine()
        {
            std::lock_guard<std::mutex> lock(mtx);
            online.insert(online.end(), offline.begin(), offline.end());
            offline.clear();
            LOG(LogLevel::INFO) << "所有主机已重新上线";
        }

        void ShowMachines()
        {
            std::lock_guard<std::mutex> lock(mtx);
            std::cout << "在线主机: ";
            for (auto id : online)
            {
                std::cout << id << " ";
            }
            std::cout << "\n离线主机: ";
            for (auto &id : offline)
            {
                std::cout << id << " ";
            }
            std::cout << std::endl;
        }
    };

    // MVC 核心控制器
    class Control
    {
    private:
        Model _model;              
        View _view;                
        LoadBalance _load_balance; 

    public:
        Control() = default;
        ~Control() = default;

    public:
        void RecoveryMachine()
        {
            _load_balance.OnlineMachine();
        }

        // 获取题目列表网页
        bool AllQuestions(std::string *html)
        {              
            std::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);
                return true;
            }
            *html = "获取题目列表失败";
            return false;
        }

        // 获取单题页面
        bool Question(const std::string &number, std::string *html)
        {
            struct Question q; // 出参
            if (_model.GetOneQuestion(number, &q))
            {
                _view.OneExpandHtml(q, html);
                return true;
            }
            *html = "题目 " + number + " 不存在";
            return false;
        }

        // 判题逻辑
        void Judge(const std::string &number, const std::string& in_json, std::string *out_json)
        {
            struct Question q; 
            if (!_model.GetOneQuestion(number, &q))
                return;
                                      
            Json::Value in_value;  // 出参
            Json::Reader reader;                         
            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 req_json = writer.write(compile_value); 

            while (true)
            {
                int id = 0;     // 出参
                Machine *m = nullptr;  // 出参
                if (!_load_balance.SmartChoice(&id, &m))
                    break;

                Client cli(m->ip, m->port);
                m->IncLoad(); 

                LOG(LogLevel::INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->ip << ":"
                                    << m->port << " 当前主机的负载是: " << m->Load();

                
                if (auto res = cli.Post("/compile_and_run", req_json, "application/json;charset=utf-8"))
                {
                    if (res->status == 200)
                    {
                        *out_json = res->body; 
                        m->DecLoad();          
                        LOG(LogLevel::INFO) << "请求编译和运行服务成功...";
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    LOG(LogLevel::ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->ip << ":" << m->port
                                         << " 可能已经离线";
                    _load_balance.OfflineMachine(id);
                    _load_balance.ShowMachines();
                }
            }
        }
    };
}

5、oj_server.cc

cpp 复制代码
#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()
{
    // 注册信号: Ctrl+\ 触发机器上线
    signal(SIGQUIT, Recovery); 

    Server svr;
    Control ctrl;
    ctrl_ptr = &ctrl;

    // 获取所有题目列表
    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;
}

6、conf

1)service_machine.conf

c 复制代码
127.0.0.1:8081
127.0.0.1:8082
127.0.0.1:8083

7、questions

1)questions.list

c 复制代码
1 判断回文数 简单 1 30000
2 求最大值 简单 1 30000
3 求最大值 简单 1 30000
4 求最大值 简单 1 30000
5 求最大值 简单 1 30000
6 求最大值 简单 1 30000
7 求最大值 简单 1 30000
8 求最大值 简单 1 30000
9 求最大值 简单 1 30000
10 求最大值 简单 1 30000
11 求最大值 简单 1 30000
12 求最大值 简单 1 30000
13 求最大值 简单 1 30000
14 求最大值 简单 1 30000
15 求最大值 简单 1 30000

2)1

a)desc.txt
c 复制代码
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:
输入: 121
输出: true

示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。

进阶:
你能不将整数转为字符串来解决这个问题吗?
b)header.cpp
c 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>

using namespace std;

class Solution{
    public:
        bool isPalindrome(int x)
        {
            //将你的代码写在下面
            
            return true;
        }
};
c)tail.cpp
c 复制代码
#ifndef COMPILER_ONLINE
#include "header.cpp"
#endif

void Test1()
{
    // 通过定义临时对象,来完成方法的调用
    bool ret = Solution().isPalindrome(121);
    if(ret){
        std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
    }
    else{
        std::cout << "没有通过用例1, 测试的值是: 121"  << std::endl;
    }
}

void Test2()
{
    // 通过定义临时对象,来完成方法的调用
    bool ret = Solution().isPalindrome(-10);
    if(!ret){
        std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
    }
    else{
        std::cout << "没有通过用例2, 测试的值是: -10"  << std::endl;
    }
}

int main()
{
    Test1();
    Test2();

    return 0;
}

8、template_html

1)all_questions.html

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>在线OJ-题目列表</title>
    <style>
        /* 全局样式重置 */
        * {
            margin: 0px;
            padding: 0px;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        /* 导航栏样式 */
        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            overflow: hidden;
        }

        /* 导航栏链接样式 */
        .container .navbar a {
            display: inline-block;
            width: 80px;
            color: white;
            font-size: large;
            line-height: 50px;
            text-decoration: none;
            text-align: center;
        }

        /* 导航栏链接悬浮效果 */
        .container .navbar a:hover {
            background-color: green;
        }

        /* 登录按钮右浮 */
        .container .navbar .login {
            float: right;
        }

        /* 题目列表区域样式 */
        .container .question_list {
            padding-top: 50px;
            width: 800px;
            height: 100%;
            margin: 0px auto;
            /* background-color: #ccc; */
            text-align: center;
        }

         /* 列表标题样式 */
        .container .question_list table {
            width: 100%;
            font-size: large;
            font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
            margin-top: 50px;
            background-color: rgb(243, 248, 246);
        }

        /* 题目表格样式 */
        .container .question_list h1 {
            color: green;
        }

        /* 表格单元格样式 */
        .container .question_list table .item {
            width: 100px;
            height: 40px;
            font-size: large;
            font-family:'Times New Roman', Times, serif;
        }

        /* 表格链接样式 */
        .container .question_list table .item a {
            text-decoration: none;
            color: black;
        }

        /* 表格链接悬浮效果 */
        .container .question_list table .item a:hover {
            color: blue;
            text-decoration:underline;
        }

        /* 页脚样式 */
        .container .footer {
            width: 100%;
            height: 50px;
            text-align: center;
            line-height: 50px;
            color: #ccc;
            margin-top: 15px;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏 -->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>

        <!-- 题目列表 -->
        <div class="question_list">
            <h1>OnlineJuge题目列表</h1>
            <table>
                <tr>
                    <th class="item">编号</th>
                    <th class="item">标题</th>
                    <th class="item">难度</th>
                </tr>
                {{#questions_list}}
                <tr>
                    <td class="item">{{number}}</td>
                    <td class="item"><a href="/question/{{number}}">{{title}}</a></td>
                    <td class="item">{{star}}</td>
                </tr>
                {{/questions_list}}
            </table>
        </div>

        <!-- 页脚 -->
        <div class="footer">
            <!-- <hr> -->
            <h4>怀旧</h4>
        </div>
    </div>
</body>

</html>

2)one_question.html

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{number}}.{{title}}</title>

    <!-- 引入 ACE 编辑器 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript"
        charset="utf-8"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js" type="text/javascript"
        charset="utf-8"></script>
    <!-- 引入 jQuery -->
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <style>
        /* 全局样式重置 */
        * {
            margin: 0;
            padding: 0;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        /* 导航栏样式 */
        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            overflow: hidden;
        }

        /* 导航栏链接 */
        .container .navbar a {
            display: inline-block;
            width: 80px;
            color: white;
            font-size: large;
            line-height: 50px;
            text-decoration: none;
            text-align: center;
        }

        /* 导航栏悬浮效果 */
        .container .navbar a:hover {
            background-color: green;
        }

        /* 登录按钮右浮动 */
        .container .navbar .login {
            float: right;
        }
        
        /* 主体内容区域 */
        .container .part1 {
            width: 100%;
            height: 600px;
            overflow: hidden;
        }

        /* 左侧题目描述 */
        .container .part1 .left_desc {
            width: 50%;
            height: 600px;
            float: left;
            overflow: scroll;
        }

        .container .part1 .left_desc h3 {
            padding-top: 10px;
            padding-left: 10px;
        }

        .container .part1 .left_desc pre {
        padding-top: 10px;
        padding-left: 10px;
        font-size: 16px;
        font-family: "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", sans-serif;
        line-height: 1.7;
        color: #333;
        }

        /* 右侧代码编辑器 */
        .container .part1 .right_code {
            width: 50%;
            float: right;
        }

        .container .part1 .right_code .ace_editor {
            height: 600px;
        }

        /* 底部提交区域 */
        .container .part2 {
            width: 100%;
            overflow: hidden;
        }

        .container .part2 .result {
            width: 300px;
            float: left;
        }

        /* 提交按钮 */
        .container .part2 .btn-submit {
            width: 120px;
            height: 50px;
            font-size: large;
            float: right;
            background-color: #26bb9c;
            color: #FFF;
            border: 0px;
            margin-top: 10px;
            margin-right: 10px;
        }

        /* 按钮悬浮效果 */
        .container .part2 button:hover {
            color:green;
        }

        /* 结果显示区域 */
        .container .part2 .result {
            margin-top: 15px;
            margin-left: 15px;
        }

        .container .part2 .result pre {
            font-size: large;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 导航栏 -->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>

        <!-- 题目描述 + 代码编辑区 -->
        <div class="part1">
            <div class="left_desc">
                <h3><span id="number">{{number}}</span>.{{title}}_{{star}}</h3>
                <pre>{{desc}}</pre>
            </div>
            <div class="right_code">
                <pre id="code" class="ace_editor"><textarea class="ace_text-input">{{pre_code}}</textarea></pre>
            </div>
        </div>

         <!-- 提交按钮 + 结果区 -->
        <div class="part2">
            <div class="result"></div>
            <button class="btn-submit" onclick="submit()">提交代码</button>
        </div>
    </div>
    
    <script>
        // 初始化代码编辑器
        editor = ace.edit("code");
        editor.setTheme("ace/theme/monokai");
        editor.session.setMode("ace/mode/c_cpp");
        editor.setFontSize(16);
        editor.getSession().setTabSize(4);
        editor.setReadOnly(false);

        // 开启代码提示
        ace.require("ace/ext/language_tools");
        editor.setOptions({
            enableBasicAutocompletion: true,
            enableSnippets: true,
            enableLiveAutocompletion: true
        });
        editor.setValue(`{{pre_code}}`, 1);

        // 提交代码
        function submit(){
            var code = editor.getSession().getValue();
            var number = $(".container .part1 .left_desc h3 #number").text();
            var judge_url = "/judge/" + number;

            // AJAX 提交判题
            $.ajax({
                method: 'Post',   
                url: judge_url,   
                dataType: 'json', 
                contentType: 'application/json;charset=utf-8', 
                data: JSON.stringify({
                    'code':code,
                    'input': ''
                }),
                success: function(data){
                    show_result(data);
                }
            });

            // 显示运行结果
            function show_result(data)
            {
                var result_div = $(".container .part2 .result");
                result_div.empty();

                var _status = data.status;
                var _reason = data.reason;

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

                if(_status == 0){
                    var _stdout = data.stdout;
                    var _stderr = data.stderr;

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

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

                    stdout_lable.appendTo(result_div);
                    stderr_lable.appendTo(result_div);
                }
            }
        }
    </script>
</body>

</html>

9、wwwroot

1)index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>这是我的个人OJ系统</title>
    <style>
        /* 全局样式重置*/
        * {
            margin: 0px;
            padding: 0px;
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        /* 导航栏样式 */
        .container .navbar {
            width: 100%;
            height: 50px;
            background-color: black;
            overflow: hidden;
        }

        /* 导航栏链接 */
        .container .navbar a {
            display: inline-block;
            width: 80px;
            color: white;
            font-size: large;
            line-height: 50px;
            text-decoration: none;
            text-align: center;
        }

        /* 鼠标悬浮效果 */
        .container .navbar a:hover {
            background-color: green;
        }
        .container .navbar .login {
            float: right;
        }

        /* 内容区域居中 */
        .container .content {
            width: 800px;
            margin: 0px auto;
            text-align: center;
            margin-top: 200px;
        }

        /* 内容文本样式 */
        .container .content .font_ {
            display: block;
            margin-top: 20px;
            text-decoration: none;
        }
    </style>
</head>

<body>
    <div class="container">
        <!-- 顶部导航栏 -->
        <div class="navbar">
            <a href="/">首页</a>
            <a href="/all_questions">题库</a>
            <a href="#">竞赛</a>
            <a href="#">讨论</a>
            <a href="#">求职</a>
            <a class="login" href="#">登录</a>
        </div>

        <!-- 主页内容 -->
        <div class="content">
            <h1 class="font_">欢迎来到我的OnlineJudge平台</h1>
            <p class="font_">这个我个人独立开发的在线OJ平台</p>
            <a class="font_" href="/all_questions">点击我开始编程啦!</a>
        </div>
    </div>
</body>

</html>
相关推荐
yujunl1 小时前
U9系统admin用户账号密码生成Do方法
开发语言
MaikieMaiky1 小时前
C++ STL 系列(一):string 容器详解与示例
开发语言·c++
之歆1 小时前
DAY_25 JavaScript 原型、原型链与值类型/引用类型 ── 深度全解(下)
开发语言·javascript·ecmascript
xingfujie1 小时前
第2章:服务器规划与基础环境配置
linux·运维·微服务·云原生·容器·kubernetes·负载均衡
段ヤシ.1 小时前
回顾Java知识点,面试题汇总Day7(持续更新)
java·开发语言
努力努力再努力wz1 小时前
【Qt入门系列】深入理解信号与槽:从事件响应到自定义信号机制
c语言·开发语言·数据结构·数据库·c++·qt·mysql
在角落发呆1 小时前
DTU 数据转发服务器:工业物联网的隐形桥梁
开发语言·php
Sakuyu434681 小时前
C语言基础--基本数据类型
c语言·开发语言
在坚持一下我可没意见1 小时前
Python 修仙修炼录 05:循环神通,省去无用苦修
开发语言·python·面试·入门·循环·复习