Linux(CentOS)/Windows-C++ 云备份项目(客户端文件操作类,数据管理模块设计,文件客户端类设计)


客户端负责的功能

  • 指定目录的文件检测,获取文件夹里面的文件

  • 判断这个文件是否需要备份,服务器备份过的文件则不需要进行备份,已经备份的文件如果修改也需要重新备份

  • 若这个文件被用户打开,则不进行备份。需要每隔一段时间检测更新备份。

  • 将需要备份的文件上传备份文件

客户端功能模块划分

  1. 数据管理模块:管理备份的文件信息
  2. 文件检测模块:监控指定文件夹,获取这个文件夹下所有的文件信息(通过获取到的备份文件信息)
  3. 文件备份模块:上传需要备份的文件数据,将数据传递给服务器

数据管理模块要管理的数据:

  1. . 判断一个文件是否需要重新备份
  2. 文件路径名,文件唯一标识符

客户端的开发环境在Windows上,使用vs2022(支持C++17即可 vs2017以上)

数据管理模块实现:

  • 内存存储:使用哈希表
  • 持久化存储:文件存储,自定义序列化格式(key Value型)

1. 客户端文件操作类

客户端数据管理模块设计和服务器相差不大可以直接移植

#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include "log.hpp"
// #include <filesystem>
#include <experimental/filesystem>
namespace CloudBackups
{
    namespace fs = std::experimental::filesystem;
    class FileUtil
    {
    private:
        std::string _filepath; // 文件名称 uri格式
        struct stat st;        // 文件属性

    public:
        FileUtil() = default;
        FileUtil(const std::string &filepath)
        {
            _filepath = filepath;
            if (stat(_filepath.c_str(), &st) < 0)
            {
                LOG(WARNING, "get file stat failed! maybe this file not exits");
            }
        }
        int64_t filesize() { return st.st_size; }         // 获取文件大小,失败返回-1
        time_t last_modify_time() { return st.st_mtime; } // 获取文件最后修改时间
        time_t last_visit_time() { return st.st_atime; }  // 获取文件最后访问时间
        // 删除文件
        bool removeFile()
        {
            if (this->isExit() == false)
            {
                return false;
            }
            if (remove(_filepath.c_str()) == 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        std::string filename() // 文件名称
        {
            size_t pos = _filepath.find_last_of("/");
            if (pos == std::string::npos)
            {
                return _filepath;
            }
            return _filepath.substr(pos + 1);
        }
        bool getPoslen(std::string &body, size_t pos, size_t len) // 从文件中读取len个字节,从pos位置开始读取,读取内容放到body中,为了实现断点续传
        {
            size_t size = this->filesize(); // 文件大小
            if (pos >= size)
            {
                LOG(ERROR, "pos is out of range!");
                return false;
            }
            if (pos + len > size)
            {
                LOG(ERROR, "pos + len is out of range!");
                return false;
            }
            std::ifstream ifs;
            ifs.open(_filepath.c_str(), std::ios::binary);
            if (!ifs.is_open())
            {
                LOG(ERROR, "open file failed!");
                return false;
            }
            ifs.seekg(pos, std::ios::beg);
            body.resize(len);
            ifs.read(&body[0], len);
            if (!ifs.good())
            {
                // 上次读取出错
                LOG(ERROR, "read file failed!");
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }
        bool getContent(std::string &body) // 获取整体的文件数据
        {
            size_t size = this->filesize();
            return getPoslen(body, 0, size);
        }
        bool setContent(const std::string &body) // 设置文件内容
        {
            std::ofstream ofs;
            ofs.open(_filepath.c_str(), std::ios::binary);
            if (!ofs.is_open())
            {
                LOG(ERROR, "open file failed! file path=" + _filepath);
                return false;
            }
            ofs.write(body.c_str(), body.size());
            if (!ofs.good())
            {
                // 上次写入出错
                LOG(ERROR, "write file failed!");
                ofs.close();
                return false;
            }
            ofs.close();
            return true;
        }
        bool isExit() { return fs::exists(_filepath); } // 判断文件是否存在
        bool mkdir()                                    // 创建文件夹
        {
            if (this->isExit())
            {
                return true;
            }
            return fs::create_directories(_filepath);
        }
        bool mkdir(const std::string &path) // 创建文件夹
        {
            if (this->isExit())
            {
                return true;
            }
            return fs::create_directories(path);
        }
        bool ls(std::vector<std::string> &files) // 扫描文件夹,并返回里面的文件
        {
            for (auto &pos : fs::directory_iterator(_filepath))
            {
                if (fs::is_directory(pos) == true)
                {
                    continue; // 目录不出来
                }
                files.push_back(fs::path(pos).relative_path().string()); // 获取文件的相对路径
            }
            return true;
        }
    };
}

2. 客户端数据管理模块设计

#pragma once
#include<string>
#include<unordered_map>
#include<sstream>
#include"fileutil.hpp"
namespace CloudBackups {
	class DataManager {
	private:
		std::string backup_file;//备份信息持久化文件,需要在创建类时加载
		std::unordered_map<std::string, std::string>backupMap;//管理文件路径和文件信息的映射
	public:
		// 切分字符串src,结果放到buff上
		bool cutString(std::string& src, const std::string sep, std::vector<std::string>&buff)
		{
			size_t pos = 0;
			size_t index = 0;
			while (true)
			{
				pos = src.find(sep, index);
				if (pos == std::string::npos) {
					break;
				}
				if (pos == index) {
					index = pos + sep.size();
					continue;
				}
				std::string str = src.substr(index, pos - index);
				buff.push_back(str);
				index = pos + sep.size();
			}
			if (index < src.size()) {
				//还剩数据
				buff.push_back(src.substr(index));
			}
			return true;
		}

		DataManager(const std::string backup_file) {
			this->backup_file = backup_file;
			InitLoad();
		}
		bool Storage() {
			//1. 获取所有备份信息
			auto pos = backupMap.begin();
			std::stringstream builder;
			while (pos != backupMap.end()) {
				//2. 将所有信息组织成特定格式
				builder << pos->first << " " << pos->second << "\n";
				pos++;
			}
			//3. 将所有信息写入文件
			FileUtil tool(backup_file);
			tool.setContent(builder.str());
			return true;
		}
		bool InitLoad() {
			//1. 从文件中读取所有数据
			FileUtil tool(backup_file);
			std::string body;
			tool.getContent(body);
			//2. 对数据进行解析,添加到backupMap中
			//按照行进行分割,每行按照空格分割
			std::vector<std::string>files;
			cutString(body, "\n", files);
			for (auto& file : files) {
				std::vector<std::string>buff;
				cutString(file, " ", buff);
				if (buff.size() != 2) {
					// LOG(ERROR, "cut string error!");
					return false;
				}
				backupMap[buff[0]] = buff[1];
			}
		}
		bool Insert(const std::string& key, const std::string& value) {
			backupMap[key] = value;
			Storage();
			return true;
		}
		bool UpDate(const std::string& key, const std::string& value) {
			backupMap[key] = value;
			Storage();
			return true;
		}
		bool GetByKey(const std::string& key, std::string& value) {
			auto pos = backupMap.find(key);
			if (pos == backupMap.end()) {
				return false;
			}
			value = pos->second;
			return true;
		}
	};
}

3. 文件客户端类设计

客户端实现:

  1. 自动将指定文件夹的备份文件备份到服务器上

流程:

  1. 遍历指定文件夹,获取文件信息
  2. 逐一判断文件是否需要备份,对需要备份的文件上传备份

需要注意的是:若文件比较大,正在拷贝过程,每次遍历文件都被修改,每次客户端都会上传不合理,所以要设置在一段时间没有被修改则上传

#pragma once
#include<string>
#include"backups.hpp"
#include<vector>
#include<sstream>
#include"httplib/httplib.h"
#include<Windows.h>
#define SEVER_IP "116.204.70.147"
#define SEVER_PORT 8081
namespace CloudBackups {
	class Client {
	private:
		std::string back_dir;
		DataManager* dataMange;
		//判断文件标识符是否需要上传
		bool CheckFileUpload(std::string filepath) {
			//文件新增或文件修改过都需要上传 1. 文件新增:看下文件备份信息 2. 文件有历史信息,但是文件标识符不一致
			std::string pre_id;
			if (dataMange->GetByKey(filepath, pre_id) != false) {
				//判断文件更新
				std::string id = GetEtag(filepath);
				if (id != pre_id) {
					//前后信息不一致,需要上传
					//文件比较大,正在拷贝过程,每次遍历文件都被修改,每次客户端都需要上传不合理,所以要设置在一段时间没有被修改则上传
					FileUtil tool(filepath);
					if (time(nullptr) - tool.last_modify_time() > 3) {
						//3秒之内未修改,需要上传给服务器
						return true;
					}
				}
				return false;//前后信息一致或者客户端文件拷贝未完成,不需要上传
			}
			//文件不存在,需要上传
			return true;
		}
	public:
		Client(const std::string& back_dir, const std::string& back_file) {
			this->back_dir = back_dir;
			dataMange = new DataManager(back_file);
			//创建上传目录文件夹
			FileUtil tool;
			tool.mkdir(back_dir);
		}
		//创建文件唯一标识符
		std::string GetEtag(const std::string& filepath) {
			//文件名-文件大小-修改时间
			FileUtil tool(filepath);
			std::stringstream builder;
			builder << tool.filename() << "-" << tool.filesize() << "-" << tool.last_modify_time();
			return builder.str();
		}
		//文件上传接口
		bool Upload(const std::string filepath) {
			//获取文件数据
			FileUtil tool(filepath);
			std::string body;
			tool.getContent(body);
			//搭建客户端发送请求
			httplib::Client client(SEVER_IP, SEVER_PORT);
			httplib::MultipartFormData item;
			item.content = body;
			//item.filename = tool.filename();
			item.name = "file";//服务器上标识的是file,需要和服务器协商
			item.content_type = "application/octet-stream";
			httplib::MultipartFormDataItems items;
			items.push_back(item);
			auto ret = client.Post("/upload", items);
			if (!ret || ret->status != 200) {
				LOG(ERROR, "file upload error!");
				return false;
			}
			return true;
		}
		//服务器运行
		bool RunModule() {
			while (true) {
				//1. 浏览需要备份的文件
				FileUtil tool(back_dir);
				std::vector<std::string>files;
				tool.ls(files);
				for (auto& file : files) {
					//2. 逐个判断文件需要上传
					if (CheckFileUpload(file)==false) {
						//不需要上传
						continue;
					}
					//需要上传服务器
					if (Upload(file) == true) {
						dataMange->Insert(file, GetEtag(file));//上传服务器完毕后写入文件配置文件
						LOG(INFO, "upload success!");
					}
					else {
						LOG(ERROR, "upload error! filepath: " + file);
					}
				}
				Sleep(10);//毫秒为单位
			}
			return true;
		}
	};
}

在这里插入图片描述
在这里插入图片描述

项目代码

Gitee

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值