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

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.mzph.cn/pingmian/79256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CGI 协议是否会具体到通讯报文?

CGI&#xff08;Common Gateway Interface&#xff09;不涉及具体的网络通讯报文格式&#xff0c;它定义的是 Web服务器与外部程序之间的数据交互方式&#xff0c;而不是像HTTP或FastCGI那样的二进制协议。下面分几个方面详细说明&#xff1a; 1. CGI 的交互方式&#xff08;非…

【Mytais系列】Type模块:类型转换

MyBatis 的 类型系统&#xff08;Type System&#xff09; 是框架处理 Java 类型与数据库类型之间映射的核心模块&#xff0c;它通过 类型处理器&#xff08;TypeHandler&#xff09;、类型别名&#xff08;TypeAlias&#xff09; 和 类型转换器 等机制&#xff0c;实现了数据库…

新华三H3CNE网络工程师认证—动态NAT

静态NAT严格地一对一进行地址映射&#xff0c;这就导致即便内网主机长时间离线或者不发送数据时&#xff0c;与之对应的共有地址也处于使用状态。为了避免地址浪费&#xff0c;动态NAT提出了地址池的概念&#xff1a;所有可用的共用地址组成地址池。 当内部主机访问外部网络时临…

华为OD机试真题 Java 实现【水库蓄水问题】

前言 博主刷的华为机考题&#xff0c;代码仅供参考&#xff0c;因为没有后台数据&#xff0c;可能有没考虑到的情况 如果感觉对你有帮助&#xff0c;请点点关注点点赞吧&#xff0c;谢谢你&#xff01; 题目描述 思路 1. 其实就是找一个最大的水坑&#xff0c;两个…

【Linux】Petalinux驱动开发基础

基于Petalinux做Linux驱动开发。 部分图片和经验来源于网络,若有侵权麻烦联系我删除,主要是做笔记的时候忘记写来源了,做完笔记很久才写博客。 专栏目录:记录自己的嵌入式学习之路-CSDN博客 目录 1 一个完整的Linux系统(针对Zynq) 1.1 PS部分 1.2 PL部分(若…

JAVA刷题记录: 递归,搜索与回溯

专题一 递归 面试题 08.06. 汉诺塔问题 - 力扣&#xff08;LeetCode&#xff09; class Solution {public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {dfs(A, B, C, A.size());}public void dfs(List<Integer> a, List<In…

YOLOv11改进:利用RT-DETR主干网络PPHGNetV2助力轻量化目标检测

这里写自定义目录标题 YOLOv11改进&#xff1a;利用RT-DETR主干网络PPHGNetV2助力轻量化目标检测1. 介绍2. 引言3. 技术背景3.1 YOLOv11概述3.2 RT-DETR与PPHGNetV23.3 相关工作 4. 应用使用场景5. 详细代码实现5.1 环境准备5.2 PPHGNetV2主干网络实现5.3 YOLOv11与PPHGNetV2集…

WPF之Button控件详解

文章目录 1. 引言2. Button控件基础Button类定义 3. Button控件的核心属性3.1 Content属性3.2 IsDefault属性3.3 IsCancel属性3.4 其他常用属性 4. 按钮样式与模板自定义4.1 简单样式设置4.2 使用Style对象4.3 触发器使用4.4 使用ControlTemplate完全自定义4.5 按钮视觉状态 5.…

【Java】2025 年 Java 学习路线:从入门到精通

文章目录 一、Java基础阶段(4-8周)1. 开发环境搭建2. 核心语法基础3. 面向对象编程(OOP)4. 核心类库二、Java进阶阶段(6-10周)1. JVM深度理解2. 并发编程3. 新特性掌握4. 设计模式三、开发框架与中间件(8-12周)1. Spring生态2. 持久层框架3. 常用中间件四、项目实战阶段…

虚幻引擎入门笔记

【虚幻5】UE5新手入门尝试 虚幻引擎的基础设置 1.验证-当文件误删的时候&#xff0c;对其进行验证&#xff0c;可以恢复。 2.虚幻引擎极其强大&#xff0c;可以实现多种复合技能&#xff0c;所在创建项目页面可以看见不只是创建游戏的项目 3.更改虚幻引擎默认的缓存地址。有些…

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】1.1 数据库核心概念与PostgreSQL技术优势

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 深度解析PostgreSQL核心架构与技术优势&#xff1a;从数据库原理到实战场景1.1 数据库核心概念与PostgreSQL技术优势1.1.1 关系型数据库核心架构解析1.1.1.1 数据库系统的底…

详解SLAM中的李群和李代数(上)

1 概述 最近阅读高翔大神的《视觉SLAM十四讲》这本书&#xff0c;感觉整本书写的非常的平实&#xff0c;用非常接地气的语言毫无保留的介绍了视觉SLAM的相关知识&#xff0c;非常值得一读。不过&#xff0c;在第4章出现的李群和李代数的相关概念就有点令人难以费解了。其实这段…

libevent库详解:高性能异步IO的利器

目录 一、libevent 简介 主要特点&#xff1a; 二、事件模型原理 1. event_base 2. event 3. evconnlistener&#xff08;TCP监听器&#xff09; 4. bufferevent 简化流程如下&#xff1a; 三、libevent 使用示例 1. 创建事件主循环 2. 创建监听器&#xff08;TCP&a…

从 “零” 做个开源音乐软件“SteadyBeat”吧!<1> 准备

换换脑子&#xff0c;做个音乐软件&#xff0c;根据调性、和弦走向&#xff08;情感&#xff09;、节拍、速度等需求&#xff0c;结合AI和一众工具&#xff0c;自动生成伴奏、Solo等&#xff0c;有点像库乐队&#xff01;自己平时也用得着&#xff0c;暂时取名叫《SteadyBeat》…

npm error code CERT_HAS_EXPIRED

npm error code CERT_HAS_EXPIRED 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开发者社区主理人 擅长.net、C、python开发&#xff0c; 如果遇到技术问题&#xff0c;即可私…

数字世界的“私人车道“:网络切片如何用Python搭建专属通信高速路?

数字世界的"私人车道"&#xff1a;网络切片如何用Python搭建专属通信高速路&#xff1f; 2024年6月&#xff0c;中国移动宣布在浙江某智能工厂完成全球首个"5G工业网络切片"规模商用——这条为生产线定制的"数字专属车道"&#xff0c;将设备控制…

VSCode Verilog编辑仿真环境搭建

VSCode Verilog环境搭建 下载Iverilog安装Iverilog验证安装VS Code安装插件 下载Iverilog 官网下载Iverilog 安装Iverilog 一定要勾选这两项 建议勾选这两项 验证安装 运行Windows PowerShell输入命令&#xff1a;iverilog输入命令&#xff1a;Get-Command gtkwave …

C++ - 数据容器之 list(创建与初始化、元素访问、容量判断、元素遍历、添加元素、删除元素)

一、创建与初始化 引入 <list> 并使用 std 命名空间 #include <list>using namespace std;创建一个空 list list<int> my_list;创建一个包含 5 个元素&#xff0c;每个元素初始化为 0 的 list list<int> my_list(5);创建一个包含 5 个元素&#xf…

自动化测试项目1 --- 唠嗑星球 [软件测试实战 Java 篇]

目录 项目介绍 项目源码库地址 项目功能测试 1.自动化实施步骤 1.1 编写测试用例 1.2 自动化测试脚本开发 1.2.1 配置相关环境, 添加相关依赖 1.2.2 相关代码编写 2. 自动化功能测试总结 2.1 弹窗的解决相关问题 2.2 断言的使用和说明 2.3 重新登录问题 项目性能…

Codeforces Round 1022 (Div. 2)(ABC)

A. Permutation Warm-Up 翻译&#xff1a; 对于长度为 n 的排列 p&#xff0c;我们定义函数&#xff1a; 给你一个数 n。你需要计算函数 f(p) 在考虑从 1 到 n 的所有可能的数字排列时&#xff0c;可以取多少个不同的值。 思路&#xff1a; 按序排列时和为0&…