当前位置: 首页 > backend >正文

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

1.文件实用工具类设计

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

文件系统库 - cppreference.com

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

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.ccbool 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()函数的实现
int64_t FileSize(); // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常

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

man 2 STAT

 

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

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结构体的字段,尽管具体字段可能会因操作系统而异:

struct stat {dev_t     st_dev;         // 文件所在设备的IDino_t     st_ino;         // 文件的inode号mode_t    st_mode;        // 文件的访问权限和类型nlink_t   st_nlink;       // 文件的硬链接数量uid_t     st_uid;         // 文件的所有者的用户IDgid_t     st_gid;         // 文件的所有者的组IDoff_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函数

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(仅秒级)更高的时间精度。

#include <time.h>  // 需要包含的头文件struct timespec {time_t   tv_sec;   // 秒(自 1970-01-01 00:00:00 UTC 的秒数)long     tv_nsec;  // 纳秒(0 到 999,999,999)
};

来看看怎么使用 

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

#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;
}

 


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

 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,那么我们只需要获取最后一个/后面到最末尾的东西即可

 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

#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

#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

cloud:cloud.cc util.hppg++ $^ -o $@
.PHONY:clean
clean:rm -f cloud

 我们编译运行一下

这个就很好了!! 

我们去看看

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

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

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


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

首先我们先创建本地仓库

 然后配置本地仓库

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

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

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

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

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

rm -rf bundle/.git cpp-httplib/.git

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

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

 

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

  • 在本地仓库中,执行以下命令来添加远程仓库的地址:

git remote add origin <远程Git仓库地址>

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

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

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

https://github.com/your/your.git

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

git remote add origin https://github.com/your/your.git

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

 

  • 检查关联是否成功,执行以下命令:

git remote -v
  • 推送到远程仓库

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

git push -f origin master

注意:-f是强制的意思 

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


 

 这很好了

git remote add origin的一些常用操作
1. 更改默认的远程仓库
在项目中可能存在多个远程仓库,如果你想更改默认仓库,可以使用如下指令:

git remote set-url origin <新的远程Git仓库地址>

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

git remote -v

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

git remote rm origin

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


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

GetPosLen()函数

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()

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

 SetContent()

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

#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函数。这是非常简单的。

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;
}

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

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

好,现在我们来测试一下

makefile

cloud:cloud.cc util.hpp bundle.cppg++ $^ -o $@ -lpthread
.PHONY:clean
clean:rm -f cloud

 cloud.cc

#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()函数的实现 

我们点开这个 

 

我们看看例子

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

// 使用实验性文件系统库(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.txtstd::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;
}

 这样子就很简单易懂了。

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

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()的实现

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

点进去看就明白了

也就是下面这个

bool exists(const path& p);

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

返回值

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

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

namespace fs = std::experimental::filesystem;
bool Exists()
{return fs::exists(_filename);
}

  •   Remove()的实现

点进去看看 

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

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

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

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

bool remove(const path& p);
  • p – 要删除的文件或空目录的路径。

  • 返回值

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

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

bool Remove()
{if(Exists() == false){return true;}remove(_filename.c_str());return true;
}

  •  CreateDirectory()的实现

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

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

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


接下来来测试一下

Util.hpp

#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 

#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

cloud:cloud.cc util.hpp bundle.cppg++ $^ -o $@ -lpthread -lstdc++fs
.PHONY:clean
clean:rm -f cloud

编译运行

发现它创建了一个目录

 

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


还是老样子!git push一下

 

 3.JSON实用工具类实现

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

JsonUtil类中包含以下成员

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的使用,接下来我们直接看函数的实现。

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 

cloud:cloud.cc util.hpp bundle.cppg++ $^ -o $@ -lpthread -lstdc++fs -ljsoncpp
.PHONY:clean
clean:rm -f cloud

cloud.cc 

 

#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",值为整型变量ageroot["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源代码

#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;}};
};

http://www.xdnf.cn/news/3470.html

相关文章:

  • 解决 Oracle EXPDP 無法鎖定 NFS 相關錯誤: ORA-27086 flock: No locks available
  • ActiveMQ 性能优化与网络配置实战(一)
  • 2025MathorCup数学应用挑战赛B题
  • 机器视觉开发-打开摄像头
  • GAMES202-高质量实时渲染(Real-time Environment Mapping)
  • 【二】 数字图像的运算 (下)【数字图像处理】
  • Java学习手册:Spring 数据访问
  • 系统架构设计师:设计模式概述
  • Centos7.9 安装mysql5.7
  • 突破zero-RL 困境!LUFFY 如何借离线策略指引提升推理能力?
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(13): ておきます ています & てあります
  • C++11新特性_Lambda 表达式
  • 世纪华通:从财报数据看其在游戏领域的成功与未来
  • 使用Java正则表达式进行分组与匹配文本提取
  • OpenAI最新发布的GPT-4.1系列模型,性能体验如何?
  • Unity 几种主流的热更新方式
  • 【C++】类和对象(中)——默认成员函数详解(万字)
  • 存算一体架构下的新型AI加速范式:从Samsung HBM-PIM看近内存计算趋势
  • Umi-OCR项目(1)
  • 产品设计三板斧与抓住事物本质的关键意义
  • 【iview】icon样式
  • Vue 生命周期全解析:理解组件从创建到销毁的全过程
  • FPGA中级项目8———UART-RAM-TFT
  • 【Android】四大组件之BroadcastReceiver
  • Lucene并不是只有倒排索引一种数据结构,支持多种数据结构
  • react学习笔记3——基于React脚手架
  • 杜邦分析法
  • Android12 Rom定制设置默认语言为中文
  • 如何拿奖蓝桥杯
  • 电机常用易混淆概念说明(伺服、舵机、多轮)