当前位置: 首页 > news >正文

云备份服务端

 文件使用工具和json序列化反序列化工具

//文件和json工具类的设计实现
#ifndef __UTIL__
#define __UTIL__
#include<iostream>
#include<fstream>
#include<string>
#include <vector>
#include<sys/stat.h>
#include"bundle.h"
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>
namespace cloud
{namespace fs = std::experimental::filesystem;class FileUtil{private:std::string _filename;// ../YUNBEIFEN/cloud.cpp,是一个带文件名的文件路径public:FileUtil(const std::string& filename):_filename(filename){}int64_t FileSize()//获取文件大小{struct stat st;if(stat(_filename.c_str(),&st)<0)//一个系统调用接口,int stat(const char *path, struct stat *buf),参数path是文件路径(名),输出型参数struct stat类型的结构体保存文件信息{std::cout<<"get file size failed!\n";return -1;}return st.st_size;//返回文件大小}time_t LastMTime()//获取文件最后一次修改时间{struct stat st;if(stat(_filename.c_str(),&st)<0){std::cout<<"get file MTime failed!\n";return -1;}return st.st_mtime;//返回最后一次修改时间}time_t LastATime()//获取文件最后一次访问时间,用于热点文件管理,判断文件是否是热点文件。{struct stat st;if(stat(_filename.c_str(),&st)<0){std::cout<<"get file ATime failed!\n";return -1;}return st.st_atime;//返回最后一次访问时间}std::string FileName()//获取文件名,../YUNBEIFEN/cloud.cpp->cloud.cpp{size_t pos=_filename.find_last_of("/");if(pos==std::string::npos){return _filename;//类成员_filename本身就是一个不带路径的文件名}return _filename.substr(pos+1);}bool GetPosLen(std::string *body,size_t pos,size_t len){size_t fsize = this->FileSize();if (pos + len > fsize)//如果获取的文件超过了文件的大小{std::cout << "file len error\n";return false;}std::ifstream ifst;ifst.open(_filename,std::ios::binary);//以二进制的方式读取文件,if (ifst.is_open() == false) //如果打开文件失败{std::cout << "read_open file failed!\n";return false;}ifst.seekg(pos, std::ios::beg);//从文件的起始位置beg偏移pos个单位body->resize(len);ifst.read(&(*body)[0], len);//读取数据到body中,&(*body)[0]是空间body首地址。if (ifst.good() == false) //如果上一次操作这里是读取异常{std::cout << "get file content failed\n";ifst.close();return false;}ifst.close();return true;}bool GetContent(std::string *body){size_t fsize=this->FileSize();return GetPosLen(body,0,fsize);}bool SetContent(const std::string &body) //将body里面的内容设置到文件中{std::ofstream ofs;//注意和ifstream区分开了,这里是进行写入数据ofs.open(_filename, std::ios::binary);//没有就直接创建,_filename是一个包含文件名的路径if (ofs.is_open() == false) //文件打开失败{std::cout << "write_open file failed!\n";return false;}ofs.write(&body[0], body.size());//将body中的数据写入到ofs当中,&(*body)[0]是空间body首地址。if (ofs.good() == false) {std::cout << "write file content failed!\n";ofs.close();return false;}ofs.close();return true;}bool Compress(const std::string &packname)//对文件进行压缩,压缩之后放入到packname中{//第一步是读取源文件数据std::string body;if (this->GetContent(&body) == false){std::cout << "compress: get file content failed!\n";return false;}//第二步对数据进行压缩std::string packed = bundle::pack(bundle::LZIP, body);//第三步将压缩的数据存储到压缩包当中FileUtil f(packname);if (f.SetContent(packed) == false){std::cout << "compress: write packed data failed!\n"; return false;}return true;}bool UnCompress(const std::string &filename)//将当前压缩文件的数据解压缩到filename当中{//读取当前压缩包的数据std::string body;if(this->GetContent(&body)==false){std::cout << "uncompress: get file content failed!\n";return false;} //对压缩数据进行解压缩std::string unpacked=bundle::unpack(body);//将解压缩的数据写入FileUtil f(filename);if(f.SetContent(unpacked)==false){std::cout << "uncompress write packed data failed!\n";return false;}return true;}bool Exists(){return fs::exists(_filename);//判断是否存在该文件}bool CreateDirectory(){if (this->Exists()) return true;//如果存在该目录就直接返回return fs::create_directories(_filename);//否则创建这个目录,多层级的目录创建}bool ScanDirectory(std::vector<std::string> *arry)//通过arry返回目录里面的所有文件的完整路径名称{for(auto& p: fs::directory_iterator(_filename)) //{if (fs::is_directory(p) == true)//如果是目录就跳过,因为实际应用中的时候我们传输的是普通文件,不可能传递文件夹m{continue;}//relative_path 是带有相对路径的文件名arry->push_back(fs::path(p).relative_path().string()); }return true;}bool Remove(){if(this->Exists()==false) return true;remove(_filename.c_str());return true;}};class JsonUtil{public:static bool Serialize(const Json::Value &root, std::string *str)//静态函数,外部可以直接调用,不需要this指针,也就是说不用实例化一个具体的对象来调用。{Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());//通过StreamWriterBuilder的newStreamWriter()来new一个StreamWriter对象std::stringstream ss;if(sw->write(root, &ss) != 0){std::cout<<"json write failed!\n";return false;}*str=ss.str();return true;}static bool UnSerialize(const std::string &str, Json::Value *root){Json::CharReaderBuilder crb;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());std::string err;cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);return true;}};} 
#endif

配置文件信息模块

 将服务端运行所需要的关键信息记录在配置文件里面,当我们运行程序的时候从我们的配置文件中读取这些关键信息出来,在程序中进行使用,使用配置文件的好处就是我们的配置信息随时可以进行更改,而更改配置信息之后我们的程序并不需要进行重新生成编译,只需要重启一下可执行程序,对于修改后的配置文件进行重新加载即可,用配置文件加载程序更加灵活。

{"hot_time" : 30,//热点判断时间"server_port" : 8080,//"server_ip" : "192.159.124.113","download_prefix" : "/download/",//用于表示客户端请求是一个下载请求。http的url里面包含了协议方案名称+服务器ip地址和端口+客户端所请求的资源路径(如果这个请求是一个文件下载请求的话后面跟的就是文件路径,文件路径肯定是存储在服务器上面的,如果外界可以随时随地访问服务器的任意一个文件就不安全,所以这个路径是一个相对根目录路径,我们会在服务器常见一个文件夹wwwroot做为这个根目录,把客户端所请求的资源都放在这个文件夹里面,当我们请求是/abc.txt是会转换成为./wwwroot/abc.txt,从而避免客户端可以任意访问服务端的所有路径。/download/listshow是下载listshow文件,而/listshow是备份列表查看。"packfile_suffix" : ".lz","pack_dir" : "./packdir/","back_dir" : "./backdir/","backup_file" : "./cloud.dat"//服务端备份信息存放文件,服务端会将我们所有的文件备份信息管理起来,这样文件就算压缩之后客户端查看列表请求因该是源文件信息查看,所以备份文件信息管理可以让客户端随时获取想要的信息。
}

使用单例模式管理系统配置信息,能够让配置信息的管理控制更加统一灵活。

// #配置文件模块:将服务端运行所需要的关键信息记录在配置文件里面每当我们运行系统的时候,从配置文件里面读取关键信息在程序中使用
// #配置文件的好处:配置信息可以随时进行更改,更改配置信息程序并不需要进行重新编译,只需要重启一下服务端程序,让服务端重新加载程序就可以了,从而让程序运行更加的灵活。 
// #配置信息
// #1.热点判断时间
// #热点管理:多长时间没有被访问的文件算是非热点文件
// #2.文件下载的url前缀路径- -用于表示客户端请求是一个下载请求
// #url: htt://192.168.122.136:9090/path
// #当用户发来个备份列表查看请求/listshow,我们如何判断这个不是一个listshow的文件下载请求
// #/download/test.txt, /download/listshow 判断为下载请求
// #3.压缩包后缀名:定义了压缩包命名规则,就是在文件原名称之后加上后缀。".lz"
// #4上传文件存放路径:决定了文件上传之后实际存储在服务器的哪里
// #5.压缩包存放路径:决定非热点文件压缩后存放的路径
// #6.服务端备份信息存放文件:服务端记录的备份文件信息的持久化存储
// #7.服务器的监听IP地址:当程序要运行在其他主机上,则不需要修改程序
// #8.服务器的监听端口
// #9.配置文件采用json的格式来进行存放
#ifndef __CONFIG__
#define __CONFIG__   
#include <mutex>
#include "util.hpp"
namespace cloud
{
#define CONFIG_FILE "./cloud.conf"class Config{private:Config()//构造函数私有化,这样就无法在类外实例化对象,一个类只能实例化一个对象。{ReadConfigFile();} static Config *_instance;//单例模式由于资源只有一个,所以采用一个静态的指针来表示他。static std::mutex _mutex;//因为懒汉模式涉及到了线程安全的额外难题,用的时候才进行加载使用,所以需要采用互斥锁来保证对象实例化的过程private:int _hot_time;//热点判断时间int _server_port;//服务器监听端口std::string _server_ip;//服务器ip地址std::string _download_prefix;//下载的url前缀路径std::string _packfile_suffix;//压缩包的后缀名称std::string _pack_dir;//压缩包存放路径std::string _back_dir;//备份文件存放路径std::string _backup_file;//备份数据信息存放文件bool ReadConfigFile()//读取配置文件{FileUtil f(CONFIG_FILE);std::string body;if(f.GetContent(&body)==false)//读取出来的body是json格式的字符串,还需要反序列化。{std::cout<<"read config file failed\n";return false;}Json::Value root;if(JsonUtil::UnSerialize(body, &root)==false){std::cout<<"parse config file failed\n";return false;}//成员变量赋值 _hot_time = root["hot_time"].asInt();_server_port = root["server_port"].asInt();_server_ip = root["server_ip"].asString();_download_prefix = root["download_prefix"].asString();_packfile_suffix = root["packfile_suffix"].asString();_pack_dir = root["pack_dir"].asString();_back_dir = root["back_dir"].asString();_backup_file = root["backup_file"].asString();return true;}public://由于成员变量都是private私有的,所以需要提供对应的public共有接口进行访问。static Config* GetInstance()//获取操作句柄,cloud::Config *config = cloud::Config::GetInstance();{if(_instance==NULL){_mutex.lock();if(_instance==NULL){_instance=new Config();}_mutex.unlock();}return _instance;}int GetHotTime() {return _hot_time;}int GetServerPort() {return _server_port;}std::string GetServerIp() {return _server_ip;}std::string GetDownloadPrefix() {return _download_prefix;}std::string GetPackFileSuffix() {return _packfile_suffix;}std::string GetPackDir() {return _pack_dir;}std::string GetBackDir() {return _back_dir;}std::string GetBackupFile() {return _backup_file;}};Config *Config::_instance = NULL;//静态成员变量类外定义std::mutex Config::_mutex;
}#endif

 数据管理模块


数据持续化存储的原因是放置服务器每次重启数据都会丢失。

// 数据管理模块:需要管理的数据有哪些
// 管理哪些数据,是因为后期要用到哪些数据
// 1.文件的实际存储路径:当客户端要下载文件时,则从这个文件中读取数据进行响应
// 2.文件压缩包存放路径名:如果这个文件是一个非热点文件会被压缩,则这个就是压缩包路径名称
// 如果客户端要下载文件,则需要先解压缩,然后读取解压后的文件数据。
// 3.文件是否压缩的标志位:判断文件是否已经被压缩了
// 4.文件大小
// 5.文件最后一-次修改时间
// 6.文件最后- -次访问时间
// 7.文件访问URL中资源路径path: /download/a.txt
// 如何管理数据:
// 1.用于数据信息访问:使用hash表在内存中管理数据,以url的path作为key值--查询速度快
// 2.持久化存储管理:使用json序列化将所有数据信息保存在文件中#ifndef __DATA__
#define __DATA__
#include <unordered_map>
#include <pthread.h>
#include "util.hpp"
#include "config.hpp"namespace cloud
{struct BackupInfo//数据信息结构体,存储数据信息,一个文件实例化一个类对象{bool pack_flag;//标志文件是否被压缩size_t fsize;//文件大小time_t mtime;//文件修改时间time_t atime;//文件访问时间std::string real_path;//文件的实际存储路径std::string pack_path;//文件压缩包实际存放路径名std::string url;//外界下载时需要的资源路径bool NewBackupInfo(const std::string &realpath)//填充 BackupInfo结构体{FileUtil fu(realpath);if(fu.Exists() == false)//如果文件并不存在就直接返回false{std::cout<<"NewBackInfo: file not exists!\n";return false;}Config *config = Config::GetInstance();std::string packdir = config->GetPackDir();//从配置文件中获取压缩包存储路径:./packdir/std::string packsuffix = config->GetPackFileSuffix();//从配置文件中获取压缩文件的后缀名:.lzstd::string download_prefix = config->GetDownloadPrefix();//从配置文件中获取客户端下载请求的前缀:/download/,用来构成文件的url成员。this->pack_flag = false;//对于新增文件,该标志位一定是falsethis->fsize = fu.FileSize();this->mtime = fu.LastMTime();this->atime = fu.LastATime();this->real_path = realpath;// ./backdir/a.txt   ->   ./packdir/a.txt.lzthis->pack_path = packdir + fu.FileName() + packsuffix;//存储路径会发生改变,并且多了一个后缀名。// ./backdir/a.txt   ->	  /download/a.txtthis->url = download_prefix + fu.FileName();return true;}};class DataManager//数据管理类{private:std::string _backup_file;//数据信息持久化存储的一个文件pthread_rwlock_t _rwlock;//数据管理类是会在不同模块中被使用的,涉及到了多线程访问,读写锁,读共享(只是获取数据),写互斥,std::unordered_map<std::string, BackupInfo> _table;//在内存中是采用哈希表存储文件信息public:DataManager()//每次重启重启的时候都要加载数据,以及每次数据发生修改或者新增都需要进行重新的持续化存储。{_backup_file = Config::GetInstance()->GetBackupFile();//持久化文件是从我们的配置文件中获取,"backup_file" : "./cloud.dat"pthread_rwlock_init(&_rwlock, NULL);//初始化读写锁NewBackupInfoNewBackupInfo锁InitLoad();//对象在构造的时候需要对其进行加载,也就是从持续化存储的文件中读取数据来初始化成员_table}~DataManager() {pthread_rwlock_destroy(&_rwlock);//销毁锁}bool Insert(const BackupInfo & info)//新增数据{pthread_rwlock_wrlock(&_rwlock);//加锁_table[info.url]=info;pthread_rwlock_unlock(&_rwlock); Storage();//当有数据新增的时候需要进行持续化存储return true;}bool Update(const BackupInfo & info)//数据发生更新或需要修改数据,代码和Insert是一样的。{pthread_rwlock_wrlock(&_rwlock);//加锁_table[info.url]=info;//当存在相同key值的时候,会对value进行覆盖。pthread_rwlock_unlock(&_rwlock); Storage();//当有数据发生修改的时候需要进行持续化存储return true;}bool GetOneByURL(const std::string &url, BackupInfo *info)//因为前端会给一个url请求要下载某个文件,因为url本身就是table的key值,所以直接可以通过find来进行查找。{pthread_rwlock_wrlock(&_rwlock);//因为要对table进行操作,所以要加锁auto it = _table.find(url);//通过find查找url,因为url就是key值。if (it == _table.end()) {pthread_rwlock_unlock(&_rwlock);return false;//没有找到}*info = it->second;pthread_rwlock_unlock(&_rwlock);return true;}bool GetOneByRealPath(const std::string &realpath, BackupInfo *info)//根据真实路径获取文件信息,因为服务器要不断检测一个目录中的所有文件,判断文件是否是一个热点文件。这里遍历的是目录,而不是遍历备份文件信息。//realpath不是table的key值,所以得通过遍历table来获取{pthread_rwlock_wrlock(&_rwlock);auto it = _table.begin();for (; it != _table.end(); ++it)//由于realpath不是key值,所以不能通过key值查找。{if (it->second.real_path == realpath)//找到了{*info = it->second;pthread_rwlock_unlock(&_rwlock);return true;}}pthread_rwlock_unlock(&_rwlock); return false;}bool GetAll(std::vector<BackupInfo> *arry)//获取所有文件信息,可以用来组织页面。{pthread_rwlock_wrlock(&_rwlock);auto it = _table.begin();for (; it != _table.end(); ++it){arry->push_back(it->second);}pthread_rwlock_unlock(&_rwlock);return true;}bool Storage()//每次数据新增或者修改都要进行持续化存储,避免数据丢失。(比如说发生数据新增或者数据修改的时候)则需要持久化存储一次,并且涉及到了json的序列化,将其转换成为json的字符串在存储到文件给当中去//将所有文件的BackupInfo存储起来,涉及到了json的序列化{//1.获取所有数据std::vector<BackupInfo> arry;this->GetAll(&arry);//2.将文件信息添加到Json::ValueJson::Value root;for(int i=0;i<arry.size();i++){Json::Value item;item["pack_flag"] = arry[i].pack_flag;item["file_size"] = (Json::Int64)arry[i].fsize;//(Json::Int64)类型强转,因为json里面并没有重载size_t这些类型item["atime"] = (Json::Int64)arry[i].atime;item["mtime"] = (Json::Int64)arry[i].mtime;item["real_path"] = arry[i].real_path;item["pack_path"] = arry[i].pack_path;item["url"] = arry[i].url;root.append(item);//添加数组元素,该数组元素是一个json value对象,关键写法。}//3. 对Json::Value序列化std::string body;JsonUtil::Serialize(root, &body);//4. 将序列化的数据写入到_backup_fileFileUtil fu(_backup_file);fu.SetContent(body);return true; }bool InitLoad()//初始化加载,每次系统重启都要加载以前的数据。初始化程序运行时从文件读取读取数据,反序列化{//1. 将数据文件中的数据读取出来FileUtil fu(_backup_file);if (fu.Exists() == false){return true;//说明以前都没有保存过数据,那就不用读了。}std::string body;fu.GetContent(&body);//2. 反序列化Json::Value root;JsonUtil::UnSerialize(body, &root);//3. 将反序列化得到的Json::Value中的数据添加到table中for (int i = 0; i < root.size(); i++) {BackupInfo info;//现在不可以采用NewBackupInfo,所有的文件信息都是从son::Value对象中加载的。info.pack_flag = root[i]["pack_flag"].asBool();info.fsize = root[i]["file_size"].asInt64();info.atime = root[i]["atime"].asInt64();info.mtime = root[i]["mtime"].asInt64();info.pack_path = root[i]["pack_path"].asString();info.real_path = root[i]["real_path"].asString();info.url = root[i]["url"].asString();Insert(info);}return true;}};
}
#endif

热点文件管理模块

热点管理模块:对服务器上备份的文件进行检测,哪些文件长时间没有被访问,则认为是非热点文件,则压
缩存储,节省磁盘空间。
实现思路:
遍历所有的文件,检测文件的最后-次访问时间,与当前时间进行相减得到差值,这个差值如果大
于设定好的非热点判断时间则认为是非热点文件,则进行压缩存放到压缩路径中,删除源文件
遍历所有的文件:
1.从数据管理模块中遍历所有的备份文件信息
2.遍历备份文件夹,获取所有的文件进行属性获取,最终判断
选择第二种:遍历文件夹,每次获取文件的最新数据进行判断,并且还可以解决数据信息缺漏的问
题,也就是某个文件上传成功了,但是漏掉了添加信息。
1.遍历备份目录,获取所有文件路径名称
2.逐个文件获取最后一次访问时间与当前系统时间进行比较判断
3.对非热点文件进行压缩处理,删除源文件
4.修改数据管理模块对应的文件信息(压缩标志修改为true)

// 热点管理模块:对服务器上备份的文件进行检测,哪些文件长时间没有被访问,则认为是非热点文件,则压 缩存储,节省磁盘空间。
// 实现思路:
// 遍历所有的文件,检测文件的最后- -次访问时间,与当前时间进行相减得到差值,这个差值如果大于设定好的非热点判断时间则认为是非热点文件,则进行压缩存放到压缩路径中,删除源文件
// 遍历所有的文件:
// 1.从数据管理模块中遍历所有的备份文件信息
// 2.遍历备份文件夹,获取所有的文件进行属性获取,最终判断
// 选择第二种:遍历文件夹,每次获取文件的最新数据进行判断,并且还可以解决数据信息缺漏的问题
// 1.遍历备份目录,获取所有文件路径名称
// 2.逐个文件获取最后一次访问时间与当前系统时间进行比较判断
// 3.对非热点文件进行压缩处理,删除源文件
// 4.修改数据管理模块对应的文件信息(压缩标志修改为true)
#ifndef __HOT__
#define __HOT__
#include <unistd.h>
#include "data.hpp"extern cloud::DataManager* _data;
namespace cloud
{class HotManager{//热点管理流程:// 1.获取备份目录下所有文件// 2.逐个判断文件是否是非热点文件// 3.非热点文件压缩处理// 4.删除源文件,修改备份信息private:std::string _back_dir;//备份文件路径std::string _pack_dir;//压缩文件路径std::string _pack_suffix;//压缩包后缀名int _hot_time;//热点的判断时间//非热点文件返回真,热点文件返回假bool HotJudege(const std::string& filename){FileUtil fu(filename);time_t last_atime=fu.LastATime();time_t cur_time=time(NULL);//获取当前系统时间if(cur_time-last_atime>_hot_time) return true;else return false;}public:HotManager(){Config* config=Config::GetInstance();_back_dir=config->GetBackDir();_pack_dir = config->GetPackDir();_pack_suffix = config->GetPackFileSuffix();_hot_time = config->GetHotTime();FileUtil tmp1(_back_dir);FileUtil tmp2(_pack_dir);tmp1.CreateDirectory();//如果目录不存在就创建tmp2.CreateDirectory();}bool RunModule(){while (1)//这个模块是一个死循环的过程{//1.遍历备份的目录,获取所有的文件名FileUtil fu(_back_dir);std::vector<std::string> array;fu.ScanDirectory(&array);//2.遍历判断文件是否是非热点文件for(auto& a:array){if(HotJudege(a)==false) continue;//是热点文件就不做额外处理//3.非热点文件则需要压缩,先获取文件的备份信息BackupInfo bi;if(_data->GetOneByRealPath(a,&bi)==false){//走打破这里说明现在有一个文件存在,但是没有备份信息bi.NewBackupInfo(a);//设置一个新的备份信息}//4.对非热点文件进行压缩FileUtil tmp(a);tmp.Compress(bi.pack_path);//5.删除源文件,修改备份信息tmp.Remove();bi.pack_flag=true;_data->Update(bi);//修改文件信息}usleep(1000);//避免空目录循环遍历,消费cpu资源过高。}return true;}};
}
#endif
cloud::DataManager *_data;//定义一个全局变量,在hot.hpp中会用到:extern cloud::DataManager* _data;
void HotTest()
{	_data=new cloud::DataManager();cloud::HotManager hot;hot.RunModule();
}int main(int argc,char* argv[])
{HotTest();return 0;
}

业务处理模块

三大业务模块


服务端业务处理模块:将网络通信模块和业务处理进行了合并(网络通信通过httplib库完成)
1.搭建网络通信服务器:借助httplib完成
2.业务请求处理
1.文件.上传请求:备份客户端上传的文件,响应上传成功
2.文件列表请求:客户端浏览器请求一个备份文件的展示页面,响应页面
3.文件下载请求:通过展示页面,点击下载,响应客户端要下载的文件数据

网络通信接设计:约定好,客户端发送什么样的请求,我们给与什么样的响应
请求:文件上传,展示页面,文件下载

 

 

断点续传模块


功能:当文件下载过程中,因为某种异常而中断,如果再次进行从头下载,效率较低,因为需要将
之前已经传输过的数据再次传输一遍。因此断点续传就是从上次下载断开的位置,重新下载即可,之前已经传输过的数据将不需要再重新传输。
目的:提高文件重新传输效率
实现思想:
客户端在下载文件的时候,要每次接收到数据写入文件后记录自己当前下载的数据量。当异常下载中断时,下次断点续传的时候,将要重新下载的数据区间(下载起始位置,结束位置)发送给服务器,服务器收到后,仅仅回传客户端需要的区间数据即可。
需要考虑的问题:如果上次下载文件之后,这个文件在服务器上被修改了,则这时候将不能重新断
点续传,而是应该重新进行文件下载操作。
在http协议中断点续传的实现:
主要关键点:
1.在于能够告诉服务器下载区间范围,
2.服务器上要能够检测上一次下载之后这个文件是否被修改过

//当我们浏览器输入192.182.142.10:9090的时候,没有任何的请求的情况下,浏览器会默认加上一个\表示这是一个根目录请求
// 服务端业务处理模块:将网络通信模块和业务处理进行了合并(网络通信通过httplib库完成)
// 1.搭建网络通信服务器:借助httplib完成
// 2.业务请求处理
// 1.文件上传请求:备份客户端上传的文件,响应上传成功
// 2.文件列表请求:客户端浏览器请求一个备份文件的展示页面, 响应页面
// 3.文件下载请求:通过展示页面,点击下载,响应客户端要下载的文件数据// 网络通信接口设计:约定好,客户端发送什么样的请求,我们给与什么样的响应
// 请求:文件上传,展示页面,文件下载
// 接口设计: 
// 1.文件上传
// POST /upload HTTP/1.1
// Content-Length:11
// Content-Type:multipart/form-data;boundary= ----WebKitFormBoundary+16字节随机字符
// ------WebKitFormBoundary
// Content-Disposition:form-data;filename="a.txt";
// hello world
// ------WebKitFormBoundary--
// 当服务器收到了一个POST方法的/upload请求,我们则认为这是一个文件上传请求
// 解析请求,得到文件数据,将数据写入到文件中
// HTTP/1.1 200 OK
// Content-Length: 0
// 2.展示页面
// GET /list HTTP/1.1
// Content-Length: 0
//当服务器收到了一个GET方法的/listshow请求,我们则认为这是一个文件页面展示请求
// HTTP/1.1 200 OK
// Content-Length:
// Content-Type: text/html// <html>..... </html> <!-- 这是展示页面的数据-->
// 3.文件下载
// GET /download/a.txt http/1.1
// Content-Length: 0
// 当服务器收到了一个GET方法的/download/请求,我们则认为这是一个文件下载请求
// HTTP/1.1 200 OK
// Content-Length: 100000
// ETags: "filename-size-mtime一个能够唯一标识文件的数据"
// Accept-Ranges: bytes
// 正文就是文件数据#ifndef __SERVICE__
#define __SERVICE__
#include "data.hpp"
#include "httplib.h"extern cloud::DataManager* _data;
namespace cloud
{class Service{private://搭建http服务器,并进行业务处理。int _server_port;std::string _server_ip;std::string _download_prefix;httplib::Server _server;public:Service(){Config *config = Config::GetInstance();_server_port = config->GetServerPort();_server_ip = config->GetServerIp();_download_prefix = config->GetDownloadPrefix();}bool RunModule(){//搭建服务器,注册映射关系,然后listen_server.Post("/upload",Upload);_server.Get("/listshow",ListShow);_server.Get("/",ListShow);//当我们浏览器访问服务器采用172.16.204.184:9090后面什么也不加没有任何的资源路径请求,浏览器会在末尾默认加一个/是一个相当于一个根目录请求,我们response一个展示页面。std::string download_url=_download_prefix+"(.*)";_server.Get(download_url,Download);//(.*)用来捕捉数据,匹配多个字符_server.listen(_server_ip.c_str(),_server_port);return true;}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;//格式错误return;}std::cout<<"ok1"<<std::endl;//有文件上传就获取数据const auto& file = req.get_file_value("file");//其中两个成员变量分别指的是: file.filename//文件名称    file.content//文件数据std::cout<<"ok2"<<std::endl;//找到文件并在备份目录下创建该文件,并且把数据写入进去。std::string back_dir = Config::GetInstance()->GetBackDir();std::cout<<"ok3"<<std::endl;std::string realpath = back_dir + FileUtil(file.filename).FileName();//FileUtil(file.filename).FileName()是为了只获取文件名,路径并不需要。std::cout<<"ok4"<<std::endl;FileUtil fu(realpath);std::cout<<realpath<<std::endl;std::cout<<"ok5"<<std::endl;fu.SetContent(file.content);//将数据写入文件中;std::cout<<"ok6"<<std::endl;BackupInfo info;info.NewBackupInfo(realpath);//组织备份的文件信息std::cout<<"ok7"<<std::endl;_data->Insert(info);//向数据管理模块添加备份的文件信息std::cout<<"ok8"<<std::endl;return;}static std::string TimetoStr(time_t t) //将时间戳转换成为易读的字符串,当前函数设置为静态函数的原因是因为ListShow是静态成员函数,里面调用的也只能是静态成员函数,否则this变量从哪里来。{std::string tmp = std::ctime(&t);return tmp;}static void ListShow(const httplib::Request &req, httplib::Response &rsp){//1.获取所有的备份文件信息std::vector<BackupInfo> arry;//用来放置所有的文件信息,后面用到页面展示_data->GetAll(&arry);//2.根据所有的备份信息,组织html文件数据std::stringstream ss;ss << "<html><head><title>Download</title></head>";ss << "<body><h1>Download</h1><table>";for (auto &a : arry){ss << "<tr>";std::string filename = FileUtil(a.real_path).FileName();ss << "<td><a href='" << a.url << "'>" << filename << "</a></td>";ss << "<td align='right'>" << TimetoStr(a.mtime) << "</td>";ss << "<td align='right'>" << a.fsize / 1024 << "k</td>";ss << "</tr>";}ss << "</table></body></html>";// '/'意味着结尾rsp.body = ss.str();rsp.set_header("Content-Type", "text/html");//设置正文数据类型,告诉浏览器这是一个html数据,你需要对其进行渲染让后展示在浏览器当中。rsp.status = 200;return ;}static std::string GetETag(const BackupInfo &info) //http的ETag头部字段存储了一个资源的唯一标识,当客户端第一次下载一个文件的时候,我们生成一个唯一标识最为响应的一部分给客户端,//客户端第二次下载的时候,就会将该信息发送给服务器,让服务器根据这个唯一标识判断这个资源自从上次下载过之后是否又被修改过,如果没有修改的话,直接使用原先缓存的数据,不用再重新下载了。{//etg:filename-fsize-mtime,这个etag是自定义的,也说明了http协议本身对于etag中是什么数据并不关心,只要你服务端可以自己标识就行了。而etag字段不仅仅是缓存用到,还有就是后边的断点续传的实现也会用到,因为断点续传也要保证文件没有被修改过。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){//http协议的Accept-Ranges: bytes字段:用于告诉客户端我支持断点续传 ,并且数据单位以字节作为单位。//1. 获取客户端请求的资源路径path,其中req.path已经组织好了,是一个url资源请求类型的路径//2. 根据资源路径,获取文件备份信息BackupInfo info;_data->GetOneByURL(req.path, &info);//3. 判断文件是否被压缩,如果被压缩,要先解压缩, if (info.pack_flag == true){FileUtil fu(info.pack_path);//packfu.UnCompress(info.real_path);//将文件解压到备份目录下//4. 删除压缩包fu.Remove();//5修改备份信息(改为没有被压缩)info.pack_flag = false;_data->Update(info);}//断点续传的判断主要在于解析请求中是否If-Range这个字段的信息bool retrans = false;//定义当前是否属于断点续传std::string old_etag;if (req.has_header("If-Range")) {old_etag = req.get_header_value("If-Range");//存在If-Range字段说明需要断点续传,这时候获取该字段的value值,也就是以前该文件的etagif (old_etag == GetETag(info)) //有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传{retrans = true;}}//如果没有If-Range字段或者有这个字段但是old_etag与当前服务器的etag不匹配的话,则必须重新返回全部的数据,也就是进入正常的下载模式。//6. 读取文件数据,放入rsp.body中FileUtil fu(info.real_path);if (retrans == false)//响应执行正常的文件下载任务{fu.GetContent(&rsp.body);//7. 设置响应头部字段: ETag, Accept-Ranges: bytes,Content-Length会自动设置,不需要我们主动设置。rsp.set_header("Accept-Ranges", "bytes");rsp.set_header("ETag", GetETag(info));rsp.set_header("Content-Type", "application/octet-stream");//Content-Type字段:决定了浏览器如何处理响应正文,如果不设置浏览器无法得知如何处理正文数据,application/octet-stream说明数据是二进制数据流,常用于文件下载。rsp.status = 200;}else//响应执行断点续传任务 {//httplib内部实现了对于区间请求也就是断点续传请求的处理//只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求//区间,从body中取出指定区间数据进行响应// std::string  range = req.get_header_val("Range"); bytes=start-endfu.GetContent(&rsp.body);rsp.set_header("Accept-Ranges", "bytes");rsp.set_header("ETag", GetETag(info));rsp.set_header("Content-Type", "application/octet-stream");//rsp.set_header("Content-Range", "bytes start-end/fsize");rsp.status = 206;//状态码206是指服务器成功处理了布冯GET请求。}}};
}
#endif



 

相关文章:

云备份服务端

文件使用工具和json序列化反序列化工具 //文件和json工具类的设计实现 #ifndef __UTIL__ #define __UTIL__ #include<iostream> #include<fstream> #include<string> #include <vector> #include<sys/stat.h> #include"bundle.h" #inc…...

Jupyter Notebook 使用教程

Jupyter Notebook 使用教程 目录 概述启动Jupyter Notebook创建新的NotebookNotebook界面介绍使用代码单元格使用Markdown单元格Notebook的基本操作保存和导出Notebook扩展功能和技巧 1. 概述 Jupyter Notebook是一个开源的Web应用程序&#xff0c;允许您创建和共享包含代码…...

Leetcode 100361100367.切割蛋糕的最小总开销

Medium&#xff1a;动态规划搜索&#xff08;实际就是优化后的dfs&#xff09; class Solution { public: int f[25][25][25][25] {0};int dp(int row1, int col1, int row2, int col2, vector<int>& horizontalCut, vector<int>& verticalCut){if(row1 …...

单网口设备的IP地址识别-还原-自组网

1.如果知道该设备所在网段&#xff1a; 此时可以使用nmap工具&#xff0c;进行网段扫描&#xff1a; nmap -sn 192.168.0.0/24 256个地址的子网10秒就能扫描一轮。关掉设备&#xff0c;打开设备&#xff0c;diff&#xff0c;基本就可以定位所要找到目标设备的IP 2.如果不知道…...

太速科技-FMC207-基于FMC 两路QSFP+光纤收发子卡

FMC207-基于FMC 两路QSFP光纤收发子卡 一、板卡概述 本卡是一个FPGA夹层卡&#xff08;FMC&#xff09;模块&#xff0c;可提供高达2个QSFP / QSFP 模块接口&#xff0c;直接插入千兆位级收发器&#xff08;MGT&#xff09;的赛灵思FPGA。支持利用Spartan-6、Virtex-6、Kin…...

昇思25天学习打卡营第13天|munger85

文本解码原理–以MindNLP为例 重要的就是怎么样把数字最后转化成真正的文字。而且自回归模型它会一个字给一个字的预测&#xff0c;下一个字应该是什么&#xff1f; 如果这个模型下载很慢&#xff0c;你就可以通过这种方式从摩大社区进行下载。 这种方式&#xff0c; 每一次候…...

Python - Word转TXT文本,或TXT文本转Word

Word文档&#xff08;.doc或.docx&#xff09;和纯文本文件&#xff08;.txt&#xff09;是两种常用的文件格式。Word文档通常用于复杂的文档处理和排版&#xff0c;而纯文本文件则用于存储和传输纯文本信息。了解如何在这两种格式之间进行转换能提高工作效率&#xff0c;并便于…...

链接追踪系列-00.es设置日志保存7天-番外篇

索引生命周期策略 ELK日志我们一般都是按天存储&#xff0c;例如索引名为"zipkin-span-2023-03-24"&#xff0c;因为日志量所占的存储是非常大的&#xff0c;我们不能一直保存&#xff0c;而是要定期清理旧的&#xff0c;这里就以保留7天日志为例。 自动清理7天以前…...

Vant Ui 最新访问地址

Vant 4 - A lightweight, customizable Vue UI library for mobile web apps. 顺带一个顶部导航栏正常写法 先使用吸顶为0&#xff0c;然后再写nav-bar <van-sticky :offset-top"0"> <van-nav-bar class"top-title" title"村集体土地公示&q…...

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(八)-通过无人机进行无线接入

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…...

PTrade量化交易终端常见问题11

盈亏分析为空。 回测详情内&#xff0c;盈亏分析内为空。 1、回测正常结束&#xff0c;并且产生多笔交易&#xff1b; 2、盈亏分析热力图无任何内容&#xff0c;检查支持版本&#xff0c;盈亏分析是在需求单号&#xff1a;202211114089&#xff0c;于PTrade1.0-QTV202301.01.…...

被动的机器人非线性MPC控制

MPC是一种基于数学模型的控制策略&#xff0c;它通过预测系统在未来一段时间内的行为&#xff0c;并求解优化问题来确定当前的控制输入&#xff0c;以实现期望的控制目标。对于非线性系统&#xff0c;MPC可以采用非线性模型进行预测和优化&#xff0c;这种方法被称为非线性模型…...

什么样的服务器是合乎直销网站标准

现在社会的发展,有着越来越多的人想要利用互联网来做直销。做好直销行业系统解决方案离不开好的服务器支持,服务器的的稳定性和速度是直接影响网站后期运作,可以看做是网站的根基。 做网站直销选择租用服务器需要注意的几点要素 一些大的直销互联网公司如安利、雅芳、康宝莱、玫…...

python 语法学习 day13

一.判断题错题反思 1.创建对象是通过调用构造方法完成的 3.python方法定义的第一个参数是self 4.一个对象只能有一个实例变量&#xff08;错&#xff09; 5.在python类中,构造方法的名称为__init__ 6.从类定义之外直接访问实例变量是不好的程序设计风格 7.在python中定义类是时…...

Spring MVC中Restful风格引入

一&#xff0c;RESTful概述 在现代Web应用开发中&#xff0c;RESTful架构风格已成为一种标准实践&#xff0c;特别是在构建可扩展的Web服务时。Spring MVC提供了全面的支持来构建遵循REST原则的Web服务。我在此介绍如何在Spring MVC中实现RESTful风格的Web服务&#xff0c;并通…...

C# Winform 系统方案目录的管理开发

在做一个中等复杂程度项目时&#xff0c;我们通常有系统全局配置&#xff0c;还要有对应的方案目录的管理和更新。 比如我们有如下需求&#xff1a;开发一个方案管理&#xff0c;可以新建、打开和保存方案&#xff0c;同时还需要保存方案中的各种文件。我设计的采用目录管理和…...

算法-二叉树常见问题详解

文章目录 1. 二叉树的三种遍历方式的实质2. 二叉树的序列化与反序列化3. 根据前序中序反序列创建二叉树4. 二叉树的路径问题5. LCA公共祖先问题6. 二叉搜索树的LCA问题7. 验证搜索二叉树8. 修建搜索二叉树9. 二叉树打家劫舍问题 1. 二叉树的三种遍历方式的实质 这个相信大家都不…...

【流媒体】 通过ffmpeg硬解码拉流RTSP并播放

简介 目前RTSP拉流是网络摄像头获取图片数据常用的方法&#xff0c;但通过CPU软解码的方式不仅延时高且十分占用资源&#xff0c;本文提供了一种从网络摄像头RTSP硬解码的拉流的方法&#xff0c;并且提供python代码以便从网络摄像头获取图片进行后续算法处理。 下载ffmpeg F…...

Go语言指针及不支持语法汇总

本文为Go语言中指针定义和示例及不支持语法汇总。 目录 指针 定义指针 关键字new定义 函数返回指针 空指针 Go不支持语法汇总 总结 指针 Go语言也有指针&#xff0c;结构体成员调用时&#xff0c;obj.name Go语言在使用指针时&#xff0c;会使用内容的垃圾回收机制&am…...

Why can‘t I access GPT-4 models via API, although GPT-3.5 models work?

题意&#xff1a;为什么我无法通过API访问GPT-4模型&#xff0c;尽管GPT-3.5模型可以工作&#xff1f; 问题背景&#xff1a; Im able to use the gpt-3.5-turbo-0301 model to access the ChatGPT API, but not any of the gpt-4 models. Here is the code I am using to tes…...

MATLAB中Simulink.SimulationData.Dataset用法

目录 语法 说明 示例 访问使用Dataset格式记录的数据 打开模型vdp 使用 Dataset 对象来组合模拟输入信号 Simulink.SimulationData.Dataset的功能是访问已记录的模拟数据或组合模拟输入数据。 语法 ds Simulink.SimulationData.Dataset ds Simulink.SimulationData.Da…...

Spring Security学习笔记(一)Spring Security架构原理

前言&#xff1a;本系列博客基于Spring Boot 2.6.x依赖的Spring Security5.6.x版本 Spring Security中文文档&#xff1a;https://springdoc.cn/spring-security/index.html 一、什么是Spring Security Spring Security是一个安全控制相关的java框架&#xff0c;它提供了一套全…...

nginx的access.log日志输出请求数

适用格式 #log_format main $remote_addr - $remote_user [$time_local] "$request" # $status $body_bytes_sent "$http_referer" # "$http_user_agent" "$http_x_forwarded_for"; 形如: 12…...

前端网站(三)-- 记事本【附源码】

开篇&#xff08;请大家看完&#xff09;&#xff1a;此网站写给挚爱&#xff0c;后续页面还会慢慢更新&#xff0c;大家敬请期待~ ~ ~ 此前端框架&#xff0c;主要侧重于前端页面的视觉效果和交互体验。通过运用各种前端技术和创意&#xff0c;精心打造了一系列引人入胜的页面…...

java——Junit单元测试

测试分类 黑盒测试&#xff1a;不输入代码&#xff0c;给输入值&#xff0c;看程序能够给出期望的值。 白盒测试&#xff1a;写代码&#xff0c;关注程序具体执行流程。 JUnit单元测试 一个测试框架&#xff0c;供java开发人员编写单元测试。 是程序员测试&#xff0c;即白…...

Scala学习笔记17: Try与异常处理

目录 第十七章 Try与异常处理1- 异常的抛出和捕获1. 异常抛出2. 异常捕获 2- 函数式的错误处理1. Try 类型2. 使用 Try3. 处理 Try 结果4. Try 的常用方法5. Try 的优势总结 end 第十七章 Try与异常处理 1- 异常的抛出和捕获 Scala 的异常处理机制与 Java非常相似, 但也有一些…...

内网信息收集——MSF信息收集浏览器记录配置文件敏感信息

文章目录 一、配置文件敏感信息收集二、浏览器密码&记录三、MSF信息收集 域控&#xff1a;windows server 2008 域内机器&#xff1a;win7 攻击机&#xff1a;kali 就是红日靶场&#xff08;一&#xff09;的虚拟机。 一、配置文件敏感信息收集 使用searchall64.exe&#…...

C++ STL中的std::remove_if 的用法详解

在现代C++编程中,标准模板库(STL)提供了一系列功能强大的算法,这些算法极大地简化了日常的编程任务。其中,std::remove_if是一个非常实用的函数,它允许我们从容器中移除满足特定条件的所有元素。本文将深入探讨std::remove_if的使用方法,并通过一个具体的例子——基于St…...

基于AT89C51单片机的16×16点阵LED显示器字符滚动显示设计(含文档、源码与proteus仿真,以及系统详细介绍)

本篇文章论述的是基于AT89C51单片机的1616点阵LED显示器字符滚动显示设计的详情介绍&#xff0c;如果对您有帮助的话&#xff0c;还请关注一下哦&#xff0c;如果有资源方面的需要可以联系我。 目录 仿真效果图 仿真图 代码 系统论文 资源下载 设计的内容和要求 熟悉51系…...

Docker 日志丢失 - 解决方案

Docker 日志默认使用的是 journald 的方式. RateLimitBurst 是 journald 的一个参数&#xff0c;用于限制日志的速率。如果日志的生成速度超过这个限制&#xff0c;journald 可能会丢弃日志。你可以通过调整这个参数来避免日志被丢弃。 调整 RateLimitBurst 和 RateLimitInte…...