云备份服务端

news2024/11/14 4:28:29

 文件使用工具和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



 

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

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

相关文章

68、Flink DataStream Connector 之文件系统详解

文件系统 1.概述 连接器提供了 BATCH 模式和 STREAMING 模式统一的 Source 和 Sink。 Flink FileSystem abstraction支持连接器对文件系统进行&#xff08;分区&#xff09;文件读写&#xff0c;文件系统连接器为 BATCH 和 STREAMING 模式提供了相同的保证&#xff0c;而且对…

数字孪生Digital Twin 结合建筑信息模型 BIM 在AIoT 智慧城市建设中Web 可视化大屏实践...

智慧城市建设通过将城市中的建筑、基础设施等构建 BIM 模型&#xff0c;并与实时采集的数据相结合&#xff0c;创建数字孪生体。可以实现对城市能源消耗、交通流量、环境质量等的实时监测和预测&#xff0c;优化城市规划和资源分配。 01 数字孪生 Digital Twin 数字孪生 Digita…

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的配置&…

【实战:python-Django发送邮件-短信-钉钉通知】

一 Python发送邮件 1.1 使用SMTP模块发送邮件 import smtplib from email.mime.text import MIMEText from email.header import Headermsg_from 306334678qq.com # 发送方邮箱 passwd luzdikipwhjjbibf # 填入发送方邮箱的授权码(填入自己的授权码&#xff0c;相当于邮箱…

SSE、Webworker 、webSocket、Http、Socket 服务器推送技术

Http协议 受浏览器的同源策略限制 HTTP 协议是一种无状态的、无连接&#xff08;短暂连接&#xff0c;客户端发送请求&#xff0c;服务器响应后即断开连接&#xff09;的、单向的应用层协议。 它采用了请求/响应模型。通信请求只能由客户端发起&#xff0c;服务端对请求做出应…

(day18) leetcode 204.计数质数

描述 给定整数 n &#xff0c;返回 所有小于非负整数 n 的质数的数量 。 示例 1&#xff1a; 输入&#xff1a;n 10 输出&#xff1a;4 解释&#xff1a;小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。示例 2&#xff1a; 输入&#xff1a;n 0 输出&#xff1a;0示例 3…

JVM--自动内存管理--JAVA内存区域

1. 运行时数据区域 灰色的线程共享&#xff0c;白色的线程独享 白色的独享就是根据个体"同生共死" 程序计数器&#xff1a; 是唯一一个没有OOM(内存溢出)的地方 是线程独享的 作用&#xff1a; 是一块较小的内存空间,是当前线程所执行的字节吗的行号指示器 由于…

智慧水利:迈向水资源管理的新时代,结合物联网、云计算等先进技术,阐述智慧水利解决方案在提升水灾害防控能力、优化水资源配置中的关键作用

本文关键词&#xff1a;智慧水利、智慧水利工程、智慧水利发展前景、智慧水利技术、智慧水利信息化系统、智慧水利解决方案、数字水利和智慧水利、数字水利工程、数字水利建设、数字水利概念、人水和协、智慧水库、智慧水库管理平台、智慧水库建设方案、智慧水库解决方案、智慧…

docker 安装 onlyoffice

1.文档地址 Installing ONLYOFFICE Docs for Docker on a local server - ONLYOFFICE 2.安装onlyoffice docker run -i -t -d -p 9000:8000 --restartalways -e JWT_ENABLEDfalse onlyoffice/documentserver 如果发现镜像无法下载,可以尝试更换镜像源 {"registry-mir…

JVM和类加载机制-01[JVM底层架构和JVM调优]

JVM底层 Java虚拟机内存模型JVM组成部分五大内存区域各自的作用虚拟机栈(线程栈)栈帧内存区域 本地方法栈程序计数器为什么jvm要设计程序计数器&#xff1f; 堆方法区 JVM优化-堆详解JVM底层垃圾回收机制jvm调优工具jvisualvm.exeArthas工具使用 Java虚拟机内存模型 JVM跨平台原…

2024年初级注册安全工程师职业资格考试首次开考!

​2024年初级注册安全工程师考试首次开考&#xff08;注&#xff1a;该考试由各省人事考试局组织考试&#xff09;。目前未取得中级注册安全工程师证书的各位同学&#xff0c;可以关注该考试&#xff0c;毕竟初级考证相对较容易&#xff0c;先去考一个。 目前初安开考地区汇总…

PHP多功能投票微信小程序系统源码

&#x1f389;一键决策&#xff0c;尽在掌握&#xff01;多功能投票小程序&#xff0c;让选择不再纠结&#x1f914; &#x1f4f2;【开篇&#xff1a;告别传统&#xff0c;拥抱便捷投票新时代】&#x1f4f2; 还在为组织投票活动手忙脚乱&#xff1f;或是面对众多选项犹豫不…

技术成神之路:设计模式(七)状态模式

1.介绍 状态模式&#xff08;State Pattern&#xff09;是一种行为设计模式&#xff0c;它允许一个对象在其内部状态改变时改变其行为。这个模式将状态的相关行为封装在独立的状态类中&#xff0c;并将不同状态之间的转换逻辑分离开来。 2.主要作用 状态模式的主要作用是让一个…

HTML5+CSS3小实例:纯CSS实现奥运五环

实例:纯CSS实现奥运五环 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-sca…

算法导论 总结索引 | 第五部分 第十八章:B树

1、B 树是 为磁盘或其他直接存取的辅助存储设备 而设计的一种平衡搜索树。B 树类似于红黑树&#xff0c;在降低磁盘 I/O 操作次数方面要更好一些。许多数据库系统 使用 B 树 或者 B 树 的变种来存储信息 2、B 树与红黑树的不同之处 在于 B 树的结点 可以有很多孩子&#xff0c…

《JavaScript权威指南第7版》中文PDF+英文PDF+源代码 +JavaScript权威指南(第6版)(附源码)PDF下载阅读分享推荐

JavaScript是Web编程语言。绝大多数网站都使用JavaScript&#xff0c;所有现代Web浏览器&#xff08;无论是桌面、平板还是手机浏览器&#xff0c;书中以后统称为浏览器&#xff09;都包含JavaScript解释器&#xff0c;这让JavaScript成为有史以来部署最广泛的编程语言。过去十…

怎么把自己写的组件发布到npm官方仓库??

一.注册npm账号 npm官网 1.注册npm 账号 2.登陆 3.登陆成功 二.搭建一个vue 项目 具体步骤参考liu.z Z 博客 或者初始化一个vue项目 vue create XXX &#xff08;工程名字&#xff09;运行代码 npm run serve三.组件封装 1.在src文件下建一个package文件&#xff0…

EMQX开源版安装

一、EMQX是什么 EMQX 是一款开源的大规模分布式 MQTT 消息服务器&#xff0c;功能丰富&#xff0c;专为物联网和实时通信应用而设计。EMQX 5.0 单集群支持 MQTT 并发连接数高达 1 亿条&#xff0c;单服务器的传输与处理吞吐量可达每秒百万级 MQTT 消息&#xff0c;同时保证毫秒…

参考——CCS联合CLion调试__开发TI芯片

一、简介 随着电赛临近&#xff0c;有些题必须指定使用TI芯片&#xff0c;那么就不得不学一下CCS。虽然CCS相较于Keil和IAR&#xff0c;显得更现代化一些&#xff0c;但还是没有代码样式、代码格式化、代码补全等功能。如果你用惯了CLion再用CCS&#xff0c;就会有些许一言难尽…

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

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