在线OJ项目

仿照 leetcode 的一个在线 OJ 系统.

项目功能

实现一个在线判题系统,用户通过浏览器获取对应题目进而编写代码并提交代码,将代码上传到后台,后台需要对提交的代码与测试用例整合并进行编译运行,将结果反馈给用户。

核心功能

  1. 在线编译运行
  2. 题目管理

模块划分

该项目被划分为四大模块:

oj_server模块

1、提供http服务,串联试题模块和编译运行模块
(1)获取题目列表
(2)获取选中的题目
(3)提交题目代码和题目描述,代码的编辑框

#include <stdio.h>
#include <string>
#include <string.h>

#include "httplib.h"
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "oj_log.hpp"
#include "compile.hpp"

int main()
{
    //需要使用httplib提供的命名空间
    using namespace httplib;
    Server svr;
    OjModel ojmodel;
  
    svr.Get("/all_questions", [&ojmodel](const Request& req, Response& resp){
            std::vector<Question> ques;
            ojmodel.GetAllQuestions(&ques);
            

            
            //使用模板技术去填充html页面
            std::string html;
            OjView::ExpandAllQuestionshtml(&html, ques);
            //LOG(INFO, html);
            resp.set_content(html,"text/html; charset=UTF-8");
            });


    svr.Get(R"(/question/(\d+))", [&ojmodel](const Request& req, Response& resp){
            // question/1
            // 1.去试题模块去查找对应题号的具体的题目信息
            //      map当中 (序号 名称 题目的地址 难度)
            //
            std::string desc;
            std::string header;
            //从querystr当中获取id
            LOG(INFO, "req.matches") << req.matches[0] << ":" << req.matches[1] << std::endl;
            // 2.在题目地址的路径下去加载单个题目的描述信息
            struct Question ques;
            ojmodel.GetOneQuestion(req.matches[1].str(), &desc, &header, &ques);
            // 3.进行组织,返回给浏览器
            std::string html;
            OjView::ExpandOneQuestion(ques, desc, header, &html);
            resp.set_content(html,"text/html; charset=UTF-8"); //  将html网页设置到响应中
            });


    svr.Post(R"(/question/(\d+))", [&ojmodel](const Request& req, Response& resp){
            //key:value
            //1.从正文当中提取出来提交的内容。主要是提取code字段所对应的内容
            //  提交的内容当中有url编码--》提交内容进行 解码
            //  提取完成后的数据放到 unordered_map<std::string, std::string>
            std::unordered_map<std::string, std::string> pram;
            UrlUtil::PraseBody(req.body, &pram);
            //for(const auto& pr:pram)
            //{
            //    LOG(INFO, "code ") << pr.second << std::endl;
            //}
            //2.编译&运行
            //   2.1 需要给提交的代码增加头文件,测试用例,main函数
            std::string code;
            ojmodel.SplicingCode(pram["code"], req.matches[1].str(), &code);  //给提交的代码增加头文件,测试用例,main函数

            //LOG(INFO, "code ") << code << std::endl;

	   // //3、构造 JSON结构的参数
            Json::Value req_json;
            req_json["code"] = code;
            //req_json["stdin"] = ""
            Json::Value Resp_json;
            Compiler::CompileAndRun(req_json, &Resp_json);
            //3.构造响应
            const std::string errorno = Resp_json["errorno"].asString();
            const std::string reason = Resp_json["reason"].asString();
            const std::string stdout_reason = Resp_json["stdout"].asString();
            std::string html;
            OjView::ExpandReason(errorno, reason, stdout_reason, &html);
            resp.set_content(html,"text/html; charset=UTF-8");
            });
    LOG(INFO, "listen in 0.0.0.0:19999") << std::endl;
    LOG(INFO, "Server ready") << std::endl;
    //listen 会阻塞
    svr.listen("0.0.0.0", 19999);
    return 0;
}

试题模块

1、从配置文件(oj_config.cfg)中加载题目
1.1 配置文件的格式(行文本文件,每一行对应一道题目)
(1)约定配置文件当中对题目的描述
(2)题目的编号、题目名称、题目难度、题目路径 (以\t分隔)
1.2 加载题目的配置文件,使用数据结构保存加载出来的题目的介绍信息,保证题目路径的正确
(1)创建Question结构体 , 以字符串形式存储题目的属性(id_\name_\start_\desc_\header.cpp\tail.hpp)
(2)按行读取 oj_config.cfg文件,并且解析(解析就是字符串分割)
1.3 根据解析结果拼装成 Question 结构体对象,针对每一道题而言,根据给出的路径进行加载
desc.txt : 题目的描述信息
header.cpp: 存放的是该题目所包含的头文件以及实现类
tail.cpp:存放测试用例以及main函数的入口
1.4 以 id:question键值对形式 存储到 unordered_map 数据结构中

2、 提供获取整个题目的接口
2.1 接口参数为 输出参数 vector
2.2 通过遍历hashtable 获取每个题目,存储在 Question结构体数组中

3、提供获取单个题目的接口(id , & qustion)
3.1 通过题目编号 , 在hashtable中找到对应的 题目输出

#include "tools.hpp"
#include "oj_log.hpp"
//试题id 试题名称 试题路径 试题难度
typedef struct Question
{
    std::string id_;
    std::string name_;
    std::string path_;
    std::string star_;
}QUES;
class OjModel
{
 public:
   //加载试题
   OjModel(){LoagQuestions("./config_oj.cfg");
   //获取题目列表
   bool GetAllQuestions(std::vector<Question>* ques);
   //获取单个题目列表
   bool GetOneQuestion(const std::string& id,std::string *desc,std::string *header,Question* ques);
   //合并题目信息和用户代码
   bool SplicingCode(std::string user_code,const std::string& ques_id,std::string* code);
   
 private:
   //加载试题
   bool LoadQuestions(const std::string& configfile_path);
   //获取题目的描述信息路径
   std::string DescPath(const std::string& ques_path);
   //获取该题目所包含的头文件以及实现类路径
   std::string HeaderPath(const std::string& ques_path);
   //测试用例以及main函数的入口路径
   std::string TailPath(const std::string& ques_path);
 private:
   std::unordered_map<std::string ,Question> model_map_;
 };

编译运行模块

1、编译
1.1 将用户提交的代码写到文件中去
1.2 fork子进程进行进程程序替换为g++程序,进行编译源码文件
1.3 获取编译结果写道便准输出文件中去或者写入到标准错误文件中去
2、 运行
2.1 如果代码走到这个阶段,说明一定产生可执行程序,fork子进程,让子进程进行进程程序替换,执行可执行程序
2.2 将程序的运行结果,保存到标准输出或者标准错中去

#include "oj_model.hpp"
class OjView
{
    public:
        //渲染html页面,并且将该页面返回给调用
        static void ExpandAllQuestionshtml(std::string* html,std::vector<Question>& ques);
        static void ExpandOneQuestion(const Question& ques,std::string& desc,std::string& header,std::string* html);
        static void ExpandReason(const std::string& errorno,const std::string& reason,const std::string& stdout_reason,std::string* html);
};

工具模块

1.提供时间戳服务
2.提供写文件操作
3.提供读文件操作
4.提供URL解码操作
5.提供字符串分割

#pragma once
#include <string.h>
#include <sys/time.h>
#include <iostream>
#include <cstdio>
#include <string>

//当前实现的log服务也是在控制台进行输出
//输出的格式
//[时间 日志等级 文件:行号] 具体的日志信息

class LogTime
{
    // 获取时间戳
    // 通过gettimeofday 函数 把时间包装成为一个结构体返回
    public:
        static int64_t GetTimeStamp()
        {
            struct timeval tv;
            gettimeofday(&tv, NULL);
            return tv.tv_sec;
        }

        //返回 年-月-日 时:分:秒
        static void GetTimeStamp(std::string* TimeStamp);
};

//日志等级
//INFO WARNING ERROR FATAL DEBUG
const char* Level[] = 
{
    "INFO",
    "WARNING",
    "ERROR",
    "FATAL",
    "DEBUG",
};

enum LogLevel
{
    INFO = 0,
    WARNING,
    ERROR,
    FATAL,
    DEBUG
};

inline std::ostream& Log(LogLevel lev, const char* file, int line, const std::string& logmsg);

#define LOG(lev, msg) Log(lev, __FILE__, __LINE__, msg)

//实现一个切割字符串的工具函数
class StringTools
{
    public:
        static void Split(const std::string input,const std::string& split_char,std::vector<std::string>* output);
};

/实现文件操作类
class FileOper
{
    public:
        static int ReadDataFromFile(std::string& filename,std::string* content);
        static int WriteDataToFile(const std::string&filename,const std::string& Data);
};

//url解码
class UrlUtil
{
    public:
        static void PraseBody(const std::string& body,std::unordered_map<std::string,std::string>* pram);
 private:
        static unsigned char ToHex(unsigned char x) ;
        static unsigned char FromHex(unsigned char x) ;
        static std::string UrlEncode(const std::string& str);
        static std::string UrlDecode(const std::string& str)    
};

技术支持

搭建 HTTP 服务器
认识 cpp-httplib
轻量级的 c++ http_server 框架,安装简单, header only 风格.
啥是 header only 风格?
c++ 的第三方库的管理一直是一个非常僵硬的话题, 安装使用第三方库都非常麻烦.
因此现代 c++ 推崇 header only 风格, 即第三方库只提供一个 .h / .hpp 头文件, 包含头文件即可使用, 不需要
额外编译库, 也不需要增加额外的编译选项(例如 -I -L -l)。
具体使用方法参见官方文档即可。

但是这个库依赖 C++ 11 中的正则表达式。而 Centos7 自带的 gcc4.8 正则表达式有 bug. 需要升级 gcc 版本

使用C++当中的文件流来加载文件,并获取文件当中的内容

在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。
fstream有两个子类:

ifstream(input file stream)和ofstream(outpu file stream),

ifstream默认以输入方式打开文件。
ofstream默认以输出方式打开文件。

试题模块中存储试题的数据结构是哪个?
map or undordered_map?选择unordered_map
map–>红黑数,有序的树形结构,查询的效率不高
undordered_map<ket,value>–>哈希表,无序,查询效率高,基本上查询的时候就是常数完成的

在c++中直接通过字符串拼接的方式构造 html
使用模板技术填充html页面–google ctemplate
可以是逻辑和界面分离,后台负责计算,在使用模板技术将计算后的值填充到预定义的html页面当中
模板类似于填空题 , 实现准备好一个 html把其中一些需要动态计算的数据挖个空留下来,
处理请求过程中,更具计算结果填写空。

两个部分
1.模板:定义界面真是的形式,预定义的html页面
2.数据字典:填充模板的数据

        数据字典
        片段(子字典) id name star
        片段(子字典) id name star

四种标记

    1.变量 {{变量名}}
    2.片段 {{#片段名}}
    3.包含 {{>模板名称}} 一个模板当中可以包含另外一个模板,对应的就是一个数据字典
    4.注释 {{!}} 定义html页面模板的时候的注释

先创建 ctemplate 对象,借助对象完成填空
1、先创建一个 template 对象 ,这个是一个总的数据对象
2、循环的往这个对象中添加一些子对象
3、每一个子对象在设置一些键值对和模板中留下的{{}} 是要对应的
4、进行数据的替换,生产最终的html

源码转义
R"()" C++11 引入的语法,原始字符串(忽略字符串中的转义字符)

Json数据格式–使用开源库JsonCpp
yum -y install epel-release
yum -y install jsoncpp-devel

jsonCpp主要包含三种类型的class:Value、Reader、Write。
Json::Value时jsonCpp中最基本、最重要的类,用于表示各种类型的对象。
Json::Writer 负责将内存中的Value对象转换成JSON文档,输出到文件或者是字符串中。
Josn::Reader用于读取,准确说是用于将字符串或者文件输入流转换为Json::Value对象的

jsonCpp中所有对象、类名都在namespace json中,使用时只要包含json.h即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值