云备份项目--工具类编写

发布于:2024-12-22 ⋅ 阅读:(15) ⋅ 点赞:(0)

4. 文件工具类的设计

4.1 整体的类

该类实现对文件进行操作

FileUtil.hpp如下

/*
该类实现对文件进行操作
*/
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <experimental/filesystem>      // c++17 的文件系统库
#include "bundle.h"

namespace cloud
{
namespace fs = std::experimental::filesystem;

class FileUtil
{
private:
    std::string _fileName;
    struct stat _st;        // 用于获取文件的各种属性
public:
    // 输入一个路径
    FileUtil(std::string fileName) : _fileName(fileName) {
        if(stat(_fileName.c_str(), &_st) < 0) {
            std::cerr << "获取文件属性失败!\nstat(_fileName.c_str(), &_st) error! why: " << strerror(errno) << '\n';
            // exit(1);
        }
    }

    // 获取文件大小(单位是字节)
    int64_t getFileSize()
    {
        return _st.st_size;      // 返回字节数,有符号的长整型
    }

    // 获取文件最后一次修改时间
    time_t getLastMTime()
    {
        return _st.st_mtime;
    }

    // 获取文件最后一次访问时间
    time_t getLastATime()
    {
        return _st.st_atime;
    }

    // 获得一个路径最后文件的名称,例如/abc/a.txt -> a.txt
    std::string getFileName()
    {
        ssize_t pos = _fileName.find_last_of('/');

        if(pos == std::string::npos) {
            // 没有/,证明这就是一个文件,没有文件夹
            return _fileName;
        }

        // 有/,要截取字符串
        return _fileName.substr(pos+1);
    }

    // 从pos位置获取len长度的数据(单位是字节),写到s中
    bool getContentFromPos(std::string *s, size_t pos, size_t len)
    {
        int64_t fileSz = getFileSize();

        if(pos + len > fileSz) {
            // 申请的长度大于文件长度
            std::cerr << "Bad size!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
            return false;
        }

        // 用来读取文件
        std::ifstream ifile(_fileName, std::ios::binary);

        // 是否打开成功
        if(ifile.is_open() == false) {
            std::cerr << "Open file error!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
            ifile.close();
            return false;
        }
        
        // 读取文件内容
        ifile.seekg(pos, std::ios::beg);
        s->resize(len);     
        ifile.read((char*)(s->c_str()), len);

        // 检测这个流的状态是否ok
        if(ifile.good() == false) {
            std::cerr << "read file error!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
            ifile.close();
            return false;
        }

        return true;
    }

    // 获取文件的所有内容,写到s中
    bool getFullContent(std::string *s) 
    {
        // 调用getContentFromPos()
        return getContentFromPos(s, 0, getFileSize());
    }

    // 将字符串s的内容写入到文件中
    bool setContent(const std::string& s)
    {
        // 用来像文件写入
        std::ofstream ofile(_fileName, std::ios::binary);

        // 是否打开成功
        if(ofile.is_open() == false) {
            std::cerr << "Open file error!\nbool setContent(const std::string& s)\n";
            ofile.close();
            return false;
        }

        ofile.write((char*)s.c_str(), s.size());

        // 检测这个流的状态是否ok
        if(ofile.good() == false) {
            std::cerr << "write file error!\nbool setContent(const std::string& s)\n";
            ofile.close();
            return false;
        }

        return true;
    }

    // 压缩,packName是压缩包的名字
    bool compresss(const std::string& packName)
    {
        // 先读取文件内容
        std::string content;
        
        if(getFullContent(&content) == false) {
            std::cerr << "bool compresss(const std::string& packName) get content error!\n";
            return false;
        }

        // 压缩文件内容,这里使用LZIP压缩格式
        std::string packContent = bundle::pack(bundle::LZIP, content);
        cloud::FileUtil newFile(packName);

        // 将内容写到新的文件
        if(newFile.setContent(packContent) == false) {
            std::cerr << "bool compresss(const std::string& packName) set content error!\n";
            return false;
        }

        return true;
    }

    // 解压,fileName是解压后新文件的名字
    bool unCompress(const std::string& fileName)
    {
        // 先读取文件内容
        std::string content;
        
        if(getFullContent(&content) == false) {
            std::cerr << "bool unCompress(const std::string& packName) get content error!\n";
            return false;
        }

        // 解压文件内容
        std::string unpackContent = bundle::unpack(content);
        cloud::FileUtil newFile(fileName);

        // 将内容写到新的文件
        if(newFile.setContent(unpackContent) == false) {
            std::cerr << "bool unCompresss(const std::string& packName) set content error!\n";
            return false;
        }

        return true;
    }

    // 判断文件是否存在,存在返回true
    bool exits()
    {
        return fs::exists(_fileName);
    }

    // 创建目录,创建成功返回true
    bool createDir()
    {
        // 如果该文件存在了,就直接返回true
        if(exits())    return true;
        return fs::create_directories(_fileName);
    }

    // 扫描文件夹下的文件,放到数组中
    bool scanDir(std::vector<std::string> *array)
    {   
        for (const fs::directory_entry& entry : fs::directory_iterator(_fileName)) {
            if(fs::is_directory(entry) == true) {
                // 如果是目录的话,跳过,该函数只扫描一般文件
                continue;
            }

            array->push_back(entry.path().relative_path().string());
        }   
    }
};

}

makefile如下

cloud : Cloud.cpp bundle.cpp FileUtil.hpp
	g++ $^ -o $@ -lpthread -lstdc++fs

.PHONY : clean
clean:
	rm -f cloud

4.2 测试

4.2.1 测试获取文件属性功能

Cloud.cpp

#include "FileUtil.hpp"

// 测试获取文件属性功能
void testFileUtil(const std::string& s)
{
    cloud::FileUtil f(s);
    printf("文件大小: %ld\n", f.getFileSize());
    printf("文件最后一次修改时间: %ld\n", f.getLastMTime());
    printf("文件最后一次访问时间: %ld\n", f.getLastATime());
    printf("获得一个路径最后文件的名称: %s\n", f.getFileName().c_str());
}

int main(int argc, char* argv[])
{
    if(argc != 2) {
        std::cerr << "usage error!\n"; 
        return -1;
    }

    testFileUtil(argv[1]);
    return 0;
}

结果如下

lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud test.txt 
文件大小: 28
文件最后一次修改时间: 1734504144
文件最后一次访问时间: 1734504157
获得一个路径最后文件的名称: test.txt
---------------------------------------------------------------------------
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud ../../lib/bundle/README.md 
文件大小: 16571
文件最后一次修改时间: 1734185453
文件最后一次访问时间: 1734185453
获得一个路径最后文件的名称: README.md
---------------------------------------------------------------------------
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud aaaa.txt
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory

4.2.2 测试文件读写功能

void testFileUtil2(const std::string& fileName)
{
    cloud::FileUtil f(fileName);
    std::string s;
    f.getFullContent(&s);
    // std::cout << s;
    cloud::FileUtil of("writeTest.txt");        // 打开一个名为writeTest.txt的文件,没有则创建
    of.setContent(s);
}

int main(int argc, char* argv[])
{
    if(argc != 2) {
        std::cerr << "usage error!\n"; 
        return -1;
    }

    testFileUtil2(argv[1]);
    return 0;
}
目前有个文件test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ cat test.txt 
abcdefghigklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
---------------------------------------------------------------------------
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud test.txt 
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ll
total 52
drwxrwxr-x  2 lyf lyf  4096 Dec 18 16:20 ./
drwxrwxr-x 23 lyf lyf  4096 Dec 18 11:31 ../
-rwxrwxr-x  1 lyf lyf 24144 Dec 18 16:20 cloud*
-rw-rw-r--  1 lyf lyf   969 Dec 18 16:16 Cloud.cpp
-rw-rw-r--  1 lyf lyf  3516 Dec 18 16:20 FileUtil.hpp
-rw-rw-r--  1 lyf lyf    67 Dec 18 16:14 makefile
-rw-rw-r--  1 lyf lyf    53 Dec 18 16:14 test.txt
-rw-rw-r--  1 lyf lyf    53 Dec 18 16:20 writeTest.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ cat writeTest.txt 
abcdefghigklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ

4.2.3 测试压缩和解压缩功能

lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud bundle.cpp 
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ls -lh
total 15M
-rw-rw-r-- 1 lyf lyf 5.4M Dec 18 19:14 bundle.cpp
-rw-rw-r-- 1 lyf lyf 668K Dec 18 19:34 bundle.cpp.lz
-rw-rw-r-- 1 lyf lyf  29K Dec 18 19:14 bundle.h
-rwxrwxr-x 1 lyf lyf 3.3M Dec 18 19:33 cloud
-rw-rw-r-- 1 lyf lyf 1.3K Dec 18 19:33 Cloud.cpp
-rw-rw-r-- 1 lyf lyf 5.0K Dec 18 19:24 FileUtil.hpp
-rw-rw-r-- 1 lyf lyf  101 Dec 18 19:28 makefile
-rw-rw-r-- 1 lyf lyf 5.4M Dec 18 19:34 newname.cpp
-rw-rw-r-- 1 lyf lyf   23 Dec 18 19:32 test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ md5sum bundle.cpp
4cb64c7a8354c82402dd6fe080703650  bundle.cpp
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ md5sum newname.cpp 
4cb64c7a8354c82402dd6fe080703650  newname.cpp

4.2.4 测试目录功能

// 测试目录操作
void testFileUtil4(const std::string& fileName)
{
    cloud::FileUtil f(fileName);
    f.createDir();      // 创建一个文件夹
    std::vector<std::string> array;
    f.scanDir(&array);
    // 打印目录下的文件内容
    for(const auto& e : array) {
        std::cout << e << '\n';
    }
}
# 获取文件夹下的所有文件
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ls
bundle.cpp  bundle.h  cloud  Cloud.cpp  FileUtil.hpp  makefile  test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud test
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ls
bundle.cpp  bundle.h  cloud  Cloud.cpp  FileUtil.hpp  makefile  test  test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ cd test/
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ ll
total 8
drwxrwxr-x 2 lyf lyf 4096 Dec 18 21:53 ./
drwxrwxr-x 3 lyf lyf 4096 Dec 18 21:53 ../
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch a.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch b.cc
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch c.java
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch d.python
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ cd ../
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud ./test
./test/c.java
./test/d.python
./test/b.cc
./test/a.txt

5. Json工具类的设计

5.1 整体的类

#pragma once
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
#include <sstream>
#include <memory>

namespace cloud
{
class JsonUtil
{
public:
    // 将root的序列化结果保存在str中
    static bool serialize(const Json::Value& root, std::string* str)
    {
        Json::StreamWriterBuilder swb;
        std::unique_ptr<Json::StreamWriter> ps(swb.newStreamWriter());
        std::stringstream ss;

        if (ps->write(root, &ss) != 0) {
            std:: cerr << "write error!\nstatic bool serialize(const Json::Value& root, std::string* str)\n";
            return false;
        } 
        
        *str = ss.str();
        return true;
    }

    // 将str的序列化结果保存在root中
    static bool unserialize(const std::string& str, Json::Value* root)
    {
        Json::CharReaderBuilder crb;
        std::unique_ptr<Json::CharReader> ps(crb.newCharReader());
        
        std::string errs;
        if (ps->parse(str.c_str(), str.c_str() + str.size(), root, &errs) == false) {
            std::cerr << "parse error!, why: " << str  << "\nstatic bool unserialize(const std::string& str, Json::Value* root)\n";
            return false;
        }

        return true;
    }
};
}

5.2 测试序列化工具类

// 测试JsonUtil
void testJsonUtil()
{
     /* 序列化 */
    std::string name = "zhangsan";
    int age = 20;
    int scores[] = {80, 90, 100};
    // 给数据对象类添加数据
    Json::Value value;
    value["name"] = name;
    value["age"] = age;
    value["score"].append(scores[0]);
    value["score"].append(scores[1]);
    value["score"].append(scores[2]);
    std::string str;
    cloud::JsonUtil::serialize(value, &str);
    printf("序列化结果:\n %s\n%s\n", str.c_str(), "========================================");

    Json::Value oValue;
    /* 反序列化, 将s反序列化*/
    cloud::JsonUtil::unserialize(str, &oValue);
    printf("反序列化结果: \nname: %s\nage: %d\n", 
            (oValue["name"].asString()).c_str(), oValue["age"].asInt());
    for(int i = 0; i < 3; ++i) {
        printf("成绩%d: %d\n", i, oValue["score"][i].asInt());
    }
}
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud 
序列化结果:
 {
	"age" : 20,
	"name" : "zhangsan",
	"score" : 
	[
		80,
		90,
		100
	]
}
========================================
反序列化结果: 
name: zhangsan
age: 20
成绩0: 80
成绩1: 90
成绩2: 100

6. 服务端所用到的配置信息

6.1 json格式的配置信息

  • 服务器访问 IP 地址

  • 服务器访问端⼝

  • 热点判断时间(多长时间没有被访问的文件属于非热点文件)

  • URL路径前缀(如http://www.example.com/path/to/file的路径前缀是/path/

  • 压缩包后缀名称 (在文件原名后面加上该后缀)

  • 上传⽂件存放路径(上传后的文件储存在服务器的哪个文件夹下)

  • 压缩⽂件存放路径(压缩文件储存在服务器的哪个文件夹下)

  • 服务端备份信息存放文件名 (可以通过数据库存放)

使用json格式存放,下面是一个实例。cloud.conf

TODO: 构造函数出错返回?
将bundle变成一个库

{
    "serverIp" : "124.70.203.1",
    "serverPort" : 9000,
    "hotTime" : 30,
    "pathPre" : "/listShow/",
    "rarSuf" : ".lz",
    "ulPath" : "./files",
    "rarPath" : "./rars",
    "backups" : "./backups.data"
}

6.2 加载配置信息类

6.2.1 完整的类

使用单例模式。config.hpp

/* 用于加载配置文件 */
#include "FileUtil.hpp"
#include "JsonUtil.hpp"
#include <mutex>
#define CONF_PATH "./cloud.conf"

namespace cloud
{
class Config
{
private:
    static Config* _instance;
    static std::mutex _mtx;
    Config(const Config& c) = delete;
    Config& operator=(const Config& c) = delete;

    Config() 
    {
        if(loadFiles() == false)    delInstance();
    }

    // 从配置文件中加载数据,放到各个属性中
    bool loadFiles()
    {   
        // 打开文件
        cloud::FileUtil fu(CONF_PATH);
        std::string content;
        
        if(fu.getFullContent(&content) == false) {
            std::cerr << "getFullContent error!\nbool loadFiles()\n";
            return false;
        }

        // json转换
        Json::Value root;
        if(cloud::JsonUtil::unserialize(content, &root) == false) {
            std::cerr << "unserialize error!\nbool loadFiles()\n";
            return false;
        }

        // 初始化
        _serverIp = root["serverIp"].asString();
        _serverPort = root["serverPort"].asInt();
        _hotTime = root["hotTime"].asInt64();
        _pathPre = root["pathPre"].asString();
        _rarSuf = root["rarSuf"].asString();
        _ulPath = root["ulPath"].asString();
        _rarPath = root["rarPath"].asString();
        _backups = root["backups"].asString();
        return true;
    }
private:
    std::string _serverIp;  // 服务器访问 IP 地址  
    int _serverPort;        // 服务器访问端⼝ 
    std::time_t _hotTime;   // 热点判断时间
    std::string _pathPre;   // URL路径前缀
    std::string _rarSuf;    // 压缩包后缀名称
    std::string _ulPath;    // 上传文件存放的路径
    std::string _rarPath;   // 压缩文件存放的路径
    std::string _backups;   // 备份文件
public:
    // 获取实例
    static Config* getInstance()
    {
        if(_instance == nullptr) {      
            // _instance为null时,才有加锁解锁的必要。外面多加一层判断可以防止每次getInstance()时都要申请锁
            std::unique_lock<std::mutex> lck(_mtx);
            if(_instance == nullptr) {
                _instance = new Config();
            }
        }
        return _instance;
    }

    // 删除实例
    static void delInstance()
    {
        if(_instance != nullptr) {
            std::cout << "log: static void delInstance()\n";
            delete _instance;
            _instance = nullptr;
        }
    }

    std::string getServerIp() { return _serverIp; }

    int getServerPort() { return _serverPort; }

    std::time_t getHotTime() { return _hotTime; }

    std::string getPathPre() { return _pathPre; }

    std::string getRarSuf() { return _rarSuf; }

    std::string getUlPath() { return _ulPath; }

    std::string getRarPath() { return _rarPath; }

    std::string getBackups() { return _backups; } 
};
Config* Config::_instance = nullptr;
std::mutex Config::_mtx;
}

6.2.2 测试

// 测试Config.hpp
void testConfig()
{   
    cloud::Config* cof = cloud::Config::getInstance();
    std::cout << cof->getServerIp() << '\n';
    std::cout << cof->getServerPort() << '\n';
    std::cout << cof->getHotTime() << '\n';
    std::cout << cof->getPathPre() << '\n';
    std::cout << cof->getRarSuf() << '\n';
    std::cout << cof->getUlPath() << '\n';
    std::cout << cof->getRarPath() << '\n';
    std::cout << cof->getBackups() << '\n';
}
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud 
124.70.203.1
9000
30
/listShow/
.lz
./files
./rars
./backups.data