云备份客户端业务实现逻辑

一、实现功能以及模块划分

客户端需要实现的功能:自动对文件夹中的文件备份,保存信息

模块划分:

  • 文件操作模块
  • 数据管理模块
  • 文件上传模块

相较于服务端,我们的客户端只需要将文件上传即可,功能并没有这么复杂

二、数据管理模块

文件是否需要备份是通过,数据管理模块管理的信息判断的。

有两种情况需要上传:

  1. 没有该文件的备份信息的
  2. 有该文件备份信息(说明该文件已经备份过一次了)但是文件被修改过,需要更新服务端的文件

 

 需要实现的函数:

	class DataManager {
	private:
		std::string _back_file;//持久化储存文件
		std::unordered_map<std::string, std::string> _table;
	public:
		//初始化、持久化、新增、更新、查询
		DataManager(std::string backfile);
		void InitLoad();
        //用于将数据解序列化的工具
		size_t Split(std::string  str,std::string sep,std::vector<std::string> *arry);
		bool Storage();
		bool Insert(std::string key, std::string val);
		bool Updata(std::string key, std::string val);
		bool GetOneByKey(const std::string& key, std::string* val);
	};

对象的初始化以及数据的持久化存储

通过hash表对数据进行储存,first是文件路径名——文件的唯一标识,second就是:filename-filesize-上一次修改时间

我们需要储存的信息:filename-filesize-上一次修改时间

这里涉及到一个序列化和反序列化的功能实现,我们储存起来的信息就是一个字符串,肯定不能直接拿起来用的,我们需要对其解析

我们这里实现了解序列化会用到的小工具。

持久化储存文件的格式:

filename-filesize-上一次修改时间 \nfilename-filesize-上一次修改时间 \nfilename-filesize-上一次修改时间 \n

这里就储存了三个文件的信息了,当我们开启项目时,读取持久化数据,就需要对其解序列化。将这个字符串分割成一个个filename-filesize-上一次修改时间  的形式管理。

小总结:

  1. 类的实例化时会读取持久化信息
  2. 通过Split函数对字符串进行分割,实现解序列化
  3. 对象销毁时(也就是程序结束时)进行持久化储存
DataManager(std::string backfile) :_back_file(backfile) {
			InitLoad();
		}
		void InitLoad() {
			//文件读写
			//1.先从文件获取数据
			FileUtil fu(_back_file);
			std::string body;
			fu.GetContent(&body);
			//2.解析数据,按照我们设定的格式,分割字符串
			// 格式:key val\n key val\n key val\n 一行行获取数据,然后再将每一行分割
			std::vector<std::string> arry;
			
			Split(body, "\n", &arry);
			//3.储存到table中去
			for (auto& a : arry) {
				std::vector<std::string> tem;
				Split(a, " ", &tem);
				if (tem.size() != 2) {
					continue;
				}
				_table[tem[0]] = tem[1];
			}
			
		}
		size_t Split(std::string  str,std::string sep,std::vector<std::string> *arry) {
			//sep是分割字符,str是字符串,把分割下来的数据储存到arry
			//找到分割字符
			size_t pos, idx=0;//idx是偏移量,pos标识分割字符位置
			size_t count=0;
			while (1) {
				pos=str.find(sep, idx);
				//然后裁剪下来
				std::string tem;
				//If no matches were found, the function returns string::npos.
				if (pos == std::string::npos) {
					break;
				}
				tem = str.substr(idx, pos - idx);
				arry->push_back(tem);
				count++;
				idx = pos + sep.size();
			}
			if (idx < str.size()) {
				arry->push_back(str.substr(idx,str.size()-idx));
				count++;
			}
			//aac afd asd 
			return count;
			
		}
		bool Storage() {
			//判断一下有没有数据
			if (_table.empty()) {
				return false;
			}
			//先将数据获取完全
			auto it = _table.begin();
			//定义一个数据流
			std::stringstream ss;
			for (; it != _table.end();it++) {
				//格式:key val\n
				ss << it->first << " " << it->second << "\n";
			}
			//然后再将数据写入文件
			FileUtil fu(_back_file);
			fu.SetContent(ss.str());
			return true;
		}
		~DataManager() {
			Storage();
		}

数据管理的增查改

		bool Insert(std::string key, std::string val) {
			//客户端这里是单线程模式,不涉及到线程安全问题
			_table[key] = val;
			Storage();
			return true;
		}
		bool Updata(std::string key, std::string val) {
			_table[key] = val;
			Storage();
			return true;
		}
		bool GetOneByKey(const std::string& key, std::string* val) {
			auto it = _table.find(key);
			if (it == _table.end()) {
				return false;
			}
			*val=it->second;
			return true;
		}

客户端和服务端有一点不同的是,客户端只有一个线程,在修改数据的时候不会对产生线程安全问题,就没有使用读写锁。

为了保证持久化数据的实时性,每次修改都会进行一次持久化操作。

数据管理类的源码

/*data.hpp*/
#ifndef __MY__DATA__
#define __MY__DATA__
#include<unordered_map>
#include<sstream>
#include<vector>
#include<string>
#include<iostream>
//通过table存储key---val格式的数据,这就是我们要组织的数据
namespace cloud {
	class DataManager {
	private:
		std::string _back_file;//持久化储存文件
		std::unordered_map<std::string, std::string> _table;
	public:
		//初始化、持久化、新增、更新
		DataManager(std::string backfile) :_back_file(backfile) {
			InitLoad();
		}
		void InitLoad() {
			//文件读写
			//1.先从文件获取数据
			FileUtil fu(_back_file);
			std::string body;
			fu.GetContent(&body);
			//2.解析数据,按照我们设定的格式,分割字符串
			// 格式:key val\n key val\n key val\n 一行行获取数据,然后再将每一行分割
			std::vector<std::string> arry;
			
			Split(body, "\n", &arry);
			//3.储存到table中去
			for (auto& a : arry) {
				std::vector<std::string> tem;
				Split(a, " ", &tem);
				if (tem.size() != 2) {
					continue;
				}
				_table[tem[0]] = tem[1];
			}
			
		}
		size_t Split(std::string  str,std::string sep,std::vector<std::string> *arry) {
			//sep是分割字符,str是字符串,把分割下来的数据储存到arry
			//找到分割字符
			size_t pos, idx=0;//idx是偏移量,pos标识分割字符位置
			size_t count=0;
			while (1) {
				pos=str.find(sep, idx);
				//然后裁剪下来
				std::string tem;
				//If no matches were found, the function returns string::npos.
				if (pos == std::string::npos) {
					break;
				}
				tem = str.substr(idx, pos - idx);
				arry->push_back(tem);
				count++;
				idx = pos + sep.size();
			}
			if (idx < str.size()) {
				arry->push_back(str.substr(idx,str.size()-idx));
				count++;
			}
			//aac afd asd 
			return count;
			
		}
		bool Storage() {
			//判断一下有没有数据
			if (_table.empty()) {
				return false;
			}
			//先将数据获取完全
			auto it = _table.begin();
			//定义一个数据流
			std::stringstream ss;
			for (; it != _table.end();it++) {
				//格式:key val\n
				ss << it->first << " " << it->second << "\n";
			}
			//然后再将数据写入文件
			FileUtil fu(_back_file);
			fu.SetContent(ss.str());
			return true;
		}
		bool Insert(std::string key, std::string val) {
			//客户端这里是单线程模式,不涉及到线程安全问题
			_table[key] = val;
			Storage();
			return true;
		}
		bool Updata(std::string key, std::string val) {
			_table[key] = val;
			Storage();
			return true;
		}
		bool GetOneByKey(const std::string& key, std::string* val) {
			auto it = _table.find(key);
			if (it == _table.end()) {
				return false;
			}
			*val=it->second;
			return true;
		}
		~DataManager() {
			Storage();
		}
	};

}
#endif

三、文件管理模块

与服务端不同的是:

  • 客户端不需要使用文件压缩
  • 客户端不需要使用序列化,因为自己实现了

这样也有效避免了json和bundle库安装的操作了,功能简单自己就能实现。

源码

/*util.hpp*/
#ifndef __MY_UTIL__
#define __MY_UTIL__
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<sys/stat.h>
#include<experimental/filesystem>
#include<stdio.h>

namespace cloud{
	namespace fs=std::experimental::filesystem;
	class FileUtil{		
		private:
			std::string _filename;
		public:
			FileUtil(const std::string &filename):_filename(filename){}
			size_t FileSize()
			{
				struct stat st;
				if(stat(_filename.c_str(),&st)<0)
				{
					std::cout<<"get file size failed!\n";
					return 0;
				}
				return st.st_size;

			}
			time_t LastMTime(){
				struct stat st;
				if(stat(_filename.c_str(),&st)<0)
				{
					std::cout<<"get file last mtimg 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 last atimg failed!\n";
					return -1;
				}
				return st.st_atime;
			}
			std::string FileName()
			{
				/*size_t pos;
				std::string filename="/"+_filename;
				pos=filename.find_last_of("/");
				if(pos==std::string::npos)
				{
					std::cout<<"find name failed!(util.hpp)\n";
					exit(1);
				}*/
				return fs::path(_filename).filename().string();
			}

			bool GetPosLen(std::string &body,size_t pos,size_t len)
			{
				if(this->FileSize()<pos+len)
				{
					std::cout<<"GetPosLen failed!\n";
					return false;
				}
				std::ifstream ifs;
				ifs.open(_filename,std::ios::binary);
				if(ifs.good()==false)
				{
					std::cout<<"open file failed!\n";
					return false;
				}
				ifs.seekg(pos,std::ios::beg);
				body.resize(len);
				ifs.read(&body[0],len);
				if(!ifs.good())
				{
					std::cout<<"read failede!\n";
					return false;
				}
				ifs.close();
				return true;
			}
			bool GetContent(std::string *body)
			{ 
				size_t fsize=this->FileSize();
				return GetPosLen(*body,0,fsize);
			}

			bool SetContent(const std::string &body)
			{
				std::ofstream ofs;
				ofs.open(_filename,std::ios::binary);
				if(ofs.is_open()==false)
				{
					std::cout<<"write open failede!\n";
					return false;
				}
				ofs.write(&body[0],body.size());
				if(!ofs.good())
				{
					std::cout<<"write failede!\n";
					return false;
				}
				ofs.close();
				return true;
			}

			bool Remove(){
				if(this->Exists()==false)
					return true;
				remove(_filename.c_str());
				return true;
			}
			bool Exists()
			{
				return fs::exists(_filename);
			}
			bool CreateDirectoryy()
			{	
				if(this->Exists()==true)
				{
					return true;	
				}		
				return fs::create_directories(_filename);
			}
			bool ScanDirectory(std::vector<std::string>* arry )
			{	
				CreateDirectoryy();

				for(auto& p:fs::directory_iterator(_filename))
				{	
					if(fs::is_directory(p))
						continue;
					arry->push_back(fs::path(p).relative_path().string());
				}
				return true;
			}
};
	
}
#endif

四、文件上传模块

需要实现的功能

  1. 创建本地服务器,实现文件上传
  2. 不断检索文件夹判断文件是否需要上传
  3. 判断文件是否需要上传

需要实现的方法:

//1监控一个文件夹,获取文件信息
//2逐一判断是否需要备份
//3将需要备份的文件上传

namespace cloud {

	#define BACKUP_FILE  "./backup.dat"
	#define SERVER_IP "xxxxxxxxxxxxxx"
	#define SERVER_PORT 9090
	class Backup {
	private:
		std::string _back_dir;
		DataManager* _data;//获取以及更新文件数据用的
		std::string GetFileIdentifier(std::string& filename);//获取文件当前信息
		bool Upload(std::string& filename);//搭建客户端服务器上传文件
		bool IsNeedUpload(std::string filename);//判断文件是否需要上传
	public:
		Backup(std::string back_dir);
		void RunModule();//检索文件夹
	};

}
#endif

判断文件是否需要上传

文件需要上传有两种可能:

  1. 没有该文件的备份信息
  2. 有备份性息但是文件被修改了

有一个特殊情况我们需要考虑:我们要上传一个较大的文件,这个文件正在被徐徐上传,而你检测文件夹的时候发现有一个文件一直被修改,这种情况我们不能再对齐进行上传了

		bool IsNeedUpload(std::string filename) {
			//有两种可能文件需要上传
			//1.没有该文件的备份信息
			//2.有该文件的备份信息但是文件被修改过
			//先获取id
			std::string old_id;
			FileUtil fu(filename);
			if (_data->GetOneByKey(filename, &old_id) == true) {
				//有该文件备份信息
				std::string new_id;
				new_id = GetFileIdentifier(filename);
				if (new_id == old_id) {
					//该文件没被修改过
					return false;
				}
				else {
					return true;
				}
			}
			//没有该文件的备份信息
			//但是有一个例外:一个很大的文件被缓缓拷贝进来,一直重复上传同一个文件是不合理的
			//通过对比上一次修改时间判断,这个文件是不是最近一直被修改
			if (time(nullptr)-fu.LastMTime()<3){
				//最近三秒被修改就不需要上传
				return false;
			}
			return true;
		}

检索文件夹以及文件上传

private:
        //获取文件当前信息
        std::string GetFileIdentifier(std::string& filename) {
			std::stringstream ss;
			FileUtil fu(filename);
			ss<<filename<<"-";
			ss<<fu.FileSize()<<"-";
			ss << fu.LastMTime();
			return ss.str();
		}
        bool Upload(std::string& filename) {
			//1.获取文件数据
			std::string body;
			FileUtil fu(filename);
			fu.GetContent(&body);
			//2.创建本地客户端
			httplib::Client client(SERVER_IP, SERVER_PORT);
			httplib::MultipartFormData item;
			item.content = body;//文件内容
			item.filename = fu.FileName();
			item.name = "file";//标识符,和服务端是一一对应的
			item.content_type = "application/octet-stream";
			httplib::MultipartFormDataItems items;
			items.push_back(item);
			//3.上传
			auto res = client.Post("/upload",items);
			if (!res || res->status != 200) {
				return false;
			}
			return true;
		}
public:
        void RunModule() {
			while (1) {
				//1.遍历文件夹,获取文件名
				FileUtil fu(_back_dir);
				std::vector<std::string> arr;
				fu.ScanDirectory(&arr);//不存在目录的话会自动创建
				//2.判断文件是否需要上传
				for (auto& a : arr) {
					if (IsNeedUpload(a) == false) {
						continue;
					}
					//3.上传文件,同时更新备份信息
					if (Upload(a) == true) {
						_data->Insert(a, GetFileIdentifier(a));
					}
				}
				Sleep(1);
			}

源码

#ifndef __MY__CLOUD__
#define __MY__CLOUD__
#include"data.hpp"
#include"util.hpp"
#include"httplib.h"
#include<Windows.h>

//1监控一个文件夹,获取文件信息
//2逐一判断是否需要备份
//3将需要备份的文件上传

namespace cloud {

	#define BACKUP_FILE  "./backup.dat"
	#define SERVER_IP "xxxxxxxxxxxxx"
	#define SERVER_PORT 9090
	class Backup {
	private:
		std::string _back_dir;
		DataManager* _data;
		std::string GetFileIdentifier(std::string& filename) {
			std::stringstream ss;
			FileUtil fu(filename);
			ss<<filename<<"-";
			ss<<fu.FileSize()<<"-";
			ss << fu.LastMTime();
			return ss.str();
		}
		bool Upload(std::string& filename) {
			//1.获取文件数据
			std::string body;
			FileUtil fu(filename);
			fu.GetContent(&body);
			//2.创建本地客户端
			httplib::Client client(SERVER_IP, SERVER_PORT);
			httplib::MultipartFormData item;
			item.content = body;//文件内容
			item.filename = fu.FileName();
			item.name = "file";//标识符,和服务端是一一对应的
			item.content_type = "application/octet-stream";
			httplib::MultipartFormDataItems items;
			items.push_back(item);
			//3.上传
			auto res = client.Post("/upload",items);
			if (!res || res->status != 200) {
				return false;
			}
			return true;
		}
		bool IsNeedUpload(std::string filename) {
			//有两种可能文件需要上传
			//1.没有该文件的备份信息
			//2.有该文件的备份信息但是文件被修改过
			//先获取id
			std::string old_id;
			FileUtil fu(filename);
			if (_data->GetOneByKey(filename, &old_id) == true) {
				//有该文件备份信息
				std::string new_id;
				new_id = GetFileIdentifier(filename);
				if (new_id == old_id) {
					//该文件没被修改过
					return false;
				}
				else {
					return true;
				}
			}
			//没有该文件的备份信息
			//但是有一个例外:一个很大的文件被缓缓拷贝进来,一直重复上传同一个文件是不合理的
			//通过对比上一次修改时间判断,这个文件是不是最近一直被修改
			if (time(nullptr)-fu.LastMTime()<3){
				//最近三秒被修改就不需要上传
				return false;
			}
			return true;
		}
	public:
		Backup(std::string back_dir) :_back_dir(back_dir) {
			_data=new DataManager(BACKUP_FILE);
		}
		void RunModule() {
			while (1) {
				//1.遍历文件夹,获取文件名
				FileUtil fu(_back_dir);
				std::vector<std::string> arr;
				fu.ScanDirectory(&arr);//不存在目录的话会自动创建
				//2.判断文件是否需要上传
				for (auto& a : arr) {
					if (IsNeedUpload(a) == false) {
						continue;
					}
					//3.上传文件,同时更新备份信息
					if (Upload(a) == true) {
						_data->Insert(a, GetFileIdentifier(a));
					}
				}
				Sleep(1);
			}
		}

	};


}
#endif

相关推荐

  1. 备份客户业务实现逻辑

    2024-03-30 16:08:01       19 阅读
  2. ZooKeeper客户实战

    2024-03-30 16:08:01       29 阅读
  3. Kafka客户实战

    2024-03-30 16:08:01       33 阅读
  4. go实现tcp客户

    2024-03-30 16:08:01       21 阅读
  5. python实现UDP客户

    2024-03-30 16:08:01       15 阅读
  6. 业务逻辑业务安全

    2024-03-30 16:08:01       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-30 16:08:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-30 16:08:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-30 16:08:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-30 16:08:01       18 阅读

热门阅读

  1. JVM之堆

    JVM之堆

    2024-03-30 16:08:01      13 阅读
  2. 面向对象设计之单一职责原则

    2024-03-30 16:08:01       20 阅读
  3. kubuntu23.10安装sdl2及附加库和 sfml2.5.1

    2024-03-30 16:08:01       17 阅读
  4. 动态堆栈类及括号匹配(考察类的构建与应用)

    2024-03-30 16:08:01       14 阅读
  5. python知识点记录

    2024-03-30 16:08:01       19 阅读
  6. JVM基础

    JVM基础

    2024-03-30 16:08:01      17 阅读
  7. contextlib.redirect_stdout 使用

    2024-03-30 16:08:01       14 阅读
  8. docker-compose运行mysql

    2024-03-30 16:08:01       14 阅读
  9. 算法——图论:判断二分图(染色问题)

    2024-03-30 16:08:01       17 阅读
  10. 什么是站群服务器?

    2024-03-30 16:08:01       15 阅读
  11. vue3父子组件之间的传值方式

    2024-03-30 16:08:01       21 阅读
  12. C# 到异常处理 暂时告一段落 开始窗体的学习

    2024-03-30 16:08:01       19 阅读