云备份实战项目

news2024/12/27 12:17:08

文章目录

  • 前言
  • 一、整体项目简介
  • 二、服务端环境及功能简介
  • 三、 客户端环境及功能简介
  • 四、服务端文件管理类的实现
    • 1. 获取文件大小,最后一次修改时间,最后一次访问时间,文件名称,以及文件内容的读写等功能
    • 2. 判断文件是否存在,创建目录,浏览目录等功能
    • 3. 对文件进行压缩和解压缩处理
  • 五、服务端Json序列化工具的实现
  • 六、 服务端读取配置文件类的实现
  • 七、服务端构建备份信息的类的实现
  • 八、服务端数据管理类的实现
  • 九、服务端热点管理的实现
  • 十、服务端网络通信及业务处理实现
  • 十一、客户端数据管理
  • 十二、客户端网络通信及备份请求
  • 总结


前言

项目主要是是将客户端的文件备份到服务端,并且在服务端合理进行压缩处理,节省空间。并且服务端还支持通过浏览器请求页面展示, 来展示服务端备份的文件,同时还支持下载请求,将服务端备份的文件下载到客户端, 若中途服务器停止运行,还支持断点续传


一、整体项目简介

在这里插入图片描述

二、服务端环境及功能简介

  • 服务端环境 Linux Ubuntu 18.04
  • 具体功能包括 文件管理,Json序列化和反序列化, 读取配置文件, 备份信息, 数据管理, 热点管理,网络通信等
  • 项目再编写服务端代码是,使用vscode远程链接Linux服务器,方便编码。
    在这里插入图片描述

三、 客户端环境及功能简介

  • 客户端环境 Windows vs2022
  • 具体功能包括 文件管理, 数据管理, 客户端网络通信及备份信息等

在这里插入图片描述

四、服务端文件管理类的实现

在这里插入图片描述

1. 获取文件大小,最后一次修改时间,最后一次访问时间,文件名称,以及文件内容的读写等功能

  • 目的, 文件管理类主要是便于后续(数据管理,热点管理等)对文件的操作
    1. 使用C语言的系统调用函数获取文件的大小,最后一次修改时间,最后一次访问时间
    1. 通过string的查找成员函数,找到路径分隔符,截取文件名称
    1. 通过ofstream将字符串设置为文件内容
    1. 通过ifstream获取文件指定位置,指定长度的内容,并复用获取整个文件内容
namespace hhbcloud
{
    // 简化命名空间
    namespace fs = std::experimental::filesystem;
    class FileUtil
    {
    public:
        FileUtil(const std::string& filename)
            :_filename(filename)
        {
        }

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

        // 获取文件大小
        size_t FileSize()
        {
            struct stat st; // 使用stat来获取文件的各种信息
            if (stat(_filename.c_str(), &st) < 0) // stat函数成功返回0,失败返回-1
            {
                std::cout << "get file size error" << std::endl;
                return -1;
            }

            return st.st_size;
        }

        // 获取文件最后一次修改时间
        time_t LastMTime()
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get lastmtime error" << std::endl;
                return -1;
            }

            return st.st_mtime;
        }

        // 获取文件最后一次访问时间
        time_t LastATime()
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get lastatime error" << std::endl;
                return -1;
            }

            return st.st_atime;
        }

        // 获取文件路径中的文件名称
        std::string FileName()
        {
            size_t pos = _filename.find_last_of("/"); // 从后往前找第一个/
            if (pos == std::string::npos)
            {
                // 此时没有找到/说明文件路径就是文件名称
                return _filename;
            }

            return _filename.substr(pos + 1);
        }


        // 设置文件内容
        bool SetContent(const std::string& body)
        {
            // 打开文件
            std::ofstream ofs;
            ofs.open(_filename, std::ios::binary); // 以二进制形式打开文件,若文件不存在会自动创建文件
            if (ofs.is_open() == false)
            {
                std::cout << "write open file error" << std::endl;
                return false;
            }

            // 打开文件成功后,将body的内容写到文件中
            ofs.write(&body[0], body.size());
            if (ofs.good() == false) // 判断文件写入过程中是否出错
            {
                std::cout << "write file error" << std::endl;
                ofs.close();
                return false;
            }

            ofs.close();
            return true;
        }

        // 读取文件指定位置 指定长度的内容, body用来接收获取到的数据
        bool GetPosLen(std::string* body, size_t pos, size_t len)
        {
            // 判断 pos + len 是否超出文件大小
            size_t size = FileSize();
            if (pos + len > size)
            {
                std::cout << "pos + len more than file size" << std::endl;
                return false;
            }

            // 读的形式打开文件
            std::ifstream ifs;
            ifs.open(_filename.c_str(), std::ios::binary);//二进制形式打开
            if (ifs.is_open() == false) // 打开失败报错
            {
                std::cout << "read open file error" << std::endl;
                return false;
            }

            // 打开成功后,先为body开辟足够的大小
            body->resize(len); // 避免多次安装

            // 从文件起始位置定位到pos位置
            ifs.seekg(pos, std::ios::beg);
            // 读取内容
            ifs.read(&(*body)[0], len);
            if (ifs.good() == false) // 判断读取文件过程是否出错
            {
                std::cout << "read file error" << std::endl;
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }

        // 读取整个文件的内容
        bool GetContent(std::string* body)
        {
            size_t size = FileSize();
            return GetPosLen(body, 0, size); // 复用获取文件指定位置指定长度函数
        }
    private:
        std::string _filename;
    };
}

测试上述功能

void TestFileUtil(const std::string& filename)
{
    hhbcloud::FileUtil fu(filename);

    std::cout << fu.FileSize() << std::endl;
    std::cout << fu.LastMTime() << std::endl;
    std::cout << fu.LastATime() << std::endl;
    std::cout << fu.FileName() << std::endl;

    std::string str = "hello world!!!--";
    fu.SetContent(str);

    std::string body;
    fu.GetContent(&body);
    std::cout << body << std::endl;

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

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cout << "argv[2]" << std::endl;
        return -1;
    }

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

在这里插入图片描述

2. 判断文件是否存在,创建目录,浏览目录等功能

  • 此处功能需要C++17的文件系统, C++14也支持
  • 需要高版本的gcc才能支持
  • 简化命名空间 可以便于使用文件系统

需要包含的头文件, #include<experimental/filesystem>
简化命名空间可以使用: namespace fs = std::experimental::filesystem;

// 判断文件是否存在
bool Exists()
{
    return fs::exists(_filename); // c++17文件系统的函数判断文件是否存在
}

// 创建目录
bool CreateDirectory()
{
    if (Exists() == true) return true; // 若文件存在则不必创建
    return fs::create_directories(_filename); // 创建目录时,传入路径,则创建多级目录
}
// 获取指定目录下的所有文件名,并存放到array数组中
bool ScanDirectory(std::vector<std::string>* array)
{
    // 使用 fs::directory_iterator 函数获取指定目录下的所有文件
    for (auto& file : fs::directory_iterator(_filename))
    {
        // 应该判断获取的文件是否是目录,因为目录下可能还有目录
        if (fs::is_directory(file) == true) // is_derectory 判断是否是目录
        {
            continue; // 如果是目录文件则跳过
        }

        // file并不是string类对象,可以使用path函数实例化,relative_path获取相对路径
        // string带路径的string的文件名
        array->push_back(fs::path(file).relative_path().string());
    }
}

测试上述功能

void TestFileUtil(const std::string& filename)
{
    hhbcloud::FileUtil fu(filename);
    fu.CreateDirectory();

    std::vector<std::string> array;
    fu.ScanDirectory(&array);

    for (auto& e : array)
    {
        std::cout << e << std::endl;
    }
    std::cout << std::endl;
}

在这里插入图片描述

3. 对文件进行压缩和解压缩处理

  • 下载第三方bundle库, 并将bundle.cpp和bundle.h 移动到当前文件夹下
  • 在编译时,bundle.cpp编译过长,所以将bundle.cpp生成静态库
  • 将bundle
  1. 生成.o文件
g++ -c bundle.cpp -o bundle.o
  1. 依赖bundle.o生成静态库
ar -cr libbundle.a bundle.o
  1. 创建lib目录,将libbundle.a放置lib目录下,完成后可以删除bundle.cpp和bundle.o,编译时需要如下
g++ -o test Test.cpp -Llib -lstdc++fs -lbundle -pthread
  • bundle库内部使用了多线程,所以编译需要注意
// 压缩文件
bool Compress(const std::string& packname)
{
    // 先将文件读出来
    std::string body;
    if (GetContent(&body) == false)
    {
        std::cout << "compress get file content error" << std::endl;
        return false;
    }

    // 使用pack函数对读出来的内容进行压缩
    std::string packed = bundle::pack(bundle::LZIP, body); // packed中存储的即为压缩后的数据

    // 将压缩后的文件写入packname文件中
    FileUtil fu(packname);
    if (fu.SetContent(packed) == false)
    {
        std::cout << "compress set packname content error" << std::endl;
        return false;
    }
    return true;
}

// 文件解压缩
bool Uncompress(const std::string& unpackname)
{
    // 先读取文件内容
    std::string body;
    if (GetContent(&body) == false)
    {
        std::cout << "uncompress get file content error" << std::endl;
        return false;
    }

    // 使用unpack解压缩文件
    std::string unpacked = bundle::unpack(body); // unpacked存储的是解压缩后的数据

    // 将解压后的文件写到解压文件中
    FileUtil fu(unpackname);
    if (fu.SetContent(unpacked) == false)
    {
        std::cout << "uncompress set file content error" << std::endl;
        return false;
    }
    return true;

}

测试压缩和解压缩功能

void TestFileUtil(const std::string& filename)
{
    hhbcloud::FileUtil fu(filename);

    std::string packname = filename + ".lz";
    fu.Compress(packname);

    std::string unpackname = "hello.txt";
    hhbcloud::FileUtil nfu(packname);
    nfu.Uncompress(unpackname);
}

在这里插入图片描述

五、服务端Json序列化工具的实现

  • json工具本质是对序列化和反序列化进行封装,方便后续操作
  • json只有两个成员函数(序列化和反序列化),使用时不要实例化类对象
  • 所以json的两个成员函数都是静态的。
  • 使用jsoncpp之前先安装jsoncpp, 并且编译时,需要加上 -ljsoncpp
class JsonUtil // json工具目的是封装序列化和反序列化的函数
    {
    public:
        // JsonUtil的序列化和反序列化是一种封装比较单一,不需要创建对象调用成员函数,所以将成员函数设置成静态
        static bool Serialization(Json::Value& root, std::string* str) // 讲一个结构体序列化为字符串
        {
            Json::StreamWriterBuilder swb; // 创建一个 json::StreamWriterBuilder 对象
            // 使用智能指针管理一个由swb创建的Json::StreamWriter 对象
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
            std::stringstream ss; // 创建stringstream,将root序列化后的字符串写入stringstream
            // 使用sw的write函数将root写入stringstream
            if (sw->write(root, &ss) != 0)
            {
                std::cout << "Serialization write error" << std::endl;
                return false;
            }

            *str = ss.str();
            return true;
        }
		// 反序列化就是将一个字符串,反序列化成一个json结构体
        static bool Deserialization(const std::string& str, Json::Value* root) 
        {
            Json::CharReaderBuilder crb; // 创建一个Json::CharReaderBuilder
            // 使用智能指针管理crb创建的son::CharReader对象
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

            std::string err; // 用来记录错误信息,cr->parse函数要求必须err参数
            if (cr->parse(str.c_str(), str.c_str() + str.size(), root, &err) == false)
            {
                std::cout << "Deserialization parse error" << std::endl;
                return false;
            }
            return true;
        }
    };

测试json的功能

void TestJsonUtil()
{
    // 序列化

    // 构建一个JSON结构体
    Json::Value root;
    root["姓名"] = "小帅";
    root["年龄"] = 18;
    float score[3] = { 88.5, 66.7, 99.8 };
    for (int i = 0;i < 3; i++)
    {
        root["分数"][i] = score[i];
    }

    // 序列化到str中
    std::string str;
    hhbcloud::JsonUtil::Serialization(root, &str);
    std::cout << str << std::endl;

    // 返序列化
    Json::Value root1;
    hhbcloud::JsonUtil::Deserialization(str, &root1);

    std::cout << root1["姓名"].asString() << std::endl;
    std::cout << root1["年龄"].asInt() << std::endl;
    for (int i = 0;i < root1["分数"].size(); i++)
    {
        std::cout << root1["分数"][i].asFloat() << std::endl;
    }
}
int main(int argc, char* argv[])
{
    TestJsonUtil();
}

在这里插入图片描述

六、 服务端读取配置文件类的实现

  • 定义配置文件如下:
    在这里插入图片描述
  • 借助文件管理类,实现读取配置配置文件的类
  • 整个项目只有一个配置文件,配置文件类只需要在使用的时候实例化一份对象
  • 所以将读取配置文件类设计成单例模式的懒汉模式

在这里插入图片描述

namespace hhbcloud
{
    class Config // 类的作用是获取配置文件中的数据,成员变量即为配置文件中所有的内容
    {
    public:
        // 因为整个系统同一时刻只需要一个读取配置文件的类,因此把他设计成单例模式,并使用懒汉模式提高局部性能
        static Config* GetInstance() // 不需要创建类对象,所以静态成员函数
        {
            if (_instance == nullptr)
            {
                _mutex.lock(); // 由于此处加锁会极大的影响效率,所以在外部加一层判断,提高效率
                if (_instance == nullptr) // 此处需要加锁,防止多线程出错
                {
                    _instance = new Config();
                }
                _mutex.unlock();
            }
            return _instance;
        }

        // 获取热点管理时间
        int GetHotTime()
        {
            return _hot_time;
        }

        // 获取服务器监听ip
        std::string GetServerIp()
        {
            return _server_ip;
        }
        // 获取服务器监听port
        int GetServerPort()
        {
            return _server_port;
        }

        // 获取下载文件的url前缀
        std::string GetDownloadPrefix()
        {
            return _download_prefix;
        }

        // 获取压缩文件的后缀
        std::string GetPackfileSuffix()
        {
            return _packfile_suffix;
        }

        // 获取压缩文件的存储路径
        std::string GetPackDir()
        {
            return _pack_dir;
        }

        // 获取备份文件的存储路径
        std::string GetBackDir()
        {
            return _back_dir;
        }

        // 获取所有文件备份信息的文件
        std::string GetBackupFile()
        {
            return _backup_file;
        }
    private:
        Config()
        {
            ReadConfigFile(); // 在创建对象时,调用读取文件信息的函数
        }

        bool ReadConfigFile() // 利用文件管理工具和json序列化反序列化工具,将配置文件的内容读到config对象中
        {
            FileUtil fu(CONFIG_FILE);
            std::string body;
            fu.GetContent(&body); // 获取整个文件的内容

            // 将字符串反序列化为JSON结构体
            Json::Value root;
            JsonUtil::Deserialization(body, &root);

            _hot_time = root["hot_time"].asInt();
            _server_ip = root["server_ip"].asString();
            _server_port = root["server_port"].asInt();
            _download_prefix = root["download_prefix"].asString();
            _packfile_suffix = root["packfile_suffix"].asString();
            _pack_dir = root["pack_dir"].asString();
            _back_dir = root["back_dir"].asString();
            _backup_file = root["backup_file"].asString();
        }

    private:
        int _hot_time; // 热点判断时间
        std::string _server_ip; // 服务器监听ip
        int _server_port; // 服务器监听端口
        std::string _download_prefix; // 下载文件的url前缀
        std::string _packfile_suffix; // 压缩文件的后缀
        std::string _pack_dir; // 压缩文件的存储路径
        std::string _back_dir; // 备份文件的存储路径
        std::string _backup_file; // 备份文件信息的存储文件

        // 外部使用config时,使用GetInstance函数,因为其是静态,无法访问非静态成员变量,所以将_instance设置为静态
        static Config* _instance; // 懒汉模式,默认先创建一个指针,需要的时候在实例化对象
        static std::mutex _mutex; // 懒汉模式在实例化对象时,需要加锁

    };

    Config* Config::_instance = nullptr; // 静态成员变量默认在类外定义
    std::mutex Config::_mutex;
}

测试读取配置文件的类

void TestConfig()
{
    hhbcloud::Config* config = hhbcloud::Config::GetInstance();

    std::cout << config->GetHotTime() << std::endl;
    std::cout << config->GetServerIp() << std::endl;
    std::cout << config->GetServerPort() << std::endl;
    std::cout << config->GetDownloadPrefix() << std::endl;
    std::cout << config->GetPackfileSuffix() << std::endl;
    std::cout << config->GetPackDir() << std::endl;
    std::cout << config->GetBackDir() << std::endl;
    std::cout << config->GetBackupFile() << std::endl;
}
int main(int argc, char* argv[])
{
    TestConfig();
    return 0;
}

在这里插入图片描述

七、服务端构建备份信息的类的实现

在这里插入图片描述

struct Backinfo // 定义并获取文件存在在服务器上的备份信息
    {
        bool NewBackinfo(const std::string& realpath)
        {
            FileUtil fu(realpath);
            if (fu.Exists() == false) // 文件不存在则报错返回
            {
                std::cout << "newBackinfo file is not exists" << std::endl;
                return false;
            }

            _pack_flag = false;

            _fsize = fu.FileSize();
            _atime = fu.LastATime();
            _mtime = fu.LastMTime();
            _real_path = realpath;

            // config获取配置文件中定义的单例类 
            Config* config = Config::GetInstance();

            // 压缩路径应该是 配置文件中的压缩目录 + 文件名称 + 文件压缩后缀
            _pack_path = config->GetPackDir() + fu.FileName() + config->GetPackfileSuffix();

            // url下载路径应该是 配置文件中下载前缀 + 文件名称
            std::string downloadPrefix = config->GetDownloadPrefix();
            std::string urlpath = downloadPrefix + fu.FileName();
            _url_path = urlpath;

            return true;
        }

        bool _pack_flag; // 文件是否被压缩标志
        size_t _fsize; // 文件的大小
        time_t _atime; // 文件最后一次访问时间
        time_t _mtime; // 文件最后一次修改时间
        std::string _real_path; // 文件实际存储路径名称
        std::string _pack_path; // 亚搜包存储路径名称
        std::string _url_path; // url下载文件的路径
    };

测试备份信息功能

void TestDataManager(const std::string& realpath)
{
    hhbcloud::Backinfo info;

    if (info.NewBackinfo(realpath) == false)
    {
        return;
    }

    std::cout << info._pack_flag << std::endl;
    std::cout << info._atime << std::endl;
    std::cout << info._mtime << std::endl;
    std::cout << info._fsize << std::endl;
    std::cout << info._pack_path << std::endl;
    std::cout << info._real_path << std::endl;
    std::cout << info._url_path << std::endl;
}

在这里插入图片描述

八、服务端数据管理类的实现

  • 数据管理类是将组织好的文件的备份信息统一存放在备份信息文件中,并且可以从文件中读取备份信息,进行增、改、 查等功能, 并可以重新写回文件的类。
    在这里插入图片描述
 class DataManager
    {
    public:
        DataManager()
        {
            // _backup_file 持久化存储文件,就是配置文件中的文件备份信息的文件
            // 对数据的管理本质就是对服务器备份的文件信息的管理,在进行操作时,需要将当前所有文件信息加载到内存中
            // 这里用哈希表来存储,提高效率

            _backup_file = Config::GetInstance()->GetBackupFile(); // 通过config单例类获取
            // 初始化读写锁
            pthread_rwlock_init(&_rwlock, nullptr);

            // 创建对象的同时,初始化加载所有备份信息数据
            InitLoad();
        }

        // 初始化加载 每次创建类都需要先初始化加载所有的数据
        bool InitLoad()
        {
            FileUtil fu(_backup_file);
            if (fu.Exists() == false)
            {
                std::cout << "DataManager InitLoad file is not exists" << std::endl;
                return false;
            }

            // 获取备份信息文件中的所有备份信息
            std::string body;
            fu.GetContent(&body);

            // 反序列化
            Json::Value root;
            JsonUtil::Deserialization(body, &root);

            // 通过反序列化得到的json结构体,构建备份信息,插入到哈希表中
            for (int i = 0;i < root.size(); i++)
            {
                Backinfo backinfo;
                backinfo._pack_flag = root[i]["pack_flag"].asBool();
                backinfo._fsize = root[i]["fsize"].asInt();
                backinfo._atime = root[i]["atime"].asInt();
                backinfo._mtime = root[i]["mtime"].asInt();
                backinfo._real_path = root[i]["real_path"].asString();
                backinfo._pack_path = root[i]["pack_path"].asString();
                backinfo._url_path = root[i]["url_path"].asString();

                // 获取的一个文件备份信息插入到哈希表中
                Insert(backinfo);
            }

            return true;
        }

        // 新增数据
        bool Insert(const Backinfo& backinfo)
        {
            // 读写锁,在写入时,将写进行加锁,读不加锁,一次只能有一个线程在执行写操作
            pthread_rwlock_wrlock(&_rwlock);

            // 所有数据都已经加载到哈希表中了,所以此时只需要操作哈希表即可
            // 在哈希表中我们以备份文件信息中的url路径和文件备份信息构成键值对
            _table[backinfo._url_path] = backinfo;
            pthread_rwlock_unlock(&_rwlock);

            // 每插入一个数据都要持久化存储
            Storage();
            return true;
        }

        // 更新数据
        bool Update(const Backinfo& backinfo)
        {
            pthread_rwlock_wrlock(&_rwlock);

            _table[backinfo._url_path] = backinfo; // key值相同,会默认覆盖之前的值
            pthread_rwlock_unlock(&_rwlock);

            // 每更新一个数据都要持久化存储
            Storage();
            return true;
        }

        // 查询数据 通过url获取单个文件的备份信息
        // 当是下载请求时,我们需要获取单个文件的信息,但是如果是页面展示请求,则需要获取多个文件的信息
        bool GetOneByUrl(const std::string& url, Backinfo* backinfo)
        {
            pthread_rwlock_wrlock(&_rwlock);

            auto it = _table.find(url);

            if (it == _table.end())
            {
                std::cout << "GetOneByUrl is not find" << std::endl;
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            pthread_rwlock_unlock(&_rwlock);
            *backinfo = it->second;
            return true;
        }

        // 通过文件真是路径来获取文件备份信息 通过真实路径查询在备份信息文件中是否有当前文件,判断文件是否被备份
        bool GetOneByRealpath(const std::string& realpath, Backinfo* backinfo)
        {
            pthread_rwlock_wrlock(&_rwlock);
            auto it = _table.begin();
            for (; it != _table.end(); ++it)
            {
                if (it->second._real_path == realpath)
                {
                    *backinfo = it->second;
                    pthread_rwlock_unlock(&_rwlock);
                    return true;
                }
            }

            pthread_rwlock_unlock(&_rwlock);
            return false;
        }

        // 获取所有备份文件的信息  用于展示请求
        bool GetAll(std::vector<Backinfo>* array)
        {
            pthread_rwlock_wrlock(&_rwlock);

            auto it = _table.begin();
            for (;it != _table.end(); ++it)
            {
                array->push_back(it->second);
            }
            pthread_rwlock_unlock(&_rwlock);
            return true;
        }

        // 持久化存储,也就是将已经操作的(插入或修改)加载到内存中的哈希表中的数据写入到备份信息的文件中
        bool Storage()
        {
            // 创建一个备份文件信息的数据
            std::vector<Backinfo> array;
            // 获取所有的备份信息到数组中
            GetAll(&array);
            // 先将所有的备份信息,存储到json数组中,然后通过序列化, 最后统一写入文件
            Json::Value root;
            for (int i = 0;i < array.size(); i++)
            {
                Json::Value item; // 构建好每一个备份文件信息
                item["pack_flag"] = array[i]._pack_flag;
                item["fsize"] = (Json::Int64)array[i]._fsize;
                item["atime"] = (Json::Int64)array[i]._atime;
                item["mtime"] = (Json::Int64)array[i]._mtime;
                item["real_path"] = array[i]._real_path;
                item["pack_path"] = array[i]._pack_path;
                item["url_path"] = array[i]._url_path;

                root.append(item); // 构建好备份文件信息后,写入json数组中
            }

            // 序列化
            std::string body;
            JsonUtil::Serialization(root, &body);
            // 此时root被序列化为一个字符串,放入body中
            FileUtil fu(_backup_file);

            if (fu.SetContent(body) == false) // 若文件不存在fu.setContent中的ofstream流在打开文件时,会自动创建
            {
                std::cout << "DataManager storage set file content error" << std::endl;
                return false;
            }

            return true;
        }



        ~DataManager()
        {
            // 析构时,销毁读写锁
            pthread_rwlock_destroy(&_rwlock);
        }
    private:
        std::string _backup_file; // 持久化存储的文件,也就是配置文件中 备份文件信息的文件
        pthread_rwlock_t _rwlock; // 读写锁, -- 读共享,写互斥
        std::unordered_map<std::string, Backinfo> _table; // 一个哈希表,用来高效管理数据
    };

测试数据管理功能

void TestDataManager(const std::string& realpath)
{
    hhbcloud::Backinfo info;

    if (info.NewBackinfo(realpath) == false)
    {
        return;
    }

    std::cout << info._pack_flag << std::endl;
    std::cout << info._atime << std::endl;
    std::cout << info._mtime << std::endl;
    std::cout << info._fsize << std::endl;
    std::cout << info._pack_path << std::endl;
    std::cout << info._real_path << std::endl;
    std::cout << info._url_path << std::endl;


    hhbcloud::DataManager DM;

    DM.Insert(info);

    hhbcloud::Backinfo backinfo;
    DM.GetOneByUrl(info._url_path, &backinfo);

    std::cout << "================Insert && GetOneByUrl=====================" << std::endl;

    std::cout << backinfo._pack_flag << std::endl;
    std::cout << backinfo._atime << std::endl;
    std::cout << backinfo._mtime << std::endl;
    std::cout << backinfo._fsize << std::endl;
    std::cout << backinfo._pack_path << std::endl;
    std::cout << backinfo._real_path << std::endl;
    std::cout << backinfo._url_path << std::endl;

    info._pack_flag = true;
    DM.Update(info);
    std::vector<hhbcloud::Backinfo> array;
    DM.GetAll(&array);

    std::cout << "===================Update && GetAll=====================" << std::endl;

    for (auto& e : array)
    {
        std::cout << e._pack_flag << std::endl;
        std::cout << e._atime << std::endl;
        std::cout << e._mtime << std::endl;
        std::cout << e._fsize << std::endl;
        std::cout << e._pack_path << std::endl;
        std::cout << e._real_path << std::endl;
        std::cout << e._url_path << std::endl;
    }

    hhbcloud::Backinfo tmp;
    DM.GetOneByRealpath(realpath, &tmp);

    std::cout << "=================GetOneByRealpath=======================" << std::endl;

    std::cout << tmp._pack_flag << std::endl;
    std::cout << tmp._atime << std::endl;
    std::cout << tmp._mtime << std::endl;
    std::cout << tmp._fsize << std::endl;
    std::cout << tmp._pack_path << std::endl;
    std::cout << tmp._real_path << std::endl;
    std::cout << tmp._url_path << std::endl;

}

在这里插入图片描述

九、服务端热点管理的实现

  • 持续检测备份目录下的文件,若满足条件则进行压缩处理。压缩后的文件放入压缩目录下,删除备份目录中的文件
    在这里插入图片描述
extern hhbcloud::DataManager* data; // 创建一个全局的数据管理类,便于对数据的操作

namespace hhbcloud
{
    class HotManager // 热点管理类
    {
    public:
        HotManager()
        {
            // 备份文件,压缩文件,最后一次访问时间,压缩文件前缀均可从配置文件中读取
            Config* config = Config::GetInstance();
            _back_dir = config->GetBackDir();
            _pack_dir = config->GetPackDir();
            _hot_time = config->GetHotTime();
            _packfile_suffix = config->GetPackfileSuffix();

            // 如果压缩文件目录和备份文件目录不存在,则创建目录
            FileUtil fu1(_back_dir);
            fu1.CreateDirectory(); // 函数内部对文件存在做了判断,若文件已经存在,则直接返回
            FileUtil fu2(_pack_dir);
            fu2.CreateDirectory();
        }

        // 运行模块
        bool RunMoudle()
        {
            // 热点管理需要不断循环检测备份文件目录下的文件因此是一个死循环
            while (1)
            {
                // 首先浏览指定目录下的所有文件
                FileUtil fu(_back_dir);
                std::vector<std::string> array;
                fu.ScanDirectory(&array);

                for (auto& e : array) // 循环每个文件,判断是否是热点文件,是否需要压缩
                {
                    if (HotJundge(e) == false)
                    {
                        // 热点文件 直接跳过,不用管
                        continue;
                    }

                    Backinfo info;
                    // 此时为非热点文件
                    //从备份信文件中无法找到,文件存在,但是没有备份
                    if (data->GetOneByRealpath(e, &info) == false) 
                    {
                        info.NewBackinfo(e);// 给文件创建一个备份信息
                    }

                    // 此时info中的已经有了备份信息(在备份信息文件中找到的, 或者对遗漏的文件创建的备份信息)
                    FileUtil tmp(e);
                    tmp.Compress(info._pack_path); // 对文件进行压缩
                    // 删除原来的备份文件
                    tmp.Remove();
                    info._pack_flag = true;
                    data->Update(info);
                }
                usleep(1000); // 为了避免空目录持续循环,造成cpu资源消耗过高,间隔1000ms也就是0.1s
            }
            return true;
        }
    private:
        bool HotJundge(const std::string& filename)
        {
            FileUtil fu(filename);
            time_t last_atime = fu.LastATime();
            time_t cur_time = time(nullptr);
            if ((cur_time - last_atime) > _hot_time)
            {
                // 最后一次访问时间 - 当前时间 如果大于 热点判断时间,说明为非热点文件,需要压缩
                return true;
            }
            return false;
        }
    private:
        std::string _back_dir; // 备份文件,热点管理类需要从备份文件中获取所有文件判断热点,所以需要备份文件
        std::string _pack_dir;  // 压缩文件,热点管理类对非热点文件进行压缩处理,并存放在压缩目录下
        std::string _packfile_suffix; // 压缩文件的后缀
        int _hot_time; // 热点判断时间
    };
}

测试热点管理功能

hhbcloud::DataManager* data;

void TestHotManager()
{
    data = new hhbcloud::DataManager;
    hhbcloud::HotManager ht;
    ht.RunMoudle();
}

在这里插入图片描述

十、服务端网络通信及业务处理实现

  • 下载httplib第三方库,并将httplib.h复制到当前目录下
  • 使用httplib第三方库搭建服务器,并处理上传,页面展示,和下载业务
    在这里插入图片描述
extern hhbcloud::DataManager* data;
namespace hhbcloud
{
    class Service
    {
    public:
        Service()
        {
            Config* config = Config::GetInstance();
            // 从配置文件中读取服务器端口,服务器ip,下载前缀
            _server_port = config->GetServerPort();
            _server_ip = config->GetServerIp();
            _download_prefix = config->GetDownloadPrefix();

            // 若文件此时没有指定备份目录,则需要创建文件目录
            std::string back_dir = config->GetBackDir();
            FileUtil fu(back_dir);
            fu.CreateDirectory(); // createDirectory函数内部对文件存在做了判断,如存在则不创建
        }

        bool RunMoudle() // 运行模块
        {
            // post请求上传文件, 并且业务处理函数Upload的参数必须是Request 和 Response,所以必须要静态,否则会有隐含的this指针参数
            _server.Post("/upload", Upload);

            _server.Get("/listshow", ListShow);
            _server.Get("/", ListShow);

            std::string download = _download_prefix + "(.*)"; // .*正则表达式表示匹配任意字符,任意次
            _server.Get(download, DownLoad);

            // listen启动服务器,并指定监听的主机和端口
            _server.listen(_server_ip.c_str(), _server_port);

            return true;

        }
    private:
        static void Upload(const httplib::Request& req, httplib::Response& resp)
        {
            auto ret = req.has_file("file"); // 判断客户端上传有没有这个字段
            if (ret == false)
            {
                // 没有这个字段,服务器无法处理,设置状态码为400,并返回
                resp.status = 400;
                return;
            }

            std::cout << "has_file && upload" << std::endl;

            // 有字段,获取文件内容
            auto file = req.get_file_value("file");
            // 在备份文件目录下创建文件,并写入数据
            std::string back_dir = Config::GetInstance()->GetBackDir();
            // 获取文件的实际存储路径 -- /backdir/filename 
            std::string realpath = back_dir + FileUtil(file.filename).FileName();
            FileUtil fu(realpath);
            // 向文件中写入数据 写入数据函数中ofstream在打开文件时,若文件不存在,会自动创建文件, 若没有指定目录,则需要自己创建
            fu.SetContent(file.content);

            // 组织备份信息
            Backinfo backinfo;
            backinfo.NewBackinfo(realpath);

            data->Insert(backinfo); // 将数据写入备份信息文件中

            return;
        }

        static std::string TimetoStr(time_t time)
        {
            return ctime(&time);
        }
        static void ListShow(const httplib::Request& req, httplib::Response& resp)
        {
            // 获取备份信息文件中的所有数据
            std::vector<Backinfo> array;
            data->GetAll(&array);

            // 使用备份信息组织html页面 
            std::stringstream ss;

            ss << "<html><meta charset = 'utf-8'><head><title>cloudBackup</title></head>";
            ss << "<body><h1>Download</h1><table>";
            for (auto& e : array)
            {
                // 构建每一行的数据 文件名称 文件最近访问时间 文件大小(k)
                ss << "<tr>";
                std::string filename = FileUtil(e._real_path).FileName();

                ss << "<td>" << "<a href = '" << e._url_path << "'>" << filename << "</a></td>";
                ss << "<td>" << TimetoStr(e._atime) << "</td>";
                ss << "<td>" << e._fsize / 1024 << " K" << "</td>";
                ss << "</tr>";
            }
            ss << "</table></body></html>";

            resp.body = ss.str();
            resp.status = 200;
            resp.set_header("Content-Type", "text/html");

            return;
        }

        // 由文件名,文件大小, 文件最后一次修改构成的ETag信息
        static std::string GetETag(const Backinfo& backinfo)
        {
            FileUtil fu(backinfo._real_path);
            std::string ETag = fu.FileName();
            ETag += "-";
            ETag += fu.FileSize();
            ETag += "-";
            ETag += fu.LastMTime();

            return ETag;
        }

        static void DownLoad(const httplib::Request& req, httplib::Response& resp)
        {
            // 通过url路径获取文件的备份信息
            // req.path就是文件的url路径
            Backinfo backinfo;
            data->GetOneByUrl(req.path, &backinfo);

            // 如果文件压缩标志是true,则需要先解压缩
            if (backinfo._pack_flag == true)
            {
                FileUtil fu(backinfo._pack_path);

                fu.Uncompress(backinfo._real_path); // 解压到真实路径
                fu.Remove(); // 删除压缩包
                // 更改文件压缩标志,并更改文件备份信息
                backinfo._pack_flag = false;
                data->Update(backinfo);
            }

            // 将解压后的文件的内容写到resp.body中
            FileUtil fu(backinfo._real_path);

            bool retrans = false; // 标记是否是断电续传
            std::string old_etag; // 下载请求时,已有的Etag值

            // 判断是否有if-Range, 若有则是断点续传,若没有则不是
            if (req.has_header("if-Range"))
            {
                old_etag = req.get_header_value("if-Range");

                if (old_etag == GetETag(backinfo))// 有if-Range,但是与当前的Etag不一致,则也需要正常下载
                {
                    retrans = true;
                }
            }

            if (retrans == false)
            {
                // 如果retrans == false ,需要断点续传,否则不需要
                fu.GetContent(&resp.body); // 读取文件内容,写入resp.body
                // 设置响应报头字段
                resp.set_header("Accept-Ranges", "bytes"); // 允许断点续传
                resp.set_header("ETag", GetETag(backinfo));
                // Content-Type 决定了浏览器如何处理数据,设置文件二进制流,常用于文件下载
                resp.set_header("Content-Type", "application/octet-stream");
                resp.status = 200;
                return;

            }
            else
            {
                // 此时已经是要断点续传了,按照Range字段中指定的区间,取出指定区间的数据进行下载
                // httplib库已经内置了,这里只需要取出数据即可
                fu.GetContent(&resp.body); // 读取文件内容,写入resp.body
                // 设置响应报头字段
                resp.set_header("Accept-Ranges", "bytes"); // 允许断点续传
                resp.set_header("ETag", GetETag(backinfo));
                // Content-Type 决定了浏览器如何处理数据,设置文件二进制流,常用于文件下载
                resp.set_header("Content-Type", "application/octet-stream");
                resp.status = 206;
                return;
            }
        }
    private:
        int _server_port; // 服务器开放端口号
        std::string _server_ip; // 服务器ip
        std::string _download_prefix; // 下载前缀
        httplib::Server _server; // 实例化一个服务器Server对象 ,自动调用构造函数初始化
    };
}

测试网络通信及业务处理功能
html页面代码:


<html>
	<body>
		<form action = "http://1.14.97.154:8080/upload" method = "post" enctype = "multipart/form-data">
		<div>
			<input type = "file"  name = "file">
		</div>
		<div><input type = "submit" value = "上传"> </div>
		</form>
	</body>

</html>

在这里插入图片描述

void TestServer()
{
    data = new hhbcloud::DataManager;
    hhbcloud::Service svr;
    svr.RunMoudle();
}

在这里插入图片描述

服务端进行多线程同时处理热点管理模块和网络通信及业务处理模块

十一、客户端数据管理

#ifndef __MY_DATAMANAGER__
#define __MY_DATAMANAGER__

#include "Util.hpp"
#include <unordered_map>

namespace hhbcloud
{
    class DataManager
    {
    public:
        DataManager(const std::string& backup_file)
            :_backup_file(backup_file)
        {
            InitLoad();
        }

        int Split(const std::string& body, const std::string& sep, std::vector<std::string>* array)
        {
            int count = 0;
            int start = 0;
            while (1)
            {
                size_t pos = body.find(sep, start);
                if (pos == std::string::npos)
                {
                    break;
                }
                if (start == pos)
                {
                    start = start + sep.size();
                    continue;
                }
              
                array->push_back(body.substr(start, (pos - start) + 1));
                count++;
                start = pos + sep.size();
            }
            if (start < body.size())
            {
                array->push_back(body.substr(start));
                count++;
            }

            return count;
                
        }

        bool InitLoad()
        {
            FileUtil fu(_backup_file);

            if (fu.Exists() == false)
            {
                std::cout << "client Data manager file is not exists" << std::endl;
                return false;
            }
            
            std::string body;
            fu.GetContent(&body);

            std::vector<std::string> array;
            Split(body, "\n", &array);

            for (auto& e : array)
            {
                std::vector<std::string> tmp;
                Split(e, " ", &tmp);

                if (tmp.size() != 2)
                {
                    continue;
                }

                _table[tmp[0]] = tmp[1];
            }

            return true;
        }


        bool Storage()
        {
            std::stringstream ss;
            auto it = _table.begin();
            for (; it != _table.end(); ++it)
            {
                ss << it->first << " " << it->second << "\n";
            }

            FileUtil fu(_backup_file);
            fu.SetContent(ss.str());
            return true;
        }

        bool Insert(const std::string& key, const std::string& val)
        {
            _table[key] = val;
            Storage();
            return true;
        }


        bool Update(const std::string& key, const std::string& val)
        {
            _table[key] = val;
            Storage();
            return true;
        }

        bool GetOneByKey(const std::string& key, std::string* val)
        {
            auto it = _table.find(key);
            if (it == _table.end())
            {
                return false;
            }

            *val = it->second;
            return true;
        }

    private:
        std::string _backup_file;
        std::unordered_map<std::string, std::string> _table;
    };
}



#endif

十二、客户端网络通信及备份请求

#define  _CRT_SECURE_NO_WARNINGSf

#ifndef __MY_BACKUP__
#define __MY_BACKUP__



#include "Util.hpp"
#include "DataManager.hpp"
#include "httplib.h"
#include <Windows.h>

#define SERVER_ADDR "1.14.97.154"
#define SERVER_PORT 8080

namespace hhbcloud
{
	class Backup
	{
	public:
		Backup(const std::string& back_dir, const std::string& backup_file)
			:_back_dir(back_dir)
		{
			FileUtil fu(_back_dir);
			if (fu.Exists() == false)
			{
				fu.CreateDirectorys();
			}
			_data = new DataManager(backup_file); // 初始化数据管理类对象
		}

		// 运行模块 持续的,并进行上传
		bool RunMoudle()
		{
			while (1)
			{
				// 浏览备份目录下的所有文件,并将数据存放在数组中
				std::vector<std::string> array;
				FileUtil fu(_back_dir);
				fu.ScanDirectory(&array);

				// 获取的文件,
				for (auto& e : array)
				{
					// 判断文件是否需要上传
					if (IsNeedUpload(e) == false)
					{
						// 不需要上传,则跳过
						continue;
					}
					// 判断文件是否上传成功
					if (Upload(e) == true)
					{
						// 文件若上传成功,在备份信息文件中插入数据
						// 创建文件唯一标识(文件名 - 文件大小 - 文件最后一次修改时间),
						std::string id = GetFileIdentify(e); 
						_data->Insert(e, id); //并将数据插入文件备份信息文件中
					}

				}

				Sleep(1); // 间隔1ms检测一次,防止cpu资源消耗过多
			}
		}

	private:
		// 创建文件唯一标识
		std::string GetFileIdentify(const std::string& filename)
		{
			std::stringstream ss;
			FileUtil fu(filename);
			ss << fu.FileName() << "-" << fu.FileSize() << "-" << fu.LastMTime();
			return ss.str();
		}

		bool IsNeedUpload(const std::string filename)
		{
			std::string id;
			if (_data->GetOneByKey(filename, &id) != false)
			{
				std::string new_id = GetFileIdentify(filename);
				if (id == new_id)
				{
					return false;
				}
			}

			FileUtil fu(filename);
			if (time(nullptr) - fu.LastMTime() < 3)
			{
				return false;
			}
			return true;
		}

		bool Upload(const std::string& filename)
		{
			FileUtil fu(filename);
			std::string body;
			fu.GetContent(&body);

			httplib::Client client(SERVER_ADDR, SERVER_PORT);


			httplib::MultipartFormData item;

			item.content = body;
			item.filename = fu.FileName();
			item.name = "file";
			item.content_type = "application/octet-stream";

			httplib::MultipartFormDataItems items;
			items.push_back(item);

			auto ret = client.Post("/upload", items);
			if (ret->status != 200 || ret == nullptr)
			{
				return false;
			}
			return true;

		}

	private:
		std::string _back_dir;
		DataManager* _data;
	};
}

#endif

  • 项目改进
    服务端对文件压缩处理时,可以使用多线程,提高效率。

总结

云备份实战项目主要是基于httplib, bundle, jsoncpp等第三方库实现客户端对文件的备份等功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2251683.html

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

相关文章

Java有关数组的相关问题

Java中的栈和堆的含义 栈 存储局部变量&#xff1a;栈主要用于存储方法中的局部变量&#xff0c;包括基本数据类型&#xff08;int、double、boolean等&#xff09;和对象的引用&#xff08;不包含对象本身&#xff09;。 遵循后进先出原则&#xff1a;当一个方法被调用时&…

使用Dify与BGE-M3搭建RAG(检索增强生成)应用-改进一,使用工作流代替Agnet

文章目录 前言Agent vs 工作流编写工作流 前言 在上一篇中&#xff0c;我们实现了一个基本的基于Dify的RAG的示范。 使用Dify与BGE-M3搭建RAG&#xff08;检索增强生成&#xff09;应用 这个效果确实很差。 我们一起来看看&#xff0c;该怎么改进。 今天我们就尝试一下&…

Python语法基础(四)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 高阶函数之map 高阶函数就是说&#xff0c;A函数作为B函数的参数&#xff0c;B函数就是高阶函数 map&#xff1a;映射 map(func,iterable) 这个是map的基本语法&#xff0c;…

Python毕业设计选题:基于django+vue的智慧社区可视化平台的设计与实现+spider

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 养老机构管理 业主管理 社区安防管理 社区设施管理 车位…

Ubuntu环境中RocketMQ安装教程

参考教程 https://blog.csdn.net/weixin_56219549/article/details/126143231 1、安装JDK&#xff0c;并配置环境变量&#xff08;略&#xff09; 2、下载RocketMQ安装包 RocketMQ下载地址&#xff0c;选择二进制包下载 unzip rocketmq-all-5.0.0-ALPHA-bin-release.zip 使…

【SSM】mybatis的增删改查

目录 代理Dao方式的增删改查 1. 创建项目 $$1. 在sql.xml里增加日志代码以及user的mapper资源。 $$ 2. 在usermapper里引入接口。 $$3. 在测试类中引入以下代码&#xff0c;并修改其中名字。 $$ 4. 实例对象User.java里属性要与表中列严格对应。 2. 查询 1>. 查询所有 …

【C++习题】23.二分查找算法_寻找旋转排序数组中的最小值

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 153. 寻找旋转排序数组中的最小值 题目描述&#xff1a; 解法 暴力解法&#xff1a;O(n) 从前往后找数组中的最小值。 二分算法&#xff1a;O(logn) 先找二段性 因为这…

Linux笔记---进程:进程终止

1. 进程终止概念与分类 进程终止是指一个正在运行的进程结束其执行的操作。以下是一些常见的导致进程终止的情况&#xff1a; 一、正常终止 完成任务当进程完成了它被设计要执行的任务后&#xff0c;就会正常终止。收到特定信号在操作系统中&#xff0c;进程可能会收到来自操作…

立创庐山派 K230 RTSP 推流

立创庐山派使用的是K230芯片&#xff0c;按照教程刷了canmv固件&#xff0c;下载canmv ide&#xff0c;使用嘉楠社区的rtsp和wlan例程&#xff0c;修改成连接wifi以及RTSP推流例程 # Description: This example demonstrates how to stream video and audio to the network us…

Oracle数据恢复—Oracle数据库sysaux文件损坏的数据恢复案例

Oracle数据库故障&分析&#xff1a; 一台Oracle数据库打开报错&#xff0c;报错信息&#xff1a; “system01.dbf需要更多的恢复来保持一致性&#xff0c;数据库无法打开”。管理员联系我们数据恢复中心寻求帮助&#xff0c;并提供了Oracle_Home目录的所有文件。用户方要求…

【kafka04】消息队列与微服务之Kafka 图形工具

Kafka 在 ZooKeeper 里面的存储结构 topic 结构 /brokers/topics/[topic] partition结构 /brokers/topics/[topic]/partitions/[partitionId]/state broker信息 /brokers/ids/[o...N] 控制器 /controller 存储center controller中央控制器所在kafka broker的信息 消费者 /c…

微众银行前端面试题及参考答案

使用的协议是 HTTP 还是 HTTPS&#xff0c;为什么没用 HTTPS&#xff1f; 在前端开发中&#xff0c;有些网站使用 HTTP 协议&#xff0c;有些使用 HTTPS 协议。 使用 HTTP 协议的情况可能是因为网站对安全性的要求不是极高&#xff0c;或者处于开发的早期阶段&#xff0c;还没有…

第144场双周赛:移除石头游戏、两个字符串得切换距离、零数组变换 Ⅲ、最多可收集的水果数目

Q1、[简单] 移除石头游戏 1、题目描述 Alice 和 Bob 在玩一个游戏&#xff0c;他们俩轮流从一堆石头中移除石头&#xff0c;Alice 先进行操作。 Alice 在第一次操作中移除 恰好 10 个石头。接下来的每次操作中&#xff0c;每位玩家移除的石头数 恰好 为另一位玩家上一次操作…

UR开始打中国牌,重磅发布国产化协作机器人UR7e 和 UR12e

近日&#xff0c;优傲&#xff08;UR&#xff09;机器人公司立足中国市场需求&#xff0c;重磅推出UR7e和UR12e 两款本地化协作机器人。它们延续优傲&#xff08;UR&#xff09;一以贯之的高品质与性能特质&#xff0c;着重优化负载自重比&#xff0c;且在价格层面具竞争力&…

应急响应靶机——Windows挖矿事件

载入虚拟机&#xff0c;开启虚拟机&#xff1a; &#xff08;账户密码&#xff1a;administrator/zgsf123&#xff09; 发现登录进去就弹出终端界面&#xff0c;自动运行powshell命令&#xff0c;看来存在计划任务&#xff0c;自动下载了一些文件&#xff0c;之后就主动结束退…

基于深度学习和卷积神经网络的乳腺癌影像自动化诊断系统(PyQt5界面+数据集+训练代码)

乳腺癌是全球女性中最常见的恶性肿瘤之一&#xff0c;早期准确诊断对于提高生存率具有至关重要的意义。传统的乳腺癌诊断方法依赖于放射科医生的经验&#xff0c;然而&#xff0c;由于影像分析的复杂性和人类判断的局限性&#xff0c;准确率和一致性仍存在挑战。近年来&#xf…

深入浅出机器学习中的梯度下降算法

大家好&#xff0c;在机器学习中&#xff0c;梯度下降算法&#xff08;Gradient Descent&#xff09;是一个重要的概念。它是一种优化算法&#xff0c;用于最小化目标函数&#xff0c;通常是损失函数。梯度下降可以帮助找到一个模型最优的参数&#xff0c;使得模型的预测更加准…

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕

PotPlayer 最新版本支持使用 Whisper 自动识别语音生成字幕 设置使用下载地址 设置 使用 下载地址 https://www.videohelp.com/software/PotPlayer

【0x0001】HCI_Set_Event_Mask详解

目录 一、命令概述 二、命令格式 三、命令参数说明 四、返回参数说明 五、命令执行流程 5.1. 主机准备阶段 5.2. 命令发送阶段 5.3. 控制器接收与处理阶段 5.4. 事件过滤与反馈阶段 5.5. 主机处理&#xff08;主机端&#xff09; 5.6. 示例代码 六、命令应用场景 …

可解释机器学习 | Python实现LGBM-SHAP可解释机器学习

机器学习 | Python实现GBDT梯度提升树模型设计 目录 机器学习 | Python实现GBDT梯度提升树模型设计基本介绍模型使用参考资料基本介绍 LightGBM(Light Gradient Boosting Machine)是一种基于决策树的梯度提升框架,是一种高效的机器学习模型。SHAP(SHapley Additive exPlan…