云备份服务端

 文件使用工具和json序列化反序列化工具

//文件和json工具类的设计实现
#ifndef __UTIL__
#define __UTIL__
#include<iostream>
#include<fstream>
#include<string>
#include <vector>
#include<sys/stat.h>
#include"bundle.h"
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>
namespace cloud
{
    namespace fs = std::experimental::filesystem;
    class FileUtil
    {
        private:
            std::string _filename;// ../YUNBEIFEN/cloud.cpp,是一个带文件名的文件路径
        public:
            FileUtil(const std::string& filename):_filename(filename){}
            int64_t FileSize()//获取文件大小
            {
                struct stat st;
                if(stat(_filename.c_str(),&st)<0)//一个系统调用接口,int stat(const char *path, struct stat *buf),参数path是文件路径(名),输出型参数struct stat类型的结构体保存文件信息
                {
                    std::cout<<"get file size failed!\n";
                    return -1;
                }
                return st.st_size;//返回文件大小
            }
            time_t LastMTime()//获取文件最后一次修改时间
            {
                struct stat st;
                if(stat(_filename.c_str(),&st)<0)
                {
                    std::cout<<"get file MTime 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 ATime failed!\n";
                    return -1;
                }
                return st.st_atime;//返回最后一次访问时间
            }
            std::string FileName()//获取文件名,../YUNBEIFEN/cloud.cpp->cloud.cpp
            {
                size_t pos=_filename.find_last_of("/");
                if(pos==std::string::npos)
                {
                    return _filename;//类成员_filename本身就是一个不带路径的文件名
                }
                return _filename.substr(pos+1);
            }
            bool GetPosLen(std::string *body,size_t pos,size_t len)
            {
                size_t fsize = this->FileSize();
				if (pos + len > fsize)//如果获取的文件超过了文件的大小
                {
					std::cout << "file len error\n";
					return false;
				}
                std::ifstream ifst;
                ifst.open(_filename,std::ios::binary);//以二进制的方式读取文件,
				if (ifst.is_open() == false) //如果打开文件失败
                {
					std::cout << "read_open file failed!\n";
					return false;
				}

				ifst.seekg(pos, std::ios::beg);//从文件的起始位置beg偏移pos个单位
				body->resize(len);
				ifst.read(&(*body)[0], len);//读取数据到body中,&(*body)[0]是空间body首地址。
				if (ifst.good() == false) //如果上一次操作这里是读取异常
                {
					std::cout << "get file content failed\n";
					ifst.close();
					return false;
                }
                ifst.close();
                return true;
            }
            bool GetContent(std::string *body)
            {
                size_t fsize=this->FileSize();
                return GetPosLen(body,0,fsize);
            }
            bool SetContent(const std::string &body) //将body里面的内容设置到文件中
            {
				std::ofstream ofs;//注意和ifstream区分开了,这里是进行写入数据
				ofs.open(_filename, std::ios::binary);//没有就直接创建,_filename是一个包含文件名的路径
				if (ofs.is_open() == false) //文件打开失败
                {
					std::cout << "write_open file failed!\n";
					return false;
				}
				ofs.write(&body[0], body.size());//将body中的数据写入到ofs当中,&(*body)[0]是空间body首地址。
				if (ofs.good() == false) 
                {
					std::cout << "write file content failed!\n";
					ofs.close();
					return false;
				}
				ofs.close();
				return true;
			}
            bool Compress(const std::string &packname)//对文件进行压缩,压缩之后放入到packname中
            {
                //第一步是读取源文件数据
                std::string body;
				if (this->GetContent(&body) == false)
                {
					std::cout << "compress: get file content failed!\n";
					return false;
				}
                //第二步对数据进行压缩
                std::string packed = bundle::pack(bundle::LZIP, body);
                //第三步将压缩的数据存储到压缩包当中
                FileUtil f(packname);
                if (f.SetContent(packed) == false)
                {
					std::cout << "compress: write packed data failed!\n"; 
					return false;
				}
                return true;
            }
            bool UnCompress(const std::string &filename)//将当前压缩文件的数据解压缩到filename当中
            {
                //读取当前压缩包的数据
                std::string body;
                if(this->GetContent(&body)==false)
                {
                    std::cout << "uncompress: get file content failed!\n";
					return false;
                } 
                //对压缩数据进行解压缩
                std::string unpacked=bundle::unpack(body);
                //将解压缩的数据写入
                FileUtil f(filename);
                if(f.SetContent(unpacked)==false)
                {
                    std::cout << "uncompress write packed data failed!\n";
					return false;
                }
                return true;
            }
            
            bool Exists()
            {
                return fs::exists(_filename);//判断是否存在该文件
            }
            bool CreateDirectory()
            {
                if (this->Exists()) return true;//如果存在该目录就直接返回
				return fs::create_directories(_filename);//否则创建这个目录,多层级的目录创建
            }
            bool ScanDirectory(std::vector<std::string> *arry)//通过arry返回目录里面的所有文件的完整路径名称
            {
                for(auto& p: fs::directory_iterator(_filename)) //
                {
					if (fs::is_directory(p) == true)//如果是目录就跳过,因为实际应用中的时候我们传输的是普通文件,不可能传递文件夹m
                    {
						continue;
					}
					//relative_path 是带有相对路径的文件名
					arry->push_back(fs::path(p).relative_path().string()); 
				}
				return true;
            }
            bool Remove()
            {
                if(this->Exists()==false) return true;
                remove(_filename.c_str());
                return true;
            }
    };




    class JsonUtil
    {
        public:
            static bool Serialize(const Json::Value &root, std::string *str)//静态函数,外部可以直接调用,不需要this指针,也就是说不用实例化一个具体的对象来调用。
            {
                Json::StreamWriterBuilder swb;
                std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());//通过StreamWriterBuilder的newStreamWriter()来new一个StreamWriter对象
                std::stringstream ss;
                if(sw->write(root, &ss) != 0)
                {
                    std::cout<<"json write failed!\n";
                    return false;
                }
                *str=ss.str();
                return true;
            }
            static bool UnSerialize(const std::string &str, Json::Value *root)
            {
                Json::CharReaderBuilder crb;
                std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
                std::string err;
                cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);
                return true;
            }
    };

} 
#endif

配置文件信息模块

 将服务端运行所需要的关键信息记录在配置文件里面,当我们运行程序的时候从我们的配置文件中读取这些关键信息出来,在程序中进行使用,使用配置文件的好处就是我们的配置信息随时可以进行更改,而更改配置信息之后我们的程序并不需要进行重新生成编译,只需要重启一下可执行程序,对于修改后的配置文件进行重新加载即可,用配置文件加载程序更加灵活。

{
	"hot_time" : 30,//热点判断时间
	"server_port" : 8080,//
	"server_ip" : "192.159.124.113",
	"download_prefix" : "/download/",//用于表示客户端请求是一个下载请求。http的url里面包含了协议方案名称+服务器ip地址和端口+客户端所请求的资源路径(如果这个请求是一个文件下载请求的话后面跟的就是文件路径,文件路径肯定是存储在服务器上面的,如果外界可以随时随地访问服务器的任意一个文件就不安全,所以这个路径是一个相对根目录路径,我们会在服务器常见一个文件夹wwwroot做为这个根目录,把客户端所请求的资源都放在这个文件夹里面,当我们请求是/abc.txt是会转换成为./wwwroot/abc.txt,从而避免客户端可以任意访问服务端的所有路径。/download/listshow是下载listshow文件,而/listshow是备份列表查看。
	"packfile_suffix" : ".lz",
	"pack_dir" : "./packdir/",
	"back_dir" : "./backdir/",
	"backup_file" : "./cloud.dat"//服务端备份信息存放文件,服务端会将我们所有的文件备份信息管理起来,这样文件就算压缩之后客户端查看列表请求因该是源文件信息查看,所以备份文件信息管理可以让客户端随时获取想要的信息。
}

使用单例模式管理系统配置信息,能够让配置信息的管理控制更加统一灵活。

// #配置文件模块:将服务端运行所需要的关键信息记录在配置文件里面每当我们运行系统的时候,从配置文件里面读取关键信息在程序中使用
// #配置文件的好处:配置信息可以随时进行更改,更改配置信息程序并不需要进行重新编译,只需要重启一下服务端程序,让服务端重新加载程序就可以了,从而让程序运行更加的灵活。 
// #配置信息
// #1.热点判断时间
// #热点管理:多长时间没有被访问的文件算是非热点文件
// #2.文件下载的url前缀路径- -用于表示客户端请求是一个下载请求
// #url: htt://192.168.122.136:9090/path
// #当用户发来个备份列表查看请求/listshow,我们如何判断这个不是一个listshow的文件下载请求
// #/download/test.txt, /download/listshow 判断为下载请求
// #3.压缩包后缀名:定义了压缩包命名规则,就是在文件原名称之后加上后缀。".lz"
// #4上传文件存放路径:决定了文件上传之后实际存储在服务器的哪里
// #5.压缩包存放路径:决定非热点文件压缩后存放的路径
// #6.服务端备份信息存放文件:服务端记录的备份文件信息的持久化存储
// #7.服务器的监听IP地址:当程序要运行在其他主机上,则不需要修改程序
// #8.服务器的监听端口
// #9.配置文件采用json的格式来进行存放
#ifndef __CONFIG__
#define __CONFIG__   
#include <mutex>
#include "util.hpp"
namespace cloud
{
#define CONFIG_FILE "./cloud.conf"
    class Config
    {
		private:
			Config()//构造函数私有化,这样就无法在类外实例化对象,一个类只能实例化一个对象。
            {
				ReadConfigFile();
			} 
			static Config *_instance;//单例模式由于资源只有一个,所以采用一个静态的指针来表示他。
			static std::mutex _mutex;//因为懒汉模式涉及到了线程安全的额外难题,用的时候才进行加载使用,所以需要采用互斥锁来保证对象实例化的过程
		private:
			int _hot_time;//热点判断时间
			int _server_port;//服务器监听端口
			std::string _server_ip;//服务器ip地址
			std::string _download_prefix;//下载的url前缀路径
			std::string _packfile_suffix;//压缩包的后缀名称
			std::string _pack_dir;//压缩包存放路径
			std::string _back_dir;//备份文件存放路径
			std::string _backup_file;//备份数据信息存放文件
            bool ReadConfigFile()//读取配置文件
            {
                FileUtil f(CONFIG_FILE);
                std::string body;
                if(f.GetContent(&body)==false)//读取出来的body是json格式的字符串,还需要反序列化。
                {
                    std::cout<<"read config file failed\n";
                    return false;
                }
                Json::Value root;
                if(JsonUtil::UnSerialize(body, &root)==false)
                {
                    std::cout<<"parse config file failed\n";
                    return false;
                }
                //成员变量赋值 
                _hot_time = root["hot_time"].asInt();
				_server_port = root["server_port"].asInt();
				_server_ip = root["server_ip"].asString();
				_download_prefix = root["download_prefix"].asString();
				_packfile_suffix = root["packfile_suffix"].asString();
				_pack_dir = root["pack_dir"].asString();
				_back_dir = root["back_dir"].asString();
				_backup_file = root["backup_file"].asString();
				return true;
            }
            public://由于成员变量都是private私有的,所以需要提供对应的public共有接口进行访问。
                static Config* GetInstance()//获取操作句柄,cloud::Config *config = cloud::Config::GetInstance();
                {
                    if(_instance==NULL)
                    {
                        _mutex.lock();
                        if(_instance==NULL)
                        {
                            _instance=new Config();
                        }
                        _mutex.unlock();
                    }
                    return _instance;
                }
                int GetHotTime() 
                {
				    return _hot_time;
			    }
			    int GetServerPort() 
                {
				    return _server_port;
			    }
			    std::string GetServerIp() 
                {
				    return _server_ip;
			    }
			    std::string GetDownloadPrefix() 
                {
				    return _download_prefix;
			    }
			    std::string GetPackFileSuffix() 
                {
				    return _packfile_suffix;
			    }
			    std::string GetPackDir() 
                {
				    return _pack_dir;
			    }
			    std::string GetBackDir() 
                {
				    return _back_dir;
			    }
			    std::string GetBackupFile() 
                {
				    return _backup_file;
			    }

    };
    Config *Config::_instance = NULL;//静态成员变量类外定义
    std::mutex Config::_mutex;
}

#endif

 数据管理模块


数据持续化存储的原因是放置服务器每次重启数据都会丢失。

// 数据管理模块:需要管理的数据有哪些
// 管理哪些数据,是因为后期要用到哪些数据
// 1.文件的实际存储路径:当客户端要下载文件时,则从这个文件中读取数据进行响应
// 2.文件压缩包存放路径名:如果这个文件是一个非热点文件会被压缩,则这个就是压缩包路径名称
// 如果客户端要下载文件,则需要先解压缩,然后读取解压后的文件数据。
// 3.文件是否压缩的标志位:判断文件是否已经被压缩了
// 4.文件大小
// 5.文件最后一-次修改时间
// 6.文件最后- -次访问时间
// 7.文件访问URL中资源路径path: /download/a.txt
// 如何管理数据:
// 1.用于数据信息访问:使用hash表在内存中管理数据,以url的path作为key值--查询速度快
// 2.持久化存储管理:使用json序列化将所有数据信息保存在文件中

#ifndef __DATA__
#define __DATA__
#include <unordered_map>
#include <pthread.h>
#include "util.hpp"
#include "config.hpp"

namespace cloud
{
    struct BackupInfo//数据信息结构体,存储数据信息,一个文件实例化一个类对象
    {
        bool pack_flag;//标志文件是否被压缩
        size_t fsize;//文件大小
        time_t mtime;//文件修改时间
        time_t atime;//文件访问时间
        std::string real_path;//文件的实际存储路径
        std::string pack_path;//文件压缩包实际存放路径名
        std::string url;//外界下载时需要的资源路径
        bool NewBackupInfo(const std::string &realpath)//填充 BackupInfo结构体
        {
            FileUtil fu(realpath);
            if(fu.Exists() == false)//如果文件并不存在就直接返回false
            {
                std::cout<<"NewBackInfo: file not exists!\n";
                return false;
            }
            Config *config = Config::GetInstance();
            std::string packdir = config->GetPackDir();//从配置文件中获取压缩包存储路径:./packdir/
			std::string packsuffix = config->GetPackFileSuffix();//从配置文件中获取压缩文件的后缀名:.lz
			std::string download_prefix = config->GetDownloadPrefix();//从配置文件中获取客户端下载请求的前缀:/download/,用来构成文件的url成员。
            this->pack_flag = false;//对于新增文件,该标志位一定是false
			this->fsize = fu.FileSize();
			this->mtime = fu.LastMTime();
			this->atime = fu.LastATime();
			this->real_path = realpath;
            // ./backdir/a.txt   ->   ./packdir/a.txt.lz
			this->pack_path = packdir + fu.FileName() + packsuffix;//存储路径会发生改变,并且多了一个后缀名。
			// ./backdir/a.txt   ->	  /download/a.txt
			this->url = download_prefix + fu.FileName();
			return true;
        }
    };
    class DataManager//数据管理类
    {
        private:
            std::string _backup_file;//数据信息持久化存储的一个文件
            pthread_rwlock_t _rwlock;//数据管理类是会在不同模块中被使用的,涉及到了多线程访问,读写锁,读共享(只是获取数据),写互斥,
		    std::unordered_map<std::string, BackupInfo> _table;//在内存中是采用哈希表存储文件信息
        public:
            DataManager()//每次重启重启的时候都要加载数据,以及每次数据发生修改或者新增都需要进行重新的持续化存储。
            {
                _backup_file = Config::GetInstance()->GetBackupFile();//持久化文件是从我们的配置文件中获取,"backup_file" : "./cloud.dat"
				pthread_rwlock_init(&_rwlock, NULL);//初始化读写锁NewBackupInfoNewBackupInfo锁
                InitLoad();//对象在构造的时候需要对其进行加载,也就是从持续化存储的文件中读取数据来初始化成员_table
            }
            ~DataManager() 
            {
				pthread_rwlock_destroy(&_rwlock);//销毁锁
			}
            bool Insert(const BackupInfo & info)//新增数据
            {
               
                pthread_rwlock_wrlock(&_rwlock);//加锁
                _table[info.url]=info;
                pthread_rwlock_unlock(&_rwlock); 
                Storage();//当有数据新增的时候需要进行持续化存储
                return true;
            }
            bool Update(const BackupInfo & info)//数据发生更新或需要修改数据,代码和Insert是一样的。
            {
                pthread_rwlock_wrlock(&_rwlock);//加锁
                _table[info.url]=info;//当存在相同key值的时候,会对value进行覆盖。
                pthread_rwlock_unlock(&_rwlock); 
                Storage();//当有数据发生修改的时候需要进行持续化存储
                return true;
            }
            bool GetOneByURL(const std::string &url, BackupInfo *info)//因为前端会给一个url请求要下载某个文件,因为url本身就是table的key值,所以直接可以通过find来进行查找。
            {
                pthread_rwlock_wrlock(&_rwlock);//因为要对table进行操作,所以要加锁
				auto it = _table.find(url);//通过find查找url,因为url就是key值。
				if (it == _table.end()) 
                {
					pthread_rwlock_unlock(&_rwlock);
					return false;//没有找到
				}
				*info = it->second;
				pthread_rwlock_unlock(&_rwlock);
				return true;
            }
            bool GetOneByRealPath(const std::string &realpath, BackupInfo *info)//根据真实路径获取文件信息,因为服务器要不断检测一个目录中的所有文件,判断文件是否是一个热点文件。这里遍历的是目录,而不是遍历备份文件信息。
            //realpath不是table的key值,所以得通过遍历table来获取
            {
                pthread_rwlock_wrlock(&_rwlock);
				auto it = _table.begin();
				for (; it != _table.end(); ++it)//由于realpath不是key值,所以不能通过key值查找。
                {
					if (it->second.real_path == realpath)//找到了
                    {
						*info = it->second;
						pthread_rwlock_unlock(&_rwlock);
						return true;
					}
                }
                pthread_rwlock_unlock(&_rwlock); 
				return false;
            }
            bool GetAll(std::vector<BackupInfo> *arry)//获取所有文件信息,可以用来组织页面。
            {
                pthread_rwlock_wrlock(&_rwlock);
				auto it = _table.begin();
				for (; it != _table.end(); ++it)
                {
					arry->push_back(it->second);
				}
				pthread_rwlock_unlock(&_rwlock);
				return true;
            }
            bool Storage()//每次数据新增或者修改都要进行持续化存储,避免数据丢失。(比如说发生数据新增或者数据修改的时候)则需要持久化存储一次,并且涉及到了json的序列化,将其转换成为json的字符串在存储到文件给当中去
            //将所有文件的BackupInfo存储起来,涉及到了json的序列化
            {
                //1.获取所有数据
                std::vector<BackupInfo> arry;
                this->GetAll(&arry);
                //2.将文件信息添加到Json::Value
                Json::Value root;
                for(int i=0;i<arry.size();i++)
                {
                    Json::Value item;
					item["pack_flag"] = arry[i].pack_flag;
					item["file_size"] = (Json::Int64)arry[i].fsize;//(Json::Int64)类型强转,因为json里面并没有重载size_t这些类型
					item["atime"] = (Json::Int64)arry[i].atime;
					item["mtime"] = (Json::Int64)arry[i].mtime;
					item["real_path"] = arry[i].real_path;
					item["pack_path"] = arry[i].pack_path;
					item["url"] = arry[i].url;
                    root.append(item);//添加数组元素,该数组元素是一个json value对象,关键写法。
                }
                //3. 对Json::Value序列化
				std::string body;
				JsonUtil::Serialize(root, &body);
				//4. 将序列化的数据写入到_backup_file
				FileUtil fu(_backup_file);
				fu.SetContent(body);
				return true; 
            }
            bool InitLoad()//初始化加载,每次系统重启都要加载以前的数据。初始化程序运行时从文件读取读取数据,反序列化
            {
                //1. 将数据文件中的数据读取出来
				FileUtil fu(_backup_file);
				if (fu.Exists() == false)
                {
					return true;//说明以前都没有保存过数据,那就不用读了。
				}
				std::string body;
				fu.GetContent(&body);
				//2. 反序列化
				Json::Value root;
				JsonUtil::UnSerialize(body, &root);
				//3. 将反序列化得到的Json::Value中的数据添加到table中
				for (int i = 0; i < root.size(); i++) 
                {
					BackupInfo info;//现在不可以采用NewBackupInfo,所有的文件信息都是从son::Value对象中加载的。
					info.pack_flag = root[i]["pack_flag"].asBool();
					info.fsize = root[i]["file_size"].asInt64();
					info.atime = root[i]["atime"].asInt64();
					info.mtime = root[i]["mtime"].asInt64();
					info.pack_path = root[i]["pack_path"].asString();
					info.real_path = root[i]["real_path"].asString();
					info.url = root[i]["url"].asString();
					Insert(info);
				}
                return true;
            }

    };
}
#endif

热点文件管理模块

热点管理模块:对服务器上备份的文件进行检测,哪些文件长时间没有被访问,则认为是非热点文件,则压
缩存储,节省磁盘空间。
实现思路:
遍历所有的文件,检测文件的最后-次访问时间,与当前时间进行相减得到差值,这个差值如果大
于设定好的非热点判断时间则认为是非热点文件,则进行压缩存放到压缩路径中,删除源文件
遍历所有的文件:
1.从数据管理模块中遍历所有的备份文件信息
2.遍历备份文件夹,获取所有的文件进行属性获取,最终判断
选择第二种:遍历文件夹,每次获取文件的最新数据进行判断,并且还可以解决数据信息缺漏的问
题,也就是某个文件上传成功了,但是漏掉了添加信息。
1.遍历备份目录,获取所有文件路径名称
2.逐个文件获取最后一次访问时间与当前系统时间进行比较判断
3.对非热点文件进行压缩处理,删除源文件
4.修改数据管理模块对应的文件信息(压缩标志修改为true)

// 热点管理模块:对服务器上备份的文件进行检测,哪些文件长时间没有被访问,则认为是非热点文件,则压 缩存储,节省磁盘空间。
// 实现思路:
// 遍历所有的文件,检测文件的最后- -次访问时间,与当前时间进行相减得到差值,这个差值如果大于设定好的非热点判断时间则认为是非热点文件,则进行压缩存放到压缩路径中,删除源文件
// 遍历所有的文件:
// 1.从数据管理模块中遍历所有的备份文件信息
// 2.遍历备份文件夹,获取所有的文件进行属性获取,最终判断
// 选择第二种:遍历文件夹,每次获取文件的最新数据进行判断,并且还可以解决数据信息缺漏的问题
// 1.遍历备份目录,获取所有文件路径名称
// 2.逐个文件获取最后一次访问时间与当前系统时间进行比较判断
// 3.对非热点文件进行压缩处理,删除源文件
// 4.修改数据管理模块对应的文件信息(压缩标志修改为true)
#ifndef __HOT__
#define __HOT__
#include <unistd.h>
#include "data.hpp"


extern cloud::DataManager* _data;
namespace cloud
{
    class HotManager
    {
        //热点管理流程:
        // 1.获取备份目录下所有文件
        // 2.逐个判断文件是否是非热点文件
        // 3.非热点文件压缩处理
        // 4.删除源文件,修改备份信息
        private:
        std::string _back_dir;//备份文件路径
        std::string _pack_dir;//压缩文件路径
        std::string _pack_suffix;//压缩包后缀名
        int _hot_time;//热点的判断时间
        //非热点文件返回真,热点文件返回假
        bool HotJudege(const std::string& filename)
        {
            FileUtil fu(filename);
            time_t last_atime=fu.LastATime();
            time_t cur_time=time(NULL);//获取当前系统时间
            if(cur_time-last_atime>_hot_time) return true;
            else return false;
        }
        public:
        HotManager()
        {
            Config* config=Config::GetInstance();
            _back_dir=config->GetBackDir();
            _pack_dir = config->GetPackDir();
			_pack_suffix = config->GetPackFileSuffix();
			_hot_time = config->GetHotTime();
            FileUtil tmp1(_back_dir);
			FileUtil tmp2(_pack_dir);
			tmp1.CreateDirectory();//如果目录不存在就创建
			tmp2.CreateDirectory();
        }
        bool RunModule()
        {
            while (1)//这个模块是一个死循环的过程
            {
                //1.遍历备份的目录,获取所有的文件名
                FileUtil fu(_back_dir);
                std::vector<std::string> array;
                fu.ScanDirectory(&array);
                //2.遍历判断文件是否是非热点文件
                for(auto& a:array)
                {
                    if(HotJudege(a)==false) continue;//是热点文件就不做额外处理
                    //3.非热点文件则需要压缩,先获取文件的备份信息
                    BackupInfo bi;
                    if(_data->GetOneByRealPath(a,&bi)==false)
                    {
                        //走打破这里说明现在有一个文件存在,但是没有备份信息
                        bi.NewBackupInfo(a);//设置一个新的备份信息
                    }
                    //4.对非热点文件进行压缩
                    FileUtil tmp(a);
                    tmp.Compress(bi.pack_path);
                    //5.删除源文件,修改备份信息
                    tmp.Remove();
                    bi.pack_flag=true;
                    _data->Update(bi);//修改文件信息
                }
                usleep(1000);//避免空目录循环遍历,消费cpu资源过高。
            }
            return true;
        }
    };
}
#endif
cloud::DataManager *_data;//定义一个全局变量,在hot.hpp中会用到:extern cloud::DataManager* _data;
void HotTest()
{	
	_data=new cloud::DataManager();
	cloud::HotManager hot;
	hot.RunModule();
}

int main(int argc,char* argv[])
{
	HotTest();
	return 0;
}

业务处理模块

三大业务模块


服务端业务处理模块:将网络通信模块和业务处理进行了合并(网络通信通过httplib库完成)
1.搭建网络通信服务器:借助httplib完成
2.业务请求处理
1.文件.上传请求:备份客户端上传的文件,响应上传成功
2.文件列表请求:客户端浏览器请求一个备份文件的展示页面,响应页面
3.文件下载请求:通过展示页面,点击下载,响应客户端要下载的文件数据

网络通信接设计:约定好,客户端发送什么样的请求,我们给与什么样的响应
请求:文件上传,展示页面,文件下载

 

 

断点续传模块


功能:当文件下载过程中,因为某种异常而中断,如果再次进行从头下载,效率较低,因为需要将
之前已经传输过的数据再次传输一遍。因此断点续传就是从上次下载断开的位置,重新下载即可,之前已经传输过的数据将不需要再重新传输。
目的:提高文件重新传输效率
实现思想:
客户端在下载文件的时候,要每次接收到数据写入文件后记录自己当前下载的数据量。当异常下载中断时,下次断点续传的时候,将要重新下载的数据区间(下载起始位置,结束位置)发送给服务器,服务器收到后,仅仅回传客户端需要的区间数据即可。
需要考虑的问题:如果上次下载文件之后,这个文件在服务器上被修改了,则这时候将不能重新断
点续传,而是应该重新进行文件下载操作。
在http协议中断点续传的实现:
主要关键点:
1.在于能够告诉服务器下载区间范围,
2.服务器上要能够检测上一次下载之后这个文件是否被修改过

//当我们浏览器输入192.182.142.10:9090的时候,没有任何的请求的情况下,浏览器会默认加上一个\表示这是一个根目录请求
// 服务端业务处理模块:将网络通信模块和业务处理进行了合并(网络通信通过httplib库完成)
// 1.搭建网络通信服务器:借助httplib完成
// 2.业务请求处理
// 1.文件上传请求:备份客户端上传的文件,响应上传成功
// 2.文件列表请求:客户端浏览器请求一个备份文件的展示页面, 响应页面
// 3.文件下载请求:通过展示页面,点击下载,响应客户端要下载的文件数据

// 网络通信接口设计:约定好,客户端发送什么样的请求,我们给与什么样的响应
// 请求:文件上传,展示页面,文件下载
// 接口设计: 
// 1.文件上传
// POST /upload HTTP/1.1
// Content-Length:11
// Content-Type:multipart/form-data;boundary= ----WebKitFormBoundary+16字节随机字符
// ------WebKitFormBoundary
// Content-Disposition:form-data;filename="a.txt";
// hello world
// ------WebKitFormBoundary--
// 当服务器收到了一个POST方法的/upload请求,我们则认为这是一个文件上传请求
// 解析请求,得到文件数据,将数据写入到文件中
// HTTP/1.1 200 OK
// Content-Length: 0
// 2.展示页面
// GET /list HTTP/1.1
// Content-Length: 0
//当服务器收到了一个GET方法的/listshow请求,我们则认为这是一个文件页面展示请求
// HTTP/1.1 200 OK
// Content-Length:
// Content-Type: text/html

// <html>..... </html> <!-- 这是展示页面的数据-->
// 3.文件下载
// GET /download/a.txt http/1.1
// Content-Length: 0
// 当服务器收到了一个GET方法的/download/请求,我们则认为这是一个文件下载请求
// HTTP/1.1 200 OK
// Content-Length: 100000
// ETags: "filename-size-mtime一个能够唯一标识文件的数据"
// Accept-Ranges: bytes
// 正文就是文件数据


#ifndef __SERVICE__
#define __SERVICE__
#include "data.hpp"
#include "httplib.h"

extern cloud::DataManager* _data;
namespace cloud
{
    class Service
    {
		private:
            //搭建http服务器,并进行业务处理。
			int _server_port;
			std::string _server_ip;
			std::string _download_prefix;
			httplib::Server _server;
        public:
            Service()
            {
                Config *config = Config::GetInstance();
				_server_port = config->GetServerPort();
				_server_ip = config->GetServerIp();
				_download_prefix = config->GetDownloadPrefix();
            }
            bool RunModule()
            {
                //搭建服务器,注册映射关系,然后listen
                _server.Post("/upload",Upload);
                _server.Get("/listshow",ListShow);
                _server.Get("/",ListShow);//当我们浏览器访问服务器采用172.16.204.184:9090后面什么也不加没有任何的资源路径请求,浏览器会在末尾默认加一个/是一个相当于一个根目录请求,我们response一个展示页面。
                std::string download_url=_download_prefix+"(.*)";
                _server.Get(download_url,Download);//(.*)用来捕捉数据,匹配多个字符
                _server.listen(_server_ip.c_str(),_server_port);
                return true;
            }
		private:
            static void Upload(const httplib::Request &req, httplib::Response &rsp) 
            {
                // post /upload  文件数据在正文中(但是正文并不全是文件数据,还有辅助信息比如说数据类型等等)
				auto ret = req.has_file("file");//判断有没有上传的文件字段,其实就是判断一下有没有文件上传
				if (ret == false)//没有文件上传
                {
					rsp.status = 400;//格式错误
					return;
				}
                std::cout<<"ok1"<<std::endl;
                //有文件上传就获取数据
                const auto& file = req.get_file_value("file");//其中两个成员变量分别指的是: file.filename//文件名称    file.content//文件数据
                std::cout<<"ok2"<<std::endl;
                //找到文件并在备份目录下创建该文件,并且把数据写入进去。
                std::string back_dir = Config::GetInstance()->GetBackDir();
                std::cout<<"ok3"<<std::endl;
				std::string realpath = back_dir + FileUtil(file.filename).FileName();//FileUtil(file.filename).FileName()是为了只获取文件名,路径并不需要。
                std::cout<<"ok4"<<std::endl;
				FileUtil fu(realpath);
                std::cout<<realpath<<std::endl;
                std::cout<<"ok5"<<std::endl;
				fu.SetContent(file.content);//将数据写入文件中;
                std::cout<<"ok6"<<std::endl;
                BackupInfo info;
				info.NewBackupInfo(realpath);//组织备份的文件信息
                std::cout<<"ok7"<<std::endl;
				_data->Insert(info);//向数据管理模块添加备份的文件信息
                std::cout<<"ok8"<<std::endl;
                return;
            }
            static std::string TimetoStr(time_t t) //将时间戳转换成为易读的字符串,当前函数设置为静态函数的原因是因为ListShow是静态成员函数,里面调用的也只能是静态成员函数,否则this变量从哪里来。
            {
				std::string tmp = std::ctime(&t);
				return tmp;
			}
            static void ListShow(const httplib::Request &req, httplib::Response &rsp)
            {
                //1.获取所有的备份文件信息
                std::vector<BackupInfo> arry;//用来放置所有的文件信息,后面用到页面展示
                _data->GetAll(&arry);
                //2.根据所有的备份信息,组织html文件数据
                std::stringstream ss;
				ss << "<html><head><title>Download</title></head>";
				ss << "<body><h1>Download</h1><table>";
				for (auto &a : arry){
					ss << "<tr>";
					std::string filename = FileUtil(a.real_path).FileName();
					ss << "<td><a href='" << a.url << "'>" << filename << "</a></td>";
					ss << "<td align='right'>" << TimetoStr(a.mtime) << "</td>";
					ss << "<td align='right'>" << a.fsize / 1024 << "k</td>";
					ss << "</tr>";
				}
				ss << "</table></body></html>";// '/'意味着结尾
				rsp.body = ss.str();
				rsp.set_header("Content-Type", "text/html");//设置正文数据类型,告诉浏览器这是一个html数据,你需要对其进行渲染让后展示在浏览器当中。
				rsp.status = 200;
				return ;
            }
            static std::string GetETag(const BackupInfo &info) //http的ETag头部字段存储了一个资源的唯一标识,当客户端第一次下载一个文件的时候,我们生成一个唯一标识最为响应的一部分给客户端,
            //客户端第二次下载的时候,就会将该信息发送给服务器,让服务器根据这个唯一标识判断这个资源自从上次下载过之后是否又被修改过,如果没有修改的话,直接使用原先缓存的数据,不用再重新下载了。
            {
				//etg:filename-fsize-mtime,这个etag是自定义的,也说明了http协议本身对于etag中是什么数据并不关心,只要你服务端可以自己标识就行了。而etag字段不仅仅是缓存用到,还有就是后边的断点续传的实现也会用到,因为断点续传也要保证文件没有被修改过。
                
				FileUtil fu(info.real_path);
				std::string etag = fu.FileName();
				etag += "-";
				etag += std::to_string(info.fsize);
				etag += "-";
				etag += std::to_string(info.mtime);
				return etag;
			}
            static void Download(const httplib::Request &req, httplib::Response &rsp)
            {
               
                //http协议的Accept-Ranges: bytes字段:用于告诉客户端我支持断点续传 ,并且数据单位以字节作为单位。
                //1. 获取客户端请求的资源路径path,其中req.path已经组织好了,是一个url资源请求类型的路径
				//2. 根据资源路径,获取文件备份信息
				BackupInfo info;
				_data->GetOneByURL(req.path, &info);
				//3. 判断文件是否被压缩,如果被压缩,要先解压缩, 
				if (info.pack_flag == true)
                {
					FileUtil fu(info.pack_path);//pack
					fu.UnCompress(info.real_path);//将文件解压到备份目录下
					//4. 删除压缩包
					fu.Remove();
                    //5修改备份信息(改为没有被压缩)
					info.pack_flag = false;
					_data->Update(info);
                }
                //断点续传的判断主要在于解析请求中是否If-Range这个字段的信息
                bool retrans = false;//定义当前是否属于断点续传
				std::string old_etag;
				if (req.has_header("If-Range")) 
                {
					old_etag = req.get_header_value("If-Range");//存在If-Range字段说明需要断点续传,这时候获取该字段的value值,也就是以前该文件的etag
					if (old_etag == GetETag(info)) //有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传
                    {
						retrans = true;
					}
				}//如果没有If-Range字段或者有这个字段但是old_etag与当前服务器的etag不匹配的话,则必须重新返回全部的数据,也就是进入正常的下载模式。

				//6. 读取文件数据,放入rsp.body中
				FileUtil fu(info.real_path);
				if (retrans == false)//响应执行正常的文件下载任务
                {
					fu.GetContent(&rsp.body);
					//7. 设置响应头部字段: ETag, Accept-Ranges: bytes,Content-Length会自动设置,不需要我们主动设置。
					rsp.set_header("Accept-Ranges", "bytes");
					rsp.set_header("ETag", GetETag(info));
					rsp.set_header("Content-Type", "application/octet-stream");//Content-Type字段:决定了浏览器如何处理响应正文,如果不设置浏览器无法得知如何处理正文数据,application/octet-stream说明数据是二进制数据流,常用于文件下载。
					rsp.status = 200;
				}
                else//响应执行断点续传任务 
                {
					//httplib内部实现了对于区间请求也就是断点续传请求的处理
					//只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求
					//区间,从body中取出指定区间数据进行响应
					// std::string  range = req.get_header_val("Range"); bytes=start-end
					fu.GetContent(&rsp.body);
					rsp.set_header("Accept-Ranges", "bytes");
					rsp.set_header("ETag", GetETag(info));
					rsp.set_header("Content-Type", "application/octet-stream");
					//rsp.set_header("Content-Range", "bytes start-end/fsize");
					rsp.status = 206;//状态码206是指服务器成功处理了布冯GET请求。
				}
            }
    };
}
#endif



 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

热门软件缺陷管理工具2024:专业评测与建议

国内外主流的10款软件缺陷管理工具软件对比&#xff1a;PingCode、Worktile、禅道、Tapd、Teambition、Tower、JIRA、Bugzilla、MantisBT、Trac。 在软件开发过程中&#xff0c;管理缺陷和漏洞常常成为一项挑战&#xff0c;尤其是在项目规模庞大时。选择一个高效的软件缺陷管理…

【Linux环境sqlite下载安装教程】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、下载路径二、安装步骤 一、下载路径 https://sqlite.org/download.html 选择Alternative Source Code Formats下的sqlite-src-3460000.zip进行下载。 二、安…

手机怎么看WiFi的IP地址

在如今数字化快速发展的时代&#xff0c;无线网络已成为我们日常生活中不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;我们可能都离不开WiFi的陪伴。然而&#xff0c;在使用WiFi的过程中&#xff0c;有时我们可能需要查看其IP地址&#xff0c;以便更好地管理我们的网…

Jira学习

1.Dev OPS DevOps简介 DEV OPS 流程 DEV OPS流程对应工具 最重要的就是持续集成–Jenkins 2.Jira 新建项目

华为HCIP Datacom H12-821 卷39

1.填空题 请2001 :0DB8:0000:C030:0000: 000: 09A0:CDEF地址进行压缩。() (若答案中存在字母&#xff0c;请采用大写格式) 参考答案&#xff1a;2001 :DB8:0:C030: :9A0:CDEF 解析&#xff1a; IPv6地址的表示方法 IPv6地址总长度为128比特&#xff0c;通常分为8组&#xff0c…

macos上latex环境搭建(homebrew安装+vscode配置+ MacTex)和B站视频、网站、教程等相关资料推荐(Overleaf、公式预览网站)

安装及配置 本机环境 本人为macos&#xff0c;已经安装了homebrew和vscode。希望得到的效果是在vscode中编辑并预览latex文件 MacTex安装 首先&#xff0c;使用brew安装MacTex(新版本的brew已经将install和install --cask合并了) brew install mactex安装后一般会置于如下…

对于GPT-5在一年半后发布的期待!

首先&#xff0c;如果GPT-5真如OpenAI首席技术官米拉穆拉蒂&#xff08;Mira Murati&#xff09;在采访中所透露的那样&#xff0c;在一年半后发布&#xff0c;并在某些领域达到博士级的智能&#xff0c;这无疑将是一个令人振奋的消息。这一预测不仅反映了AI技术的快速发展&…

uniapp 微信小程序根据后端返回的文件链接打开并保存到手机文件夹中【支持doc、docx、txt、xlsx等类型的文件】!

项目场景&#xff1a; 我们在使用uniapp官方提供的uni.downloadFile以及uni.saveFile时&#xff0c;会发现这个文件下载的默认保存位置和我们预想的不太一样&#xff0c;容易找不到&#xff0c;而且没有提示&#xff0c;那么我们就需要把文件打开自己保存并且有提示保存到哪个…

leetcode算法题(反转链表)

思路1&#xff1a; 创建新的链表&#xff0c;遍历原链表&#xff0c;将原链表的节点进行头插到新链表中。 struct ListNode* reverseList(struct ListNode* head) {struct ListNode* next NULL;struct ListNode* new_head NULL;if (head NULL ||head->next NULL) // 空…

【C语言】全面解析冒泡排序

文章目录 什么是冒泡排序&#xff1f;冒泡排序的基本实现代码解释冒泡排序的优化冒泡排序的性能分析冒泡排序的实际应用结论 在C语言编程中&#xff0c;排序算法是一个非常基础且重要的概念。冒泡排序作为最简单、最易理解的排序算法之一&#xff0c;广泛应用于各种编程教学和实…

设计模式学习(二)工厂模式——抽象工厂模式

设计模式学习&#xff08;二&#xff09;工厂模式——抽象工厂模式 背景抽象工厂模式优点与缺点参考文章 背景 现在我需要开发一个相机操作模块&#xff0c;它可能在Windows下运行&#xff0c;也可能在Linux下运行。由于在厂家提供的SDK中&#xff0c;Windows下的SDK和Linux下…

K8S 中的 CRI、OCI、CRI shim、containerd

哈喽大家好&#xff0c;我是咸鱼。 好久没发文了&#xff0c;最近这段时间都在学 K8S。不知道大家是不是和咸鱼一样&#xff0c;刚开始学 K8S、Docker 的时候&#xff0c;往往被 CRI、OCI、CRI shim、containerd 这些名词搞得晕乎乎的&#xff0c;不清楚它们到底是干什么用的。…

踩坑日记 | 记一次流程图问题排查

踩坑日记&#xff1a;记一次流程图问题排查 标签&#xff1a; activiti | 流程 引言 今天排查了一个流程图问题&#xff0c;耗时2个小时终于解决&#xff0c;记录下来 现象 流程审批驳回报错&#xff1a;Unknown property used in expression: ${xxxx} 使用的是 activiti …

AI视频教程下载-ChatGPT速成课程:工作中的ChatGPT入门

使用ChatGPT提升你的生产力&#xff1a;利用OpenAI的革命性ChatGPT模型。 你准备好深入人工智能交流的世界&#xff0c;彻底改变你的职业生涯了吗&#xff1f;本课程适合技术背景和非技术背景的人士&#xff0c;它以独特、有趣且专业的方式&#xff0c;教授如何使用OpenAI的Ch…

【Linux取经之路】Linux常见指令

目录 基本指令 常见指令 1&#xff09;ls —— 对于目录&#xff0c;列出该目录下的所有子目录和文件&#xff1b;对于文件&#xff0c;将列出文件名及其他信息 2&#xff09;pwd —— 显示当前所在的目录 ​编辑 3&#xff09;cd —— 切换到指定路径下 4&#xff09;t…

服务客户,保证质量:腾讯云产品的质量实践

分享主题是“服务客户&#xff0c;保证质量”。自从20年开始&#xff0c;我们把质量提升到了一个前所未有的高度。为什么会如此重视质量呢&#xff1f;在竞争激烈和复杂的市场环境中&#xff0c;产品质量对于企业的重要性不言而喻。一旦出现了质量事故&#xff0c;对客户和企业…

实战案例:用百度千帆大模型API开发智能五子棋

前随着人工智能技术的迅猛发展&#xff0c;各种智能应用层出不穷。五子棋作为一款经典的棋类游戏&#xff0c;拥有广泛的爱好者。将人工智能技术与五子棋结合&#xff0c;不仅能提升游戏的趣味性和挑战性&#xff0c;还能展现AI在复杂决策问题上的强大能力。在本篇文章中&#…

CV12_ONNX转RKNN模型(谛听盒子)

暂时简单整理一下&#xff1a; 1.在边缘设备上配置相关环境。 2.配置完成后&#xff0c;获取模型中间的输入输出结果&#xff0c;保存为npy格式。 3.将onnx格式的模型&#xff0c;以及中间输入输出文件传送到边缘设备上。 4.编写一个python文件用于转换模型格式&#xff0c…

LLM之Prompt(四)| OpenAI、微软发布Prompt技术报告

摘要 生成式人工智能 &#xff08;GenAI&#xff09; 系统正越来越多地部署在各行各业和研究机构。开发人员和用户通过使用提示或提示工程与这些系统进行交互。虽然提示是一个广泛提及且被研究的概念&#xff0c;但由于该领域的新生&#xff0c;存在相互矛盾的术语和对构成提示…

Spring MVC 全注解开发

1. Spring MVC 全注解开发 文章目录 1. Spring MVC 全注解开发2. web.xml 文件 的替代2.1 Servlet3.0新特性2.2 编写 WebAppInitializer 3. Spring MVC的配置3.1 Spring MVC的配置&#xff1a;开启注解驱动3.2 Spring MVC的配置&#xff1a;视图解析器3.3 Spring MVC的配置&…