项目实现:云备份服务端③(热点模块、服务端业务处理模块实现)

前言

依旧是老话题,在开始看前,对整个项目不熟悉的老铁可以看看小编先前的博客:云备份项目介绍,看完之后再回过来当前内容会好很多。

下面不再多说,接下来直接开始进入正题:

热点模块

  • 热点管理模块功能:对服务器上备份的文件进行检测,判断哪些文件长时间没有被访问,长时间没有被访问的文件就是非热点文件

对于非热点文件的处理,就是将原文件进行压缩存储,节省磁盘空间。主要的实现思路如下:

  1. 遍历备份目录,获取所有的文件路径名
  2. 逐个遍历每个文件,获取到文件的最后一次访问时间与当前系统时间进行比较
  3. 对非热点文件进行压缩处理,删除源文件
  4. 修改数据管理模块对应的文件信息,将文件的压缩标志位修改为:true

热点管理类

  • RunModule:对非热点文件进行管理
  • HotJudge:热点文件判断

在 src 目录下创建 hot.hpp 文件,设计HotManager类:

#ifndef __MY_HOT__
#define __MY_HOT__

#include "data.hpp"
#include <unistd.h>

extern cloud::DataManager *_data; //用作全局

namespace cloud
{
    // 热点管理类
    class HotManager
    {
    public:
        HotManager()
        {
            // 从配置文件获取信息
            Config *config = Config::GetInstance();
            _pack_dir = config->GetPackDir();
            _pack_suffix = config->GetPackFileSuffix();
            _back_dir = config->GetBackDir();
            _hot_time = config->GetHotTime();

            // 目录不存在直接创建
            FileUtil tmp1(_pack_dir);
            FileUtil tmp2(_back_dir);
            tmp1.CreateDirectory();
            tmp2.CreateDirectory();
        }

        bool RunModule()
        {
            //实时监测热点文件
            while (1)
            {
                // 1.遍历备份目录,获取所有的文件路径名
                FileUtil fu(_back_dir);
                std::vector<std::string> array;
                // 将获取的文件名存放到array
                fu.ScanDirectory(&array);
                // 2.逐个判断是否为热点文件
                for (auto &e : array)
                {
                    if (HotJudge(e) == false)
                    {
                        // 热点文件不做处理
                        continue;
                    }
                    // 3.获取文件的备份信息
                    BackupInfo bi;
                    // 防止有备份文件的信息没有录入
                    if (_data->GetOneByRealPath(e, &bi) == false)
                    {
                        // 将当前文件信息进行重新设置
                        bi.NewBackupInfo(e);
                    }
                    // 4.对非热点文件进行压缩处理
                    FileUtil tmp(e);
                    tmp.Compress(bi.pack_path); // 压缩+压缩后缀名
                    // 5.删除源文件,修改备份信息
                    tmp.Remove();
                    _data->UpDate(bi); // 更新数据
                }
                //休眠,减少CUP运行
                usleep(1000);
            }
        }
    private:
        // 非热点文件判断:非热点true,热点false
        bool HotJudge(std::string &filename)
        {
            FileUtil fu(filename);
            // 文件最后访问时间
            time_t last_time = fu.LastATime();
            time_t now_time = time(NULL);
            // 当前系统时间 - 最后访问时间
            if (now_time - last_time > _hot_time)
                return true;
            return false;
        }
    private:
        std::string _pack_dir;
        std::string _pack_suffix; // 压缩包后缀名
        std::string _back_dir;
        size_t _hot_time; // 热点时间
    };
}
#endif

业务处理模块

由于云备份项目借用了 httplib 第三方库,网络通信直接通过httplib库完成。将网络通信模块和业务处理进行了合并,这里只需要着重关注如何处理业务处理即可。

业务请求处理包含以下几点:

  1. 文件上传请求:备份客户端上传的文件,响应上传成功
  2. 文件列表请求:客户端浏览器请求一个备份文件的展示页面,响应页面
  3. 文件下载请求:通过展示页面,用户点击下载,响应客户端要下载的文件数据

业务处理类

  • RunModule业务处理函数
  • UpLoad文件上传 请求处理函数
  • ListShow文件总页面展示 请求处理函数
  • DownLoad文件下载 请求处理函数

在 scr 目录下创建 server.hpp 文件,编写业务处理类代码:

#ifndef __My_SERVER__
#define __My_SERVER__

#include "data.hpp"
#include "httplib.h"

extern cloud::DataManager* _data;

namespace cloud
{
    class server
    {
    public:
        server()
        {
            //使用配置文件进行初始化处理
            Config* config = Config::GetInstance();
            _server_port = config->GetServerPort();
            _server_ip = config->GetServerIP();
            _download_prefix = config->GetDownloadPrefix();
        }
        //业务处理函数
        bool RunModule()
        {
            //文件上传请求
            _server.Post("/upload", Upload);

            //文件总页面展示请求
            _server.Get("/listshow", ListShow);
            _server.Get("/", ListShow); //默认请求

            //文件下载请求
            std::string download_url = _download_prefix + "(.*)"; 
            //(.*)是正则表达式;. 点表示任意字符,*表示前面的字符可以出现任意次数,一次或者多次
            _server.Get(download_url.c_str(), Download);

            //捕获客户端连接
            _server.listen(_server_ip.c_str(), _server_port);
            return true;
        }

    private:
        //文件上传请求处理函数
        static void Upload(const httplib::Request &req, httplib::Response &rsp)
        {
            //整个请求报文的正文不全为文件数据
            auto ret = req.has_file("file"); 
            if(ret == false)  //判断有没有上传文件区域
            {
                rsp.status = 400;
                return ;
            }

            const auto& file = req.get_file_value("file"); //获取请求中的文件数据
            //file.filename文件名   file.content文件内容

            //备份文件所在的目录
            std::string back_dir = Config::GetInstance()->GetBackDir();
            //文件路径 == 备份文件所在的目录+文件名
            std::string realpath = back_dir + FileUtil(file.filename).FileName();

            FileUtil fu(realpath);
            //将上传文件的内容写入到realpath中
            fu.SetContent(file.content);

            //数据备份好,将数据管理起来
            BackupInfo info;
            info.NewBackupInfo(realpath); //组织备份文件的信息
            _data->Insert(info);//向数据管理模块插入新的备份文件信息

        }
		//时间戳转特定的时间格式
        static std::string TimeToStr(const time_t t)
        {
            std::string tmp = std::ctime(&t);
            return tmp;
        }
        //文件总页面展示请求处理函数
        static void ListShow(const httplib::Request &req, httplib::Response &rsp)
        {
			//1.获取所有的备份文件信息
            std::vector<BackupInfo> array;
            _data->GetAll(&array);

            //2.根据备份文件的信息,组织html文件格式
            std::stringstream ss;
            ss << "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'>";
            ss << "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
            ss << "<title>文件下载页面</title><style>";
            ss << "body {font-family: Arial, sans-serif;margin: 40px;}";
            ss << ".download-table{width: 100%;border-collapse: collapse; margin-top: 20px;}";
            ss << ".download-table th, .download-table td {border: 1px solid #ddd;padding: 8px;text-align: left;}";
            ss << ".download-table th { background-color: #f2f2f2;}";
            ss << ".download-table .date, .download-table .size {text-align: right;}";
            ss << ".download-link {text-decoration: none;color: #007bff; }";
            ss << ".download-link:hover { text-decoration: underline; }";
            ss << " </style> </head><body><h1>文件下载</h1>";
            ss << "<p>请从下面的列表中选择要下载的文件。</p> <table class='download-table'>";
            ss << "<thead><tr><th>文件名</th><th class='date'>日期</th> <th class='size'>大小</th></tr></thead><tbody>";

            for(auto& a : array)
            {
                ss << "<tr><td>";
                std::string filename = FileUtil(a.real_path).FileName();
                ss << "<a href='" << a.url << "'class='download-link'>" << filename << "</a>";
                ss << "</td><td class='date'>" << TimeToStr(a.mtime) << "</td>"; //显示最后修改时间
                ss << "<td class='size'>" << a.fsize / 1024 << "k</td></tr>"; //文件大小以k显示
            }
            ss << "</tbody></table></body></html>";

            //将刚刚编好的html格式回应给客户端
            rsp.body = ss.str();
            //设置头部内容为html文本
            rsp.set_header("Content-Type", "text/html");
            rsp.status = 200;
        }
        
		//获取唯一标识符:文件名+文件大小+文件最后一次修改时间
        static 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)
        {
			/1.获取客户端请求的资源路径path   req.path
            //2.根据资源路径,获取文件备份信息
            BackupInfo info;
            _data->GetOneByUrl(req.path, &info); //通过资源路径获取备份文件的信息

            //3.文件是否被压缩?是,先进行解压;删除对应的压缩包,修改文件数据信息
            if(info.pack_flag == true)
            {
                FileUtil fu(info.pack_path); //压缩文件所在位置
                //将文件解压到备份文件路径下
                fu.UnCompress(info.real_path);
                fu.Remove();
                //更新数据信息
                info.pack_flag = false;
                _data->UpDate(info);
            }
            //4.读取文件信息,将内容设置到rsp中
            FileUtil fu(info.real_path); //备份文件
            fu.GetContent(&rsp.body);
            //5.设置响应报头字段:ETag、Accept-Ranges:bytes;
            rsp.set_header("Accept-Ranges", "bytes");//通过特定的区间来访问资源
            rsp.set_header("ETag", GetETag(info)); //用于确定文件是否被修改
            rsp.set_header("Content-Type", "application/octet-stream"); //响应的内容被视为任意的二进制数据
            rsp.status = 200;
        }
    private:
        int16_t _server_port;
        std::string _server_ip;
        std::string _download_prefix; // 文件下载资源路径
        httplib::Server _server;
    };
}

#endif

断点续传功能

当文件下载过程中,因为某种异常而中断,如果再次进行从头下载,效率较低。为了提高效率,实现断点续传功能。

  • 功能介绍:断点续传就是从上次下载断开的位置重新下载,之前已经传输过的数据将不需要在重新传输

客户端在下载文件的时候,要每次接收到数据写入文件后记录自己当前下载的数据量。当异常下载中断时,下次断点续传的时候,将要重新下载的数据区间(下载起始位置,结束位置)发送给服务器。服务器收到后,仅仅回传客户端需要的区间数据即可。

实现断点续传需要考虑这样的一个问题:如果上次下载文件之后,这个文件在服务器上被修改了,则这时候将不能重新断点续传,而是应该重新进行文件下载操作

改写文件下载请求处理函数,增加断点续传功能:

 // 文件下载请求处理函数
static void Download(const httplib::Request &req, httplib::Response &rsp)
{
    // 1.获取客户端请求的资源路径path   req.path
    // 2.根据资源路径,获取文件备份信息
    BackupInfo info;
    _data->GetOneByUrl(req.path, &info); // 通过资源路径获取备份文件的信息

    // 3.文件是否被压缩?是,先进行解压;删除对应的压缩包,修改文件数据信息
    if (info.pack_flag == true)
    {
        FileUtil fu(info.pack_path); // 压缩文件所在位置
        // 将文件解压到备份文件路径下
        fu.UnCompress(info.real_path);
        fu.Remove();
        // 更新数据信息
        info.pack_flag = false;
        _data->UpDate(info);
    }
    // 4.读取文件信息,将内容设置到rsp中
    FileUtil fu(info.real_path); // 备份文件
    fu.GetContent(&rsp.body);

    bool retrans = false; // 用于标识是否符合断点续传
    std::string old_etag;
    if (req.has_header("If-Range"))
    {
        old_etag = req.get_header_value("If-Range");
        // 含有If-Range字段,If-Range字段的etag值与请求的文件最新的etag一致,则符合断点续传
        if (old_etag == GetETag(info))
            retrans = true;
    }

    // 5.设置响应报头字段:ETag、Accept-Ranges:bytes;
    if (retrans == false) 
    {
        // 不符合断点续传,直接传整个备份文件
        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.status = 200;
    }
    else // 符合断点续传
    {
        //httplib库内部实现了断点续传的功能
        //在这里只需要将文件的内容读取到body,httplib内部函数会自动处理对应区间数据
        //从body中取出指定区间数据进行响应

        fu.GetContent(&rsp.body);
        rsp.set_header("Accept-Ranges", "bytes"); // 通过特定的区间来访问资源
        rsp.set_header("ETag", GetETag(info));
        rsp.status = 206;
    }
}

服务端整体功能联调

为了能够联动服务端各个模块的功能,下面采取多线程的方式分别执行热点模块、业务处理模块:

#include <thread>

#include "hot.hpp"
#include "server.hpp"

cloud::DataManager* _data;

void HotTask()
{
    cloud::HotManager hot;
    //进行热点文件检测
    hot.RunModule();
}

void ServerTask()
{
    cloud::server srv;
    //启动服务器进行业务处理
    srv.RunModule();
}

int main(int argc, char *argv[])
{
    _data = new cloud::DataManager();

    //热点模块任务
    std::thread thread_Hot(HotTask);
    //业务处理模块
    std::thread thread_Server(ServerTask);

    //等待线程
    thread_Hot.join();
    thread_Server.join();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值