目录

1.基于MVC结构的oj服务设计2.model.hpp编写3.view.hpp的编写4.control.hpp的编写5.oj_server.cc的编写6.model_sql.hpp的编写7.完整的工具类展示

1.基于MVC结构的oj服务设计

在oj_server这个目录下我们完成项目前中后的中端核心模块oj_server模块,该模块我们设计的文件有oj_server.cc,这个文件引入网络模块,给用户提供首页、给用户提供题目列表、给用户提供某道题的做题界面和把用户提交的json串发送给后端compile模块进行编译运行,返回结果给用户,还有model.hpp、view.hpp和control.hpp,就是对应的MVC。 这个目录的总体结构框架跟编译模块类似如下图

model.hpp这个文件我们实现跟数据进行交换,就是把文件中的题目数据或数据库中的题目加载到内存管理起来,所以model.hpp我们设计文件版和数据库版,使用文件版model.hpp我们就要在该模块目录下创建一个子目录questions用来存储所有题目的数据,在questions目录下为每一道题都创建一个目录,题目编号就是目录名称,在该目录下就是有三个文件分别是题目的描述desc.txt、题目的头文件就是渲染到用户编写界面的代码接口,题目的尾文件tail.cpp就是包含我们的main和所有的测试用例。 desc.txt文件 header.cpp tail.cpp 我们把desc.txt和header.cpp 渲染到单道题目界面,用户在编辑框中写代码,提交后我们拿到代码再和题目对应的tail.cpp拼接成一个文件就是最后要编译的源文件。control.hpp中我们会按照编译模块规定的接受编译的json串形式构建包含源代码、输入、时间和空间限制的json串发送给编译端。

view.hpp就是拿到数据后进行网页构建,数据渲染的,我们在oj_server目录下创建一个子目录template_html,该子目录下创建一个questionlist.html用来构建题目列表,设置好结构用到ctemplate第三方开源渲染库,把题目编号、名称和难度都渲染到questionlist.html中最终效果就成了用户看到的题目列表。同样的构建用户选定题目后的编写界面question.html 我们也把特定题号的题目数据渲染到该界面,形成用户看到的编辑界面。

control.hpp模块就是oj_server端的核心模块,在这里我们要引入负载均衡模块,我们会把编译服务启动多个,在不同的机器上运行,当然我们现在是练习,所有就在一台机器上多建几个会话,创建多个ip相同端口号不同的编译服务,oj_server端请求编译端的编译服务,会选择当前在运行的编译机器中负载最轻的机器发起http请求。同时在该文件中我们调用model.hpp中的方法加载所有题目数据,调用view.hpp中的方法,根据用户请求构建网页,还有实现判断功能,有oj_server.cc来调用。

2.model.hpp编写

我们设计一个存储单道题数据的结构体question,实现一个Model类,成员数据就是一个哈希表,题目编号为key值,question为value 通过这样的数据结构把我们questions目录下的所有题目数据加载到了内存中。在该类中我们实现加载题目的方法loadquestion、获取所有题目的方法getallquestion和获取单道题目的方法getonequestion。

#pragma once

#include

#include"../common/log.hpp"

#include"../common/util.hpp"

#include

#include

#include

#include

#include

//这个模块 用来加载所有题目数据到内存中,提供获取所有题目的方法,和获取指定题目的方法

using namespace std;

namespace lcy_model

{

using namespace lcy_log;

using namespace lcy_util;

struct question //保存题目数据的结构

{

string number; //题目编号

string title; //题目标题

string grade; //难度等级

int cpu_limit; //运行时间限制

int mem_limit; //占用内存限制

string desc; //题目描述

string header; //给用户预置的代码

string tail; //测试用例代码

};

const string pathlist="./questions/question.list";

const string path="./questions/";

class Model{

private:

unordered_mapquestions; //key 题目编号 value 题目的所有数据 哈希表存储所有题目

public:

Model()

{assert(loadquestion());}

~Model()

{}

bool loadquestion() //根据指定目录下的题目列表信息把所有题目加载到内存

{

ifstream in(pathlist.c_str());

if(!in.is_open())

{

LOG(FATAL)<<"打开题目列表文件失败,请检查该文件是否存在或格式问题"<<"\n";

return false;

}

string line;

while(getline(in,line))

{

question q;

vectortoken;

Stringutil::splitstring(line,&token," ");

if(token.size()!=5) //文件列表里每个题目是一行数据 用空格隔开 分别是 编号 标题 难度 时间限制 空间限制

{

LOG(WARNING)<<"获取部分题目失败"<<"\n";

continue;

}

q.number=token[0];

q.title=token[1];

q.grade=token[2];

q.cpu_limit=atoi(token[3].c_str());

q.mem_limit=atoi(token[4].c_str());

string filepath=path; //拼接每个题目 desc 文件 header 文件 tail 文件的路径 去读取

filepath+=q.number;

filepath+="/";

Filehandle::readfile(filepath+"desc.txt",&q.desc,true); // 读题目描述的文件内容

Filehandle::readfile(filepath+"header.cpp",&q.header,true); //读取题目预置代码内容

Filehandle::readfile(filepath+"tail.cpp",&q.tail,true); //读取 题目测试用例数据

questions.insert({q.number,q}); //把每一题的数据存到内存哈希表中去

}

LOG(INFO)<<"加载题目成功"<<"\n";

in.close();

return true;

}

bool getallquestion(vector*vq)

{

if(questions.size()==0)

{

LOG(ERROR)<<"获取题库失败"<<"\n";

return false;

}

else

{

for(auto &e:questions)

(*vq).push_back(e.second);

}

LOG(INFO)<<"获取题库成功"<<"\n";

return true;

}

bool getonequestion(const string num,question*q)

{

auto it=questions.find(num);

if(it==questions.end())

{

LOG(WARNING)<<"获取题目"<

return false;

}

else

{

(*q)=it->second;

}

return true;

}

};

}

在工具类中实现字符串分割splitstring方法

static void splitstring(const std::string& str,std::vector*token,const std::string gap)

{

boost::split(*token,str,boost::is_any_of(gap),boost::algorithm::token_compress_on);

}

3.view.hpp的编写

这里我们要引入一个库ctemplate提供渲染的方法,渲染的方式就是我们编写一个统一的html文件模板,比如显示具体题目页面,我们要显示题目描述,题目头文件,在html的一个显示内容的标签中我们就用<标签>{{key}} 我们在view.hpp 中就以相同的key加value,value就是我们实际的数据,ctemplate的方法就会帮我们把value替换到html中key的位置上去。 这里就要编写前面提到的questionlist.html和question.html这里就简单显示一下html骨架,精简篇幅。 questionlist.html

题目列表

{{#question_list}} /*这里就是一个循环把所有题目的编号、名称和难度渲染到网页形成题目列表*/

#这里就用到了渲染,

{{/question_list}}

题目编号题目名称难度
{{number}}{{title}}{{grade}}

question.html

{{number}}.{{title}} {{grade}}

{{desc}}

view.hpp

#pragma once

#include

//#include "oj_model.hpp"

#include"oj_model_sql.hpp"

#include

using namespace std;

//提取我们存储的题目数据对网页进行渲染 获取题目列表网页和单个题目的网页信息 用到ctemplate 库

namespace lcy_view

{

const string src_html="./template_html/"; //一个存储网页模板的路径, 我们提取不同的题目拿到数据对其进行渲染,形成相应的html信息

using namespace lcy_model;

class View

{

public:

View(){}

~View(){}

void listexpandhtml(const vector&qs,string *html) //在control 模块调用该方法形成列表网页,我们需要用到所用题目数据

{

ctemplate::TemplateDictionary root("allquestion"); //参数相当于取个变量名

for(auto &e:qs)

{ //形成字数据字典名字一定要和html文件中循环渲染处的名字相同

ctemplate::TemplateDictionary *pt=root.AddSectionDictionary("question_list");

pt->SetValue("number",e.number); // key value 键值插入到root的子字典中 key值都要对应html中要被渲染的部分

pt->SetValue("title",e.title);

pt->SetValue("grade",e.grade);

}

ctemplate::Template* cr= ctemplate::Template::GetTemplate(src_html+"questionlist.html",ctemplate::DO_NOT_STRIP);

cr->Expand(html,&root);

}

void oneexpandhtml(const question&q,string *html)

{

ctemplate::TemplateDictionary root("onequestion");

root.SetValue("number",q.number);

root.SetValue("title",q.title);

root.SetValue("grade",q.grade);

root.SetValue("desc",q.desc);

root.SetValue("header",q.header);

ctemplate::Template* pt=ctemplate::Template::GetTemplate(src_html+"question.html",ctemplate::DO_NOT_STRIP);

pt->Expand(html,&root);

}

};

}

题目列表页面效果 我们要注意,项目是设计一个简易的在线oj平台,题目的数量,内容和测试用例的工作就是其他工作人员的事情,我们要增加题目就是在文件版model.hpp时我们在questions目录下新建题目目录,设计题目,在数据库model.hpp就是往表中新插入一条记录罢了。 具体题目界面

4.control.hpp的编写

首先编写负载均衡的内容,我们后端有多台机器运行编译服务,在oj_server目录下创建一个子目录conf,在这个子目录下创建一个文件machine_list.conf记录机器的ip和端口号。 只是练习我们就模拟有三台机器的场景的负载均衡。 我们要有描述机器的结构体Machine,结构体成员有ip、端口、负载情况还要有锁,再多用户请求时对负载情况加加减减要时原子性的。成员方法有默认构造Machine、负载加一、负载减一、负载清零、获取负载。

#pragma once

#include

#include

#include

#include

#include

#include

#include "../common/log.hpp"

#include "../common/util.hpp"

//#include "oj_model.hpp"

#include"oj_model_sql.hpp"

#include "oj_view.hpp"

using namespace std;

namespace lcy_ctrol

{

using namespace lcy_log;

using namespace lcy_util;

using namespace lcy_view;

using namespace lcy_model;

using namespace httplib;

//我们后端有多台机器用来编译并运行代码 这里我们进行负载均衡模块的编写,每次把用户提交的代码交给负载较轻的机器来处理

class Machine //记录一台机器的结构体

{

public:

string ip; //机器的ip

int port; //后端编译并运行服务绑定的端口号

uint64_t load; //当前需要执行的任务数 负载情况

mutex* mtx; //我们还需要锁 对负载 ++ -- 都要是原子的,面对可能非常多的用户请求分配到该机器上,

//但我们要用地址,mutex是禁止拷贝的,可以拷贝地址,方便后面用vector存储所有机器

Machine()

:ip("")

,port(0)

,load(0)

,mtx(nullptr)

{}

void incload() //对负载++ -- 获取都保持一样的形式,用锁保持原子性

{

if(mtx)mtx->lock();

++load;

if(mtx)mtx->unlock();

}

~Machine(){}

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 getload()

{

uint64_t _load=0;

if(mtx)mtx->lock();

_load=load;

if(mtx)mtx->unlock();

return _load;

}

};

在control.hpp 文件下紧接着写一个负载均衡器类Loadbalance,成员数据有一个vector存储所有的机器结构体、一个vector存储当前在线的机器、一个vector存储当前离线的机器还有一个锁在选择机器请求编译服务时加锁,在上线和下线机器时加锁。所有我们实现的成员方法有loadmachine加载所有机器到第一个vector中并且数组下标就是机器的编号,machineassign方法,在此方法中选择负载最轻的机器来执行编译任务,onlinemachine方法用来上线机器,我们统一一上线就上线所有机器,offlinemachine方法下线某台机器,为了测试我们还可以实现一个打印机器的方法printfmachine。

const string confsrc="./conf/machine_list.conf"; //所有准备运行我们后端编译模块的机器的ip和port 我们用一个配置文件记录

class Loadbalance

{

private:

vectormachines; //存放所有的机器

vectoronline; //存放所有在线的机器在machines中的下标

vectoroffline; //存放所有下线的机器在machines中的下标

mutex mtx; //在进行选择某台机器时我们也加锁保持所有用户提交分配机器来服务时是串行的

public:

Loadbalance()

{

assert(loadmachine());

}

~Loadbalance(){}

bool loadmachine()

{

ifstream in(confsrc);

if(!in.is_open())

{

LOG(FATAL)<<"获取机器配置信息失败"<<"\n";

return false;

}

string str;

while(getline(in,str))

{

vectortoken;

Stringutil::splitstring(str,&token,":");

if(token.size()!=2)

{

LOG(WARNING)<<"机器配置信息有误"<<"\n";

continue;

}

Machine m;

m.ip=token[0];

m.port=atoi(token[1].c_str());

m.mtx=new mutex;

online.push_back(machines.size()); //先加入这台机器在machines中的下标到在线数组中

machines.push_back(m); //把Machine 中的mutex 设置为指针就是因为这里要进行拷贝

}

LOG(INFO)<<"机器信息加载成功"<<"\n";

in.close();

return true;

}

bool machineassign(int*id,Machine**m) //按照负载均衡的算法,通过输出型参数 返回我们分配的机器的id,和机器的地址

{

//1.负载均衡的算法有随机+hash //随机分配一台机器

//2.轮询+hash 查看所有在线机器选择负载最轻的机器

mtx.lock();

int on_num=online.size();

if(on_num==0)

{

LOG(FATAL)<<"没有在线的机器,需要快速检查问题"<<"\n";

mtx.unlock(); //第一次忘写 导致死锁

return false;

}

*id=online[0];

uint64_t min_load=machines[online[0]].getload();

*m=&machines[online[0]];

for(int i=1;i

{

if(min_load>machines[online[i]].getload())

{

*id=online[i];

min_load=machines[online[i]].getload();

*m=&machines[online[i]];

}

}

mtx.unlock();

return true;

}

void onlinemachine()

{

//上线就统一上线所有机器

mtx.lock();

online.insert(online.end(),offline.begin(),offline.end());

offline.clear();

mtx.unlock();

LOG(INFO)<<"重新上线所有机器"<<"\n";

}

void offlinemachine(int id) //下线某台机器

{

mtx.lock();

for(auto it=online.begin();it!=online.end();++it)

{

if(*it==id)

{

online.erase(it);

offline.push_back(id); //第一次写时 放在循环外,出bug

machines[id].resetload(); //下线一台机器就是把它的id从online中移到offline中,负载要清零,

break; //防止立马重启这台机器时它历史上的负载还在记录,影响判断

}

}

mtx.unlock();

}

在control.hpp文件中接下来写核心控制类Control在该类中我们就要在总体上控制model.hpp、view.hpp、上面实现的Loadbalance这三者功能的整合,调用它们的方法完成控制逻辑。成员数据就是创建三个对象Model对象,View对象,Loadbalance对象,这样就能分别调用它们的方法了。我们实现构建题目列表网页方法allquestion、题目具体网页onequestion、重新上线所有机器方法restartmachines、判题功能的方法judgequestion。

class Control

{

Model _model;

View _view;

Loadbalance _load;

public:

Control(){}

~Control(){}

//根据后端数据构建题目列表输出网页

bool allquestion(string *html)

{

vectorqs;

if(_model.getallquestion(&qs))

{ //给题目按编号升序排列一下

sort(qs.begin(),qs.end(),[](const struct question&l,const struct question&r){

return atoi(l.number.c_str())

});

_view.listexpandhtml(qs,html);

return true;

}

else

{

LOG(FATAL)<<"构建题目列表网页信息失败"<<"\n";

return false;

}

}

void restartmachines()

{

_load.onlinemachine();

_load.printmachine();

}

bool onequestion(string num,string *html) //构建特定题目的网页信息

{

question q;

if(_model.getonequestion(num,&q))

{

_view.oneexpandhtml(q,html);

return true;

}

else

{

LOG(ERROR)<<"没有找到题目"<

return false;

}

}

void judgequestion(const string &num,const string&injson,string *outjson)

{ //该方法把用户以json串形式提交的数据做解析 提取出需要编译运行的代码,分配给后端某台机器执行,形成返回的json串返回给用户

//1 .根据题目编号 拿到题目的数据

question q ;

// cout<<"收到请求3"<

_model.getonequestion(num,&q); //这里不用判断返回真假,我们给用户题目界面,用户提交代码,这里是按num获取题目一定会成功

//2 .根据用户传来json串反序列化拿到用户代码 用户输入

// cout<<"收到请求4"<

Json::Reader reader;

Json::Value invalue;

reader.parse(injson,invalue);

//3. 根据前两步拿到的数据形成需要传给后端编译运行模块的json串 拼接用户代码和测试用例代码形成源代码

Json::Value outvalue;

outvalue["input"]=invalue["input"].asString();

outvalue["code"]=invalue["code"].asString()+"\n"+q.tail; //这里再中间加一个换行,防止总会出现测试用例文件上面的预编译指令因为#跟在用户上传代码后面而失效

outvalue["timelimit"]=q.cpu_limit;

outvalue["memorylimit"]=q.mem_limit;

Json::FastWriter writer;

string compilestr=writer.write(outvalue);

//4.选择负载最低的机器让其编译运行代码

int id=0;

Machine* m=nullptr;

//这里选择机器,直到运行成功或者机器全部下线才会跳出循环

while(true)

{

if(!_load.machineassign(&id,&m))

{

break;

}

//5 确定了机器后就向该机器发起http请求

Client client(m->ip,m->port);

m->incload();

LOG(INFO)<<"为测试题目"<ip<<" port:"<port<<" load:"<getload()<<"\n";

if(auto res=client.Post("/compile_run",compilestr,"application/json;charset=utf-8"))

{

if(res->status==200)

{//6.运行成功 返回执行结果的json串

LOG(INFO)<<"编译运行服务完成"<<"\n";

*outjson=res->body;

m->decload();

break;

} //运行后状态码不是200 说明处理过程不正确,重新再来

m->decload();

}

else //这就是http请求没有响应 机器可能出故障了

{ //这里对这台有故障的主机负载就不用--了下线该主机负 载会清零

LOG(ERROR)<<"主机:"<

_load.offlinemachine(id);

_load.printmachine();

}

}

}

};

}

5.oj_server.cc的编写

这里我们就是实现编写main函数引入网络模块,为了完整性,我们在编写一个主页界面,用户可以点击开始编程或题库跳转到题目列表界面,点击特定题目尽量具体题目界面,创建一个wwwroot目录,在该目录下写一个index.html编写主页内容。

欢迎来到我的onlineoj平台

这是我个人独立开发的一个在线oj平台

开始编程

省略了样式就简单展示一下骨架 在oj_server.cc 中我们在对一个信号注册一个一下我们的重新上线所有机器方法,有时候某台机器故障了我们下线了该机器,相关工作人员检查故障机器的问题,解决问题后,我们重新上线所有机器,就使用SIGQUIT信号,按ctrl+\就能重新上线所有机器。这里用httplib的方法返回给用户主页、题目列表、具体题目还有请求后端编译服务。

#include

#include

#include

#include"oj_control.hpp"

#include

using namespace std;

using namespace httplib;

using namespace lcy_ctrol;

static Control* ctr=nullptr;

void restart(int signo)

{

ctr->restartmachines();

}

int main()

{

signal(SIGQUIT,restart);

Server server;

Control crol;

//注册一个信号,给我们用来重新上线所有机器

ctr=&crol;

//1 用户请求题目列表

server.Get("/question_list",[&crol](const Request&req, Response &resp){

string out_html;

crol.allquestion(&out_html);

resp.set_content(out_html,"text/html;charset=utf-8");

});

//2 用户根据题目编号请求特定题目

server.Get(R"(/question/(\d+))",[&crol](const Request&req,Response &resp){

std::string question_no=req.matches[1];

string out_html;

crol.onequestion(question_no,&out_html);

resp.set_content(out_html,"text/html;charset=utf-8");

});

// 3 对用户提交的题目 使用我们的判题功能

server.Post(R"(/judge/(\d+))",[&crol](const Request&req,Response &resp){

// cout<<"收到请求1"<

std::string question_no=req.matches[1];

// resp.set_content("指定题目的判题"+question_no,"text/plain;charset=utf-8");

std::string injson=req.body;

// LOG(DEBUG)<

std::string outjson;

//cout<<"收到请求2"<

crol.judgequestion(question_no,req.body,&outjson);

//cout<<"收到请求0"<

resp.set_content(outjson,"application/json;charset=utf-8");

});

server.set_base_dir("./wwwroot");

server.listen("0.0.0.0",8100);

return 0;

}

要补充说明的是在control.hpp 中从json串中拿到用户提交的代码拼接我们的测试用例时 我们要屏蔽上图#include"header.cpp"这条指令,我们在后端编译源文件的g++指令中要加上 -D COMPILE_ONLINE,给源文件加上这个宏,使源文件完整。

至此项目总体上就完成了。我们可以在实现mysql数据库版的model.hpp不再用我们的文件来存储题目数据,而是用创建一个数据库,在该库下创建一张表来存储题目数据,比文件版更易操作。

6.model_sql.hpp的编写

我们使用mysql创建一个用户创建一个数据库专门管理题目数据的,给该用户赋权在该数据库下的所有权限,创建一张表ojdata来存储题目数据。 创建表sql

CREATE TABLE `ojdata` (

`number` int NOT NULL AUTO_INCREMENT COMMENT '题目编号',

`title` varchar(60) NOT NULL COMMENT '题目标题',

`grade` varchar(8) NOT NULL COMMENT '题目难度',

`desc` text NOT NULL COMMENT '题目描述',

`header` text NOT NULL COMMENT '包含头文件和预设代码',

`tail` text NOT NULL COMMENT '测试用例',

`cpu_limit` int DEFAULT '1' COMMENT '时间限制',

`mem_limit` int DEFAULT '30000' COMMENT '内存限制',

PRIMARY KEY (`number`)

) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb3

接下来就是写一份数据库版的model.hpp 。其中两个方法getallquestion和getonequestion都不变,我们只改动model.hpp 上层调用要维持不变接口就不能改变。操作上我们就要使用mysql官网提供的c语言连接数据库操作数据库的库。具体编写如下。

#pragma once

#include

#include"../common/log.hpp"

#include"../common/util.hpp"

#include

#include

#include

//这个模块 用来加载所有题目数据到内存中,提过获取所有题目的方法,和获取指定题目的方法

//这是数据库版的model模块 从数据库中拿到题目数据

using namespace std;

namespace lcy_model

{

using namespace lcy_log;

using namespace lcy_util;

struct question //保存题目数据的结构

{

string number; //题目编号

string title; //题目标题

string grade; //难度等级

int cpu_limit; //运行时间限制

int mem_limit; //占用内存限制

string desc; //题目描述

string header; //给用户预置的代码

string tail; //测试用例代码

};

const string ip="127.0.0.1";

unsigned int port=8080;

const string user="ojcmd";

const string psword="123456";

const string db="oj";

const string table="ojdata";

const string Sql="select * from ";

class Model{

//两个主要的接口获取全部题目和单个题目和文件版相同,我们只要拼接不同的sql指令获取相应结果即可

public:

~Model()

{}

bool sqlquery(const string&sql,vector*vq) //执行sql指令拿到相应结果返回

{ //1创建句柄

MYSQL*my=mysql_init(nullptr);

cout<<"mysql version:"<

// 2 连接数据库

if(nullptr==mysql_real_connect(my,ip.c_str(),user.c_str(),psword.c_str(),db.c_str(),port,nullptr,0))

{

LOG(FATAL)<<"连接数据库失败"<<"\n";

cout<

return false;

}

//3 设置编码格式

mysql_set_character_set(my,"utf8"); //必须设置编码格式

// 4 执行sql指令

if(0!=mysql_query(my,sql.c_str())) //返回0 是执行成功 要注意不是失败

{

cout<

LOG(ERROR)<<"sql执行失败"<<"\n";

return false;

}

//5 拿到结果

MYSQL_RES* result=mysql_store_result(my);

int row=mysql_num_rows(result); //获取结果记录数

int col=mysql_num_fields(result); //获取字段数

//cout<

question q;

for(int i=0;i

{

MYSQL_ROW line=mysql_fetch_row(result);

q.number=line[0]; //题目结构体和题目表字段一一对应直接填充

q.title=line[1];

q.grade=line[2];

q.desc=line[3];

q.header=line[4];

q.tail=line[5];

q.cpu_limit=atoi(line[6]);

q.mem_limit=atoi(line[7]);

vq->push_back(q);

}

free(result); //关闭动态开辟的内存

mysql_close(my); //关闭句柄

return true;

}

bool getallquestion(vector*vq)

{

string s=Sql;

s+=table;

if(sqlquery(s,vq))

{

LOG(INFO)<<"加载全部题目成功"<<"\n";

return true;

}

else

{

return false;

}

}

bool getonequestion(const string num,question*q)

{

bool falt=false;

string s=Sql;

s+=table;

s+=" where number=";

s+=num;

vectorvq;

if(sqlquery(s,&vq))

{

if(vq.size()==1)

{

LOG(INFO)<<"加载题目"<

*q=vq[0];

falt=true;

return falt;

}

}

LOG(ERROR)<<"加载题目"<

return falt;

}

};

}

7.完整的工具类展示

#pragma once

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

namespace lcy_util

{

class Timehandle

{

public:

static std::string gettimestamp() //返回时间 秒

{

struct timeval tv;

gettimeofday(&tv,nullptr); //这里使用系统函数 gettimeofday获取时间秒

return std::to_string(tv.tv_sec);

}

static std::string gettimems() //返回时间 毫秒

{

struct timeval tv; //该结构体中一个数据类型是秒 一个是微妙

gettimeofday(&tv,nullptr);

return std::to_string(tv.tv_sec*1000+tv.tv_usec/1000); //转换成毫秒 返回字符串

}

};

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

class Utilpath //各种文件名拼接的静态方法

{

static std::string jointfile(const std::string& file_name,const std::string& suffix)

{

std::string filename=temp_file;

filename+=file_name;

filename+=suffix;

return filename;

}

public:

//编译文件时形成的源文件名、可执行文件名、编译出错错误信息写入编译出错文件名

static std::string makesrc(const std::string& file_name) //单单是文件名要形成在当前临时文件目录下的

{ //带.cpp后缀的源文件、编译成功后的.exe文件

return jointfile(file_name,".cpp"); //编译失败错误信息会输出的标准错误中,我们形成一个带错误信息的文件.stderr

}

static std::string makeexe(const std::string& file_name)

{

return jointfile(file_name,".exe");

}

static std::string compileerror(const std::string& file_name)

{

return jointfile(file_name,".compilerror");

}

// 编译完成后,我们要运行代码,形成运行时输入数据保存在输入临时文件中,输出数据保存在输出临时文件中,运行出错信息保存在出错临时文件中

// stdinfile stdoutfile stderrfile

static std::string Runstdin(const std::string& file_name)

{

return jointfile(file_name,".stdin");

}

static std::string Runstdout(const std::string& file_name)

{

return jointfile(file_name,".stdout");

}

static std::string Runstderr(const std::string& file_name)

{

return jointfile(file_name,".stderr");

}

};

class Filehandle

{

public:

static void removefile(const std::string&filename) //一次用户提交上来的数据我们做出来返回后,形成的临时文件可以调用该方法即时删除

{

std::string src=Utilpath::makesrc(filename);

if(isexist(src))

unlink(src.c_str());

std::string comerror=Utilpath::compileerror(filename);

if(isexist(comerror))

unlink(comerror.c_str());

std::string exe=Utilpath::makeexe(filename);

if(isexist(exe))

unlink(exe.c_str());

std::string runin=Utilpath::Runstdin(filename);

if(isexist(runin))

unlink(runin.c_str());

std::string runout=Utilpath::Runstdout(filename);

if(isexist(runout))

unlink(runout.c_str());

std::string runerror=Utilpath::Runstderr(filename);

if(isexist(runerror))

unlink(runerror.c_str());

}

static bool isexist(const std::string& filename) //判断文件是否存在

{

struct stat status;

if(stat(filename.c_str(),&status)==0)

{

//获取文件属性成功,说明文件存在

return true;

}

return false;

}

static std::string uniquename() //形成唯一文件名 我们以毫秒级时间戳和原子性自增值来构成唯一的文件名

{

static std::atomic_uint id(0);

++id;

return to_string(id)+"##"+Timehandle::gettimems(); //形成唯一的文件名

}

static bool writefile(const std::string&src,const std::string&data) //写入文件

{

std::ofstream out(src.c_str());

if(!out.is_open())

{

return false;

}

out.write(data.c_str(),data.size());

out.close();

return true;

}

static bool readfile(const std::string&src,std::string*data, bool need)

{

(*data).clear();

std::ifstream in(src);

if(!in.is_open())

{

return false;

}

std::string line;

while(getline(in,line)) //getlin 按行读取不会保存行分割符,我们可以自己选择要不要分隔符

{

(*data)+=line;

(*data)+=(need?"\n":"");

}

in.close();

return true;

}

};

class Stringutil

{

public:

static void splitstring(const std::string& str,std::vector*token,const std::string gap)

{

boost::split(*token,str,boost::is_any_of(gap),boost::algorithm::token_compress_on);

}

};

}

推荐文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: