自动监控备份系统--项目详情

自动监控备份系统

源码地址:https://github.com/JustDocat/project-for-backup

项目性能测试:https://blog.csdn.net/Thinker_serious/article/details/99954314

  • 项目功能介绍

    • 客户端主机上对指定目录进行监控,能够对新文件/修改后的文件进行自动备份到服务端 并且服务端向用户提供网页访问和文件下载的功能 服务端为了节省磁盘空间,对热度低(不经常下载)的文件进行压缩存储。
      这里存在可扩展的地方为如果服务端中包含大量的小文件,为了节约inode节点号,可进行打 包压缩,将多个小文件打包为一个进行压缩存储。

    项目环境搭建

    • 客户端
    • 运行环境:windows10
    • 开发软件:VS2013 X64平台
    • 服务端
    • 运行环境:CentOS7
    • 内核版本:3.10.0-862
    • 开发软件:vim、gcc (GCC) 5.3.1 20160406、makefile
    • 依赖库:httplib、boost库-64位、zlib库
      • httplib--用于搭建http服务端与客户端
      • boost库--用于文件相关的检测
      • zlib库--用于对文件进行压缩存储

    项目模块划分

    • 客户端
    • 对指定目录进行监控,检测哪些文件需要备份
    • 对需要备份的文件进行上传
    • 服务端
    • 对客户端上传的文件进行备份
    • 通过网页能够向用户展示文件列表
    • 通过网页向用户提供文件下载的功能
    • 能够对不经常下载的文件进行压缩存储,节约磁盘空间

    具体模块实现与代码解析

    • 客户端
    • 一、读取备份信息记录,获取已有备份信息,避免重复上传
    • 二、检测目录中的文件信息
    • 三、判断文件是否需要上传备份
    • 四、上传备份
    • 五、记录文件备份信息
一、每次客户端启动时,读取备份信息记录,获取已有备份信息
备份信息格式为"filename etag"
bool GetBackupInfo() {
		// 备份信息记录格式
		// filename1 etag\n
		// filename2 etag\n
		
		// #define CLIENT_BACKUP_INFO_FILE "back.list"
		// back.list为备份记录文件
		// namespace bf=boost::filesystem;
		bf::path path(CLIENT_BACKUP_INFO_FILE);	// 创建path对象
		if (!bf::exists(path)) {
			std::cerr << "list file " << path.string() << " is not exists" << std::endl;
			return false;
		}
		int64_t fsize = bf::file_size(path);
		if (fsize == 0) {
			std::cerr << "have no backup info" << std::endl;
			return false;
		}

		std::string body;
		body.resize(fsize);
		std::ifstream file(CLIENT_BACKUP_INFO_FILE, std::ios::binary);
		if (!file.is_open()) {
			std::cerr << "list file open error" << std::endl;
			return false;
		}
		file.read(&body[0], fsize);
		if (!file.good()) {
			std::cerr << "read list file body error" << std::endl;
			return false;
		}
		file.close();

		std::vector<std::string> list;
		boost::split(list, body, boost::is_any_of("\n"));
		// 将整个文件按照"\n"进行分割
		for (auto& e : list)
		{
			size_t pos = e.find(" ");
			if (pos == std::string::npos) {
				continue;
			}
			std::string key = e.substr(0, pos);
			std::string val = e.substr(pos + 1);
			_backup_list[key] = val;
		}
		return true;
	}
  • 功能实现流程
  • 0. 需要先知道文件中的备份信息格式为 "filename etag\n"
  • 1. 第一次启动客户端,备份文件不存在,需要创建back.list文件
  • 2. 不是第一次启动时,先打开back.list文件读取其中的内容,获取备份记录
  • 3. 定义string对象body,将文件中的内容读取到body中
  • 4. 创建一个vector数组,将每个文件的备份信息分割开保存在vector中
  • 5. 遍历vector按" "进行分割,文件的filename当作key,etag信息当作value保存在unordered_map中
  • 6. 此时,已经备份过的文件保存在map中了,查询map即可知道文件是否备份
  • 使用的依赖库接口
  • bf::exists(path); // path文件是否存在
  • bf::file_size(path); // path文件的大小
  • boost::split(list, body, boost::is_any_of("\n")); // 将body中的内容以"\n"分割,加入到list中
二、检测目录中的文件信息
// 将文件中的信息加载到map中
bool AddBackInfo(const std::string& file) {
		// etag = "mtime-fsize"
		std::string etag;
		if (GetFileEtag(file, etag) == false) {
			return false;
		}

		_backup_list[file] = etag;
		return true;
}
// 目录监控
bool BackupDirListen(const std::string& path) {
		bf::path file(path);
		
		// 文件迭代器
		bf::directory_iterator item_begin(file);
		bf::directory_iterator item_end;
		for (; item_begin != item_end; ++item_begin) {
			if (bf::is_directory(item_begin->status())) {
				BackupDirListen(item_begin->path().string());
				continue;
			}
			// FileIsNeedBackup文件是否需要备份
			if (FileIsNeedBackup(item_begin->path().string()) == false) {
				continue;
			}
			std::cerr << "file:[" << item_begin->path().string() << " need backup" << std::endl;
			// PutFileData上传文件数据
			if (PutFileData(item_begin->path().string()) == false) {
				std::cerr << "PutFileData false" << std::endl;
				continue;
			}
			// AddBackInfo添加备份信息
			AddBackInfo(item_begin->path().string());
		}
		return true;
	}
  • 功能实现流程
  • 1. 使用boost库中的文件迭代器遍历监控目录
  • 2. 如果迭代器遍历到的时目录,则递归遍历子目录
  • 3. 遍历到的文件,取出文件的文件名,看是否需要备份
  • 4. 如果遍历到的文件需要备份,则上传备份,否则跳过当前文件
  • 5. 上传的文件成功后,添加上传备份信息到map中,防止重复上传
  • 使用的依赖库接口
  • bf::directory_iterator item_begin(file); // 监控目录下的迭代器开始位置
  • bf::directory_iterator item_end; // 监控目录下的迭代器结束位置
  • bf::is_directory // 判断当前遍历的文件是否是目录
三、判断文件是否需要上传备份
// etag格式hex-hex 两个16进制的数字
bool GetFileEtag(const std::string& file, std::string& etag) {
		bf::path path(file);
		if (!bf::exists(path)) {
			std::cerr << "get file " << file << " etag error" << std::endl;
			return false;
		}
		int64_t fsize = bf::file_size(path);
		int64_t mtime = bf::last_write_time(path);

		std::stringstream tmp;
		tmp << std::hex << fsize << "-" << std::hex << mtime;
		etag = tmp.str();
		// std::cout << "GetEtag information : " << tmp.str() << std::endl;
		return true;
}

bool FileIsNeedBackup(const std::string& file) {
		std::string etag;
		if (GetFileEtag(file, etag) == false) {
			return false;
		}
		auto it = _backup_list.find(file);

		if (it != _backup_list.end() && it->second == etag) {
			return false;
		}
		return true;
}
  • 功能实现流程
  • 0. etag信息为文件大小 --- 文件最后一次修改时间
  • 1. 获取遍历到文件的etag信息,etag信息为两个16进制的数字
  • 2. 在unordered_map中查找遍历到的文件,判断之前是否上传过
  • 3. 如果查找到当前文件,对比etag信息是否相同
  • 4. etag信息相同,说明不需要备份,如果etag信息不相同或文件查找不到则需要备份
  • 使用的依赖库接口
  • bf::exists // 文件是否存在
  • bf::file_size // 文件大小
  • bf::last_write_time // 文件最后一次修改时间
四、上传备份
// 上传,线程的创建
bool PutFileData(const std::string& file) {
		// 按分块大小 RANGE_MAX_SIZE(10M) 对文件进行分块传输
		// 通过获取分块传输是否成功判断整个文件是否上传成功
		// 选择多线程处理

		// 1. 获取文件大小
		std::cerr << "PutFileData()" << std::endl;
		int64_t fsize = bf::file_size(file);
		if (fsize <= 0) {
			std::cerr << "file " << file << "unnecessary backup" << std::endl;
		}
		// 2. 计算总共需要分多少块,得到每块大小以及起始位置
		// 3. 循环创建线程,在线程中上传文件数据
		int count = (int)(fsize / RANGE_MAX_SIZE);
		std::vector<ThrBackUp> thr_res;
		std::vector<ThrBackup> thr_list;
		std::cerr << "file:[" << file << "] fsize:[" << fsize << "] count:[" << count + 1 << "]" << std::endl;
		for (int i = 0; i <= count; ++i)
		{
			int64_t range_start = i * RANGE_MAX_SIZE;
			int64_t range_end = (i + 1) * RANGE_MAX_SIZE - 1;
			if (i == count) {
				range_end = fsize - 1;
			}
			int64_t range_len = (range_end - range_start + 1);
			// 直接使用线程,参数不能全部传递过去,分装线程
			ThrBackUp backup_info(file, range_start, range_len);
			thr_res.push_back(backup_info);
			
		}
		for (int i = 0; i <= count; ++i) {
			thr_list.push_back(std::thread(thr_start, &thr_res[i]));
		}

		// 4. 等待所有线程退出,判断文件上传结果
		bool ret = true;
		for (int i = 0; i <= count; ++i) {
			thr_list[i].join();
			if (thr_res[i]._res == true) {
				continue;
			}
			ret = false;
		}
		// 5. 上传成功,则添加文件的备份信息记录
		if (ret == false) {
			return false;
		}
		std::cerr << "file:[" << file << "] backup success" << std::endl;
		return true;
}

// 启动线程开始上传
static void thr_start(ThrBackUp* backup_info) {
		std::cerr << "into thread" << std::endl;
		backup_info->Start();
}

// 线程类
class ThrBackUp {
private:
	std::string _file;
	int64_t _range_start;
	int64_t _range_len;
public:
	bool _res;
public:
	ThrBackUp(const std::string& file, int64_t start, int64_t len) 
		: _res(true)
		, _file(file)
		, _range_start(start)
		, _range_len(len)
	{}
	void Start() {
		// 获取文件的range分块

		std::ifstream path(_file, std::ios::binary);
		if (!path.is_open()) {
			std::cerr << "range backup file " << _file << std::endl;
			_res = false;
			return;
		}
		// 跳转到range的起始位置
		path.seekg(_range_start, std::ios::beg);
		std::string body;
		body.resize(_range_len);
		// 读取文件中的range分块的数据
		path.read(&body[0], _range_len);
		if (!path.good()) {
			std::cerr << "read file " << _file << " range data failed" << std::endl;
			_res = false;
			return;
		}
		path.close();

		std::cerr << "read over" << std::endl;

		// 上传range数据
		bf::path name(_file);
		// 组织上传的url路径 method url version
		// PUT /list/filename HTTP/1.1
		std::string url = BACKUP_URI + name.filename().string();
		// 实例化一个httplib的客户端对象
		httplib::Client cli(SERVER_IP, SERVER_PORT);
		// 定义http请求的头信息
		httplib::Headers hdr;
		hdr.insert(std::make_pair("Content-Length", std::to_string(_range_len)));
		std::stringstream tmp;
		tmp << "bytes=" << _range_start << "-" << (_range_start + _range_len - 1);
		hdr.insert(std::make_pair("Range", tmp.str().c_str()));
		// 通过实例化的client向服务端发送PUT请求
		auto rsp = cli.Put(url.c_str(), hdr, body, "text/plain");
		if (rsp && rsp->status == 200) {
			std::stringstream ss;
			ss << "backup file [" << _file << "] range:[" << _range_start << "-" << _range_len << "] backup success" << std::endl;
			std::cout << ss.str();
		}
		else {	
			std::cerr << "rsp put error" << std::endl;
			_res = false;
		}
		
	}

};
  • 功能实现流程
  • 0. 每个分块的大小 #define RANGE_MAX_SIZE (10 << 20) = 10M
  • 1. 获取文件大小,计算需要的分块,每个分块启动一个线程上传,至少一个线程
  • 2. 将创建的线程类对象加入vector中,方便循环启动
  • 3. 遍历vector,循环启动线程开始上传
  • 4. 将启动的线程加入等待的vector中,方便等待,判断返回值,看是否上传成功
  • 5. 每个线程将数据写入body中组织一个HTTP客户端,将分块开始位置,正文大小写入头信息中
  • 6. 客户端调用PUT方法,将正文数据上传,等待服务端返回的状态码,如果状态码为200则上传成功,否则上传失败,等待下次上传
  • 7. 循环判断每一个线程是否上传成功,只要有一个线程上传失败,则整个文件上传失败,不进行上传成功记录;如果上传成功,则添加上传备份信息
  • 使用的依赖库接口
  • httplib::Client cli(SERVER_IP, SERVER_PORT); // 实例化http客户端
  • httplib::Headers hdr; // 定义http的头信息
  • cli.Put(url.c_str(), hdr, body, "text/plain"); // 客户端的上传操作
五、记录文件备份信息
bool SetBackupInfo() {
		std::string body;
		for (auto& e : _backup_list)
		{
			body += e.first + " " + e.second + "\n";
		}
		std::ofstream file(CLIENT_BACKUP_INFO_FILE, std::ios::binary);
		if (!file.is_open()) {
			std::cerr << "open list file error" << std::endl;
			return false;
		}
		file.write(&body[0], body.size());
		if (!file.good()) {
			std::cerr << "set backup info error" << std::endl;
			return false;
		}
		return true;
	}
  • 功能实现流程
  • 1. 取出map中保存好的文件备份信息,保存到body中
  • 2. 打开备份文件,将body写入,将备份信息记录在文件中
服务端
  • 一、httplib服务器实现对文件的上传存储功能
  • 二、httplib服务器实现向用户展示文件列表的功能
  • 三、httplib服务器实现对文件下载功能
  • 四、压缩存储模块对不经常访问的文件进行压缩存储,删除原有文件
  • 文件的安全操作类向服务端提供的接口
  • 文件列表获取
  • 文件数据获取
  • 文件数据存储
  • 文件的安全操作类向压缩模块提供的接口
  • 检测指定目录,对不经常访问的文件进行压缩存储
一、httplib服务器实现对文件的上传存储功能
static void PutFileData(const Request &req, Response &rsp){
      std::cerr<< "backup file " << req.path << std::endl;
      if(!req.has_header("Range")) {
        rsp.status = 400;
        return;
      }
      std::string range = req.get_header_value("Range");
      std::cout << range << std::endl;
      int64_t range_start;

	  // RangeParse:计算上传的文件数据的分块开始位置与分块大小
      if(RangeParse(range, range_start)== false) {
        rsp.status = 400;
        std::cerr << "RangeParse() error" << std::endl;
        return;
      }
      std::string realpath = SERVER_BASE_DIR + req.path;
      // 压缩类给服务端提供的文件存储接口
      cstor.SetFileData(realpath, req.body, range_start);
    }
    // 计算文件的开始位置与文件大小
static bool RangeParse(std::string& range, int64_t &start) {
      // Range: bytes=start-end
      size_t pos1 = range.find("=");
      size_t pos2 = range.find("-");
      std::cout << pos1 << "-" << pos2 << std::endl;
      if(pos1 == std::string::npos || pos2 == std::string::npos) {
        std::cerr << "range:[" << range << "] format error";
        return false;
      }
      std::stringstream rs;
      rs << range.substr(pos1 + 1, pos2 - pos1 - 1);
      rs >> start;
      return true;
    }
// Compress类提供的文件存储接口
 bool SetFileData(const std::string& file, const std::string& body, const int64_t offset) {
      int fd = open(file.c_str(), O_CREAT|O_WRONLY, 0664);
      if(fd < 0) {
        std::cerr << "open file " << file << " error" << std::endl;
        return false;
      }
      flock(fd, LOCK_EX);
      lseek(fd, offset, SEEK_SET);
      int ret = write(fd, &body[0], body.size());
      if(ret < 0) {
        std::cerr << "store file" << file << " data error" << std::endl;
        flock(fd, LOCK_UN);
        return false;
      }
      flock(fd, LOCK_UN);
      close(fd);
      AddFileRecord(file, "");
      return true;
}

 bool AddFileRecord(const std::string& file, const std::string& gzip) {
      pthread_rwlock_wrlock(&_rwlock);
      _file_list[file] = gzip; 
      pthread_rwlock_unlock(&_rwlock);
      std::cerr << file << "-" << gzip << std::endl;
      return true;
}
  • 功能实现流程
  • 0. range分块的格式range=start-end
  • 1. 判断上传的PUT请求的头部中是否包含range分块信息,若没有返回400状态码
  • 2. 取出分块开始的位置,组织文件路径和名称,传入压缩类提供给服务端的接口中
  • 3. 只写方式打开传入路径下的文件,如果不存在就创建
  • 4. 对文件进行加锁操作,防止多线程导致文件存储出错
  • 5. 跳转读写位置进行写入,写入完成后,解锁,将文件信息写入unordered_map中保存
  • 6. 对map的读取、写入需要加读写锁进行保证安全
  • 调用接口解析
  • req为http客户端发来的请求信息,rsp为服务端对客户端的响应
  • req.has_header("range"); // 判断头信息中是否含有range信息
  • req.get_header_value("range"); // 获取range信息
  • flock(); // 文件锁,可以加LOCK_SH共享锁,与读写锁特性相同;LOCK_EX互斥锁;LOCK_UN解锁
  • _list_file // unordered_map保存文件的压缩信息,这里还没有压缩,所以不需要添加压缩包名称
二、httplib服务器实现向用户展示文件列表的功能
// 获取文件列表
    static void GetFileList(const Request &req, Response &rsp){

      std::vector<std::string> list;
      cstor.GetFileList(list);
      std::string body;
      body += "<html><body><ol><hr />";
      for(auto& e : list)
      {
        bf::path path(e);
        
        std::string file = path.filename().string();
        std::string uri = "/list/" + file;

        body += "<h4><li>";

        body += "<a href = '";
        body += uri;
        body += "'>";

        body += file; 
        body += "</a>";
        body += "</li></h4>";

        // "<h4><li><a href = '/list/filename'>filename</a></li></h4>"
      }
      body += "<hr /></ol></body></html>";
      rsp.set_content(&body[0],"text/html");
      return;
}

// 向外提供获取文件列表功能
    bool GetFileList(std::vector<std::string>& list) {
      pthread_rwlock_rdlock(&_rwlock);
      for(auto& e : _file_list) {
        list.push_back(e.first);
      }
      pthread_rwlock_unlock(&_rwlock);
      return true;
}
  • 功能实现流程
  • 1. 定义vector用于保存文件名称
  • 2. 文件有可能被压缩,所以遍历监控目录,不能获取全部文件。所以遍历map,map中保存着所有的文件以及对应压缩文件信息
  • 3. 取出map中所有的first,添加到vector中
  • 4. 服务端将所有的文件组织为一个HTML页面响应给客户端,向用户展示文件列表
  • 调用接口解析
  • rsp.set_content // 为响应设置正文数据

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

三、httplib服务器实现对文件下载功能
static void GetFileData(const Request &req, Response &rsp){
      std::string realpath = SERVER_BASE_DIR + req.path;
      std::string body;

      cstor.GetFileData(realpath, body);
      rsp.set_content(body, "text/plain");
}

// 压缩类向服务端提供的文件下载接口
bool GetFileData(std::string& file, std::string& body) {
      if(bf::exists(file)) {
        // 1. 非压缩文件获取
        GetNormalFile(file, body);
      }else {
        // 2. 压缩文件获取
        // 获取压缩包名称 gzip

        std::cerr << "i find file: "<< file << std::endl;
        std::string gzip;
        GetFileGzip(file, gzip);
        std::cerr << "GetFileGzip: gzip: " << gzip << std::endl;
        UnCompressFile(gzip, file);
        GetNormalFile(file, body);
      }
      return true;
}

// 获取文件压缩后的名称
bool GetFileGzip(std::string& file, std::string& gzip) {
      std::cerr <<"file - gzip: " <<file << _file_list[file] <<  std::endl;
      pthread_rwlock_rdlock(&_rwlock);

      auto it = _file_list.find(file);
      if(it == _file_list.end()) {
        pthread_rwlock_unlock(&_rwlock);
        return false;
      }
      gzip = it->second;

      pthread_rwlock_unlock(&_rwlock);
      return true;
}

// 获取未压缩文件数据
bool GetNormalFile(std::string& name, std::string& body) {
      int64_t fsize = bf::file_size(name);
      body.resize(fsize);

      int fd = open(name.c_str(), O_RDONLY);
      if(fd < 0) {
        std::cerr << "open file " << name << " failed" << std::endl;
        return false;
      }

      flock(fd, LOCK_SH);
      int ret = read(fd, &body[0], fsize);
      flock(fd, LOCK_UN);

      if(ret != fsize) {
        std::cerr << "get file " << name << " body error" << std::endl;
        close(fd);
        return false;
      }
      close(fd);
      return true;
}

// 解压缩
bool UnCompressFile(std::string& gzip, std::string& file) {
      int fd = open(file.c_str(), O_CREAT|O_WRONLY, 0664);
      if(fd < 0) {
        std::cerr << "open file " << file << "failed" << std::endl;
        return false;
      }
      gzFile gf = gzopen(gzip.c_str(), "rb");
      if(gf == NULL) {
        std::cerr << "open gzip " << gzip << " failed" << std::endl;
        close(fd);
        return false;
      }
      int ret;
      char buf[1024];
      flock(fd, LOCK_EX);
      while((ret = gzread(gf, buf, 1024)) > 0) {
        int len = write(fd, buf, ret);
        if(len < 0) {
          std::cerr << "get gzip data failed" << std::endl;
          gzclose(gf);
          close(fd);
          flock(fd, LOCK_UN);
          return false;
        }
      }
      flock(fd, LOCK_UN);
      gzclose(gf);
      close(fd);
      unlink(gzip.c_str());
      return true;
}
  • 功能实现流程
  • 1. 服务端根据客户端请求,组织一个请求路径
  • 2. 先遍历未压缩目录,看是否被压缩,如果没有压缩,直接进行数据读取
  • 3. 打开文件,将数据读取到body中,使用body将数据返回
  • 4. 对于压缩文件需要,先解压缩,先获取需要文件的对应压缩包名称
  • 5. 遍历_file_list找到当前文件对应的压缩包名称,需要使用读写锁,保证线程安全
  • 6. 使用zlib库中提供的接口,定义操作句柄,打开压缩文件,将文件读取到正常文件中保存,删除压缩文件
  • 7. 重复未压缩文件获取步骤,获取文件数据
  • 8. 将body设置为响应正文,响应给客户端进行下载
  • 调用接口解析
  • rsp.set_content // 设置响应正文,且只能设置一次
  • bf::exists // 判断文件是否存在
  • gzFile gf // 定义一个压缩文件的文件句柄
  • gzopen("rb") // 打开一个压缩文件,以只读二进制流打开
  • gzread // 读取压缩文件数据,数据还原的过程由这个接口自己实现
  • gzclose // 关闭压缩文件描述符

在这里插入图片描述

四、压缩存储模块对不经常访问的文件进行压缩存储,删除原有文件
	// 对热度低的文件进行压缩存储
    // 因为压缩存储时流程是死循环,因此需要启动线程
    bool LowHeatFileStore() {
      // 1. 获取记录信息
      GetListRecord();
      while(1) {
        // 2. 目录检测,文件压缩存储
          // 2.1 获取list目录下文件名称
          // 2.2 判断文件是否需要压缩存储
          // 2.3 对文件进行压缩存储
        DirectoryCheck();
        // 3. 存储记录信息 
        SetListRecord();
        sleep(3);

      }
      return true;
    }
    
    // 获取文件的压缩信息
    bool GetListRecord() {
      // filename gzipfilename\n
      bf::path name(RECORD_FILE);
      if(!bf::exists(name)) {
        std::cerr << "record file is not exists" << std::endl;
        return false;
      }
      std::ifstream file(RECORD_FILE,std::ios::binary);
      if(!file.is_open()) {
        std::cerr << "open record file error" << std::endl;
        return false;
      }
      int64_t fsize = bf::file_size(name);
      std::string body;
      body.resize(fsize);
      file.read(&body[0], fsize);
      if(!file.good()) {
        std::cerr << "record file body read error" << std::endl;
        return false;
      }
      file.close();

      std::vector<std::string> list;
      boost::split(list, body, boost::is_any_of("\n"));
      for(auto& e : list) {
        // filename gzipname
        size_t pos = e.find(" ");
        if(pos == std::string::npos) {
          continue;
        }
        std::string key = e.substr(0, pos);
        std::string val = e.substr(pos + 1);
        _file_list[key] = val;
      }
      return true;
    }
    
    // 2. 每次压缩存储完毕,都要将列表信息,存储到文件中
    bool SetListRecord() {
      std::stringstream tmp;
      for(auto& e : _file_list) {
        tmp << e.first << " " << e.second << "\n";
      }

      std::ofstream file(RECORD_FILE, std::ios::binary|std::ios::trunc);
      if(!file.is_open()) {
        std::cerr << "record file open error" << std::endl;
        return false;
      }
      file.write(tmp.str().c_str(), tmp.str().size());
      if(!file.good()) {
        std::cerr << "recode file write body error" << std::endl;
        return false;
      }
      file.close();
      return true;
    }
    
	// 目录检测,获取目录中的文件名
    // 1. 判断文件是否需要压缩存储
    // 2. 文件压缩存储
    bool DirectoryCheck() {
      if(!bf::exists(UNGZIPFILE_PATH)) {
        bf::create_directory(UNGZIPFILE_PATH);
      }
      bf::directory_iterator item_begin(UNGZIPFILE_PATH);
      bf::directory_iterator item_end;
      for(; item_begin != item_end; ++item_begin) {
        if(bf::is_directory(item_begin->status())) {
          continue;
        }
        std::string name = item_begin->path().string();

        if(IsNeedCompress(name)) {
          std::string gzip = GZIPFILE_PATH +  item_begin->path().filename().string() + ".gz";
          CompressFile(name, gzip);
          AddFileRecord(name, gzip);
        }
      }
      return true;
    }
    
	// 2.2. 判断文件是否需要压缩存储
    bool IsNeedCompress(std::string& file) {
      struct stat st;
      if(stat(file.c_str(), &st) < 0) {
        std::cerr << "get file:[" << file << "] stat error" << std::endl;
        return false;
      }
      time_t cur_time = time(NULL);
      time_t acc_time = st.st_atime;
      if((cur_time - acc_time) < HEAT_TIME) {
        return false;
      }
      return true;
    }
    
    // 2.3. 对文件进行压缩存储
    bool CompressFile(std::string& file, std::string& gzip) {
      int fd = open(file.c_str(), O_RDONLY);
      if(fd < 0) {
        std::cerr << "com open file:[" << file << "] error" << std::endl;
        return false;
      }
      gzFile gf = gzopen(gzip.c_str(), "wb");
      if(gf == NULL) {
        std::cerr << "com open gzip:[" << gzip << "] error" << std::endl;
        return false;
      }
      int ret;
      char buf[1024];
      flock(fd, LOCK_SH);
      while((ret = read(fd, buf, 1024)) > 0) {
        gzwrite(gf, buf, ret);
      }
      flock(fd, LOCK_UN);
      close(fd);
      gzclose(gf);
      unlink(file.c_str());
      // 文件正在被系统或其他进程使用时无法删除
      return true;
    }

  • 功能实现流程
  • 0. 压缩文件记录格式 filename gzipname\n
  • 1. 获取文件压缩信息
  • 2. 打开压缩记录文件,将数据读取到body中,根据"\n"进行分割,存储在vector中,分割字符串存储在map中,存储的格式为filename->gzipname
  • 3. 启动线程对文件进行循环监控,判断文件是否需要压缩存储
  • 4. 遍历未压缩目录,对目录下每一个文件进行判断是否超时未访问
  • 5. 使用Linux下的接口stat获取文件的最后一次访问时间,使用当前时间-最后一次访问时间,若大于设定的低热度时间,就判定未需要压缩的文件,否则跳过
  • 6. 打开一个压缩文件,将文件中的数据读取到buf中,再将buf中的数据写入压缩文件中,压缩过程由gzwrite完成
  • 7. 压缩完成后,将源文件删除,并将源文件与压缩文件的对应关系记录在map中
  • 8. 将map中保存的文件对应信息写入压缩信息文件中,下一次服务端启动时,源文件与压缩文件的对应关系可以直接读取,如果没有记录,下一次启动,之前压缩后的文件就找不到了
  • 调用接口解析
  • bf::create_directory // boost库中创建目录接口,未压缩文件与压缩文件不保存在一起
  • stat // Linux下的接口,用于获取文件的最后一次访问时间
  • gzwrite // 写入压缩文件中,压缩的过程由接口中的代码完成
  • unlink // Linux下的接口,用于删除一个文件,如果文件正在被使用,则报错退出

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

源码地址:https://github.com/JustDocat/project-for-backup

可扩展的功能
  • 文件的上传信息,与压缩信息的备份信息,可保存在数据库中
  • 可以使用安全的https进行数据传输
  • 在极端条件下(文件的大小未发生改变,文件的修改日期也没有发生改变),则不会上传,可以使用摘要技术,扫描全文,内容改变则进行上传

项目性能测试地址:https://blog.csdn.net/Thinker_serious/article/details/99954314

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面我将详细地介绍如何编写一个学校招生管理系统,并按照软件工程的开发流程进行管理。 1. 需求分析 在需求分析阶段,我们需要明确系统的功能需求、性能需求、安全需求等,并制定相应的需求规格说明书。 (1)功能需求: 学生信息管理模块:包括学生基本信息、考试成绩、入学志愿等信息的录入、查询、修改和删除等功能。 招生计划管理模块:包括招生计划的制定、审核、发布等功能。 录取管理模块:包括录取通知书的发送、录取结果的查询等功能。 统计分析模块:包括各招生计划的报名情况、录取情况、分数分布情况等统计分析功能。 系统管理模块:包括用户管理、权限管理、日志管理等功能。 (2)性能需求: 系统响应时间应在3秒以内。 系统能够同时支持1000个并发用户。 (3)安全需求: 系统数据应进行加密存储,并设置访问控制机制。 系统应能够对非法访问、篡改等行为进行检测和防御。 2. 设计设计阶段,我们需要根据需求规格说明书,设计系统的总体结构和各模块之间的关系,并编写详细设计文档。 (1)总体结构设计: 学生信息管理模块、招生计划管理模块、录取管理模块、统计分析模块和系统管理模块应分别设计为一个独立的子系统。 各子系统之间通过接口进行数据交换和通信。 系统采用B/S架构,即浏览器/服务器架构。 (2)模块设计: 学生信息管理模块: 学生信息录入:包括学生基本信息、考试成绩、入学志愿等信息的录入。 学生信息查询:支持按照学生姓名、考生号、志愿等条件进行查询。 学生信息修改和删除:支持对学生信息进行修改和删除操作。 招生计划管理模块: 招生计划制定:根据学校需要和招生政策,制定招生计划。 招生计划审核:对招生计划进行审核。 招生计划发布:将审核通过的招生计划发布到系统中。 录取管理模块: 录取通知书发送:根据录取结果,自动生成录取通知书并发送给录取的学生。 录取结果查询:支持查询录取结果。 统计分析模块: 报名情况统计:统计各招生计划的报名情况。 录取情况统计:统计各招生计划的录取情况。 分数分布情况统计:统计各科目的分数分布情况。 系统管理模块: 用户管理:支持对用户进行添加、修改、删除等操作。 权限管理:支持对用户的权限进行管理。 日志管理:记录用户的操作日志。 3. 编码 在编码阶段,根据设计文档进行编码实现,同时进行代码审查和单元测试。 在实现过程中,需要注意以下几点: (1)程序代码应符合规范,易于维护和扩展。 (2)程序应具有较好的性能,能够支持大量的用户并发访问。 (3)程序应具有较好的安全性,能够对非法访问、篡改等行为进行检测和防御。 4. 测试 在测试阶段,需要进行系统测试、性能测试、安全测试等,并修复发现的问题。 在测试过程中,需要注意以下几点: (1)测试应尽可能接近实际应用场景,以模拟用户使用的真实情况。 (2)测试应覆盖所有功能模块和各种异常情况。 (3)测试结果应详细记录,并及时修复发现的问题。 5. 上线 在上线阶段,将系统部署到服务器上,并进行系统运行和监控。 在上线过程中,需要注意以下几点: (1)部署前需要进行备份和恢复测试,保证系统数据的完整性和可恢复性。 (2)系统运行后需要进行监控和维护,及时发现和解决系统问题。 (3)定期进行系统升级和优化,以保证系统的稳定性和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值