//业务处理模块 #ifndef SERVICE_H #define SERVICE_H #include "data.hpp" #include "httplib.h" extern nmzcloud::DataManager* _data;//因为也会访问数据管理类 namespace nmzcloud{ class Service{ private: int _server_port; std::string _server_ip; std::string _download_prefix;//下载前缀路径 httplib::Server _server; private: //业务处理接口 //上传业务处理接口 static void Upload(const httplib::Request &req,httplib::Response &rsp) { //文件上传是一个post请求的upload才可以 //文件数据在正文中,是一个分区存储(正文并不全是文件数据) auto ret = req.has_file("file");//判断有没有这个上传的文件区域 if(ret == false) { rsp.status = 400;//设置状态码为400 return; } const auto& file = req.get_file_value("file");//如果有的话获取文件数据 //里面就包含了文件名称和正文数据 // file.filename;//文件名称 file.content;//文件数据 //此时需要在备份目录下创建这个文件把数据写入进去 std::string back_dir = Config::GetInstance()->GetBackDir();//获取备份目录 std::string realpath = back_dir + FileUtil(file.filename).FileName();//文件的实际存储路径 FileUtil fu(realpath);//将文件的实际路径给进去 fu.SetContent(file.content);//将数据写入文件中 BackupInfo info; info.NewBackupInfo(realpath);//组织备份的文件信息 _data->Insert(info);//向数据管理模块添加备份的文件信息 return ; } //实现一个接口将时间戳转换成时间格式的字符串 static std::string TimeToStr(time_t t) { std::string tmp = std::ctime(&t); return tmp; } //展示页面获取处理接口 static void ListShow(const httplib::Request &req,httplib::Response &rsp) { //展示页面的获取就是获取一个html页面,html页面前面已经搞好了 // 最终要组织的就是拥有所有文件信息的html文件数据,最终把这个数据响应给客户端就可以了。 // 所以在处理这个展示页面的时候,我们有两个过程 // 1、获取所有的文件备份信息 // 服务器一运行就从原来备份的持久化存储文件中加载数据 std::vector<BackupInfo> array; _data->GetAll(&array);//获取到所有的信息 // 2、根据所有备份信息组织成一个html文件数据 // html中要根据不同的文件进行不同的改变 std::stringstream ss;//使用字符串流 ss << "<html><head><title>DownLoad</title></head>"; ss << "<body>DownLoad<table>"; for(auto & a: array) { //根据每一个不同的文件组织中间的数据 ss << "<tr>"; //url链接随着资源的不同而不同 //html中不区分单引号和双引号,所以改成单引号//进来之后我们要写入我们的url链接了 std::string filename = FileUtil(a.real_path).FileName();//只要文件名 ss << "<td> <a rel="nofollow" href = '" << a.url << "'>" << filename << "</a></td>"; ss << "<td align = 'right'>" << TimeToStr(a.mtime) << "</td>";//a.mtime是一个时间戳并不是格式时间 ss << "<td align = 'right'>" << a.fsize/1024 << "K</td>"; //文件大小,以K为单位 ss << "</tr>"; } ss<< "</table></body></html>";//结尾 rsp.body = ss.str(); //设置头信息 rsp.set_header("Content-Type","text/html"); rsp.status = 200; return ; } //下载处理接口 //先生成一个GetETag,获取文件的ETag,传递一个文件的路径进来 static std::string GetETag(const BackupInfo &info) { //ETag:filename-fsize-mtime FileUtil fu(info.real_path); std::string etag = fu.FileName(); etag += "-"; etag += std::to_string(info.fsize); etag += "-"; etag += std::to_string(info.mtime); return etag; } static void Download(const httplib::Request &req,httplib::Response &rsp) { // 1、获取客户端请求的资源路径path---其实就是req里面的path // 2、根据req资源路径,获取文件备份信息 BackupInfo info; _data->GetOneByURL(req.path,&info); // 3、判断文件是否被压缩,如果被压缩要先解压缩 if(info.pack_flag == true) { FileUtil fu(info.pack_path); fu.UnCompress(info.real_path); // 删除压缩包,修改文件信息(此时已经没有被压缩) info.pack_flag = false; fu.Remove(); info.pack_flag = false; _data->Update(info);//修改 }
//判断是否存在某个头部字段,如果有就是断点续传,如果没有就不是断点续传
bool retrans = false;//用于标记当前是否是断点续传
//如果没有If-Range字段则是正常下载,或者如果有这个字段,但是他的值与当前文件的etag不一致
// 则也必须重新返回全部数据,为新的文件下载
std::string old_etag;//就是原来我们下载的那个etag的值,到时候请求的时候会把etag的值给我们放到
// 请求的If-range里面去,我们就可以把他取出来
if(req.has_header("If-Range"))
{
old_etag = req.get_header_value("If-Range");
//有If-Range字段且这个字段的值与请求文件的最新etag一致则符合断点续传的要求
if(old_etag == GetETag(info))
{
retrans = true;
}
}
// 4、读取文件数据,放入rsp.body中
FileUtil fu(info.real_path);
//如果retrans==false则是正常的文件下载
if(retrans == false){
fu.GetContent(&rsp.body);
// 5、设置响应头部字段:ETag,Accept-Ranges:bytes
rsp.set_header("Accept-Ranges","bytes");
rsp.set_header("ETag",GetETag(info));
rsp.set_header("Content-Type","application/octet-stream");//表示想要的正文是一个二进制数据流,常用于文件下载
rsp.status = 200;
}
else{
//此时就表示是一个断点续传,我们就要取出range字段了解请求区间的范围然后去读取指定区间的一个数据
// 但是我们不需要这样做了,因为当初设计这个项目的时候,我们的httplib里面已经支持了断点续传的响应
// httplib里面已经进行了解析,判断解析了之后有没有range头部字段,内部请求的区间范围是什么
// 所以我们目前要做的就是读取文件数据到rsp.body中去
fu.GetContent(&rsp.body);
rsp.set_header("Accept-Ranges","bytes");
rsp.set_header("ETag",GetETag(info));
rsp.set_header("Content-Type","application/octet-stream");//表示想要的正文是一个二进制数据流,常用于文件下载
rsp.status = 206;//其实我们不用给,httplib里都已经操作了
}
}
public:
//构造函数
Service()
{
//_server直接实例化完毕,不需要进行初始化
//而剩下的成员变量中的数据是从配置文件中获取到的
nmzcloud::Config* config = Config::GetInstance();//获取句柄
_server_port = config->GetServerPort();
_server_ip = config->GetServerIp();
_download_prefix = config->GetDownloadPrefix();
//我们还要考虑文件的备份目录,如果文件备份目录不存在肯定是要进行创建的
}
//运行模块
bool RunModule()
{
//搭建服务器
//注册一个映射关系,哪一个请求方法的什么请求应该用哪一个函数处理先提前注册告知我们的server
// 收到请求进行调用处理
_server.Post("/upload",Upload);//Post请求的upload则表示文件资源的上传
_server.Get("/listshow",ListShow);//文件列表请求
_server.Get("/",ListShow);//文件列表请求
//第一个参数是文件名,那么这个文件名到底是什么呢?所以这时候就用到了正则表达式的规则匹配,只要能够
// 去匹配一个字符串就行了,不管后面是什么样的文件名,所以后面加上括号,捕捉这个数据
std::string download_url = _download_prefix + "(.*)";
_server.Get(download_url,Download);//文件下载请求,.*表示匹配任意一个字符任意次
_server.listen(_server_ip.c_str(),_server_port);
return true;
}
};
} #endif
标签:info,文件,string,处理,业务,server,std,模块,rsp From: https://blog.51cto.com/u_15562309/7380300