【项目】云备份

news2024/11/22 15:43:42

云备份

  • 云备份
    • 概述
      • 框架
    • 功能演示
      • 服务端
      • 客户端
    • 公共模块
      • 文件操作模块
      • 目录操作模块
    • 服务端模块
      • 功能划分
      • 功能细分模块
        • 数据管理
        • 热点管理
    • 客户端模块
      • 功能划分
      • 功能细分模块
        • 数据管理
        • 目录检查
        • 文件备份

云备份

概述

自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。并且能够随时通过浏览器进行查看并且下载,其中下载过程支持断点续传功能,而服务器也会对上传文件进行热点管理,将非热点文件进行压缩存储,节省磁盘空间。

框架

实现客户端和服务端双端程序,合作实现总体的自动云备份功能

本项目有三大模块:

  • 客户端模块
  • 服务端模块
  • 公共模块

除了自己实现的模块之外,还引入了三方库作为辅助:

  • Jsoncpp进行序列化辅助
  • Httplib进行网络传输辅助
  • c++17–filestream进行目录检测辅助

功能演示

服务端

  • 若客户端有文件上传,则会自动下载到服务端的backupDir目录下

    在这里插入图片描述

  • 若此文件超过30s时间没有被获取则会自动压缩到packDir目录下

    在这里插入图片描述

  • 从浏览器访问备份呢列表

    在这里插入图片描述

  • 点击文件名下载文件:

    在这里插入图片描述

    当服务端接收到下载命令时,若此文件已在压缩目录下,则先将其解压到备份目录中,再将内容发送给客户端

  • 若此时有用户访问了服务端并下载了某个文件,下载一半时,服务端异常关闭导致用户下载中断,服务端恢复后,用户能继续正常的从上次中断的地方继续下载, 实现断点续传

    在这里插入图片描述

客户端

  • 找到backup目录,若没有时则创建(可以考虑写成读取配置文件)
    在这里插入图片描述

    第一次载入时,若没有此目录会自动创建

  • 不断轮询检测目录backup,一旦有文件新增或更新,就会发送到远端,实现云备份

    在这里插入图片描述

  • 注意,不一定所有更新都会发送到远端,若有一份很大的文件正在拷贝进backup目录,相当于这份文件在实时更新,但实际是此文件还没有拷贝完毕,不能发送到远端,因此客户端的策略是对某个更新的文件,若此文件上一次更新时间与当前时间相比超过了3s钟才进行云备份

    在这里插入图片描述

公共模块

公共模块是完成一系列对文件或者目录上的操作,本项目中细分为文件操作模块和目录操作模块,以及日志等简单的公共模块

文件操作模块

namespace cloud
{
    //example: you can nest your own log system
	void LOG(const char* str)
	{
		printf("[%s |%d]: %s\n", __FILE__, __LINE__, str);
	}
	//for operation
	namespace 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::vector<std::string>* fileArray)
		{
			if (!Exists())
			{
				return fs::create_directories(_dirPath);
			}

			for (auto& file : fs::directory_iterator(_dirPath))
			{
				//to do, directory continue temporarily
				if (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.conf"
      	class 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.h>
    
    void TestJsonCpp()
    {
       std::string str = R"({"name":"nicky", "age":18, "score":[50,80,90]})";
       Json::Value root;
       Json::CharReaderBuilder builder;
       std::unique_ptr<Json::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 -ljsoncpp
      

      在这里插入图片描述

    JsonUtil类

    • class JsonUtil{
      public:
      	static bool Serialize(const Json::Value &root, std::string *str){
      		Json::StreamWriterBuilder swb;
      		std::unique_ptr<Json::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_ptr<Json::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 changed
                Config *config = Config::GetInstance(); // single instance
                std::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 BackuoInfo
    
        class DataManager
        {
        private:
            std::string _persistentPath;
            pthread_rwlock_t _rwLock;
            std::unordered_map<std::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());
    
                //Unserialization
                Json::Value root;
                JsonUtil::UnSerialize(body, &root);
                //insert into table
                for (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::vector<BackupFileInfo> *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 table
                std::vector<BackupFileInfo> allData;
                GetAll(&allData);
    
                //add to json
                Json::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!");
    
                //serialization
                std::string body;
                JsonUtil::Serialize(root, &body);
                LOG("serialization success!");
    
                //write to file
                FileUtil 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.h"
    
    void 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.h"
    
    void 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 path
              DirUtil du(_backupFileDirPath);
              std::vector<std::string> files;
              du.ScanDirectory(&files);

              //check all files in dir
              for (auto &file : files)
              {
                  //if hot file, continue
                  if(!HotJudge(file))
                  {
                      LOG("file is not hot, continue!");
                      continue;
                  }

                  //not a hot file, pack up
                  //get backupinfo
                  BackupFileInfo info;
                  if(!dataManager->GetInfoByFilePath(file, &info))
                  {
                      info.NewBackupFileInfo(file);
                  }

                  //use info to compress file
                  FileUtil fu(file);
                  if(!fu.Compress(info._packPath))
                  {
                      LOG(std::string("compress file failed: " + file).c_str());
                      continue;
                  }

                  //remove file
                  fu.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 -- 浏览器下载请求

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C86134%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20240909183712693.png&pos_id=img-liw4TmM1-1725878337219)

```c++
static void ListShow(const httplib::Request &req, httplib::Response &res)
{
  LOG(std::string("receive request: " + req.path).c_str());
  std::vector<BackupFileInfo> files;
  dataManager->GetAll(&files);

  // construct html
  std::stringstream ss;
  ss << "<html><head><title>Download</title></head>";
  ss << "<body><h1>Download</h1><table>";
  for (auto &file : files)
  {
      ss << "<tr>";
      std::string filename = FileUtil(file._realPath).FileName();
      ss << "<td><a href='" << file._urlPath << "'>" << filename << "</a></td>";
      ss << "<td align='right'>" << TimeToStr(file._mtime) << "</td>";
      ss << "<td align='right'>" << 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-mtime
  FileUtil 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.dat
    std::unordered_map<std::string, std::string> _dataTable;
    
    public:
    DataManager(const std::string& backupFile) :
    _backupFile(backupFile)
    {
        InitLoad();
    }
	//load the info like "a.txt a.txt-123456-20240823" into table
    bool InitLoad()
    {}
	//persistent storage, for next time to recover
    void Storage()
    {}
	//if file is a new one, load into table
    void Update(const std::string& pathName, const std::string& id)
    {}
};//end DataManager
目录检查
  • c++17–filesystem库

客户端其实本质上就是一个不断对一个文件夹进行检查的死循环,因此需要将这个模块管理起来,提供成员方法来使其模块化,进入循环后,不断扫描检测文件夹,判断其是否满足更新条件,满足则上传到云备份并记录到数据管理模块中

目录模块的实现在公共模块中

文件备份
  • Httplib库

此模块是客户端的主要任务模块,通过不断轮询检查扫描文件夹,判断文件是否需要上传,并完成其中的上传和数据管理的处理,其中上传云备份是最重要的部分,我们使用到了httblib库来完成网络传输

class BackUp
{
    #define SERVER_IP "xxx"
    #define SERVER_PORT xxx

    private:
    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 array
            DirUtil dirUtil(_backDir);
            std::vector<std::string> pathNames;
            if (!dirUtil.ScanDirectory(&pathNames))
            {
                LOG("scan directory error");
                Sleep(1);
            }
            for (auto& pathName : pathNames)
            {
                // judge if the file should be uploaded
                if (!IsNeedUpload(pathName))
                {
                    continue;
                }
                // upload if needed
                if (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 array
        DirUtil dirUtil(_backDir);
        std::vector<std::string> pathNames;
        if (!dirUtil.ScanDirectory(&pathNames))
        {
            LOG("scan directory error");
            Sleep(1);
        }
        for (auto& pathName : pathNames)
        {
            // judge if the file should be uploaded
            if (!IsNeedUpload(pathName))
            {
                continue;
            }
            // upload if needed
            if (Upload(pathName))
            {
                LOG("upload successfully");
                _dataManager->Update(pathName, CreateIdByPathName(pathName));
            }
            else LOG("upload error");
        }
        Sleep(1);
        LOG("-------------loop once end--------------");
    }
}// end RunModule

};//end BackUp






























本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118807.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络原理】❤️Tcp 核心机制❤️ 通晓可靠传输的秘密, 保姆式教学, 建议收藏 !!!

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

QT QxOrm CRUD增删改查mysql数据库操作

QT QxOrm CRUD增删改查mysql数据库操作 QxOrm 是一个 C 库&#xff0c;旨在为 C 用户提供对象关系映射 (ORM) 功能。 基于每个类的简单 C 设置函数&#xff08;如 Java 中的 Hibernate XML 映射文件&#xff09;&#xff0c;QxOrm 库提供以下功能&#xff1a; 持久性&#xff1…

安宝特案例 | AR如何大幅提升IC封装厂检测效率?

前言&#xff1a;如何提升IC封装厂检测效率&#xff1f; 在现代电子产品的制造过程中&#xff0c;IC封装作为核心环节&#xff0c;涉及到复杂处理流程和严格质量检测。这是一家专注于IC封装的厂商&#xff0c;负责将来自IC制造商的晶圆进行保护、散热和导通处理。整个制程繁琐…

C语言俄罗斯方块(VS2022版)

C语言俄罗斯方块 演示视频一、前置知识1.Win32 API 的使用2.宽字符的使用 二、封装核心数据与框架介绍三、核心操作介绍旋转操作检测操作水平检测竖直检测代码化简 四、源码展示在 tetris.h 中&#xff1a;在 tetris.c 中&#xff1a;在 test.c 中&#xff1a; 以下代码环境为 …

小阿轩yx-Zabbix企业级分布式监控环境部署

小阿轩yx-Zabbix企业级分布式监控环境部署 前言 “运筹帷幄之中&#xff0c;决胜千里之外”监控在 IT 运维中占据着重要地位&#xff0c;按比例说占 30% 也不为过在监控系统开源软件中有很多可选择的工具&#xff0c;但是真正符合要求的、能够真正解决业务问题的监控系统软件…

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时&#xff0c;首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值&#xff0c;还要能够激起人们的分享欲望。对于许多企业和个人来说&#xff0c;尤其是那些缺乏创意和写作能力的人来说&#xff0c;…

OpenHarmony鸿蒙开发( Beta5.0)智能甲醛检测系统实践

样例简介 本项目是基于BearPi套件开发的智能甲醛检测系统Demo&#xff0c;该设备硬件部分主要由小熊派单板套件和和甲醛检测传感器组成。智能甲醛检测系统可以通过云和手机建立连接&#xff0c;可以在手机上设置甲醛浓度阈值&#xff0c;传感器感知到的甲醛浓度超过阈值之后&a…

QQ邮箱“已发送”邮件竟然无法一键清空?看我操作,怎么删除12万+已发送邮件

最近遇到了一个问题&#xff0c;QQ邮箱提示我空间已满&#xff0c;所以我就专门去看看有哪些邮件可以删除&#xff0c;释放点空间。 我直接暴力删除了很多文件夹的邮件&#xff0c;在文件夹管理界面 有“清空”按钮&#xff0c;点一个即可清空。 但是。。。不出意外的话要出意…

南卡、韶音、墨觉:精选三款旗舰骨传导耳机全面对比评测!

在科技日新月异的今天&#xff0c;耳机作为我们日常生活中不可或缺的音频伴侣&#xff0c;正经历着前所未有的变革。特别是骨传导耳机&#xff0c;凭借其独特的声音传导方式和出色的佩戴体验&#xff0c;逐渐成为了运动爱好者和户外探索者的首选。在众多品牌中&#xff0c;南卡…

Pycharm的安装与Conda环境的配置

目录 第一步&#xff1a;下载并安装 PyCharm 社区版 第二步&#xff1a;创建新项目并配置 Python 解释器 第三步&#xff1a;配置 Conda 环境 第四步&#xff1a;验证环境 第五步&#xff1a;测试 PyTorch 第六步&#xff1a;测试基本 PyTorch 代码 第一步&#xff1a;下…

替代区块链

随着比特币的成功&#xff0c;人们逐渐意识到区块链技术的潜力&#xff0c;并随之出现了迅速的发展&#xff0c;各种区块链协议、应用程序和平台相应产生。 需要指出的是&#xff0c;在这种多元的局面下&#xff0c;很多项目迅速失去了它们的吸引力。事实上&#xff0c;有不少项…

深圳MES系统在制造业的应用与发展

深圳MES在制造业的应用与发展呈现以下几个特点&#xff1a; 应用范围广泛&#xff1a;深圳制造业涵盖了电子、通信、汽车、机械等多个领域&#xff0c;MES系统在这些领域的应用非常广泛。不同行业的企业可以根据自身的需求和特点&#xff0c;定制化地应用MES系统来实现生产管理…

测试即服务(TaaS):概念、优势及应用场景!

引言 随着数字化转型的深入发展&#xff0c;软件质量和用户体验变得愈发重要。传统的软件测试方法已经难以满足现代企业对于快速迭代和高质量交付的需求。在此背景下&#xff0c;“测试即服务”(Testing as a Service, TaaS) 模式应运而生&#xff0c;为软件测试带来了新的解决…

基于SpringBoot+Vue+MySQL的足球俱乐部管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统足球俱乐部管理…

Gtest(Google Test)使用

下面Gtest是在arm-linux下运行的 https://download.csdn.net/download/qq_31868891/89729426 一、下载编译 1.下载gtest代码 https://github.com/google/googletest 2.配置编译 vscode安装CMake Tools 将上面下载的gtest代码文件夹拖到vscode里&#xff0c;然后选择对应的…

SAP 凭证的替代传输GGB1

SAP 凭证的替代传输GGB1 之前没有留意过&#xff0c;前人一直是直接改的&#xff0c;搜索了一下是可以这样弄得 1.一般通过OBBH&#xff0c;配置的凭证替代&#xff0c;产生的请求号&#xff0c;从开发机传输不到生产机。只能通过GGB1来传输。在GGB1里面选择要传输的替代 选中…

BookStack在线文档管理系统本地Docker部署与远程访问详细教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

LEAN类型系统属性之规范性(Regularity)注解

在 《小结》 中&#xff0c;列出LEAN类型系统所定义的全部规律&#xff0c;下面 关于 LEAN 属性 的一些推论&#xff08;Lemma&#xff09;进行注解。主要是其规范性&#xff08;Regularity&#xff09;&#xff0c;以说明LEAN类型系统是完备构建的&#xff08;well founded&am…

Java+selenium+chrome+linux/windows实现数据获取

背景&#xff1a;在进行业务数据获取或者自动化测试时&#xff0c;通常会使用模拟chrome方式启动页面&#xff0c;然后获取页面的数据。在本地可以使用windows的chromedriver.exe进行打开chrome页面、点击等操作。在linux 下通常使用无界面无弹窗的方式进行操作。接下来是实现方…

【AI绘画】Midjourney之Lighting详解

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;为什么要学习光影控制光影控制的作用 &#x1f4af;强化主题hard lighting&#xff08;硬光 &#xff09;soft lighting&#xff08;软光/柔光&#xff09;测试 &…