文章目录
- 1. 客户端文件操作类
- 2. 客户端数据管理模块设计
- 3. 文件客户端类设计
- 项目代码
客户端负责的功能
-
指定目录的文件检测,获取文件夹里面的文件
-
判断这个文件是否需要备份,服务器备份过的文件则不需要进行备份,已经备份的文件如果修改也需要重新备份
-
若这个文件被用户打开,则不进行备份。需要每隔一段时间检测更新备份。
-
将需要备份的文件上传备份文件
客户端功能模块划分
- 数据管理模块:管理备份的文件信息
- 文件检测模块:监控指定文件夹,获取这个文件夹下所有的文件信息(通过获取到的备份文件信息)
- 文件备份模块:上传需要备份的文件数据,将数据传递给服务器
数据管理模块要管理的数据:
- . 判断一个文件是否需要重新备份
- 文件路径名,文件唯一标识符
客户端的开发环境在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; } // 获取文件大小,失败返回-1time_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. 文件客户端类设计
客户端实现:
- 自动将指定文件夹的备份文件备份到服务器上
流程:
- 遍历指定文件夹,获取文件信息
- 逐一判断文件是否需要备份,对需要备份的文件上传备份
需要注意的是:若文件比较大,正在拷贝过程,每次遍历文件都被修改,每次客户端都会上传不合理,所以要设置在一段时间没有被修改则上传
#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