【云备份】服务端工具类实现

1.文件实用工具类设计

不管是客户端还是服务端,文件的传输备份都涉及到文件的读写,包括数据管理信息的持久化也是如此,因此首先设 计封装文件操作类,这个类封装完毕之后,则在任意模块中对文件进行操作时都将变的简单化。

文件系统库 - cppreference.com

文件实用工具类FileUtil主要包含以下成员:

cpp 复制代码
namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
    class FileUtil
    {
    public:
        FileUtil(const std::string& filename) :_filename(filename);
        //主要针对普通文件的接口
        int64_t FileSize(); // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
        time_t LastModtime(); // 获取文件最后一次修改时间
        time_t LastAccTime(); // 获取文件最后一次访问时间
        std::string FileName(); // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc

        bool SetContent(const std::string& body); // 向文件写入数据
        bool GetContent(std::string* body); // 获取文件内容
        bool GetPosLen(std::string* body, size_t pos, size_t len); // 获取文件指定区间的数据

        bool Compress(const std::string& packname); // 压缩文件
        bool UnCompress(const std::string& filename); // 解压缩文件

        //主要针对目录文件的接口
        bool Exists(); // 判断文件是否存在
        bool Remove(); // 删除文件
        bool CreateDirectory(); // 创建文件目录
        bool ScanDirectory(std::vector<std::string>* array); // 浏览指定目录下的所有文件,保存了该目录下所有的文件名称
    private:
        std::string _filename; // 文件名--包含路径
    };
};
  • _filename:文件名称(包含路径),例如 a/b/c/test.cc;
  • FileUtil:构造函数;
  • FileSize:获取文件大小;
  • LastModtime:获取文件最后一次修改时间;
  • LastAccTime:获取文件最后一次访问 时间,这个是为了我们后面的热点文件管理,如果一个文件很久都没有被访问过,我们就要对它进行压缩处理
  • FileName:获取文件路径中的纯文件名称,比如说我们传入了路径名 a/b/c/test.cc ,使用Filename只获取最后的文件名test.cc
  • GetPosLen:获取文件指定区间的数据;这个主要是为了我们后面的断点续传功能。
  • GetContent:获取文件内容;这里函数参数使用指针的原因是它是一个输入输出型参数,文件的内容都会被传入到这个指针指向的那块地方。当然你使用引用也是可以的。
  • SetContent:向文件写入数据;这里函数参数使用引用的原因是它是一个输入型参数,使用引用能提高效率
  • Compress:压缩文件;
  • UnCompress:解压缩文件;
  • Exists:判断文件是否存在;
  • Remove:删除文件;
  • CreateDirectory:创建文件目录;
  • GetDirectory:浏览指定目录下的所有文件;我们说Linux下一切皆是文件,目录也是。

我们这里不单单对普通文件进行处理,我们对目录文件也设计了接口。

首先我们打开我们的云服务器看看

这些都是我们之前实验下来的那些文件啊,我们现在把不必要的文件进行删除

我们创建一个util.hpp,把我们上面的东西给搞进去。

2.文件实用工具类实现

2.1.获取文件属性操作的实现

  • FileSize()函数的实现
cpp 复制代码
int64_t FileSize(); // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常

这个需要我们了解一下不太常用的接口函数------stat

cpp 复制代码
man 2 STAT

stat函数用于获取与指定路径名相关联的文件或目录的属性,并将这些属性填充到一个struct stat结构体中。以下是stat函数的函数原型:

cpp 复制代码
int stat(const char *pathname, struct stat *statbuf);
  • pathname是要获取属性的文件或目录的路径名
  • statbuf是一个指向struct stat结构体的指针,用于存储获取到的属性信息;

stat函数返回一个整数值,如果操作成功,返回0;

如果出现错误,返回-1,并设置errno全局变量以指示错误的类型。

  • struct stat类型

我们来看看能获取到什么文件信息!

在C语言中,struct stat是一个用于表示文件或文件系统对象属性的结构体类型。

这个结构体通常用于与文件和目录相关的操作,例如获取文件的大小、访问权限、最后修改时间等信息。

struct stat类型的定义通常由操作系统提供,因此其具体字段可能会因操作系统而异。

以下是一个典型的struct stat结构体的字段,尽管具体字段可能会因操作系统而异:

cpp 复制代码
struct stat {
    dev_t     st_dev;         // 文件所在设备的ID
    ino_t     st_ino;         // 文件的inode号
    mode_t    st_mode;        // 文件的访问权限和类型
    nlink_t   st_nlink;       // 文件的硬链接数量
    uid_t     st_uid;         // 文件的所有者的用户ID
    gid_t     st_gid;         // 文件的所有者的组ID
    off_t     st_size;        // 文件的大小(以字节为单位)
    time_t    st_atime;       // 文件的最后访问时间
    time_t    st_mtime;       // 文件的最后修改时间
    time_t    st_ctime;       // 文件的最后状态改变时间
    blksize_t st_blksize;     // 文件系统I/O操作的最佳块大小
    blkcnt_t  st_blocks;      // 文件占用的块数
};

我们也可以在上面的界面往下滑!看看我的系统的真实情况是啥

现在我们就应该知道怎么设计这个FileSize函数了吧!因为我们这有sz_size函数

cpp 复制代码
int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
{
    struct stat st;
    int re = stat(_filename.c_str(), &st);
    if (re < 0)//stat函数获取文件属性失败了
    {
        std::cout << "Get FileSize Failed!!" <<std::endl;
        return -1;
    }
    return st.st_size;
}
  • LastModtime()和LastAcctime()的实现

其实这个和上面的FileSize()是差不多的,只不过......算了先开下面这个

struct timespec 是 C 语言中用于表示高精度时间 的结构体,尤其在 Linux/Unix 系统中广泛使用。它设计用来存储时间的秒(tv_sec)和纳秒(tv_nsec)分量,提供比传统的 time_t(仅秒级)更高的时间精度。

cpp 复制代码
#include <time.h>  // 需要包含的头文件

struct timespec {
    time_t   tv_sec;   // 秒(自 1970-01-01 00:00:00 UTC 的秒数)
    long     tv_nsec;  // 纳秒(0 到 999,999,999)
};

来看看怎么使用

测试函数------获取当前时间(纳秒级)

cpp 复制代码
#include <stdio.h>
#include <time.h>

int main() {
    struct timespec ts;
    // 获取系统实时时钟(CLOCK_REALTIME)
    if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
        perror("clock_gettime");
        return 1;
    }

    printf("Current time: %lld seconds, %ld nanoseconds\n",
           (long long)ts.tv_sec, ts.tv_nsec);
    return 0;
}

现在我们就应该知道怎么使用了

cpp 复制代码
 time_t LastModtime() // 获取文件最后一次修改时间
 {
     struct stat st;
     int re = stat(_filename.c_str(), &st);
     if (re < 0)//stat函数获取文件属性失败了
     {
         std::cout << "Get LastModtime Failed!!" <<std::endl;
         return -1;
     }
     return st.st_mtim.tv_sec;
 }
 time_t LastAcctime() // 获取文件最后一次访问时间
 {
     struct stat st;
     int re = stat(_filename.c_str(), &st);
     if (re < 0)//stat函数获取文件属性失败了
     {
         std::cout << "Get LastAccTime Failed!!" <<std::endl;
         return -1;
     }
     return st.st_atim.tv_sec;

 }
  • Filename()的实现

我们知道我们的路径名都是用/来进行分割的,比如./a/b.txt,那么我们只需要获取最后一个/后面到最末尾的东西即可

cpp 复制代码
 std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
 {
     size_t pos = _filename.find_last_of("/");//寻找最后一个/
     if (pos == std::string::npos)//没找到,说明没有/
     {
         return _filename;
     }
     return _filename.substr(pos + 1);//从pos+1位置截取到末尾
 }
  • 小测试

我们目前写的代码就是现在这样子

util.hpp

cpp 复制代码
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
    class FileUtil
    {
    public:
        FileUtil(const std::string& filename):_filename(filename){}
        //主要针对普通文件的接口
        int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
	{
		struct stat st;
		int re=stat(_filename.c_str(),&st);
		if(re<0)//stat函数获取文件属性失败了
		{
			std::cout<<"Get FileSize Failed!!"<<std::endl;
			return -1;
		}
		return st.st_size;
	}
	time_t LastModtime() // 获取文件最后一次修改时间
	{
	   	 struct stat st;
   		 int re = stat(_filename.c_str(), &st);
    		if (re < 0)//stat函数获取文件属性失败了
   		 {
        		std::cout << "Get LastModtime Failed!!" << std::endl;
        		return -1;
   		 }
    		return st.st_mtim.tv_sec;
	}
        time_t LastAcctime() // 获取文件最后一次访问时间
	{
		struct stat st;
		int re = stat(_filename.c_str(), &st);
		if (re < 0)//stat函数获取文件属性失败了
 		{
         	 std::cout << "Get LastAcctime Failed!!" << std::endl;
          	 return -1;
 		}
          	return st.st_atim.tv_sec;

	}
        std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
	{
		size_t pos=_filename.find_last_of("/");//寻找最后一个/
		if(pos==std::string::npos)//没找到,说明没有/
		{
			return _filename;
		}
		return _filename.substr(pos+1);//从pos截取到末尾
	}

    private:
        std::string _filename; // 文件名--包含路径
    };
};

代码写到这里我们,我们必须进行测试我们的代码对不对,要不然写到后面我们一直报错就不好了

cloud.cc

cpp 复制代码
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
        cloud::FileUtil fu(filename);
        std::cout<<fu.FileSize()<<std::endl;
        std::cout<<fu.LastModtime()<<std::endl;
        std::cout<<fu.LastAcctime()<<std::endl;
        std::cout<<fu.FileName()<<std::endl;


}
int main(int argc,char*argv[])
{       

        FileUtilTest(argv[1]);
}

makefile

cpp 复制代码
cloud:cloud.cc util.hpp
        g++ $^ -o $@
.PHONY:clean
clean:
        rm -f cloud

我们编译运行一下

这个就很好了!!

我们去看看

我们只是在main函数里面添加了一句retrun 0,就发现它们都修改了

我们再看Filename()的测试,也是很不错!!

这就说明我们完成的代码就很好了


接下来我要讲一下git,如果只想看项目的可以往下一节去了

首先我们先创建本地仓库

然后配置本地仓库

接着提交,发现git add *报错了

我们有两种解决方法,但是我只选择下面这种

直接包含子目录代码(不保留 Git 历史)

如果这些目录不需要独立维护(例如你只是复制了代码,不需要跟踪它们的更新),可以删除它们的 .git 文件夹,再添加到父仓库。

删除子目录中的 .git 文件夹

cpp 复制代码
rm -rf bundle/.git cpp-httplib/.git

重新添加所有文件,提交更改

接下来要把它推送到远程仓库里面去,首先我们要先创建一个远程仓库

有了本地仓库和远程仓库后,可以将二者关联起来,以便推送和拉取代码:

  • 在本地仓库中,执行以下命令来添加远程仓库的地址:
cpp 复制代码
git remote add origin <远程Git仓库地址>

其中,<远程Git仓库地址>是你的远程Git仓库的网址。

对于如何获取远程Git仓库地址,我们举例说明:

比如,你的远程Git仓库地址为:

bash 复制代码
https://github.com/your/your.git

那么你在本地使用"git remote add origin"指令的语法就应该是:

bash 复制代码
git remote add origin https://github.com/your/your.git

执行这条指令之后,你的本地项目就与远程Git仓库建立了连接,你就可以开始对你的代码进行版本追踪和协作开发了。

  • 检查关联是否成功,执行以下命令:
cpp 复制代码
git remote -v
  • 推送到远程仓库

关联完成后,可以将本地仓库中的代码推送到远程仓库中:

cpp 复制代码
git push -f origin master

注意:-f是强制的意思

这样就将本地仓库中的代码推送到了远程仓库的 master 分支上。如果是第一次推送,可能需要输入用户名和密码进行身份认证。


这很好了

git remote add origin的一些常用操作

  1. 更改默认的远程仓库

在项目中可能存在多个远程仓库,如果你想更改默认仓库,可以使用如下指令:

cpp 复制代码
git remote set-url origin <新的远程Git仓库地址>
  1. 查看当前的远程仓库

如果你想查看当前项目的远程仓库,可以使用如下指令:

cpp 复制代码
git remote -v
  1. 删除远程仓库

如果你需要删除已经添加的远程仓库,可以使用如下指令:

cpp 复制代码
git remote rm origin

执行这条指令之后,Git就会将已经添加的名为"origin"的仓库删除。


2.2.文件的读写操作的实现

GetPosLen()函数

cpp 复制代码
bool GetPosLen(std::string* body, size_t pos, size_t len)
{
    std::ifstream ifs;
    ifs.open(_filename, std::ios::binary);//打开文件,以二进制方式来读取数据
    if (ifs.is_open() == false)//打开失败
    {
        std::cout << "GetPosLen: open file failed!" << std::endl;
        return false;
    }

    size_t fsize = this->FileSize();//获取文件大小
    if (pos + len > fsize)//超过文件大小了
    {
        std::cout << "GetPosLen: get file len error" << std::endl;
        return false;
    }

    ifs.seekg(pos, std::ios::beg); // 将文件指针定位到pos处
    body->resize(len);//把存储读取的数据的载体的大小修改到够大的

    ifs.read(&(*body)[0], len);//读取数据,注意这里body是指针,需要先解引用
    if (ifs.good() == false)//上次读取出错了
    {
        std::cout << "GetPosLen: get file content failed" << std::endl;
        ifs.close();
        return false;
    }

    ifs.close();
    return true;
}

GetContent()

cpp 复制代码
bool GetContent(std::string* body)
{
    size_t fsize = FileSize();
    return GetPosLen(body, 0, fsize);
}

SetContent()

cpp 复制代码
bool SetContent(const std::string& body)//写入数据
{
    std::ofstream ofs;//也就是输出
    ofs.open(_filename, std::ios::binary);//以二进制模式打开
    if (ofs.is_open() == false)//打开失败
    {
        std::cout << "SetContent: write open file failed" << std::endl;
        return false;
    }
    ofs.write(&body[0], body.size());
    if (ofs.good() == false)//上次写入文件数据出错了
    {
        std::cout << "SetContent: write open file failed" << std::endl;
        ofs.close();
        return false;
    }
    ofs.close();
    return true;
}

接下来我们来测试一下

cloud.cc

cpp 复制代码
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
        cloud::FileUtil fu(filename);
        std::string body;
        fu.GetContent(&body);


        cloud::FileUtil nfu("./hello.txt");
        nfu.SetContent(body);



}
int main(int argc,char*argv[])
{

        FileUtilTest(argv[1]);
        return 0;

我们进去看看

简直一模一样。但是看着一样是不一定一样的,我们需要借助工具来看看是不是一样的

很显然是一样的了。


同样的,在这里,我们还是需要使用git来进行备份一下

2.3.文件压缩和解压缩操作

接下来来实现Compress函数和Uncompress函数。这是非常简单的。

cpp 复制代码
bool Compress(const std::string& packname)
{
    // 1.获取源文件数据
    std::string body;
    if (this->GetContent(&body) == false)//源文件数据都存储在body里面
    {
        //获取源文件数据失败
        std::cout << "compress get file content failed" << std::endl;
        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!" << std::endl;
        return false;
    }

    return true;
}

接下来看解压缩的操作

cpp 复制代码
bool UnCompress(const std::string& filename)
{
    // 1.将当前压缩包数据读取出来
    std::string body;
    if (this->GetContent(&body) == false)
    {
        std::cout << "Uncompress get file content failed!" << std::endl;
        return false;
    }

    // 2.对压缩的数据进行解压缩
    std::string unpacked = bundle::unpack(body);

    // 3.将解压缩的数据写入到新文件中
    FileUtil fu(filename);
    if (fu.SetContent(unpacked) == false)
    {
        std::cout << "Uncompress write packed data failed!" << std::endl;
        return false;
    }

    return true;
}

由于我们bundle库是第三方库,所以不要忘记了添加头文件

除此之外还是不够的,我们还需要将bundle.h和bundle.cpp拷贝到当前目录下来

好,现在我们来测试一下

makefile

cpp 复制代码
cloud:cloud.cc util.hpp bundle.cpp
        g++ $^ -o $@ -lpthread
.PHONY:clean
clean:
        rm -f cloud

cloud.cc

cpp 复制代码
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
        cloud::FileUtil fu(filename);
        fu.Compress(filename+".lz");
        

        cloud::FileUtil pfu(filename+".lz");
        pfu.UnCompress("hello.txt");

}
int main(int argc,char*argv[])
{

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

我们编译运行一下

这些警告不管。

2.4.目录文件操作实现

我们先来认识一个接口------scandir,c语言里面浏览一个目录的内容

看到三级指针就蒙蔽了!所以这个接口用起来是不太好用的,这个时候就需要借助C++17所支持的filesystem了。

std::experimental::filesystem 库是 C++ 标准库的一部分,最早出现在 C++17 中,并被视为实验性的文件系统库。它提供了一组类和函数,用于处理文件系统操作,如文件和目录的创建、访问、遍历、复制、删除等。这个库的目的是使文件系统操作更加便捷,同时具有跨平台性,因此你可以在不同操作系统上执行相同的文件操作,而不需要考虑底层细节。

以下是一些 std::experimental::filesystem 库的主要特性和功能:

  1. std::experimental::filesystem::path 类:用于表示文件路径。它可以自动处理不同操作系统的路径分隔符,使得代码更具可移植性。
  2. 文件和目录操作:这个库提供了许多函数来执行常见的文件和目录操作,包括文件创建、复制、移动、删除,以及目录的创建、删除、遍历等。
  3. 文件属性查询:你可以使用这个库来查询文件和目录的属性,如文件大小、修改时间等。
  4. 异常处理:std::experimental::filesystem 库定义了一些异常类,以处理与文件系统操作相关的错误,如文件不存在或无法访问等问题。
  5. 迭代器:你可以使用迭代器来遍历目录中的文件和子目录,这是一个非常方便的功能,用于递归遍历文件系统。

需要注意的是,尽管 std::experimental::filesystem 在 C++17 中引入,但它是一个实验性的特性,并且不一定在所有编译器和平台上都得到完全支持。因此,一些编译器可能需要特定的编译选项或配置才能使用这个库。

从 C++17 开始,文件系统库已正式成为 C++ 标准的一部分,并迁移到 std::filesystem 命名空间中,而不再是实验性的特性。因此,在新的 C++标准中,建议使用 std::filesystem 库来执行文件系统操作。

大家想要了解具体内容请去:文件系统库 - cppreference.com

  • ScanDirectory()函数的实现

我们点开这个

我们看看例子

可能大家看不太懂,我加注释给你们看看

cpp 复制代码
// 使用实验性文件系统库(C++17 前需用 experimental 命名空间,C++17 后改为 std::filesystem)
#include <experimental/filesystem>
#include <fstream>  // 文件流操作(如创建文件)
#include <iostream> // 输入输出流

// 为实验性文件系统库定义别名 fs,简化代码
namespace fs = std::experimental::filesystem;

int main() {
    // 1. 创建嵌套目录 "sandbox/a/b"
    // create_directories 会递归创建所有不存在的父目录
    fs::create_directories("sandbox/a/b");

    // 2. 在 sandbox 目录下创建两个空文件
    // 使用 std::ofstream 的构造函数直接创建文件(文件内容为空)
    std::ofstream{"sandbox/file1.txt"}; // 创建 file1.txt
    std::ofstream{"sandbox/file2.txt"}; // 创建 file2.txt

    // 3. 遍历 sandbox 目录下的所有条目并打印路径
    // directory_iterator 用于遍历目录内容
    // entry 是目录条目,包含文件/子目录的信息
    std::cout << "目录内容:\n";
    for (const fs::directory_entry& entry : fs::directory_iterator{"sandbox"}) {
        // 直接输出 entry 会显示其完整路径(需支持 operator<< 重载)
        std::cout << "  " << entry << '\n'; 
    }

    // 4. 递归删除整个 sandbox 目录及其内容
    // remove_all 会删除目录、子目录和所有文件
    fs::remove_all("sandbox");

    return 0;
}

这样子就很简单易懂了。

我们完全可以将其复制下来,就能知道怎么使用这个代码了。

cpp 复制代码
bool ScanDirectory(std::vector<std::string> *array)
    {
        for(auto& p : fs::directory_iterator(_filename)) // 迭代器遍历指定目录下的文件
        {
            if(fs::is_directory(p) == true) continue;
            // relative_path 带有路径的文件名
            array->push_back(fs::path(p).relative_path().string());
        }
        return true;
    }

注意:迭代器返回的p不是string,不能直接将p添加进array里面,我们需要使用path将其转换成string

我们进去看看怎么使用

我们发现它打印的都是带有\的文件名,这就是我们要找的。

此外注意relative_path函数返回的是一个path对象

而我们是要string的,所以我们还是需要借助path的接口string(),这个自己去官网看


  • Exists()的实现

我们去刚刚那个网站,就很容易看到下面这个

点进去看就明白了

也就是下面这个

cpp 复制代码
bool exists(const path& p);

检查给定的路径(path)是否对应一个实际存在的文件或目录。

返回值

  • 若路径 p 对应的文件或目录存在,返回 true;否则返回 false

现在我们很容易就写出下面这个

cpp 复制代码
namespace fs = std::experimental::filesystem;
bool Exists()
{
   return fs::exists(_filename);
}

  • Remove()的实现

点进去看看

我们借助DeepSeek帮我们解析这个函数的用法

  • remove :删除单个文件或空目录(类似 POSIX 的 remove)。

    • 符号链接处理:删除符号链接本身,而非其指向的目标。

    • 限制:若路径是目录,必须为空才能删除,否则失败。

cpp 复制代码
bool remove(const path& p);
  • p -- 要删除的文件或空目录的路径。
  • 返回值

    • 成功删除或文件不存在时返回 true

    • 若路径存在但删除失败(如目录非空或权限不足),返回 false

cpp 复制代码
bool Remove()
{
    if(Exists() == false)
    {
        return true;
    }
    remove(_filename.c_str());
    return true;
}

  • CreateDirectory()的实现

还是熟悉的配方,我不过多说,大家不懂的可以去这里看:Filesystem library - cppreference.com

cpp 复制代码
bool CreateDirectory()
{
    if (Exists()) return true;//如果存在,则直接返回true即可
    return fs::create_directories(_filename);//不存在的话,创建一个文件
}

注意要包含头文件#include <experimental/filesystem>


接下来来测试一下

Util.hpp

cpp 复制代码
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include"bundle.h"
#include <experimental/filesystem>

namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
	namespace fs = std::experimental::filesystem;
	class FileUtil
	{
		public:
			FileUtil(const std::string& filename):_filename(filename){}
			//主要针对普通文件的接口
			int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
			{
				struct stat st;
				int re=stat(_filename.c_str(),&st);
				if(re<0)//stat函数获取文件属性失败了
				{
					std::cout<<"Get FileSize Failed!!"<<std::endl;
					return -1;
				}
				return st.st_size;
			}
			time_t LastModtime() // 获取文件最后一次修改时间
			{
				struct stat st;
				int re = stat(_filename.c_str(), &st);
				if (re < 0)//stat函数获取文件属性失败了
				{
					std::cout << "Get LastModtime Failed!!" << std::endl;
					return -1;
				}
				return st.st_mtim.tv_sec;
			}
			time_t LastAcctime() // 获取文件最后一次访问时间
			{
				struct stat st;
				int re = stat(_filename.c_str(), &st);
				if (re < 0)//stat函数获取文件属性失败了
				{
					std::cout << "Get LastAcctime Failed!!" << std::endl;
					return -1;
				}
				return st.st_atim.tv_sec;

			}
			std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
			{
				size_t pos=_filename.find_last_of("/");//寻找最后一个/
				if(pos==std::string::npos)//没找到,说明没有/
				{
					return _filename;
				}
				return _filename.substr(pos+1);//从pos截取到末尾
			}
			bool GetPosLen(std::string* body, size_t pos, size_t len)
			{
				std::ifstream ifs;
				ifs.open(_filename, std::ios::binary);//打开文件,以二进制方式来读取数据
				if (ifs.is_open() == false)//打开失败
				{
					std::cout << "GetPosLen: open file failed!" << std::endl;
					return false;
				}

				size_t fsize = this->FileSize();//获取文件大小
				if (pos + len > fsize)//超过文件大小了
				{
					std::cout << "GetPosLen: get file len error" << std::endl;
					return false;
				}

				ifs.seekg(pos, std::ios::beg); // 将文件指针定位到pos处
				body->resize(len);//把存储读取的数据的载体的大小修改到够大的

				ifs.read(&(*body)[0], len);//读取数据
				if (ifs.good() == false)//上次读取出错了
				{
					std::cout << "GetPosLen: get file content failed" << std::endl;
					ifs.close();
					return false;
				}

				ifs.close();
				return true;
			}
			bool GetContent(std::string* body)
			{
				size_t fsize = 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 << "SetContent: write open file failed" << std::endl;
					return false;
				}
				ofs.write(&body[0], body.size());
				if (ofs.good() == false)//上次写入文件数据出错了
				{
					std::cout << "SetContent: write open file failed" << std::endl;
					ofs.close();
					return false;
				}
				ofs.close();
				return true;
			}
			bool Compress(const std::string& packname)
			{
				// 1.获取源文件数据
				std::string body;
				if (this->GetContent(&body) == false)//源文件数据都存储在body里面
				{
					//获取源文件数据失败
					std::cout << "compress get file content failed" << std::endl;
					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!" << std::endl;
					return false;
				}

				return true;
			}
			bool UnCompress(const std::string& filename)
			{
				// 1.将当前压缩包数据读取出来
				std::string body;
				if (this->GetContent(&body) == false)
				{
					std::cout << "Uncompress get file content failed!" << std::endl;
					return false;
				}

				// 2.对压缩的数据进行解压缩
				std::string unpacked = bundle::unpack(body);

				// 3.将解压缩的数据写入到新文件中
				FileUtil fu(filename);
				if (fu.SetContent(unpacked) == false)
				{
					std::cout << "Uncompress write packed data failed!" << std::endl;
					return false;
				}

				return true;
			}
			bool Exists()
			{
				return fs::exists(_filename);
			}
			bool Remove()
			{
				if (Exists() == false)
				{
					return true;
				}
				remove(_filename.c_str());
				return true;
			}
			bool CreateDirectory()
			{
				if (Exists()) return true;
				return fs::create_directories(_filename);
			}
			bool ScanDirectory(std::vector<std::string>* array)
			{
				for (auto& p : fs::directory_iterator(_filename)) // 迭代器遍历指定目录下的文件,从那个网站上面复制下来的
				{
					if (fs::is_directory(p) == true)//如果是目录,就不添加进当前目录的文件里
						continue;
					// relative_path 带有路径的文件名
					array->push_back(fs::path(p).relative_path().string());//添加文件
					//注意迭代器返回的p不是string,不能直接将p添加进array里面,我们需要使用path将其
				}
				return true;
			}
		private:
			std::string _filename; // 文件名--包含路径
	};
};

cloud.cc

cpp 复制代码
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
        cloud::FileUtil fu(filename);
        fu.CreateDirectory();
        std::vector<std::string>arry;
        fu.ScanDirectory(&arry);
        for(auto&a:arry)
        {
                std::cout<<a<<std::endl;
        }
}
int main(int argc,char*argv[])
{

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

注意我们这个是使用了c++17里面的文件系统,这是需要我们额外链接的!

makefile

cpp 复制代码
cloud:cloud.cc util.hpp bundle.cpp
        g++ $^ -o $@ -lpthread -lstdc++fs
.PHONY:clean
clean:
        rm -f cloud

编译运行

发现它创建了一个目录

这个文件管理写的很好了吧!!


还是老样子!git push一下

3.JSON实用工具类实现

Jsoncpp已经为我们你提供了序列化与反序列化接口,但是为了使得实用更加便捷,我们可以自己再封装一个JsonUtil的类。

JsonUtil类中包含以下成员

cpp 复制代码
class JsonUtil
{
public:
    //这里使用static,是为了方便我们直接调用即可
    static bool Serialize(const Json::Value &root, std::string *str); // 序列化操作
    static bool Unserialize(const std::string &str, Json::Value *root); // 反序列化操作
};

由于前面的章节已经介绍过Json的使用,接下来我们直接看函数的实现。

cpp 复制代码
class JsonUtil {
public:
    /**
     * @brief 将 Json::Value 对象序列化为字符串
     * @param root 输入的 JSON 数据结构(待序列化)
     * @param str 输出的序列化后的字符串
     * @return true 序列化成功,false 序列化失败
     */
    static bool Serialize(const Json::Value &root, std::string *str) {
        // 1. 创建 JSON 流写入器构建器(可配置格式化选项,如缩进)
        Json::StreamWriterBuilder swb;
        // 2. 通过构建器生成 StreamWriter 对象(unique_ptr 自动管理内存)
        std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

        std::stringstream ss;  // 用于存储序列化结果

        // 3. 将 JSON 数据写入流
        // 返回值 0 表示成功,非 0 表示失败(JsonCpp 的约定)
        if (sw->write(root, &ss) != 0) {
            std::cout << "JSON 序列化失败!" << std::endl;
            return false;
        }

        // 4. 将 stringstream 内容转为字符串
        *str = ss.str();
        return true;
    }

    /**
     * @brief 将字符串反序列化为 Json::Value 对象
     * @param str 输入的 JSON 格式字符串
     * @param root 输出的解析后的 JSON 数据结构
     * @return true 解析成功,false 解析失败
     */
    static bool Unserialize(const std::string &str, Json::Value *root) {
        // 1. 创建 JSON 字符读取器构建器
        Json::CharReaderBuilder crb;
        // 2. 通过构建器生成 CharReader 对象(unique_ptr 自动管理内存)
        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

        std::string err;  // 存储解析错误信息

        // 3. 解析字符串
        // 参数说明:
        // - str.c_str():字符串起始地址
        // - str.c_str() + str.size():字符串结束地址
        // - root:输出解析后的 JSON 对象
        // - &err:错误信息输出
        bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);

        if (!ret) {
            std::cout << "JSON 解析错误: " << err << std::endl;
            return false;
        }
        return true;
    }
};

接下来来测试一下

makefile

cpp 复制代码
cloud:cloud.cc util.hpp bundle.cpp
        g++ $^ -o $@ -lpthread -lstdc++fs -ljsoncpp
.PHONY:clean
clean:
        rm -f cloud

cloud.cc

cpp 复制代码
#include"util.hpp"
void JsonUtilTest()
{
        // 定义并初始化一个常量字符指针,指向字符串"小明"
    // 定义一个整型变量并初始化为18,表示年龄
    int age  = 18;
    // 定义一个浮点型数组,存储三门课程的成绩
    float score[] = {77.5, 88, 93.6};

    // 定义一个Json::Value对象,作为JSON数据的根节点
    Json::Value root;
    // 向root中添加一个键值对,键为"name",值为name所指向的字符串
    root["name"] ="xiaoming";
    // 向root中添加一个键值对,键为"age",值为整型变量age
    root["age"] = age;
    // 向root中添加一个键为"成绩"的数组,并依次添加score数组中的元素
    // 使用append函数向数组中插入数据
    root["chengji"].append(score[0]);
    root["chengji"].append(score[1]);
    root["chengji"].append(score[2]);
    std::string json_str;
    cloud::JsonUtil::Serialize(root,&json_str);
    std::cout<<json_str<<std::endl;

    Json::Value val;
   cloud::JsonUtil::Unserialize(json_str,&val);
   std::cout<<val["name"].asString()<<std::endl;
   std::cout<<val["age"].asInt()<<std::endl;
   for(int i=0;i<val["chengji"].size();i++)
   {
        std::cout<<val["chengji"][i].asFloat()<<std::endl;
   }
}
int main(int argc,char*argv[])
{
        JsonUtilTest();
        return 0;
}

我们编译运行一下

很完美


好了,我们git push一下即可

4.util.hpp源代码

cpp 复制代码
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include"bundle.h"
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>

namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
	namespace fs = std::experimental::filesystem;
	class FileUtil
	{
		public:
			FileUtil(const std::string& filename):_filename(filename){}
			//主要针对普通文件的接口
			int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
			{
				struct stat st;
				int re=stat(_filename.c_str(),&st);
				if(re<0)//stat函数获取文件属性失败了
				{
					std::cout<<"Get FileSize Failed!!"<<std::endl;
					return -1;
				}
				return st.st_size;
			}
			time_t LastModtime() // 获取文件最后一次修改时间
			{
				struct stat st;
				int re = stat(_filename.c_str(), &st);
				if (re < 0)//stat函数获取文件属性失败了
				{
					std::cout << "Get LastModtime Failed!!" << std::endl;
					return -1;
				}
				return st.st_mtim.tv_sec;
			}
			time_t LastAcctime() // 获取文件最后一次访问时间
			{
				struct stat st;
				int re = stat(_filename.c_str(), &st);
				if (re < 0)//stat函数获取文件属性失败了
				{
					std::cout << "Get LastAcctime Failed!!" << std::endl;
					return -1;
				}
				return st.st_atim.tv_sec;

			}
			std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
			{
				size_t pos=_filename.find_last_of("/");//寻找最后一个/
				if(pos==std::string::npos)//没找到,说明没有/
				{
					return _filename;
				}
				return _filename.substr(pos+1);//从pos截取到末尾
			}
			bool GetPosLen(std::string* body, size_t pos, size_t len)
			{
				std::ifstream ifs;
				ifs.open(_filename, std::ios::binary);//打开文件,以二进制方式来读取数据
				if (ifs.is_open() == false)//打开失败
				{
					std::cout << "GetPosLen: open file failed!" << std::endl;
					return false;
				}

				size_t fsize = this->FileSize();//获取文件大小
				if (pos + len > fsize)//超过文件大小了
				{
					std::cout << "GetPosLen: get file len error" << std::endl;
					return false;
				}

				ifs.seekg(pos, std::ios::beg); // 将文件指针定位到pos处
				body->resize(len);//把存储读取的数据的载体的大小修改到够大的

				ifs.read(&(*body)[0], len);//读取数据
				if (ifs.good() == false)//上次读取出错了
				{
					std::cout << "GetPosLen: get file content failed" << std::endl;
					ifs.close();
					return false;
				}

				ifs.close();
				return true;
			}
			bool GetContent(std::string* body)
			{
				size_t fsize = 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 << "SetContent: write open file failed" << std::endl;
					return false;
				}
				ofs.write(&body[0], body.size());
				if (ofs.good() == false)//上次写入文件数据出错了
				{
					std::cout << "SetContent: write open file failed" << std::endl;
					ofs.close();
					return false;
				}
				ofs.close();
				return true;
			}
			bool Compress(const std::string& packname)
			{
				// 1.获取源文件数据
				std::string body;
				if (this->GetContent(&body) == false)//源文件数据都存储在body里面
				{
					//获取源文件数据失败
					std::cout << "compress get file content failed" << std::endl;
					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!" << std::endl;
					return false;
				}

				return true;
			}
			bool UnCompress(const std::string& filename)
			{
				// 1.将当前压缩包数据读取出来
				std::string body;
				if (this->GetContent(&body) == false)
				{
					std::cout << "Uncompress get file content failed!" << std::endl;
					return false;
				}

				// 2.对压缩的数据进行解压缩
				std::string unpacked = bundle::unpack(body);

				// 3.将解压缩的数据写入到新文件中
				FileUtil fu(filename);
				if (fu.SetContent(unpacked) == false)
				{
					std::cout << "Uncompress write packed data failed!" << std::endl;
					return false;
				}

				return true;
			}
			bool Exists()
			{
				return fs::exists(_filename);
			}
			bool Remove()
			{
				if (Exists() == false)
				{
					return true;
				}
				remove(_filename.c_str());
				return true;
			}
			bool CreateDirectory()
			{
				if (Exists()) return true;
				return fs::create_directories(_filename);
			}
			bool ScanDirectory(std::vector<std::string>* array)
			{
				for (auto& p : fs::directory_iterator(_filename)) // 迭代器遍历指定目录下的文件,从那个网站上面复制下来的
				{
					if (fs::is_directory(p) == true)//如果是目录,就不添加进当前目录的文件里
						continue;
					// relative_path 带有路径的文件名
					array->push_back(fs::path(p).relative_path().string());//添加文件
					//注意迭代器返回的p不是string,不能直接将p添加进array里面,我们需要使用path将其
				}
				return true;
			}
		private:
			std::string _filename; // 文件名--包含路径
	};
	class JsonUtil {
		public:
			/**
			 * @brief 将 Json::Value 对象序列化为字符串
			 * @param root 输入的 JSON 数据结构(待序列化)
			 * @param str 输出的序列化后的字符串
			 * @return true 序列化成功,false 序列化失败
			 */
			static bool Serialize(const Json::Value &root, std::string *str) {
				// 1. 创建 JSON 流写入器构建器(可配置格式化选项,如缩进)
				Json::StreamWriterBuilder swb;
				// 2. 通过构建器生成 StreamWriter 对象(unique_ptr 自动管理内存)
				std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

				std::stringstream ss;  // 用于存储序列化结果

				// 3. 将 JSON 数据写入流
				// 返回值 0 表示成功,非 0 表示失败(JsonCpp 的约定)
				if (sw->write(root, &ss) != 0) {
					std::cout << "JSON 序列化失败!" << std::endl;
					return false;
				}

				// 4. 将 stringstream 内容转为字符串
				*str = ss.str();
				return true;
			}

			/**
			 * @brief 将字符串反序列化为 Json::Value 对象
			 * @param str 输入的 JSON 格式字符串
			 * @param root 输出的解析后的 JSON 数据结构
			 * @return true 解析成功,false 解析失败
			 */
			static bool Unserialize(const std::string &str, Json::Value *root) {
				// 1. 创建 JSON 字符读取器构建器
				Json::CharReaderBuilder crb;
				// 2. 通过构建器生成 CharReader 对象(unique_ptr 自动管理内存)
				std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

				std::string err;  // 存储解析错误信息

				// 3. 解析字符串
				// 参数说明:
				// - str.c_str():字符串起始地址
				// - str.c_str() + str.size():字符串结束地址
				// - root:输出解析后的 JSON 对象
				// - &err:错误信息输出
				bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);

				if (!ret) {
					std::cout << "JSON 解析错误: " << err << std::endl;
					return false;
				}
				return true;
			}
	};
};
相关推荐
Y淑滢潇潇16 分钟前
RHCE 防火墙实验
linux·运维·rhce
wadesir44 分钟前
当前位置:首页 > 服务器技术 > 正文Linux网络HSRP协议(实现路由器热备份与高可用性的实用指南)
linux·服务器·网络
稻谷君W1 小时前
Ubuntu 远程访问 Win11 WSL2 并固定访问教程
linux·运维·ubuntu
泡沫·1 小时前
4.iSCSI 服务器
运维·服务器·数据库
胡八一1 小时前
解决PHP未检测到您服务器环境的sqlite3数据库扩展报错
服务器·数据库·php
不解不惑1 小时前
OpenAI whisper 语音识别服务器搭建
服务器·whisper·语音识别
gaize12131 小时前
适合业务规模较大的场景的服务器测评
服务器
悠悠121382 小时前
告别Zabbix?我用Netdata只花10分钟就搞定了50台服务器的秒级监控(保姆级实战)
运维·服务器·zabbix
天庭鸡腿哥2 小时前
大小只有4K的软件,可让系统瞬间丝滑!
运维·服务器·windows·microsoft·everything
虚伪的空想家2 小时前
华为昇腾Atlas 800 A2物理服务器开启VT-d模式
运维·服务器·ubuntu·kvm·vt-d·直通