4. 文件工具类的设计
4.1 整体的类
该类实现对文件进行操作
FileUtil.hpp
如下
/*
该类实现对文件进行操作
*/
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <experimental/filesystem> // c++17 的文件系统库
#include "bundle.h"
namespace cloud
{
namespace fs = std::experimental::filesystem;
class FileUtil
{
private:
std::string _fileName;
struct stat _st; // 用于获取文件的各种属性
public:
// 输入一个路径
FileUtil(std::string fileName) : _fileName(fileName) {
if(stat(_fileName.c_str(), &_st) < 0) {
std::cerr << "获取文件属性失败!\nstat(_fileName.c_str(), &_st) error! why: " << strerror(errno) << '\n';
// exit(1);
}
}
// 获取文件大小(单位是字节)
int64_t getFileSize()
{
return _st.st_size; // 返回字节数,有符号的长整型
}
// 获取文件最后一次修改时间
time_t getLastMTime()
{
return _st.st_mtime;
}
// 获取文件最后一次访问时间
time_t getLastATime()
{
return _st.st_atime;
}
// 获得一个路径最后文件的名称,例如/abc/a.txt -> a.txt
std::string getFileName()
{
ssize_t pos = _fileName.find_last_of('/');
if(pos == std::string::npos) {
// 没有/,证明这就是一个文件,没有文件夹
return _fileName;
}
// 有/,要截取字符串
return _fileName.substr(pos+1);
}
// 从pos位置获取len长度的数据(单位是字节),写到s中
bool getContentFromPos(std::string *s, size_t pos, size_t len)
{
int64_t fileSz = getFileSize();
if(pos + len > fileSz) {
// 申请的长度大于文件长度
std::cerr << "Bad size!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
return false;
}
// 用来读取文件
std::ifstream ifile(_fileName, std::ios::binary);
// 是否打开成功
if(ifile.is_open() == false) {
std::cerr << "Open file error!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
ifile.close();
return false;
}
// 读取文件内容
ifile.seekg(pos, std::ios::beg);
s->resize(len);
ifile.read((char*)(s->c_str()), len);
// 检测这个流的状态是否ok
if(ifile.good() == false) {
std::cerr << "read file error!\nbool getContentFromPos(std::string *s, size_t pos, size_t len)\n";
ifile.close();
return false;
}
return true;
}
// 获取文件的所有内容,写到s中
bool getFullContent(std::string *s)
{
// 调用getContentFromPos()
return getContentFromPos(s, 0, getFileSize());
}
// 将字符串s的内容写入到文件中
bool setContent(const std::string& s)
{
// 用来像文件写入
std::ofstream ofile(_fileName, std::ios::binary);
// 是否打开成功
if(ofile.is_open() == false) {
std::cerr << "Open file error!\nbool setContent(const std::string& s)\n";
ofile.close();
return false;
}
ofile.write((char*)s.c_str(), s.size());
// 检测这个流的状态是否ok
if(ofile.good() == false) {
std::cerr << "write file error!\nbool setContent(const std::string& s)\n";
ofile.close();
return false;
}
return true;
}
// 压缩,packName是压缩包的名字
bool compresss(const std::string& packName)
{
// 先读取文件内容
std::string content;
if(getFullContent(&content) == false) {
std::cerr << "bool compresss(const std::string& packName) get content error!\n";
return false;
}
// 压缩文件内容,这里使用LZIP压缩格式
std::string packContent = bundle::pack(bundle::LZIP, content);
cloud::FileUtil newFile(packName);
// 将内容写到新的文件
if(newFile.setContent(packContent) == false) {
std::cerr << "bool compresss(const std::string& packName) set content error!\n";
return false;
}
return true;
}
// 解压,fileName是解压后新文件的名字
bool unCompress(const std::string& fileName)
{
// 先读取文件内容
std::string content;
if(getFullContent(&content) == false) {
std::cerr << "bool unCompress(const std::string& packName) get content error!\n";
return false;
}
// 解压文件内容
std::string unpackContent = bundle::unpack(content);
cloud::FileUtil newFile(fileName);
// 将内容写到新的文件
if(newFile.setContent(unpackContent) == false) {
std::cerr << "bool unCompresss(const std::string& packName) set content error!\n";
return false;
}
return true;
}
// 判断文件是否存在,存在返回true
bool exits()
{
return fs::exists(_fileName);
}
// 创建目录,创建成功返回true
bool createDir()
{
// 如果该文件存在了,就直接返回true
if(exits()) return true;
return fs::create_directories(_fileName);
}
// 扫描文件夹下的文件,放到数组中
bool scanDir(std::vector<std::string> *array)
{
for (const fs::directory_entry& entry : fs::directory_iterator(_fileName)) {
if(fs::is_directory(entry) == true) {
// 如果是目录的话,跳过,该函数只扫描一般文件
continue;
}
array->push_back(entry.path().relative_path().string());
}
}
};
}
makefile如下
cloud : Cloud.cpp bundle.cpp FileUtil.hpp
g++ $^ -o $@ -lpthread -lstdc++fs
.PHONY : clean
clean:
rm -f cloud
4.2 测试
4.2.1 测试获取文件属性功能
Cloud.cpp
#include "FileUtil.hpp"
// 测试获取文件属性功能
void testFileUtil(const std::string& s)
{
cloud::FileUtil f(s);
printf("文件大小: %ld\n", f.getFileSize());
printf("文件最后一次修改时间: %ld\n", f.getLastMTime());
printf("文件最后一次访问时间: %ld\n", f.getLastATime());
printf("获得一个路径最后文件的名称: %s\n", f.getFileName().c_str());
}
int main(int argc, char* argv[])
{
if(argc != 2) {
std::cerr << "usage error!\n";
return -1;
}
testFileUtil(argv[1]);
return 0;
}
结果如下
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud test.txt
文件大小: 28
文件最后一次修改时间: 1734504144
文件最后一次访问时间: 1734504157
获得一个路径最后文件的名称: test.txt
---------------------------------------------------------------------------
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud ../../lib/bundle/README.md
文件大小: 16571
文件最后一次修改时间: 1734185453
文件最后一次访问时间: 1734185453
获得一个路径最后文件的名称: README.md
---------------------------------------------------------------------------
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud aaaa.txt
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
4.2.2 测试文件读写功能
void testFileUtil2(const std::string& fileName)
{
cloud::FileUtil f(fileName);
std::string s;
f.getFullContent(&s);
// std::cout << s;
cloud::FileUtil of("writeTest.txt"); // 打开一个名为writeTest.txt的文件,没有则创建
of.setContent(s);
}
int main(int argc, char* argv[])
{
if(argc != 2) {
std::cerr << "usage error!\n";
return -1;
}
testFileUtil2(argv[1]);
return 0;
}
目前有个文件test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ cat test.txt
abcdefghigklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
---------------------------------------------------------------------------
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud test.txt
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ll
total 52
drwxrwxr-x 2 lyf lyf 4096 Dec 18 16:20 ./
drwxrwxr-x 23 lyf lyf 4096 Dec 18 11:31 ../
-rwxrwxr-x 1 lyf lyf 24144 Dec 18 16:20 cloud*
-rw-rw-r-- 1 lyf lyf 969 Dec 18 16:16 Cloud.cpp
-rw-rw-r-- 1 lyf lyf 3516 Dec 18 16:20 FileUtil.hpp
-rw-rw-r-- 1 lyf lyf 67 Dec 18 16:14 makefile
-rw-rw-r-- 1 lyf lyf 53 Dec 18 16:14 test.txt
-rw-rw-r-- 1 lyf lyf 53 Dec 18 16:20 writeTest.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ cat writeTest.txt
abcdefghigklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
4.2.3 测试压缩和解压缩功能
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud bundle.cpp
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ls -lh
total 15M
-rw-rw-r-- 1 lyf lyf 5.4M Dec 18 19:14 bundle.cpp
-rw-rw-r-- 1 lyf lyf 668K Dec 18 19:34 bundle.cpp.lz
-rw-rw-r-- 1 lyf lyf 29K Dec 18 19:14 bundle.h
-rwxrwxr-x 1 lyf lyf 3.3M Dec 18 19:33 cloud
-rw-rw-r-- 1 lyf lyf 1.3K Dec 18 19:33 Cloud.cpp
-rw-rw-r-- 1 lyf lyf 5.0K Dec 18 19:24 FileUtil.hpp
-rw-rw-r-- 1 lyf lyf 101 Dec 18 19:28 makefile
-rw-rw-r-- 1 lyf lyf 5.4M Dec 18 19:34 newname.cpp
-rw-rw-r-- 1 lyf lyf 23 Dec 18 19:32 test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ md5sum bundle.cpp
4cb64c7a8354c82402dd6fe080703650 bundle.cpp
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ md5sum newname.cpp
4cb64c7a8354c82402dd6fe080703650 newname.cpp
4.2.4 测试目录功能
// 测试目录操作
void testFileUtil4(const std::string& fileName)
{
cloud::FileUtil f(fileName);
f.createDir(); // 创建一个文件夹
std::vector<std::string> array;
f.scanDir(&array);
// 打印目录下的文件内容
for(const auto& e : array) {
std::cout << e << '\n';
}
}
# 获取文件夹下的所有文件
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ls
bundle.cpp bundle.h cloud Cloud.cpp FileUtil.hpp makefile test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud test
获取文件属性失败!
stat(_fileName.c_str(), &_st) error! why: No such file or directory
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ls
bundle.cpp bundle.h cloud Cloud.cpp FileUtil.hpp makefile test test.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ cd test/
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ ll
total 8
drwxrwxr-x 2 lyf lyf 4096 Dec 18 21:53 ./
drwxrwxr-x 3 lyf lyf 4096 Dec 18 21:53 ../
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch a.txt
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch b.cc
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch c.java
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ touch d.python
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份/test$ cd ../
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud ./test
./test/c.java
./test/d.python
./test/b.cc
./test/a.txt
5. Json工具类的设计
5.1 整体的类
#pragma once
#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
#include <sstream>
#include <memory>
namespace cloud
{
class JsonUtil
{
public:
// 将root的序列化结果保存在str中
static bool serialize(const Json::Value& root, std::string* str)
{
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> ps(swb.newStreamWriter());
std::stringstream ss;
if (ps->write(root, &ss) != 0) {
std:: cerr << "write error!\nstatic bool serialize(const Json::Value& root, std::string* str)\n";
return false;
}
*str = ss.str();
return true;
}
// 将str的序列化结果保存在root中
static bool unserialize(const std::string& str, Json::Value* root)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> ps(crb.newCharReader());
std::string errs;
if (ps->parse(str.c_str(), str.c_str() + str.size(), root, &errs) == false) {
std::cerr << "parse error!, why: " << str << "\nstatic bool unserialize(const std::string& str, Json::Value* root)\n";
return false;
}
return true;
}
};
}
5.2 测试序列化工具类
// 测试JsonUtil
void testJsonUtil()
{
/* 序列化 */
std::string name = "zhangsan";
int age = 20;
int scores[] = {80, 90, 100};
// 给数据对象类添加数据
Json::Value value;
value["name"] = name;
value["age"] = age;
value["score"].append(scores[0]);
value["score"].append(scores[1]);
value["score"].append(scores[2]);
std::string str;
cloud::JsonUtil::serialize(value, &str);
printf("序列化结果:\n %s\n%s\n", str.c_str(), "========================================");
Json::Value oValue;
/* 反序列化, 将s反序列化*/
cloud::JsonUtil::unserialize(str, &oValue);
printf("反序列化结果: \nname: %s\nage: %d\n",
(oValue["name"].asString()).c_str(), oValue["age"].asInt());
for(int i = 0; i < 3; ++i) {
printf("成绩%d: %d\n", i, oValue["score"][i].asInt());
}
}
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud
序列化结果:
{
"age" : 20,
"name" : "zhangsan",
"score" :
[
80,
90,
100
]
}
========================================
反序列化结果:
name: zhangsan
age: 20
成绩0: 80
成绩1: 90
成绩2: 100
6. 服务端所用到的配置信息
6.1 json格式的配置信息
服务器访问 IP 地址
服务器访问端⼝
热点判断时间(多长时间没有被访问的文件属于非热点文件)
URL路径前缀(如
http://www.example.com/path/to/file
的路径前缀是/path/
)压缩包后缀名称 (在文件原名后面加上该后缀)
上传⽂件存放路径(上传后的文件储存在服务器的哪个文件夹下)
压缩⽂件存放路径(压缩文件储存在服务器的哪个文件夹下)
服务端备份信息存放文件名 (可以通过数据库存放)
使用json
格式存放,下面是一个实例。cloud.conf
TODO: 构造函数出错返回?
将bundle变成一个库
{
"serverIp" : "124.70.203.1",
"serverPort" : 9000,
"hotTime" : 30,
"pathPre" : "/listShow/",
"rarSuf" : ".lz",
"ulPath" : "./files",
"rarPath" : "./rars",
"backups" : "./backups.data"
}
6.2 加载配置信息类
6.2.1 完整的类
使用单例模式。config.hpp
/* 用于加载配置文件 */
#include "FileUtil.hpp"
#include "JsonUtil.hpp"
#include <mutex>
#define CONF_PATH "./cloud.conf"
namespace cloud
{
class Config
{
private:
static Config* _instance;
static std::mutex _mtx;
Config(const Config& c) = delete;
Config& operator=(const Config& c) = delete;
Config()
{
if(loadFiles() == false) delInstance();
}
// 从配置文件中加载数据,放到各个属性中
bool loadFiles()
{
// 打开文件
cloud::FileUtil fu(CONF_PATH);
std::string content;
if(fu.getFullContent(&content) == false) {
std::cerr << "getFullContent error!\nbool loadFiles()\n";
return false;
}
// json转换
Json::Value root;
if(cloud::JsonUtil::unserialize(content, &root) == false) {
std::cerr << "unserialize error!\nbool loadFiles()\n";
return false;
}
// 初始化
_serverIp = root["serverIp"].asString();
_serverPort = root["serverPort"].asInt();
_hotTime = root["hotTime"].asInt64();
_pathPre = root["pathPre"].asString();
_rarSuf = root["rarSuf"].asString();
_ulPath = root["ulPath"].asString();
_rarPath = root["rarPath"].asString();
_backups = root["backups"].asString();
return true;
}
private:
std::string _serverIp; // 服务器访问 IP 地址
int _serverPort; // 服务器访问端⼝
std::time_t _hotTime; // 热点判断时间
std::string _pathPre; // URL路径前缀
std::string _rarSuf; // 压缩包后缀名称
std::string _ulPath; // 上传文件存放的路径
std::string _rarPath; // 压缩文件存放的路径
std::string _backups; // 备份文件
public:
// 获取实例
static Config* getInstance()
{
if(_instance == nullptr) {
// _instance为null时,才有加锁解锁的必要。外面多加一层判断可以防止每次getInstance()时都要申请锁
std::unique_lock<std::mutex> lck(_mtx);
if(_instance == nullptr) {
_instance = new Config();
}
}
return _instance;
}
// 删除实例
static void delInstance()
{
if(_instance != nullptr) {
std::cout << "log: static void delInstance()\n";
delete _instance;
_instance = nullptr;
}
}
std::string getServerIp() { return _serverIp; }
int getServerPort() { return _serverPort; }
std::time_t getHotTime() { return _hotTime; }
std::string getPathPre() { return _pathPre; }
std::string getRarSuf() { return _rarSuf; }
std::string getUlPath() { return _ulPath; }
std::string getRarPath() { return _rarPath; }
std::string getBackups() { return _backups; }
};
Config* Config::_instance = nullptr;
std::mutex Config::_mtx;
}
6.2.2 测试
// 测试Config.hpp
void testConfig()
{
cloud::Config* cof = cloud::Config::getInstance();
std::cout << cof->getServerIp() << '\n';
std::cout << cof->getServerPort() << '\n';
std::cout << cof->getHotTime() << '\n';
std::cout << cof->getPathPre() << '\n';
std::cout << cof->getRarSuf() << '\n';
std::cout << cof->getUlPath() << '\n';
std::cout << cof->getRarPath() << '\n';
std::cout << cof->getBackups() << '\n';
}
lyf@hcss-ecs-3db9:~/pro/pro24_12_18云备份$ ./cloud
124.70.203.1
9000
30
/listShow/
.lz
./files
./rars
./backups.data