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

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;
			}
	};
};
相关推荐
努力学习的小廉3 分钟前
深度理解linux系统—— 进程切换和调度
linux·运维·服务器
徐宜芳-Q18 分钟前
【Linux调整FTP端口】
linux·运维·服务器
AWS官方合作商23 分钟前
AWS CloudFront全球加速利器:解析出海业务的核心优势与最佳实践
服务器·云计算·aws
Davina_yu2 小时前
finereport Linux下Tomcat手动部署FineReport工程
linux·运维·tomcat
二七有头发2 小时前
Android短信监控技术实现:合法合规的远程采集方案
android·服务器·后端
眠修2 小时前
Nginx 核心功能
linux·服务器·nginx
悄悄敲敲敲2 小时前
Linux:信号(一)
linux·运维·服务器
alex88862 小时前
万界星空科技QMS质量管理系统几大核心功能详解
运维·经验分享·科技·5g·能源·创业创新·制造
今天努力了吗??3 小时前
Centos 7安装 NVIDIA CUDA Toolkit
linux·运维·centos
Waitccy3 小时前
CentOS 7 磁盘阵列搭建与管理全攻略
linux·运维·缓存·centos