国家城乡建设部网站首页,wordpress鼠标点击,wordpress 有没有上级目录的写权限,怎么在公众号上做网站云备份 云备份概述框架 功能演示服务端客户端 公共模块文件操作模块目录操作模块 服务端模块功能划分功能细分模块数据管理热点管理 客户端模块功能划分功能细分模块数据管理目录检查文件备份 云备份
概述
自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。… 云备份 云备份概述框架 功能演示服务端客户端 公共模块文件操作模块目录操作模块 服务端模块功能划分功能细分模块数据管理热点管理 客户端模块功能划分功能细分模块数据管理目录检查文件备份 云备份
概述
自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。并且能够随时通过浏览器进行查看并且下载其中下载过程支持断点续传功能而服务器也会对上传文件进行热点管理将非热点文件进行压缩存储节省磁盘空间。
框架
实现客户端和服务端双端程序合作实现总体的自动云备份功能
本项目有三大模块
客户端模块服务端模块公共模块
除了自己实现的模块之外还引入了三方库作为辅助
Jsoncpp进行序列化辅助Httplib进行网络传输辅助c17–filestream进行目录检测辅助
功能演示
服务端 若客户端有文件上传则会自动下载到服务端的backupDir目录下 若此文件超过30s时间没有被获取则会自动压缩到packDir目录下 从浏览器访问备份呢列表 点击文件名下载文件 当服务端接收到下载命令时若此文件已在压缩目录下则先将其解压到备份目录中再将内容发送给客户端 若此时有用户访问了服务端并下载了某个文件下载一半时服务端异常关闭导致用户下载中断服务端恢复后用户能继续正常的从上次中断的地方继续下载 实现断点续传
客户端 找到backup目录若没有时则创建可以考虑写成读取配置文件 第一次载入时若没有此目录会自动创建 不断轮询检测目录backup一旦有文件新增或更新就会发送到远端实现云备份 注意不一定所有更新都会发送到远端若有一份很大的文件正在拷贝进backup目录相当于这份文件在实时更新但实际是此文件还没有拷贝完毕不能发送到远端因此客户端的策略是对某个更新的文件若此文件上一次更新时间与当前时间相比超过了3s钟才进行云备份
公共模块
公共模块是完成一系列对文件或者目录上的操作本项目中细分为文件操作模块和目录操作模块以及日志等简单的公共模块
文件操作模块
namespace cloud
{//example: you can nest your own log systemvoid LOG(const char* str){printf([%s |%d]: %s\n, __FILE__, __LINE__, str);}//for operationnamespace fs std::experimental::filesystem;class FileUtil{private:std::string _fileName;public:FileUtil(const std::string fileName) :_fileName(fileName) {}std::string FileName(){return fs::path(_fileName).filename().string();}time_t LastMTime(){struct stat st;if (stat(_fileName.c_str(), st) 0){LOG(file error);return -1;}return st.st_mtime;}size_t FileSize() noexcept{struct stat st;if (stat(_fileName.c_str(), st) 0){LOG(get file size error);return 0;}return st.st_size;}bool GetContentByPosLen(std::string* body, size_t pos, size_t len){size_t fsize FileSize();if (pos len fsize){LOG(file len error);return false;}std::ifstream ifs;ifs.open(_fileName, std::ios::binary);if (!ifs.is_open()){LOG(open failed);return false;}ifs.seekg(pos, std::ios::beg);body-resize(len);ifs.read((*body)[0], len);if (!ifs.good()){LOG(read failed);ifs.close();return false;}ifs.close();return true;}bool SetContent(const std::string body){std::ofstream ofs;ofs.open(_fileName, std::ios::binary);if (!ofs.is_open()){LOG(open file error);return false;}ofs.write(body[0], body.size());if (!ofs.good()){LOG(write error);ofs.close();return false;}ofs.close();return true;}};// end FileUtil
}// end namespace 目录操作模块
class DirUtil{private:std::string _dirPath;public:explicit DirUtil(const std::string dirName) :_dirPath(dirName) {}bool Exists(){return fs::exists(_dirPath);}bool ScanDirectory(std::vectorstd::string* fileArray){if (!Exists()){return fs::create_directories(_dirPath);}for (auto file : fs::directory_iterator(_dirPath)){//to do, directory continue temporarilyif (fs::is_directory(file))continue;fileArray-push_back(fs::path(file).relative_path().string());}return true;}};//end DirUtil服务端模块
功能划分 针对客户端上传的文件进行备份存储 能够对文件进行热点文件管理对非热点文件进行压缩存储节省磁盘空间。 支持客户端浏览器查看访问文件列表。 支持客户端浏览器下载文件并且下载支持断点续传。 备份信息持久化存储和状态恢复
功能细分模块
数据管理
数据管理模块需要完成配置文件的加载以及数据的管理维护工作因此大部分设计都集中在这一块简要概括以下几点 配置文件的加载单独划分为一个模块并设计为单例模式由数据管理者启动时加载并维护 配置文件需要有以下信息做到服务端功能灵活使用 热点判断时间服务器监听窗口下载的url前缀路径压缩包后缀名称备份文件存放目录压缩包存放目录服务器ip地址数据信息持久化存储文件带路径名称 Config类 namespace cloud {
#define CONFIG_FILE ./cloud.confclass Config{private:Config(){ReadConfigFile();}static Config *_instance;static std::mutex _mutex;private:int _hot_time;int _server_port;std::string _server_ip;std::string _download_prefix;std::string _packfile_suffix;std::string _pack_dir;std::string _back_dir;std::string _backup_file;bool ReadConfigFile() {FileUtil fu(CONFIG_FILE);std::string body;if(fu.GetContent(body) false){std::cout load 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:static 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 易错点类内静态成员变量需要在类外初始化 Jsoncpp
服务器启动时需要根据配置文件中的备份文件将上一次退出时服务器的备份信息状态加载进本次服务的启动中恢复状态因此需要将备份数据的信息管理起来但由于服务端对数据需要的管理较多若单纯的使用字符串分割符切割存储肯定是不行的因此使用Json序列化的方式将数据管理信息序列化后再存储进备份信息文件中启动服务器时根据配置文件找到备份信息文件并通过反序列化加载数据管理信息模块进行状态恢复。
配置文件信息也属于数据管理模块的范畴只不过重新封装一个解耦和也是通过Json对配置文件信息进行反序列化而配置文件需要用特定的格式完成编写以便序列化加载配置信息 使用Json 使用命令安装环境CentOS Linux release 7.9.2009 (Core) sudo yum install epel-release
sudo yum install jsoncpp-devel安装完后在系统头文件路径下就会有json需要使用的头文件(自动放的) 使用时只需要包含此头文件的系统路径即可 //example
#include iostream
#include string
#include memory
#include fstream
#include jsoncpp/json/value.h
#include jsoncpp/json/json.hvoid TestJsonCpp()
{std::string str R({name:nicky, age:18, score:[50,80,90]});Json::Value root;Json::CharReaderBuilder builder;std::unique_ptrJson::CharReader reader(builder.newCharReader());std::string err;reader-parse(str.c_str(), str.c_str() str.size(), root, err);std::cout root[name].asCString() std::endl;std::cout root[age].asInt() std::endl;int sz root[score].size();for (int i 0; i sz; i){std::cout root[score][i].asFloat() std::endl; }
}int main()
{TestJsonCpp();return 0;
}注意编译时需要链接上jsoncpp的静态库 g xxx.cpp -o xxx -ljsoncppJsonUtil类 class JsonUtil{
public:static bool Serialize(const Json::Value root, std::string *str){Json::StreamWriterBuilder swb;std::unique_ptrJson::StreamWriter sw(swb.newStreamWriter());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_ptrJson::CharReader cr(crb.newCharReader());std::string err;bool ret cr-parse(str.c_str(), str.c_str() str.size(), root, err);if (ret false) {std::cout parse error: err std::endl;return false; }return true;}
};DataManager 客户端使用简单的映射来建立数据信息管理的唯一性–file, file-fsize-fmtime而由于服务端还需要管理上传文件的压缩包路径和文件备份路径因此我们选择将文件的信息单独分出来一个模块用来管理文件的备份信息–BackupInfo而DataManager中的映射表关系就是建立文件名和文件对应的备份信息之间的映射 namespace cloud
{class BackupFileInfo{public:bool _pack_flag;size_t _fsize;time_t _mtime;time_t _atime;std::string _urlPath;std::string _realPath;std::string _packPath;BackupFileInfo(){}explicit BackupFileInfo(const std::string realPath){FileUtil fu(realPath);if (!fu.Exists()){LOG(backup path not exist!);return;}_pack_flag false;_fsize fu.FileSize();_mtime fu.LastMTime();_atime fu.LastATime();_realPath realPath;// get from config -- which can be changedConfig *config Config::GetInstance(); // single instancestd::string packDir config-GetPackDir();std::string packSuffix config-GetPackSuffix();std::string downPrefix config-GetDownPrefix();_packPath packDir fu.FileName() packSuffix; // ./packDir/a.txt.lz_urlPath downPrefix fu.FileName(); // /download/a.txt}}; // end BackuoInfoclass DataManager{private:std::string _persistentPath;pthread_rwlock_t _rwLock;std::unordered_mapstd::string, cloud::BackupFileInfo _backupTable;public:DataManager(){_persistentPath Config::GetInstance()-GetPersistentPath();pthread_rwlock_init(_rwLock, NULL);InitLoad();}void Insert(BackupFileInfo info){pthread_rwlock_wrlock(_rwLock);_backupTable[info._realPath] info;pthread_rwlock_unlock(_rwLock);LOG(Insert into table success!);}void InitLoad(){FileUtil fu(_persistentPath);if(!fu.Exists()){LOG(persistent path not exist!);return;}std::string body;fu.GetContentByPosLen(body, 0, fu.FileSize());//UnserializationJson::Value root;JsonUtil::UnSerialize(body, root);//insert into tablefor (auto item : root){BackupFileInfo info;info._atime item[atime].asInt64();info._mtime item[mtime].asInt64();info._fsize item[fsize].asInt64();info._pack_flag item[packflag].asBool();info._packPath item[packPath].asString();info._realPath item[realPath].asString();info._urlPath item[urlPath].asString();Insert(info);}LOG(InitLoad success!);}void GetAll(std::vectorBackupFileInfo *allData){pthread_rwlock_rdlock(_rwLock);for (auto item : _backupTable){allData-push_back(item.second);}pthread_rwlock_unlock(_rwLock);LOG(GetAll success!);}void Storage(){//get all data in tablestd::vectorBackupFileInfo allData;GetAll(allData);//add to jsonJson::Value root;for (auto item : allData){Json::Value value;value[atime] (Json::Int64)item._atime;value[mtime] (Json::Int64)item._mtime;value[fsize] (Json::Int64)item._fsize;value[packflag] item._pack_flag;value[packPath] item._packPath;value[realPath] item._realPath;value[urlPath] item._urlPath;root.append(value);}LOG(add to json success!);//serializationstd::string body;JsonUtil::Serialize(root, body);LOG(serialization success!);//write to fileFileUtil fu(_persistentPath);fu.SetContent(body);LOG(write to file success!);LOG(Storage success!);}~DataManager(){pthread_rwlock_destroy(_rwLock);Storage();}}; // end DataManager
} // end namespace#endif
热点管理
对服务器上备份的文件进行检测哪些文件长时间没有被访问则认为是非热点文件则压缩存储节省磁盘空间。
实现思路: 遍历所有的文件检测文件的最后一次访问时间与当前时间进行相减得到差值这个差值如果大于设定好的非热点判断时间则认为是非热点文件则进行压缩存放到压缩路径中删除源文件遍历所有的文件: 1.从数据管理模块中遍历所有的备份文件信息2.遍历备份文件夹获取所有的文件进行属性获取最终判断选择第二种:遍历文件夹每次获取文件的最新数据进行判断并且还可以解决数据信息缺漏的题 1.遍历备份目录获取所有文件路径名称 2.逐个文件获取最后一次访问时间与当前系统时间进行比较判断 3.对非热点文件进行压缩处理删除源文件 4.修改数据管理模块对应的文件信息(压缩标志-》true) 使用bundle 使用第三方模块bundle来对文件进行压缩和解压 从git克隆bundle库 sudo yum install git
git clone https://github.com/r-lyeh-archived/bundle.git将bundle库中的bundle.h和bundle.cpp拷贝到工程目录下 使用时包含bundle的头文件并在编译时链接cpp即可 //example:compress:#include iostream
#include string
#include fstream
#include bundle.hvoid TestBundle(int argc, char* argv[])
{if(argc 3){std::cout Usage: ./cloud origin path bundle name std::endl;return ;}std::string origin_path argv[1];std::string bundle_name argv[2];std::ifstream ifs;std::ofstream ofs;std::cout origin_path std::endl;std::cout bundle_name std::endl;ifs.open(origin_path, std::ios::binary);if(!ifs.is_open()){std::cout Open file failed! std::endl;return ;}ifs.seekg(0, std::ios::end);size_t fsize ifs.tellg();ifs.seekg(0, std::ios::beg);std::string body;std::cout fsize std::endl;body.resize(fsize);ifs.read(body[0], fsize);ofs.open(bundle_name, std::ios::binary);if(!ofs.is_open()){std::cout Open file failed! std::endl;return ;}std::string packed bundle::pack(bundle::LZIP, body);ofs.write(packed.c_str(), packed.size());ifs.close();ofs.close();
}int main(int argc, char* argv[])
{TestBundle(argc, argv);return 0;
}//example : uncompress
#include iostream
#include fstream
#include bundle.hvoid TestUncompress(int argc, char* argv[])
{if(argc 3){std::cout Usage: ./uncompress origin_path unpacked std::endl;return;}std::ifstream ifs;std::ofstream ofs;ifs.open(argv[1], std::ios::binary);if(!ifs.is_open()){std::cout open file failed std::endl;return;}std::string body;ifs.seekg(0, std::ios::end);size_t fsize ifs.tellg();ifs.seekg(0, std::ios::beg);body.resize(fsize);ifs.read(body[0], fsize);if(!ifs.good()){std::cout read failed std::endl;return;}ofs.open(argv[2], std::ios::binary);if(!ifs.is_open()){std::cout open failed std::endl;return;}std::string unpacked bundle::unpack(body);ofs.write(unpacked[0], unpacked.size());ifs.close();ofs.close();}
int main(int argc, char* argv[])
{TestUncompress(argc, argv);
} 编译时需要带上bundle的源文件并链接上线程库 尝试压缩某个文件并计算压缩前文件的MD5值 尝试解压刚压缩的文件并计算解压后的MD5值 比较md5值发现相同即文件内容一致压缩与解压成功 HotManager类 extern cloud::DataManager* dataManager;
namespace cloud { class HotManager { private: int _hotTime; std::string _packFileDirPath; std::string _backupFileDirPath; std::string _packfileSuffix; public: HotManager() { Config* config Config::GetInstance(); _packFileDirPath config-GetPackDir(); _backupFileDirPath config-GetBackupDir(); _packfileSuffix config-GetPackSuffix(); _hotTime config-GetHotTime(); DirUtil du1(_packFileDirPath);if(!du1.Exists()){if(!du1.CreateDirectory()){LOG(create pack dir failed!);}}DirUtil du2(_backupFileDirPath);if (!du2.Exists()){if (!du2.CreateDirectory()){LOG(create backup dir failed!);}}LOG(hot manager init success!);}bool HotJudge(std::string file){FileUtil fu(file);time_t lastATime fu.LastATime();time_t curTime time(NULL);if(curTime - lastATime _hotTime){return true;}return false;}void RunModule(){while (true){//scan dir pathDirUtil du(_backupFileDirPath);std::vectorstd::string files;du.ScanDirectory(files);//check all files in dirfor (auto file : files){//if hot file, continueif(!HotJudge(file)){LOG(file is not hot, continue!);continue;}//not a hot file, pack up//get backupinfoBackupFileInfo info;if(!dataManager-GetInfoByFilePath(file, info)){info.NewBackupFileInfo(file);}//use info to compress fileFileUtil fu(file);if(!fu.Compress(info._packPath)){LOG(std::string(compress file failed: file).c_str());continue;}//remove filefu.Remove();info._pack_flag true;dataManager-Update(info);}sleep(1);}}}; // end HotManager} // end namespace
#endif
#### 业务处理此项目中客户端能通过浏览器访问服务端ip服务端基于客户端访问的ip地址来返回相应的页面展示目前已经备份在服务器的文件并可通过点击文件将备份文件重新下载至本地因此我们需要维护一个模块用来处理客户端的发送请求,并根据请求做出相应的响应我们使用到了c的库httplib来完成HTTP网络通信此项目中我们只需维护3个url请求 listshow 或者是 ’/’ -- 网页展示 upload -- 客户端上传请求 dowload -- 浏览器下载请求c
static void ListShow(const httplib::Request req, httplib::Response res)
{LOG(std::string(receive request: req.path).c_str());std::vectorBackupFileInfo files;dataManager-GetAll(files);// construct htmlstd::stringstream ss;ss htmlheadtitleDownload/title/head;ss bodyh1Download/h1table;for (auto file : files){ss tr;std::string filename FileUtil(file._realPath).FileName();ss tda href file._urlPath filename /a/td;ss td alignright TimeToStr(file._mtime) /td;ss td alignright file._fsize / 1024 k/td;ss /tr;}ss /table/body/html;res.body ss.str();res.set_header(Content-Type, text/html);res.status 200;LOG(std::string(list show success, file count: std::to_string(files.size())).c_str());return;
}static void Upload(const httplib::Request req, httplib::Response res)
{auto ret req.has_file(file);if (!ret){LOG(no file);res.status 400;return;}const auto file req.get_file_value(file);std::string backupFileDirPath Config::GetInstance()-GetBackupDir();std::string fullPath backupFileDirPath FileUtil(file.filename).FileName();FileUtil fu(fullPath);fu.SetContent(file.content);BackupFileInfo info;info.NewBackupFileInfo(fullPath);dataManager-Insert(info);LOG(process upload success);return;
}static std::string TimeToStr(time_t time)
{std::string tmp std::ctime(time);return tmp;
}static std::string GetETag(const BackupFileInfo info)
{// etag : filename-fsize-mtimeFileUtil fu(info._realPath);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 res)
{LOG(std::string(receive request: req.path).c_str());BackupFileInfo info;if (!dataManager-GetInfoByUrl(req.path, info)){LOG(url not found: );res.status 400;return;}if (info._pack_flag true){FileUtil fu(info._packPath);if (!fu.UnCompress(info._realPath)){LOG(uncompress error);res.status 400;return;}fu.Remove();info._pack_flag false;dataManager-Update(info);}bool retrans false;std::string old_etag;if (req.has_header(If-Range)){old_etag req.get_header_value(If-Range);if (old_etag GetETag(info)){retrans true;LOG(retrans open);}}FileUtil fu(info._realPath);if (retrans){std::string range req.get_header_value(Range);fu.GetContentByPosLen(res.body, 0, fu.FileSize());res.set_header(Accept-Ranges, bytes);res.set_header(ETag, GetETag(info));res.set_header(Content-Type, application/octet-stream);// res.set_header(Content-Range, bytes start-end/fsize);res.status 206;}else{fu.GetContentByPosLen(res.body, 0, fu.FileSize());res.set_header(Accept-Ranges, bytes);res.set_header(ETag, GetETag(info));res.set_header(Content-Type, application/octet-stream);res.status 200;}
}客户端模块
功能划分
能够自动检测客户机指定文件夹中的文件并判断是否需要备份目前遇到目录是跳过后期可更新 将需要备份的文件逐个上传到服务器 备份信息持久化存储与状态恢复
功能细分模块
数据管理
为了更方便的判断指定文件夹中的文件是否是需要备份的文件将备份的文件信息利用 先描述再组织 的方式管理起来并提供将备份的数据信息持久化存储和还原的接口使得本次进程生命周期结束后不会丢失掉已经管理的备份数据信息并在下一次进程重启时加载已经备份的数据信息
由于客户端只需要知道某个文件的大小和其上次修改时间就能够管理这份文件因此不需要使用json序列化存储只需要用unordered_map建立简单的映射关系就可以完成管理工作
在本项目中规定某个文件的唯一标识格式为文件名-文件大小-文件上次修改时间建立文件和文件唯一标识的映射关系快速判断目录内文件是否为需要备份的文件并在退出客户端时将这个表持久化存储下一次启动客户端时加载已经备份的信息即可。
class DataManager
{private:std::string _backupFile; //persistent storage file name -- back.datstd::unordered_mapstd::string, std::string _dataTable;public:DataManager(const std::string backupFile) :_backupFile(backupFile){InitLoad();}//load the info like a.txt a.txt-123456-20240823 into tablebool InitLoad(){}//persistent storage, for next time to recovervoid Storage(){}//if file is a new one, load into tablevoid Update(const std::string pathName, const std::string id){}
};//end DataManager目录检查
c17–filesystem库
客户端其实本质上就是一个不断对一个文件夹进行检查的死循环因此需要将这个模块管理起来提供成员方法来使其模块化进入循环后不断扫描检测文件夹判断其是否满足更新条件满足则上传到云备份并记录到数据管理模块中
目录模块的实现在公共模块中
文件备份
Httplib库
此模块是客户端的主要任务模块通过不断轮询检查扫描文件夹判断文件是否需要上传并完成其中的上传和数据管理的处理其中上传云备份是最重要的部分我们使用到了httblib库来完成网络传输
class BackUp
{#define SERVER_IP xxx#define SERVER_PORT xxxprivate:std::string _backDir;DataManager* _dataManager;public:explicit BackUp(const std::string backDir, const std::string backFile):_backDir(backDir) {}~BackUp(){}std::string CreateIdByPathName(const std::string pathName){}bool IsNeedUpload(const std::string pathName){// upload : 1. a new file -- by checking dataTable// 2. an old file which have been modified compared with last time -- by checking identifier// judge if a file are modifing, if a file is not be modified in three second,// it can be considered that this file can be uploaded// a new file or an old file that able to upload}bool Upload(const std::string pathName){//httplib construct and send by POST}bool RunModule(){while (1){// scan file in dir, add all files into arrayDirUtil dirUtil(_backDir);std::vectorstd::string pathNames;if (!dirUtil.ScanDirectory(pathNames)){LOG(scan directory error);Sleep(1);}for (auto pathName : pathNames){// judge if the file should be uploadedif (!IsNeedUpload(pathName)){continue;}// upload if neededif (Upload(pathName)){LOG(upload successfully);_dataManager-Update(pathName, CreateIdByPathName(pathName));}else LOG(upload error);}Sleep(1);LOG(-------------loop once end--------------);}}// end RunModule
};//end BackUp//httplib construct and send by POST
}bool RunModule()
{while (1){// scan file in dir, add all files into arrayDirUtil dirUtil(_backDir);std::vectorstd::string pathNames;if (!dirUtil.ScanDirectory(pathNames)){LOG(scan directory error);Sleep(1);}for (auto pathName : pathNames){// judge if the file should be uploadedif (!IsNeedUpload(pathName)){continue;}// upload if neededif (Upload(pathName)){LOG(upload successfully);_dataManager-Update(pathName, CreateIdByPathName(pathName));}else LOG(upload error);}Sleep(1);LOG(-------------loop once end--------------);}
}// end RunModule};//end BackUp