在线OJ项目(二)

在线OJ项目(二)

一、回顾oj_server模块整体框架:

oj_server.cpp

#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()
{
    using namespace httplib;
    Server svr;
    //1.获取题目列表接口
    
    //2.获取单个题目接口
    
    //3.服务器接收用户通过浏览器提交code接口 
    svr.listen("0.0.0.0", 19999);
    return 0;
}

在线OJ项目(一)当中已经完成了第一步:获取题目列表接口并返回给浏览器。

二、封装日志模块

封装日志模块方便记录日志和调试

1. 封装时间模块

gettimeofday函数:

int gettimeofday(struct timeval *tv, struct timezone *tz);

timeval结构体:

struct timeval {
               time_t      tv_sec;     /* seconds */
               suseconds_t tv_usec;    /* microseconds */
           };

time:函数

time_t time(time_t *t);

localtime:函数

struct tm *localtime(const time_t *timep);

tm结构体:

struct tm {
               int tm_sec;         /* seconds */
               int tm_min;         /* minutes */
               int tm_hour;        /* hours */
               int tm_mday;        /* day of the month */
               int tm_mon;         /* month */
               int tm_year;        /* year */
               int tm_wday;        /* day of the week */
               int tm_yday;        /* day in the year */
               int tm_isdst;       /* daylight saving time */
           };

oj_log.hpp:

#pragma once
#include <string.h>
#include <sys/time.h>
#include <iostream>
#include <cstdio>
#include <string>
class LogTime
{
    public:
    	//为后面区分不同用户文件做铺垫
        static int64_t GetTimeStamp()
        {
            struct timeval tv;
            gettimeofday(&tv, NULL);
            //返回秒
            return tv.tv_sec;
        }

        //返回 年-月-日 时:分:秒
        static void GetTimeStamp(std::string* TimeStamp)
        {
            time_t SysTime;//秒
            time(&SysTime);
            struct tm* st = localtime(&SysTime);
            char buf[30] = {'\0'};
            snprintf(buf, sizeof(buf) - 1, "%04d-%02d-%02d %02d:%02d:%02d", 
            st->tm_year + 1900, st->tm_mon + 1, st->tm_mday, 
            st->tm_hour, st->tm_min, st->tm_sec);
            
            TimeStamp->assign(buf, strlen(buf));
        }
};

2. 封装日志等级

oj_log.hpp:

//日志等级
//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)
{
    std::string level_info = Level[lev];
    
    std::string TimeStamp;
    LogTime::GetTimeStamp(&TimeStamp);
    
    //[时间 日志等级 文件:行号] 具体的日志信息
    std::cout << "[" << TimeStamp << " " << level_info << " " 
    							<< file << ":"
        << line << "]" << logmsg;
    return std::cout;
}

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

注意

  • Log函数必须是内联函数,否则拿到的永远都是oj_log.hpp所在的行号,我们封装的目的是为了更快的查错,每次都拿到oj_log.hpp所在的行号是没有任何意义的,而inline函数是在编译阶段展开,就可以拿到展开处的行号,就可以记录日志

三、获取单个题目

1. 在工具模块定义文件读写接口

既然要获取单个题目信息,就需要从对应题目的路径下获取题目的相关描述、代码头,所以先提供一个读写文件的操作:

tools.hpp

//实现文件操作的类
class FileOper
{
    public:
    	//const std::string& filename:文件所在路径及文件名
    	//std::string* content:用来保存文件的内容
        static int ReadDataFromFile(const std::string& filename, std::string* content)
        {
            std::ifstream file(filename.c_str());
            if(!file.is_open())
            {
                LOG(ERROR, "Open file failed! filename is") << filename << std::endl;
                return -1;
            }

            std::string line;
            while(std::getline(file, line))
            {
                *content += line + "\n";
            }
            file.close();
            return 0;
        }

        static int WriteDataToFile(const std::string& filename, const std::string& Data)
        {
            std::ofstream file(filename.c_str());
            if(!file.is_open())
            {
                LOG(ERROR, "Open file failed") << filename << std::endl;
                return -1;
            }

            file.write(Data.data(), Data.size());
            file.close();
            return 0;
        }
};

2. 获取单个文件信息接口

我们指定了<a href="/question/{{id}}">即当用户点击某个具体的题目之后,浏览器应该跳转到对应题目的页面,但是还没有实现

所以我们应该先在oj_model模块提供一个获取单个题目信息的接口

oj_model.cpp:

//const std::string& id:代表某个题的id
//std::string* desc:保存读取后文件的题目描述信息
//std::string* header:保存读取后文件的代码头部信息
//Question* ques:题号为id的题目路径
bool GetOneQuestion(const std::string& id, std::string* desc, std::string* header, Question* ques)
        {
            //1.根据id去查找对应题目的信息,最重要的就是这个题目在哪里加载
            auto iter = model_map_.find(id);

			//如果没找到直接return false;
            if(iter == model_map_.end())
            {
                LOG(ERROR, "Not Found Question id is") << id << std::endl;
                return false;
            }
            //iter->second.path_; + 文件名称(desc.txt header.cpp)
            *ques = iter->second;

            //加载具体的单个题目信息,从保存的路径上面去加载
            //从具体的题目文件当中去获取两部分信息,描述, header头
            int ret = FileOper::ReadDataFromFile(DescPath(iter->second.path_), desc);
            if(ret == -1)
            {
                LOG(ERROR, "Read desc failed") << std::endl;
                return false;
            }

            ret = FileOper::ReadDataFromFile(HeaderPath(iter->second.path_), header);
            if(ret == -1)
            {
                LOG(ERROR, "Read desc failed") << std::endl;
                return false;
            }
            return true;
        }
     private:
     	//这几个函数的作用就是把路径和文件名称组合在一起
     	//  ./xxx/desc.txt
		std::string DescPath(const std::string& ques_path)
        {
            return ques_path + "desc.txt";
        }
        //  ./xxx/tail.cpp
        std::string HeaderPath(const std::string& ques_path)
        {
            return ques_path + "tail.cpp";
        }
        //  ./xxx/tail.cpp
        std::string TailPath(const std::string& ques_path)
        {
            return ques_path + "tail.cpp";
        }

注意:

  • 在加载单个题目的时候,需要在对应题目的路径下增加题目的描述desc.txt题目的代码头header.cpp以及tail.cpp的main函数入口

3. 组织单个题目的预定义html页面

对于每个题目的html页面当中,我们应该提供两部分信息:题目详细描述 + 用户答题窗口

同样我们应该采用模版技术进行动态更新!

question.html:

<html>
<head>
	<title>在线oj</title>
</head>
<body>
<!--描述-->
    <div>{{id}}.{{name}} {{star}}</div>
    <div><pre>{{desc}}</pre></div>
<!--代码的编辑框-->
<div>
	<!--action:代表向服务器提交-->
	<!--method:代表提交方法-->
	<!--textarea name:代表编辑框的名称-->
	<!--rows=50 cols=150:代码编辑框的行列-->
	<!--input type="submit":代表提交-->
	<!--formenctype="appliaction/json":代表提交的数据格式-->
    <form action="/question/{{id}}" method="POST">
        <textarea name="code" rows=50 cols=150>{{header}}</textarea>
        <br>
        <!--<textarea name="stdin" rows=10 cols=150></textarea>-->
        <input type="submit" formenctype="appliaction/json" value="Submit">
    </form>

</div>
</body>
</html>

4. 对单个题目的预定义html页面进行填充渲染

oj_view.hpp:

		//id name star desc header  ==> string html
		//const Question& ques:单个题目的结构
		//std::string& desc:单个题目的描述信息
		//std::string& header:单个题目的代码头部信息
		//std::string* html:渲染完成后的html页面
        static void ExpandOneQuestion(const Question& ques, std::string& desc, 
                std::string& header, std::string* html)
        {
            ctemplate::TemplateDictionary dict("question");
            dict.SetValue("id", ques.id_);
            dict.SetValue("name", ques.name_);
            dict.SetValue("star", ques.star_);
            
            dict.SetValue("desc", desc);
            dict.SetValue("header", header);

			//./template/question.html:代表预定义html页面的路径
			//ctemplate::DO_NOT_STRIP:代表逐行逐句进行填充
            ctemplate::Template* tpl = ctemplate::Template::GetTemplate("./template/question.html", ctemplate::DO_NOT_STRIP);

			//渲染
            tpl->Expand(html, &dict);
        }

5. 将单个题目的页面返回给浏览器

完整的获取单个题目并返回给浏览器的代码:

oj_server.hpp:

	//正则表达式
    //      \b:单词的分界
    //      *:匹配任意字符串
    //      \d:匹配一个个位数字
    //		\d+:匹配一个数字(一位或者多位)
    //      ():分组应用 (\d+)-(\d+)-(\d+)
    //                   12 -   13  - 14
    //                   arr[0] = 12
    //                   arr[1] = 13
    //                   arr[2] = 14
    //源码转义:特殊字符就按照特殊字符字面源码来编译
    //      R"(str)"
    // 使用正则表达式匹配任意题目id
		svr.Get(R"(/question/(\d+))", [&ojmodel](const Request& req, Response& resp){
            // question/1
            // 1.去试题模块去查找对应题号的具体的题目信息
            //      map当中 (序号 名称 题目的地址 难度)
            std::string desc;
            std::string header;
            //从querystring当中获取id
            //cout<<req.path.c_str()<<endl;path就是querystring:/question/(\d+)
            //cout<<req.matches[0]<<endl;matches就是切割querystring字符串的
            //matches[0]就是整个querystring
            //matches[1]就是querystring中正则表达式部分(\d+)
            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");
            });
发布了150 篇原创文章 · 获赞 89 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/wolfGuiDao/article/details/105233808