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

news2024/11/15 10:51:11

文章目录

  • 1. 客户端文件操作类
  • 2. 客户端数据管理模块设计
  • 3. 文件客户端类设计
  • 项目代码

客户端负责的功能

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

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

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

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

客户端功能模块划分

  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

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

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

相关文章

纯分享万岳外卖跑腿系统客户端源码uniapp目录结构示意图

系统买的是商业版&#xff0c;使用非常不错有三端uniapp开源代码&#xff0c;自从上次分享uniapp后有些网友让我分享下各个端的uniapp下的各个目录结构说明 我就截图说以下吧&#xff0c;

【python】网络编程socket TCP UDP

文章目录 socket常用方法TCP客户端服务器UDP客户端服务器网络编程就是实现两台计算机的通信 互联网协议族 即通用标准协议,任何私有网络只要支持这个协议,就可以接入互联网。 socket socke模块的socket()函数 import socketsock = socket.socket(Address Family, type)参…

网络套接字补充——UDP网络编程

五、UDP网络编程 ​ 1.对于服务器使用智能指针维护生命周期&#xff1b;2.创建UDP套接字&#xff1b;3.绑定端口号&#xff0c;包括设置服务器端口号和IP地址&#xff0c;端口号一般是2字节使用uint16_t&#xff0c;而IP地址用户习惯使用点分十进制格式所以传入的是string类型…

<深度学习入门学习笔记P1>——《深度学习》

一、深度学习概述 1.深度学习入门概念及介绍 注&#xff1a; &#xff08;1&#xff09;感知机是深度学习网络算法的起源&#xff0c;神经网络是深度学习算法的中心。 &#xff08;2&#xff09;损失函数和梯度下降是用来对模型优化和训练的一种方式。 &#xff08;3&#xff…

AugmentedReality之路-显示隐藏AR坐标原点(3)

本文介绍如何显示/隐藏坐标原点&#xff0c;分析AR坐标原点跟手机的位置关系 1、AR坐标原点在哪里 当我们通过AugmentedReality的StartARSession函数打开AR相机的那一刻&#xff0c;相机所在的位置就是坐标原点。 2、创建指示箭头资产 1.在Content/Arrow目录创建1个Actor类…

NanoMQ的安装与部署

本文使用docker进行安装&#xff0c;因此安装之前需要已经安装了docker 拉取镜像 docker pull emqx/nanomq:latest 相关配置及密码认证 创建目录/usr/local/nanomq/conf以及配置文件nanomq.conf、pwd.conf # # # # MQTT Broker # # mqtt {property_size 32max_packet_siz…

|行业洞察·趋势报告|《2024旅游度假市场简析报告-17页》

报告的主要内容解读&#xff1a; 居民收入提高推动旅游业发展&#xff1a;报告指出&#xff0c;随着人均GDP的提升&#xff0c;居民的消费能力增强&#xff0c;旅游需求从传统的观光游向休闲、度假游转变&#xff0c;国内人均旅游消费持续增加。 政府政策促进旅游市场复苏&…

对象内存布局

对象头 对象标记Mark Word 所以New一个对象 没有其他信息 就是16字节 Object obj = new Object();

设计模式之原型模式讲解

原型模式本身就是一种很简单的模式&#xff0c;在Java当中&#xff0c;由于内置了Cloneable 接口&#xff0c;就使得原型模式在Java中的实现变得非常简单。UML图如下&#xff1a; 我们来举一个生成新员工的例子来帮助大家理解。 import java.util.Date; public class Employee…

Git--08--Git分支合并操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Git分支合并操作案例流程客户端&#xff1a;GitExtensions操作步骤&#xff1a;A操作步骤&#xff1a;B操作步骤&#xff1a;C操作步骤&#xff1a;D操作步骤&#…

Ubuntu20.04安装OpenCV并在vsCode中配置

1. 安装OpenCV 1.1 安装准备&#xff1a; 1.1.1 安装cmake sudo apt-get install cmake 1.1.2 依赖环境 sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev sudo apt-get install libgtk2.0-d…

opencv如何利用掩码将两张图合成一张图

最近在学opencv&#xff0c; 初学者。 里面有提到如何将两张图合成一张图&#xff0c; 提供了两个方法 一种是直接通过图片透明度权重进行融合 img1 cv.imread(ml.png) img2 cv.imread(opencv-logo.png) dst cv.addWeighted(img1,0.7,img2,0.3,0) cv.imshow(dst,dst) cv.…

iOS —— 初识KVO

iOS —— 初始KVO KVO的基础1. KVO概念2. KVO使用步骤注册KVO监听实现KVO监听销毁KVO监听 3. KVO基本用法4. KVO传值禁止KVO的方法 注意事项&#xff1a; KVO的基础 1. KVO概念 KVO是一种开发模式&#xff0c;它的全称是Key-Value Observing (观察者模式) 是苹果Fundation框架…

MySQL ② —— 索引原理

1. 索引 1.1 分类 主键索引、唯一索引、普通索引、组合索引、以及全文索引 主键索引 非空唯一索引&#xff0c;一个表只有一个主键索引&#xff1b;在 innodb 中&#xff0c;主键索引的 B 树包含表数据信息。 唯一索引 不可以出现相同的值&#xff0c;可以有 NULL 值。 …

3.28学习总结

java 封装 封装体现了java的面向对象的特点,用户不用知道程序是如何运行的,只用按照所给的格式输入参数,便可得到对应的结果. 一个完整的封装需要每个实例变量都用private来修饰并拥有相应的public getter和setter方法. 代码 public class girl {private int age;public st…

TABLE使用篇之奇门异术

table使用时候有时候会有下面界面 hover提示框&#xff0c;用element的el-popover可以显示该操作&#xff0c;但是会发现 取消操作的时候&#xff0c;el-popover不会自动消失&#xff0c;虽然失去焦点改框会自动消失&#xff0c;但是看起来对用户不是很友好 解决办法&#xff…

CDH集群hive初始化元数据库失败

oracle数据库操作&#xff1a; 报错如下&#xff1a;命令 (Validate Hive Metastore schema (237)) 已失败 截图如下&#xff1a; 后台日志部分摘录&#xff1a; WARNING: Use “yarn jar” to launch YARN applications. SLF4J: Class path contains multiple SLF4J binding…

Vite 为什么比 Webpack 快?

目录 1. Webpack 的构建原理 2. Script 的模块化&#xff08;主流浏览器对 ES Modules 的支持&#xff09; 3. Webpack vs Vite 开发模式的差异 对 ES Modules 的支持 底层语言的差异 热更新的处理 1. Webpack 的构建原理 前端之所以需要类似于 Webpack 这样的构建工具&…

vue纯前端过滤嵌套数据,通过关键字搜索过滤嵌套数据

1.过滤效果&#xff1a; 2. cardList 数据源&#xff1a; [ { "id": 4, "createTime": "2024-03-28 02:47:18", "updateTime": "2024-03-28 02:47:18", "uniqueId": "…

前端学习-CSS基础-Day3

一、CSS三大特性 1.1层叠性 相同选择器给设置相同的样式&#xff0c;此时一个样式就会覆盖&#xff08;层叠&#xff09;另一个冲突的样式。层叠性主要解决样式冲突的问题 层叠性原则&#xff1a; 1.样式冲突&#xff0c;遵循的原则是就近原则&#xff0c;哪个样式离结构近&a…