当前位置: 首页 > news >正文

【云备份项目两万字总结】服务端篇 -----附源码

在这里插入图片描述

项目总结

  • 整体回顾
  • 逐步实现
    • utill.hpp
    • config.hpp
    • data.hpp
    • hot.hpp
    • service.hpp
  • 代码

整体回顾

服务端的目标是:

  1. 对客户端的请求进行处理
  2. 管理客户端上传的文件

于客户端进行数据交换,我们需要引入网络,所以我们引入第三方库----httplib.h库,搭建服务端。在进行网络通讯时,数据需要被系列化和反序列化,否则有数据丢失的风险,还需引入json
在管理文件时,需要进行热点管理,把非热点文件进行压缩也需要引入第三方库 ----bundle.h,对文件进行压缩。

因此我们可以给服务端的各种功能实现划分模块,逐步实现服务端的整体功能。

要对上传的文件的文件进行管理,我们需要---- 文件管理模块
同时,在将文件管理后,我们需要对其进行更一步的热带管理模块,热点管理的实现是在文件管理模块的实现之上的。
在本地测试好了上述两个模块后,我们可以进行网络通讯了。
需要一个网络通讯模块,通过其搭建我们的服务端,
能进行网络通讯后,还需要一个业务处理模块,处理客户端发送过来的请求,并予以响应。

逐步实现

先创建一个库,util.hpp工具库文件
里面有我们自己实现的几个util工具类。

工欲善其事,必先利其器。

utill.hpp

class FileUtil{}
创建一个FileUtil 类,文件工具类,对系统的文件接口进行封装,便于我们对文件快捷操作,提供对文件的增删查改

class FileUtil{
private:std::string _name;
public:FileUtil(const std::string &name);size_t FileSize();// 文件大小time_t LastATime(); // 最后一次查看文件时间time_t LastMTime(); // 最后一次修改文件的时间std::string FileName();   //文件名字bool GetPosLen(std::string *content, size_t pos, size_t len);  //获取文件流pos 后len个长度的数据bool GetContent(std::string *content);  //获取文件内容bool SetContent(std::strint *content);   //写入文件bool Compress(const std::string &packname);   //压缩文件bool UnCompress(const std::string &filename);  //解压文件bool Exists();     //判断文件是否存在bool CreateDirectory();    //创建一个目录bool ScanDirectory(std::vector<std::string> *arry);  //查看目录下的文件内容}
  1. FileUtile 工具提供了对文件的增删查改的功能,
  2. 也提供了对目录的查看功能,和创建目录的功能

其中,对文件压缩解压缩时,我们需要借用bundle.h库的函数,如何使用bundle库里的函数,在GitHub上有完整的教程。
在这里插入图片描述

同时,我们在查看目录时,需要借助filesystem库的使用,但是只有在c++17以上的版本才支持filesystem

在这里插入图片描述

注意:

  • 在Windows下,我们要选择了vs2017以上的版本
  • 在Linux下,我们需要将gcc进行升级,7.3版本

class jsonutil

jsonutil类为网络通讯时的数据提供系列化和反序列化的功能,当然需要引入json库

在这里插入图片描述

至此,我们的基础工具已经完善,可以以此为基础,更一步完善服务端的功能。

config.hpp

项目配置信息的管理,启动项目时,会自动从 .conf文件加载项目的配置信息。需要修改部分内容时,不需要在代码上修改,只需要修改配置文件,然后重启服务器即可。

采用json 格式将配置信息存放在Cloud.conf中,当启动服务器时,由服务器从.conf文件中读取关键数据。

Cloud.conf 文件

{
“hot_time” : 30,
“server_port” : 9090,
“server_ip” : “1.1.1.1”,
“url_prefix” : “/download/”,
“arc_suffix” : “.lz”,
“pack_dir” : “./packdir/”,
“back_dir” : “./backdir/”,
“manager_file” : “./back.dat”
}

#define CONFIG_FILE "./cloud.conf"
class Config{
private:time_t _hot_time;int _server_port;std::string _server_ip;std::string _url_prefix;std::string _arc_suffix;std::string _pack_dir;std::string _back_dir;std::string _manager_file;//备份信息的配置管理
private:static std::mutex _mutex;static Config *_instance;Config();
public:bool ReadConfig(const std::string &filename);int GetHotTime();int GetServerPort();std::string GetServerIp();std::string GetURLPrefix();std::string GetArcSuffix();std::string GetPackDir();std::string GetBackDir();std::string GetManagerFile();
public:static Config *GetInstance();
};

且,在实现配置信息类时,我们采取单例模式。

data.hpp

data.hpp是数据管理模块的主要部分。

要管理文件数据,就得先对文件的信息进行组织。
struct BackupInfo{}

typedef struct BackupInfo{bool pack_flag; // 文件是否被压缩的标识time_t atime;   // 最后一次查看时间time_t mtime;   // 最后一次修改时间size_t fsize;    //文件大小std::string real_path; // 文件在服务器上的真实存储路径std::string url;       // 客户端访问文件时的请求urlstd::string packpath;  // 压缩包存储路径bool FillBackupInfo(const std::string &realpath){}}BackupInfo;

有了这些数据后,我们能准确的描述一个文件,并可以很好的进行管理。

上传的文件信息都以BackuoInfo的模式,以json的格式存储在backup_file中,当程序启动时,需要去文件加载数据到内存。同时,在新上传文件后,我们也需要将文件数据永久化存储到backup_file中。也需要支持对已经被管理的文件信息的增删查改(我们暂时不支持对信息的删除,现在只涉及最基础的功能实现,更多功能在已经构建好整个框架后会进一步实现)。

整个数据管理模块,也为让上层迅速查找文件的备份信息

class DateManager{}

class DataManager{
private:FileUtil _backup_file;pthread_rwlock_t _rwlock;   //  读写锁std::unordered_map<std::string, BackupInfo> _table;
public:DataManager();bool InitLoad();//初始化程序运行时从文件读取数据bool Storage();    //每次有信息改变则需要持久化存储一次bool Insert(const std::string &key, const BackupInfo &val);bool Update(const std::string &key, const BackupInfo &val);bool GetOneByURL(const std::string &key, BackupInfo *info);bool GetOneByRealPath(const std::string &key, BackupInfo *info);bool GetAll(std::vector<BackupInfo> *arry);
};

其具体实现内容在项目日志时已经说过,在此不再重复。

注意:
我们是对 _table 加上了rwlock 读写锁,因为这里的并发访问场景更多的是读读,读写场景,能提高服务器运行速度。
同时,加锁的原因是:
httplib库中,使用了线程池的技术,当服务端accept一个客户端后,会另起一个线程在服务端处理请求,所以, _table属于临界资源,需要加锁保护。

我们的Storage()覆盖式存储,是将 内存中 _table里的所有数据进行反序列,将 backup_file里的内容进行覆盖。

hot.hpp

热点管理模块就压要简单一点,很大一部分工作在数据管理模块已经完成。

循环遍历目录下的所有文件,然后通过文件最后一次修改时间来判断该文件是否为热点文件,然后压缩至指定目录即可。

extern cloud::DataManager *_data;
class HotManager{
private:std::string _back_dir;std::string _pack_dir;std::string _pack_suffix;time_t _hot_time;
public:
HotManager();bool HotJudge(const std::string &file);bool RunModule();
};

因为数据管理是要在多个模块中访问的,因此将其作为全局数据定义。

service.hpp

云备份项目中 ,业务处理模块是针对客户端的业务请求进行处理,并最终给与响应。而整个过程中包含以下要实现
的功能:

  1. 借助网络通信模块httplib库搭建http服务器与客户端进行网络通信
  2. 针对收到的请求进行对应的业务处理并进行响应(文件上传,列表查看,文件下载(包含断点续传))

响应给客户端的 rsp在之前的项目日志里也有描述。

class Service{
private:int _server_port;std::string _server_ip;std::string _url_prefix;httplib::Server _srv;
private:static void Upload(const httplib::Request &req, httplib::Response &rsp);static void List(const httplib::Request &req, httplib::Response &rsp);static void Download(const httplib::Request &req,httplib::Response &rsp);
public:Service();bool RunModule();
}

注意:
业务处理的回调函数没有传入参数的地方(大概是因为,回调函数的模板被固定化了),因此无法直接访问外部的数据管理模块数据,因此将数据管理模块的对象定义为全局数据,在这里声明一下,就可以在任意位置访问了,且回调函数必须为静态函数,(类内函数成员变量会隐藏一个this指针)。

文件上传函数和文件列表查看函数都按照思路来写。

文件下载函数有部分事项需要注意:

1 . 服务端要判断是否需要进行断点重传,判断条件:
有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传
也就是说需要在客户端请求里有If-Range字段,且在这段时间内,文件的数据内容没有被修改过。
这是Download 函数正常的响应rsp:

HTTP/1.1 200 OK
Content-Length: 100000
ETags: "filename-size-mtime一个能够唯一标识文件的数据"
Accept-Ranges: bytes

Accept-Ranges报头

服务器使用 HTTP 响应头 Accept-Ranges 标识自身支持范围请求 (partial
requests)。字段的具体值用于定义范围请求的单位。
当浏览器发现Accept-Ranges头时,可以尝试继续中断了的下载,而不是重新开始。

这是Download执行断点续传的rsp:

HTTP/1.1 206 Partial Content
Content-Length:
Content-Range: bytes 89-999/100000
Content-Type: application/octet-stream
ETag: "inode-size-mtime一个能够唯一标识文件的数据"
Accept-Ranges: bytes

httplib内部实现了对于区间请求也就是断点续传请求的处理
只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求
区间,从body中取出指定区间数据进行响应
,并且会自动填充rsp内容。

代码

代码里边会有博主的一些思考和理解,各位大佬见笑了😅

util.hpp

#pragma once#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <experimental/filesystem>
#include "bundle.h"
#include <jsoncpp/json/json.h>namespace Cloud
{namespace fs = std::experimental::filesystem;class FileUtil{private:std::string _Filename;public:FileUtil(std::string fname) : _Filename(fname){}int64_t Filesize() // 提取文件大小{struct stat st;if (stat(_Filename.c_str(), &st) < 0){std::cerr << "get Filesize fail"<< std::endl;return -1;}return st.st_size;}std::string Filename() // 提取文件名{// /a/b/文件名size_t pos = _Filename.find_last_of("/");if (pos == std::string::npos){return _Filename;}return _Filename.substr(pos + 1);}time_t LastMtime() // 提取文件最后一次的修改时间(文件内容){struct stat st;if (stat(_Filename.c_str(), &st) < 0){std::cerr << "get File LastMtime fail\n"<< std::endl;return -1;}return st.st_mtime;}time_t LastAtime() // 提取文件最后一次的访问时间{struct stat st;if (stat(_Filename.c_str(), &st) < 0){std::cerr << "get File LastAtime fail\n"<< std::endl;return -1;}return st.st_atime;}time_t LastCtime() // 提取文件最后一次的修改时间(文件内容 || 文件属性){struct stat st;if (stat(_Filename.c_str(), &st) < 0){std::cerr << "get File LastCtime fail\n"<< std::endl;return -1;}return st.st_ctime;}bool Remove(){remove(_Filename.c_str());return true;}bool GetPosLen(std::string &body, size_t pos, size_t len){size_t fsize = this->Filesize();if (pos + len > fsize){std::cout << "get file len is error\n";return false;}std::ifstream ifs;ifs.open(_Filename, std::ios::binary);if (ifs.is_open() == false){std::cout << "read open file failed!\n";return false;}ifs.seekg(pos, std::ios::beg);body.resize(len);ifs.read(&body[0], len);if (ifs.good() == false){std::cout << "get file content failed\n";ifs.close();return false;}ifs.close();return true;}bool GetContent(std::string &body){size_t fsize = this->Filesize();return GetPosLen(body, 0, fsize);}bool SetContent(const std::string &body){std::ofstream ofs;ofs.open(_Filename, std::ios::binary);if (ofs.is_open() == false){std::cout << "write open file failed!\n";return false;}ofs.write(&body[0], body.size());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){// 1. 获取源文件数据std::string body;if (this->GetContent(body) == false){std::cout << "compress get file content failed!\n";return false;}// 2. 对数据进行压缩std::string packed = bundle::pack(bundle::LZIP, body);// 3. 将压缩的数据存储到压缩包文件中FileUtil fu(packname);if (fu.SetContent(packed) == false){std::cout << "compress write packed data failed!\n";return false;}return true;}bool UnCompress(const std::string &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 fu(filename);if (fu.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){CreateDirectory();for (auto &p : fs::directory_iterator(_Filename)){if (fs::is_directory(p) == true){continue;}// relative_path 带有路径的文件名arry.push_back(fs::path(p).relative_path().string());}return true;}};class jsonutil{public:static bool Serialize(const Json::Value &root, std::string &str) // 序列化{Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());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;bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err);if (ret == false){std::cout << "parse error: " << err << std::endl;return false;}return true;}};
}

data.hpp

#pragma once#include <unordered_map>
#include <pthread.h>
#include <iostream>
#include "util.hpp"
#include "config.hpp"
#include <string>
#include <stdio.h>// 服务端要管理文件数据,需要  先描述,在组织, 构建一个文件属性结构体,通过这个结构体来管理所有的文件
namespace Cloud
{typedef struct BackupInfo{bool pack_flag; // 文件是否被压缩的标识time_t atime;   // 最后一次查看时间time_t mtime;   // 最后一次修改时间size_t fsize;std::string real_path; // 文件在服务器上的真实存储路径std::string url;       // 客户端访问文件时的请求urlstd::string packpath;  // 压缩包存储路径bool FillBackupInfo(const std::string &realpath){FileUtil ft(realpath);if (ft.Exists() == false){std::cerr << "fill backupinfo:file not exists" << std::endl;return false;}Config *cf = Config::Getinstance();pack_flag = false;atime = ft.LastAtime();mtime = ft.LastMtime();fsize = ft.Filesize();real_path = realpath;// ./backdir/a.txt   ->	  /download/a.txturl = cf->GetDownloadPrefix() + ft.Filename();// ./packdir/a.txt   ->   ./packdir/a.txt.lzpackpath = cf->GetPackDir() + ft.Filename() + cf->GetPackFileSuffix();return true;}} BackupInfo;class DateManager{private:std::string _backup_file;                           // 文件的信息都会以json的格式存放在 一个backup文件里pthread_rwlock_t _rwlock;                           // 对backup文件会存在并发访问的问题     ------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   ????????????????? 不懂为什么有锁std::unordered_map<std::string, BackupInfo> _table; //  以hash结构,url 为key ,BackupInfo 为val ,查找迅速public:// 从文件中读取数据进行初始化    对文件增删查改   对文件的永久化储存DateManager(){// printf("准备Datemanager初始化\n");_backup_file = Config::Getinstance()->GetBackupFile();pthread_rwlock_init(&_rwlock, nullptr);initload();// std::cout<<"初始化成功"<<std::endl;}bool insert(const BackupInfo &bf){//  ?  我自己的思路   --------- 传入一个 filename ,然后在insert函数中,自己填充BackupInfo数据 ,在插入进_table中  ----》 可以减少上层的工作量 (我觉得)pthread_rwlock_wrlock(&_rwlock);std::string url = bf.url;_table[url] = bf;pthread_rwlock_unlock(&_rwlock);Storage();return true;}bool update(const BackupInfo &bf){//   ?  问题同 insert函数pthread_rwlock_wrlock(&_rwlock);std::string url = bf.url;_table[url] = bf;pthread_rwlock_unlock(&_rwlock);Storage();return true;}bool GetOneByURL(const std::string &url, BackupInfo *info){pthread_rwlock_rdlock(&_rwlock);auto it = _table.find(url);if (it != _table.end()){*info = it->second;}else{pthread_rwlock_unlock(&_rwlock);return false;}pthread_rwlock_unlock(&_rwlock);return true;}bool GetOneByRealpath(const std::string &realpath, BackupInfo *info){// std::cout<<"准备拿锁"<<std::endl;pthread_rwlock_rdlock(&_rwlock);// std::cout<<"拿到锁了"<<std::endl;std::unordered_map<std::string, BackupInfo>::iterator it = _table.begin();// std::cout<<"找到初始位置了"<<std::endl;for (; it != _table.end(); ++it){if (it->second.real_path == realpath){*info = it->second;pthread_rwlock_unlock(&_rwlock);return true;}}pthread_rwlock_unlock(&_rwlock);return false;}void GetAll(std::vector<BackupInfo> *arry){pthread_rwlock_wrlock(&_rwlock);for (auto it = _table.begin(); it != _table.end(); ++it){arry->push_back(it->second);}pthread_rwlock_unlock(&_rwlock);}bool Storage() // 每次有信息改变则需要持久化存储一次{// 1. 获取所有数据std::vector<BackupInfo> arry;this->GetAll(&arry);// 2. 添加到Json::ValueJson::Value root;for (int i = 0; i < arry.size(); i++){Json::Value item;item["pack_flag"] = arry[i].pack_flag;item["fsize"] = (Json::Int64)arry[i].fsize;item["atime"] = (Json::Int64)arry[i].atime;item["mtime"] = (Json::Int64)arry[i].mtime;item["real_path"] = arry[i].real_path;item["packpath"] = arry[i].packpath;item["url"] = arry[i].url;root.append(item); // 添加数组元素}// 3. 对Json::Value序列化std::string body;jsonutil::Serialize(root, body);// 4. 写文件FileUtil fu(_backup_file);fu.SetContent(body);return true;}bool initload() 初始化程序运行时从文件读取数据      ------->  为什么不从备份目录中提取数据?  --- 备份目录下的文件会被压缩至压缩目录{// 1. 从文件中读取数据// printf("准备读数据\n");FileUtil ft(_backup_file);// printf("读数据成功\n");if (ft.Exists() == false) // 如果文件不存在,说明还没有数据存入数据文件,也就是还没有创建数据文件{//  printf("文件不存在\n");return true;}std::string str;// printf("准备获得文\n");ft.GetContent(str);//  printf("获得文成功\n");// 2. 将数据反序列化//  printf("准备序列化\n");Json::Value root;jsonutil::UnSerialize(str, root);// printf("反序列化成功\n");// 3. 将数据插入_table// printf("准备插入数据:%d\n",root.size());for (int i = 0; i < root.size(); i++){// std::cout<<"开始插入数据"<<std::endl;BackupInfo info;info.pack_flag = root[i]["pack_flag"].asBool();info.fsize = root[i]["fsize"].asInt64();info.atime = root[i]["atime"].asInt64();info.mtime = root[i]["mtime"].asInt64();info.packpath = root[i]["packpath"].asString();info.real_path = root[i]["real_path"].asString();info.url = root[i]["url"].asString();// std::cout<<"插入:"<<info.url<<std::endl;insert(info);}return true;}~DateManager(){pthread_rwlock_destroy(&_rwlock);}};}

hot.hpp

#pragma once#include <unistd.h>
#include "data.hpp"
#include <iostream>extern Cloud::DateManager *_data;namespace Cloud
{class HotManager{private:std::string _back_dir;std::string _pack_dir;std::string _pack_suffix;int _hot_time;public:HotManager(){Config *cng = Config::Getinstance();_back_dir = cng->GetBackDir();_pack_dir = cng->GetPackDir();_pack_suffix = cng->GetPackFileSuffix();_hot_time = cng->GetHotTime();FileUtil tmp1(_back_dir);FileUtil tmp2(_pack_dir);tmp1.CreateDirectory();tmp2.CreateDirectory();}bool HotJudge(const std::string &filename) // 返回true 说明为非热点文件{FileUtil fu(filename);time_t curtime = time(NULL);if (curtime - fu.LastAtime() > _hot_time)return true;return false;}void RunModel() // 不断循环检测 back_dir 目录下的文件 ,进行热点管理{while (true){// 1. 遍历备份目录,获取所有文件名FileUtil fu(_back_dir);std::vector<std::string> arry;fu.ScanDirectory(arry);// std::cout<<"准备判断是否为热点文件"<<std::endl;//,没什么问题//  2. 判断文件是否为热点文件// std::cout<<arry.size()<<std::endl;for (const auto &it : arry){// std::cout<<"开始遍历判断是否为热点文件"<<std::endl;// std::cout<<it<<std::endl;if (HotJudge(it) == false){// std::cout<<"不是热点文件"<<std::endl;continue;}// 获取文件的备份信息BackupInfo info;//  std::cout<<"准备执行HOT里的GetonebyRealpath"<<std::endl;if (_data->GetOneByRealpath(it, &info) == false){//     std::cout<<"准备执行fillBackupInfo"<<std::endl;info.FillBackupInfo(it);}// 3. 对非热点文件进行压缩FileUtil tmp(it);tmp.Compress(info.packpath);// 4. 删除源文件,修改备份信息tmp.Remove();info.pack_flag = true;_data->update(info);}// std::cout<<"准备进入睡眠"<<std::endl;usleep(1000);}}};
}

service.hpp

#pragma once#include <errno.h>
#include <string>
#include "data.hpp"
#include "hot.hpp"
#include "httplib.h"// 服务端构建服务器, 为客户端提供  上传文件(upload)   下载文件(get)         文件列表查看()三个req
// 并对客户端 进行响应              响应上传成功         响应下载的文件数据    响应一个展示文件备份列表的前端页面extern Cloud::DateManager *_data;namespace Cloud
{std::string totimestring(const time_t &tm){struct tm *tmp = localtime(&tm);char buffer[1024];snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_yday,tmp->tm_hour, tmp->tm_min, tmp->tm_sec);return buffer;}class Service{private:std::string _server_ip;uint16_t _server_port;std::string _download_prefix; // 我自己粗略认为这是充当客户端请求下载文件时url中的前一部分httplib::Server _server;public:Service(){printf("Service 开始初始化\n");Config *config = Config::Getinstance();_server_ip = config->GetServerIp();_server_port = config->GetServerPort();_download_prefix = config->GetDownloadPrefix();printf("ser 初始化成功\n");}void RunModule(){printf("Server RunModulem 开始\n");_server.Post("/Upload", Upload);_server.Get("/", Showlist);_server.Get("/Showlist", Showlist);// 下载文件需要匹配具体的文件名,需要借用正则表达式_server.Get(_download_prefix + ".*", Download);printf("server 开始listen\n");std::cout << _server_ip << "::" << _server_port << std::endl;if (_server.listen(_server_ip.c_str(), _server_port) == false){std::cout << "listen error" << errno << std::strerror(errno) << std::endl;}}private:static void Upload(const httplib::Request &req, httplib::Response &rsp) // 上传文件数据{// 1. 对req进行反序列化(httplib已经帮我们做过了) ,提取数据printf("收到一个upload请求\n");auto ret = req.has_file("file"); // 判断req请求中是否包含 上传的文件字段if (ret == false){rsp.status = 400;return;}// 2. 拿到文件名,拿到文件数据const auto &file = req.get_file_value("file");std::string filename = file.filename;std::string filecontent = file.content;// 3. 将其保存至 backdir目录下 ,std::string backdir = Config::Getinstance()->GetBackDir();std::cout << "backfilename:" << backdir + filename << std::endl;FileUtil fu(backdir + filename);fu.SetContent(filecontent);// 修该组织文件备份的管理信息BackupInfo info;info.FillBackupInfo(backdir + filename);_data->insert(info);// 4. 同时填充rsprsp.status = 200;printf("upload 完成\n");return;}// 唯一标识符 filename-filesize-lastmtimestatic std::string GetETag(const BackupInfo &info){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)// {//   printf("收到一个Download请求\n");//   // 1. 从req中提取url,通过url找到 获取文件备份信息//   BackupInfo info;//   _data->GetOneByURL(req.path, &info);//   std::cout << req.path << std::endl;//   std::cout << info.packpath << ":" << info.real_path << std::endl;//   std::cout << info.pack_flag << std::endl;//   printf("提取到info信息\n");//   // 3. 判断是否被压缩//   if (info.pack_flag == true)//   {//     printf("在解压缩文件\n");//     // 4. 如果被压缩,需要进行解压缩,同时修改备份信息//     FileUtil fu(info.packpath);//     fu.UnCompress(info.real_path);//     fu.Remove();//     info.pack_flag = false;//     _data->update(info);//     printf("解压缩完成\n");//   }//   bool retrans = false;//   std::string old_etag;//   if (req.has_header("If-Range") == true)//   {//     old_etag = req.get_header_value("If-Range");//     // 有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传//     if (old_etag == GetETag(info))//     {//       retrans = true;//     }//   }//   printf("retrans:%d\n", retrans);//   // 5. 填充rsp 设置响应头部字段: ETag, Accept-Ranges: bytes//   printf("填充rsp中\n");//   FileUtil fu(info.real_path);//   if (retrans == false)//   {//     fu.GetContent(rsp.body);//     rsp.set_header("Accept-Ranges", "bytes");                   // 告诉客户端支持断点重传功能//     rsp.set_header("ETag", GetETag(info));                      // etag 是一个标识文件的数据//     rsp.set_header("Content-Type", "application/octet-stream"); //  告诉客户实际返回的内容的内容类型//     rsp.status = 200;//   }//   else//   {//     // 需要进行断点续传//     // httplib内部实现了对于区间请求也就是断点续传请求的处理//     // 只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求//     // 区间,从body中取出指定区间数据进行响应//     //   也就是说,下边的代码可以省略,但是我们需要知道httplib 库给我做了什么工作//     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;//   }//   printf("Download 请求完毕\n");//   return;// }static void Download(const httplib::Request &req, httplib::Response &rsp){// 1. 获取客户端请求的资源路径path   req.path// 2. 根据资源路径,获取文件备份信息printf("收到一个Download请求\n");BackupInfo info;_data->GetOneByURL(req.path, &info);// 3. 判断文件是否被压缩,如果被压缩,要先解压缩,if (info.pack_flag == true){FileUtil fu(info.packpath);fu.UnCompress(info.real_path); // 将文件解压到备份目录下// 4. 删除压缩包,修改备份信息(已经没有被压缩)fu.Remove();info.pack_flag = false;_data->update(info);}bool retrans = false;std::string old_etag;if (req.has_header("If-Range")){old_etag = req.get_header_value("If-Range");// 有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传if (old_etag == GetETag(info)){retrans = true;}}printf("retrans:%d\n", retrans);// 4. 读取文件数据,放入rsp.body中FileUtil fu(info.real_path);if (retrans == false){fu.GetContent(rsp.body);// 5. 设置响应头部字段: ETag, Accept-Ranges: bytesrsp.set_header("Accept-Ranges", "bytes");rsp.set_header("ETag", GetETag(info));rsp.set_header("Content-Type", "application/octet-stream");rsp.status = 200;}else{// httplib内部实现了对于区间请求也就是断点续传请求的处理// 只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求// 区间,从body中取出指定区间数据进行响应//  std::string  range = req.get_header_val("Range"); bytes=start-endfu.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*****}printf("Download 请求结束\n");}static void Showlist(const httplib::Request &req, httplib::Response &rsp){//  1. 获取所有的文件备份信息printf("收到一个showlist请求\n");std::vector<BackupInfo> arry;_data->GetAll(&arry);//std::cout << "文件信息准备完毕,size:" << arry.size() << std::endl;//  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'>" << totimestring(a.atime) << "</td>";ss << "<td align='right'>" << a.fsize / 1024 << "k"<< "</td>";ss << "</tr>";}ss << "</table></body></html>";//std::cout << "文件信息填充完毕,开始填写rsq" << std::endl;//  3. 填充rsp响应rsp.body = ss.str();rsp.status = 200;rsp.set_header("Content-Type", "text/html");printf("showlist请求完毕\n");return;}};}

相关文章:

【云备份项目两万字总结】服务端篇 -----附源码

项目总结 整体回顾逐步实现utill.hppconfig.hppdata.hpphot.hppservice.hpp 代码 整体回顾 服务端的目标是&#xff1a; 对客户端的请求进行处理管理客户端上传的文件 于客户端进行数据交换&#xff0c;我们需要引入网络&#xff0c;所以我们引入第三方库----httplib.h库&am…...

蓝眼开源云盘部署全过程(手动安装)

环境概述&#xff1a; 系统-Centos7.4 数据库-MySQL8 云盘系统-Tank4.0.1 前提&#xff1a;操作系统已完成安装&#xff0c;有外部网络。 一.安装数据库 cd到合适的目录进行下载安装操作&#xff0c;期间不要切换出去。 wget https://dev.mysql.com/get/mysql80-community-r…...

aliyun Rest ful api V3版本身份验证构造

aliyun Rest ful api V3版本身份验证构造 参考官网&#xff1a;https://help.aliyun.com/zh/sdk/product-overview/v3-request-structure-and-signature?spma2c4g.11186623.0.0.787951e7lHcjZb 构造代码 &#xff1a;使用GET请求进行构造&#xff0c;算法使用sha256 使用postm…...

windows10上使用Visual Studio对树莓派进行交叉编译示例

本文主要介绍通过Visual Studio对树莓派进行交叉编译的方法。 1 环境 宿主机&#xff1a; 系统&#xff1a;Windows10 开发平台&#xff1a;Visual Studio 2022 (我用的是社区版) VisualGDB: VisualGDB - Download (我下的试用版本) GNU工具链: Prebuilt GNU toolchain f…...

flutter开发web应用支持浏览器跨域设置

开发web应用难免会遇到跨域问题&#xff0c;所以flutter设置允许web跨域的设置是要在你的flutter安装路径下面 flutter\bin\cache 找到flutter_tools.stamp文件&#xff0c;然后删除掉&#xff1a;这个文件是临时缓存文件 然后找到 flutter\packages\flutter_tools\lib\src\web…...

C#调用C++动态库接口函数和回调函数方法

这篇文章主要介绍了C#调用C动态库接口函数和回调函数方法&#xff0c;通过C端编写接口展开内容&#xff0c;文章介绍详细具有一定的参考价值&#xff0c;需要的小伙伴可以参考一下 需求: 当前C已经写好了一个动态库&#xff0c;完成了产品开发需求&#xff0c;C#需要调用C编写…...

3D造型渲染软件DAZ Studio mac中文版介绍

DAZ Studio mac是一款3D造型和渲染软件&#xff0c;由 Daz 3D 公司开发。它允许用户创建、编辑、动画化并渲染精美的数字图像与动画。DAZ Studio 还提供了一个虚拟的3D艺术家工作室环境&#xff0c;让用户可以轻松地设置场景、布置角色和应用材质。 用户可以通过 DAZ Studio 中…...

破解tomcat密码并上传webshell

tomcat基础认证爆破 暴力破解 进入vulnhub的tomcat8目录&#xff0c;启动环境 由于tomcat密码默认最大尝试错误次数为5次&#xff0c;需要修改server.xml&#xff0c;修改下面字段 failureCount"10000000000" lockOutTime"0"tomcat默认界面&#xff0c;…...

Java 8 Stream 的使用场景

Java 8 Stream 的使用场景 只符合所筛选条件至多一条 CommonArea l common.stream().filter(item ->item.getName().equals("aa")).findAny().orElse(null);返回多条记录 List<Object> list common.stream().filter(item -> item.getName().equals(&…...

图片转换到PDF

把一系列图片整合到PDF格式 Python代码 import os from io import BytesIO from PIL import Imageos.environ[NLS_LANG] SIMPLIFIED CHINESE_CHINA.UTF8 SUPPORT_SUFFIX ["jpg", "jpeg", "png"]def pic_to_pdf(image_bytes: bytes) -> byt…...

代码模版-实现重置按钮清空表单数据,vue+elementUI

文章目录 界面代码 界面 页面上可能会有「搜索」按钮 也会有「重置」按钮 重置 btn 的作用是为了清空前面 form 表单中的数据 代码 我们使用 elementUI vue 来做 解释&#xff1a;我们在 el-form 组件中加上 ref"searchFormRef"&#xff0c;后续 js 中通过 thi…...

人格障碍在线测试,人格障碍筛查和判断 PDQ-4+

每个人都是独一无二的&#xff0c;每个人都存在人格上的偏差&#xff0c;日常生活中我们携带着自己的人格在忙碌&#xff0c;在不够成对学习、生活和工作的影响下&#xff0c;我们认为都是健康的人格&#xff0c;反之则属于人格障碍。 人格障碍给我们的日常生活带来极大的影响…...

redis相关文章汇总

一、redis结构 1-redis-功能分类 跳转 redis数据结构对比跳转 以下链接忽略&#xff1a; 01-Redis数据结构-汇总跳转 02-Redis数据结构-List跳转 03-Redis数据结构-dict跳转 二、redis问题攻克难点 缓存穿透、缓存雪崩、缓存击穿区别和解决方案跳转 DB和缓存一致性的问题…...

安防监控展示预约小程序的作用如何

监控在生活中的用途非常广泛&#xff0c;普遍应用于小区门户、商业大厦、产业基地、家庭、汽车等场景中&#xff0c;市场需求较大&#xff0c;同时随着科技发展&#xff0c;安防监控产品更新迭代也比较快&#xff0c;衍生出的经销店、安装技术工等产业近些年也比较火。 安防监…...

(Matalb回归预测)WOA-BP鲸鱼算法优化BP神经网络的多维回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matalb平台编译&#xff0c;将WOA(鲸鱼算法)与BP神…...

某头部通信企业:SDLC+模糊测试,保障数实融合安全发展

某头部通信企业是全球领先的综合通信信息解决方案提供商&#xff0c;为全球电信运营商、政企客户和消费者提供创新的技术与产品解决方案。该企业持续关注核心技术攻关&#xff0c;深入打造系列化标杆项目和价值场景&#xff0c;加强数字化平台的推广应用&#xff0c;加快共建开…...

【fbtft】如何添加fbtft驱动

获取lcd ic的datasheet&#xff0c;或者直接找到其他平台&#xff08;linux&#xff0c;stm32&#xff0c;esp32&#xff09;的驱动 我用的是合宙的esp32驱动&#xff0c;注意是c语言的&#xff0c;合宙上层用lua封装了&#xff0c;需要找到sdk源码。 源码路径&#xff1a; …...

【2023云栖】郭瑞杰:阿里云搜索产品智能化升级

本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;郭瑞杰 | 阿里云资深技术专家、搜索负责人 演讲主题&#xff1a;阿里云搜索产品智能化升级发布 近日在2023云栖大会上&#xff0c;阿里云搜索负责人郭瑞杰对阿里云搜索产品智…...

数据库事务相关问题

1. 什么是数据库事务&#xff1f; 事务&#xff0c;由一个有限的数据库操作序列构成&#xff0c;这些操作要么全部执行,要么全部不执行&#xff0c;是一个不可分割的工作单位。 假如A转账给B 100 元&#xff0c;先从A的账户里扣除 100 元&#xff0c;再在 B 的账户上加上 100 …...

Digicert证书:您的网络安全守护神

在当今数字化的世界中&#xff0c;网络安全已经成为每一个企业和个人必须面对的问题。而Digicert品牌证书&#xff0c;就是您网络安全的最佳选择。它不仅具有强大的安全性和稳定性&#xff0c;还能广泛应用于各种场景&#xff0c;为您提供全方位的保护。 首先&#xff0c;我们要…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...