项目实现:云备份③(配置文件加载模块、数据管理模块的实现)

云备份

前言

书接上回:云备份(实用工具类)实现后,接下来会逐步实现不同模块的功能。会频繁用到工具类,如果不熟悉的老铁还是先看看前面的内容,对接下来要讲解的模块实现会有比较好理解。

配置文件加载模块

配置信息的设计

  1. 热点时间判断:设定一个合适的时间,决定在这个时间过后的文件是非热点文件。对于非热点文件进行压缩存储。
  2. 文件下载 URL 前缀路径:通常这个路径是用来判断用户的下载请求,可能是下载文件请求,也可能是查看备份文件列表的请求。
  3. 压缩文件的后缀名 :在这里默认将压缩后的文件后缀设置为 .lz
  4. 上传文件的存放路径:决定了用户上传文件的存储到服务器的哪里。
  5. 压缩包存放位置:对于非热点文件、压缩包 和 热点文件、普通文件要区分存储。决定了非热点文件、压缩文件的存储位置。
  6. 服务器备份信息存储到文件:记录备份文件信息,保证数据的持久化
  7. 服务器的监听IP、监听端口:当客户端运行到其他主机时,不再需要修改程序

单例文件配置类设计

这里的单例模式采用懒汉模式,对饿汉、懒汉单例模式的实现可以参考小编的这篇文章:特殊类设计

单例文件配置类是用来配置信息的,其中主要实现以下几个功能:

  • GetHotTime:提供热点判断时间
  • GetServerPort:提供服务器监听端口
  • GetServerIP:提供服务器监听IP
  • GetDownloadPrefix:提供下载的 URL 前缀路径
  • GetPackFileSuffix:提供压缩包后缀名
  • GetBackDir:提供备份文件的存放目录
  • GetPackDir:提供压缩包存放的目录
  • GetBackupFile:提供信息存储的文件

下面开始进行代码的编写。

  1. 首先创建 cloud.conf 配置文件,编写配置信息。使用的是Json格式进行编写:
bash 复制代码
{
	"hot_time" : 30,
	"server_port" : 9090,
	"server_ip" : "xxx.xxx.xxx.xxx",
	"download_prefix" : "/download/",
	"packfile_suffix" : ".lz",
	"pack_dir" : "./packdir/",
	"back_dir" : "./backdir/",
	"backup_file" : "./cloud.dat"
}

这里的服务器 IP 设置按照个人需求进行改写,当然端口号也是。

  1. 单例文件配置类的实现,创建 config.hpp 文件进行代码的编写:
cpp 复制代码
#ifndef CLOUD_HPP
#define CLOUD_HPP

#include <mutex>
#include "util.hpp"

namespace cloud
{
#define CONFIG_FILE "./cloud.conf"

    class Config
    {
    public:
        // 获取单例对象指针
        static Config *GetInstance()
        {
            // 双检查加锁
            if (_instance == nullptr)
            {
                // 加锁
                _mutex.lock();
                if (_instance == nullptr)
                {
                    return _instance = new Config();
                }
                // 解锁
                _mutex.unlock();
            }
        }

        //提供热点判断时间
        int GetHotTime() { return _hot_time; }
        //提供服务器监听端口
        int GetServerPort() { return _server_port; }
        //提供服务器监听IP
        std::string GetServerIP() { return _server_ip; }
        //提供下载的URL前缀路径 
        std::string GetDownloadPrefix() { return _download_prefix; }
        //提供压缩包后缀名
        std::string GetPackFileSuffix() { return _packfile_suffix; }
        //提供备份文件的存放目录
        std::string GetBackDir() { return _back_dir; }
        //提供压缩包存放的目录
        std::string GetPackDir() { return _pack_dir; }
        //提供信息存储的文件
        std::string GetBackupFile() { return _backup_file; }

    private:
        // 获取cloud.conf文件信息,进行初始化
        bool ReadConfigFile()
        {
            cloud::FileUtil fu(CONFIG_FILE);
            std::string body;
            // 提取文件信息
            if (fu.GetContent(&body) == false)
            {
                std::cout << "load 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();
            _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;
        }
        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;     // 提供信息存储的文件

    private:
        Config()
        {
            ReadConfigFile();
        }
        // 防止单例的拷贝与赋值
        Config(const Config &config) = delete;
        Config &operator=(const Config &config) = delete;
        static Config *_instance;
        static std::mutex _mutex;
    };
    // 静态成员的初始化:类外进行
    Config *Config::_instance = nullptr;
    std::mutex Config::_mutex;
}
#endif

测试代码:

cpp 复制代码
#pragma once
#include "util.hpp"
#include "config.hpp"
void ConfigTest()
{
    // 实例化单例
    cloud::Config *config = cloud::Config::GetInstance();

    std::cout << config->GetHotTime() << std::endl;
    std::cout << config->GetServerPort() << std::endl;
    std::cout << config->GetServerIP() << std::endl;
    std::cout << config->GetDownloadPrefix() << std::endl;
    std::cout << config->GetPackFileSuffix() << std::endl;
    std::cout << config->GetBackDir() << std::endl;
    std::cout << config->GetPackDir() << std::endl;
    std::cout << config->GetBackupFile() << std::endl;
}
int main(int argc, char *argv[])
{
    ConfigTest();
    return 0;
}

实现效果:

可以看到,这里将 cloud.conf 文件内容进行了正常输出,初始化也没有问题。

数据管理模块

好的管理模式,在后期可以帮助我们去更好的处理大量的数据。

为了后期更好的管理数据,并且使用这些数据,首先要对文件的以下这些数据信息进行封装管理:

  1. 文件的实际存储路径:客户端要下载文件时,从这个文件中读取数据进行响应。
  2. 文件压缩包存放路径:对于非热点文件会被压缩处理,压缩包的路径也是需要管理的。当用户需要这个文件时,要先对压缩包进行解压,让后再读取响应的内容。
  3. 文件是否压缩的标志位:用于判断文件是否压缩
  4. 文件大小
  5. 文件最后一次访问时间
  6. 文件最后一次修改时间
  7. 文件访问URL中的资源路径path:/download/a.txt

数据信息类设计

在 src 文件下创建 data.hpp 文件,对 BackupInfo 数据信息类进行代码编写。

  • 实现 BackupInfo 数据信息类

BackupInfo 主要功能是对数据信息进行封装,实现如下:

c++ 复制代码
#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;       // URL中的资源路径path

        //初始化信息
        bool NewBackupInfo(const std::string& realpath)
        {
            FileUtil fu(realpath);
            if(fu.Exists() == false)
            {
                std::cout << "new backupbnfo : file is not exists!\n";
                return false;
            }
            Config* config = Config::GetInstance();
            //获取配置文件信息
            std::string packdir = config->GetPackDir();
            std::string packsuffix = config->GetPackFileSuffix(); //获取压缩文件后缀
            std::string download_prefix = config->GetDownloadPrefix(); //文件下载路径

            pack_flag = false; //初始设置为普通文件
            //赋值处理
            fsize = fu.FileSize(); 
            mtime = fu.LastMTime();
            atime = fu.LastATime();
            real_path =  realpath; //文件实际所在的路径
            //./backdir/a.txt -> ./packdir/a.txt.lz
            pack_path = packdir + fu.FileName() + packsuffix; //压缩包路径
            //./backdir/a.txt -> /download/a.txt
            url = download_prefix + fu.FileName(); //文件下载路径
            return true;
        }
    };
}

测试代码:

c++ 复制代码
#include "data.hpp"

void DataTest(const std::string &filename)
{
    cloud::BackupInfo info;
    info.NewBackupInfo(filename);
	//输出BackupInfo数据信息
    std::cout << info.pack_flag << std::endl;
    std::cout << info.fsize << std::endl;
    std::cout << info.mtime << std::endl;
    std::cout << info.atime << std::endl;
    std::cout << info.real_path << std::endl;
    std::cout << info.pack_path << std::endl;
    std::cout << info.url << std::endl;
}
int main(int argc, char *argv[])
{
    DataTest(argv[1]);
    return 0;
}

这里直接拿 src 当前目录下的文件进行测试,实现效果:

数据管理类实现

为了更快的访问数据信息,实现一个数据管理类。

在这里采用 hash 表在内存中管理数据;以 URL 的 path 路径作为key值;拿数据信息类 BackupInfo 对象作为为 val值。

其中,DataManager 数据管理类主要实现以下几个功能:

  1. Insert:支持插入新的数据信息
  2. Update:支持更新数据信息
  3. GetOneByUrl:通过URL资源路径查找对应文件的数据信息
  4. GetOneByRealPath:通过文件路径查找对应的数据信息
  5. GetAll:获取所有的文件数据信息
  • 实现 DataManager 数据管理类

在实现前要讲一点:由于数据可以被多人访问,但是对于写只能允许一个人进行操作。

由此要引入一个 pthread_rwlock_t 读写锁

  • 读写锁 允许多个线程同时读取共享资源,但只允许一个线程进行写操作

下面是 DataManager 类的实现代码:

cpp 复制代码
#include "config.hpp"
#include <unordered_map>
#include <pthread.h>

class DataManager
{
public:
    DataManager()
    {
        // 初始化读写锁
        pthread_rwlock_init(&_rwlock, nullptr);
        // 将拷贝文件的信息进行初始化
        _backup_file = Config::GetInstance()->GetBackupFile();
    }

    ~DataManager()
    {
        // 销毁读写锁
        pthread_rwlock_destroy(&_rwlock);
    }
    // 插入数据信息
    bool Insert(const BackupInfo &info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁
        _table[info.url] = info;         // 数据不存在直接进行插入
        pthread_rwlock_unlock(&_rwlock); // 解锁

        return true;
    }
    // 更新数据信息
    bool UpDate(const BackupInfo &info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁
        _table[info.url] = info;         // 数据不存在直接进行插入,否则更新对应的内容
        pthread_rwlock_unlock(&_rwlock); // 解锁
        return true;
    }
    // 通过URL的文件资源路径,获取对应的备份文件的数据信息
    bool GetOneByUrl(const std::string &url, BackupInfo *info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁

        auto it = _table.find(url);
        if (it == _table.end())
        {
            // 没有找到
            pthread_rwlock_unlock(&_rwlock); // 解锁
            return false;
        }

        // 找到了
        *info = it->second;              // 迭代器的second就是val值
        pthread_rwlock_unlock(&_rwlock); // 解锁

        return true;
    }
    // 通过文件路径查找备份文件的数据信息
    bool GetOneByRealPath(const std::string &realpath, BackupInfo *info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁

        // 遍历整个_table
        auto it = _table.begin();
        while (it != _table.end())
        {
            if (it->second.real_path == realpath)
            {
                // 文件数据信息匹配到了
                *info = it->second;
                pthread_rwlock_unlock(&_rwlock); // 解锁
                return true;
            }
            ++it;
        }

        // 没有找到
        pthread_rwlock_unlock(&_rwlock); // 解锁
        return false;
    }
    //获取整个数据信息
    bool GetAll(std::vector<BackupInfo> *array) //输出型参数
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁

        //遍历整个_table
        auto it = _table.begin();
        while(it != _table.end())
        {
            //_table表中的val值都插入array中
            array->push_back(it->second);
            ++it;
        }
        pthread_rwlock_unlock(&_rwlock); // 解锁
        return true;
    }

private:
    pthread_rwlock_t _rwlock;                           // 读写锁
    std::string _backup_file;                           // 备份文件
    std::unordered_map<std::string, BackupInfo> _table; // 文件路径,数据信息对象
};

测试代码:

cpp 复制代码
#include "data.hpp"

void PrintBackUpInfo(const cloud::BackupInfo &info)
{
    std::cout << info.pack_flag << std::endl;
    std::cout << info.fsize << std::endl;
    std::cout << info.mtime << std::endl;
    std::cout << info.atime << std::endl;
    std::cout << info.real_path << std::endl;
    std::cout << info.pack_path << std::endl;
    std::cout << info.url << std::endl;
}
void DataTest(const std::string &filename)
{
    cloud::BackupInfo info;
    info.NewBackupInfo(filename); // 初始化
    cloud::DataManager data;
    std::cout << "------------------Insert------------------------\n";
    data.Insert(info); // 将备份文件数据信息进行插入

    cloud::BackupInfo tmp;
    data.GetOneByUrl("/download/bundle.h", &tmp); // 查看下载目录下是否存在bundle.h文件
    // 打印对应的数据信息
    PrintBackUpInfo(tmp);
    std::cout << "------------------UpDate------------------------\n";
    // 修改刚刚插入数据的内容
    info.pack_flag = true;
    data.UpDate(info);
    std::vector<cloud::BackupInfo> array;
    data.GetAll(&array);
    for (auto &e : array)
    {
        PrintBackUpInfo(e);
    }
    std::cout << "------------------GetOneByRealPath------------------------\n";
    data.GetOneByRealPath(filename, &tmp);
    PrintBackUpInfo(tmp);
}
int main(int argc, char *argv[])
{
    DataTest(argv[1]);
    return 0;
}

在这里还是以 src 目录下的 bundle.h 文件为测试案例,测试结果如下:

数据持久化存储

为了持久化存储管理,采用Json序列化将所有的数据保存在文件中。

完善 DataManager 类,实现 Storage 成员函数。实现步骤如下:

  1. 获取 _table 中所有数据
  2. 将数据添加到 Json::Value 对象中
  3. 对 Json::Value 对象进行序列化操作
  4. 将序列化数据填写到文件

下面只展示当前功能代码,如下:

cpp 复制代码
class DataManager
{
public:
	//...其他成员函数
	
    // 数据持久化操作
    bool Storage()
    {
        // 1.获取所有的数据信息
        std::vector<BackupInfo> array;
        GetAll(&array);
        // 2.将获取的数据添加到Json::Value对象
        Json::Value root;
        for (int i = 0; i < array.size(); i++)
        {
            Json::Value tmp;
            tmp["pack_flag"] = array[i].pack_flag;
            // Json不认识 time_t、size_t类型,需要强制类型转换
            tmp["fsize"] = (Json::Int64)array[i].fsize;
            tmp["mtime"] = (Json::Int64)array[i].mtime;
            tmp["atime"] = (Json::Int64)array[i].atime;
            tmp["real_path"] = array[i].real_path;
            tmp["pack_path"] = array[i].pack_path;
            tmp["url"] = array[i].url;
            // 整个数据不仅仅只有一份
            root[i].append(tmp);
        }
        // 3.序列化操作
        std::string body;
        if(JsonUtil::Serialize(root, &body) == false)
        {
            std::cout << "Data write failed!\n";
            return false;
        }
        // 4.将数据存储到文件
        FileUtil fu(_backup_file); // 写入到备份文件
        fu.SetContent(body);
        return true;
    }
private:
    pthread_rwlock_t _rwlock;                           // 读写锁
    std::string _backup_file;                           // 备份文件
    std::unordered_map<std::string, BackupInfo> _table; // 文件路径,数据信息对象
};

数据持久化操作,常用于新的数据插入后、数据更新后。

对此,需要对先前的 插入数据、更新数据的函数增添数据持久化功能,下面是 Insert、UpDate 函数修改后的代码:

cpp 复制代码
// 插入数据信息
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)
{
    pthread_rwlock_wrlock(&_rwlock); // 上锁
    _table[info.url] = info;         // 数据不存在直接进行插入,否则更新对应的内容
    pthread_rwlock_unlock(&_rwlock); // 解锁

    //数据持久化
    Storage();
    return true;
}

初始化加载实现

初始化程序运行时,从文件读取数据。

完善 DataManager 类,实现 InitLoad 成员函数。实现步骤如下:

  1. 将数据从文件中读取出来
  2. 反序列化操作
  3. 反序列化得到的数据添加到 _table 中

下面只展示当前功能代码,如下:

cpp 复制代码
class DataManager
{
public:
	//...其他成员函数
	
	//数据初始化加载
    bool InitLoad()
     {
         //1.将数据从文件中读取出来
         FileUtil fu(_backup_file);
         if(fu.Exists() == false)
         {
             //文件不存在
             std::cout << "The back file does not exist\n";
             return false;
         }
         std::string body;
         fu.GetContent(&body);

         //2.反序列化
         Json::Value root;
         if(JsonUtil::UnSerialize(body, &root) == false)
         {
             std::cout << "Deserialization failed\n";
             return false;
         }

         //3.将反序列化的数据添加到_table
         for(int i = 0; i < root.size(); i++)
         {
             BackupInfo info;
             info.pack_flag = root[i]["pack_flag"].asBool();
             info.fsize = root[i]["fsize"].asInt64();
             info.mtime = root[i]["mtime"].asInt64();
             info.atime = root[i]["atime"].asInt64();
             info.pack_path = root[i]["pack_path"].asString();
             info.real_path = root[i]["real_path"].asString();
             info.url = root[i]["url"].asString();

             //将info添加到_table中
             Insert(info);
         }
         return true;
     }
private:
    pthread_rwlock_t _rwlock;                           // 读写锁
    std::string _backup_file;                           // 备份文件
    std::unordered_map<std::string, BackupInfo> _table; // 文件路径,数据信息对象
};

初始化一般都会在构造函数内部进行。实现初始化信息加载后,需要对构造函数进行功能添加:

cpp 复制代码
 DataManager()
 {
     // 初始化读写锁
     pthread_rwlock_init(&_rwlock, nullptr);
     // 将拷贝文件的信息进行初始化
     _backup_file = Config::GetInstance()->GetBackupFile();
     //初始化加载
     InitLoad();
 }

到这里,数据管理模块的功能基本实现。

该篇文章主要实现了云备份项目的两个功能模块,由于篇幅过长,剩下模块的内容会在后续博客中体现。喜欢的老铁可以点赞 + 收藏!你们的关注是我持续更新的动力,感谢大家的观看。

相关推荐
m0_631270401 小时前
标准C++(二)
开发语言·c++·算法
banjin1 小时前
AI驱动TDSQL-C Serverless 数据库技术实战营-ai学生选课系统数据分析
c语言·人工智能·serverless
沫刃起1 小时前
Codeforces Round 972 (Div. 2) C. Lazy Narek
数据结构·c++·算法
DdddJMs__1351 小时前
C语言 | Leetcode C语言题解之第413题等差数列划分
c语言·leetcode·题解
轩轶子1 小时前
【C-项目】网盘(一期,无限进程版)
服务器·c语言·网络
0224号比邻星1 小时前
[C语言]第十节 函数栈帧的创建和销毁一基础知识到高级技巧的全景探索
c语言·开发语言
GZM8888882 小时前
配置VS Code以进行C/C++编程:深入探讨与实操指南
c++
martian6652 小时前
学懂C++(六十):C++ 11、C++ 14、C++ 17、C++ 20新特性大总结(万字详解大全)
开发语言·c++·c++20
S+叮当猫2 小时前
第五部分:2---信号的介绍、产生、处理
linux·运维·服务器
QXH2000002 小时前
Leetcode—环形链表||
c语言·数据结构·算法·leetcode·链表