【云备份】客户端实现 及 项目整体总结

文章目录

  • 客户端
    • 客户端实现思想
    • 客户端文件操作类的设计与拷贝
      • Util.hpp的设计
      • data.hpp的设计
        • Storage —— 持久化存储
        • Initload——数据初始化加载
      • cloud.hpp的设计
        • GetFileIdentifier——创建文件唯一标识
        • Upload—— 文件上传
        • IsNeedupload —— 客户端文件是否需要上传判断
        • RunModule —— 运行模块
  • 项目总结
  • 整体代码
    • 服务器端(Linux下实现)
      • util.hpp(实用文件工具模块)
      • service.hpp(业务处理模块)
      • makefile
      • hot.hpp(热点管理模块)
      • data.hpp (数据管理模块)
      • config.hpp(配置加载文件模块)
      • cloud.conf
    • 客户端(VS2022下实现)
      • util.hpp(实用文件工具模块)
      • data.hpp (数据管理模块)
      • cloud.hpp (文件备份模块)
      • cloud.cpp

客户端

客户端实现思想

客户端实现功能:
自动对指定文件夹的文件进行备份


数据管理模块的实现思想:
内存存储 :高访问效率 使用hash表_table
持久化存储:文件存储
文件存储涉及到数据的序列化 而在VS中安装jsoncpp麻烦 直接自定义序列化格式 key value
key表示文件路径名 value表示 文件唯一标识
文件的唯一标识:用于判断上次文件上传后有没有被修改过


客户端文件操作类的设计与拷贝

以下操作都在VS中进行

Util.hpp的设计

客户端文件操作类 与 服务端的文件实用工具类 基本没有差别
所以直接复制 util.hpp
把 FileUtil类中 的 compress 压缩函数 与 uncompress 解压缩 函数 以及 json类(序列化与反序列化) 删除

data.hpp的设计

创建 data.hpp
在cloud命名空间中 设计 DataManger 类 用于设计客户端的数据管理模块


_backup_file 用于备份信息的持久化存储文件
_table 是一个 key值为string value值为string 的哈希表 用于存储数据


Storage —— 持久化存储

遍历_table哈希表,将表中的key和value值都写入到ss字符串流中

使用 _backup_file 备份信息持久化存储文件 实例化一个 FileUtil类的 对象 fu
调用 FileUtil类的 SetContent 函数 将 ss字符串流中的数据 写入到 fu中


Initload——数据初始化加载

想要实现 InitLoad函数 就需要先实现分割功能

按照换行进行分割 得到一行一行的数据
在每一行中 按照空格进行分割 即可得到 文件名唯一标识


所以创建Split函数 实现分割功能

定义查找到分隔符的位置pos 以及 起始偏移量 idx 并初始化为0


find函数 的第一个参数为查找的字符 第二个参数为 偏移量
在while循环中 依次寻找对应的分隔符位置pos 以idx起始偏移量起始寻找


说明数据之前存在两个以上的分隔符 ,则偏移量为当前pos分隔符位置再加上分隔符个数
并重新查找分隔符位置pos


substr函数的第一个参数为 截取起始位置 第二个参数为长度


例如: abc qwe sdf
借助 substr 函数 从idx偏移量处 进行截取 pos位置为当前空格位置 pos-idx 即 截取的数据长度
再将tmp添加到 arry数组中
count 数量加1 表示 数据增添一个


若为最后一个数据 则查找不到空格直接跳出循环 还需将最后一个数据放入数组arry中



通过 _backup_file (备份信息的持久化存储文件) 实例化一个 FileUtil类的对象 fu
将_backup_file中的数据 读取到body字符串中


再将body中的数据通过 Split 函数 进行分割成一行一行的数据 并添加到arry数组中
此时arry数组中的元素 即为一行数据


想要把一行数据分割成文件名和唯一标识
就还需要借助 Split 函数 将一行数据通过空格分割开
最终在哈希表中使文件名和唯一标识 一 一对应



cloud.hpp的设计

设计Backup类 设置私有成员变量
_back_dir 要监控的文件夹
_data 数据管理类


GetFileIdentifier——创建文件唯一标识

通过filename 实例化一个对象fu
创建一个 字符串流 ss
通过调用 FileUtil类的函数 将文件名 - 文件大小 - 最后一次修改时间 写入到ss中
最后返回ss的字符串格式


Upload—— 文件上传

用 宏定义 IP地址 192.144.206.100(云服务器的公网IP) 为 SERVER_ADDR
用 宏定义 port端口号 9090 为 SERVER_PORT


通过filename 实例化一个 FileUtil类的对象 fu
再通过FileUtil类的 GetContent 函数 将 filename中的数据 传入 body字符串中


由于需要httplib,h 头文件 所以要把linux下的 该头文件 先传到桌面上 再导入 VS中

将属于linux下的 httplib.h 头文件 发送到桌面上


将VS的cloud_client 客户端 的路径复制下来 并打开


将httplib.h头文件托送到当前路径下


打开ckoud_client 客户端 添加 头文件 httplib.h


通过宏定义的 IP地址和 端口号 实例化一个 Client类的对象 client


MultipartFormDataMap 内部包含四个字段
name为字段名称
content为文件内容
filename为文件名称
coneent_type 为正文类型


创建 MultipartFormDataMap 类型的对象 item
文件内容为 body字符串中的数据
文件名称为 FileName函数 返回 的文件名
辨别字段为 file(与服务器端的serice 中的 upload函数 相同)
application / octet - stream 表示二进制流数据


调用httplib库中的Client类的 Post请求
因为要与服务器端的资源路径相统一 所以使用 /upload 进行 Post请求
以及items(将文件内容 文件名称 数据类型等上传)


IsNeedupload —— 客户端文件是否需要上传判断

若 文件是新增的 则需判断是否为历史备份信息

调用Datamanger 类的 GetOneBykey 函数 判断是否为唯一标识
若返回false 则说明找到了对应的历史备份信息 并将标识信息存储到id中

调用cloud类的 GetFileIdentifier 函数 获取新的标识信息 new_id
若 id 与 new_id 相等 则说明与历史文件信息一致 就不需要再次上传了


若id与new_id不相等 则说明 备份信息被修改了 需要重新上传

但在其中需要考虑一种特殊情况
有可能一个文件比较大 所以正在一点一点拷贝到这个目录下
拷贝需要一个过程 如果每次遍历 都会判断标识不一致 需要上传
就可能上传 上百次 这样就非常不合理
因此应该判断一个文件 在一段时间都没有被修改过 才能上传

time(NULL) 表示当前系统时间
LastMtime 函数表示 最后一次修改时间
若3秒内被修改过 就认为文件还在修改中 就不需要上传


RunModule —— 运行模块

通过 _back_dir 要监控的文件夹 实例化一个 FileUtil类的对象 fu
再通过 FileUtil类的 ScanDirectory 函数 将_back_dir的数据 传入 arry数组中


遍历arry数组 将数组中的每一个元素 通过 IsNeedUpload 函数 进行判断
看是否需要上传 若不需要上传 则重新遍历到下一个数组元素

通过Upload 函数 进行上传
若上传成功 则将文件名称 和唯一标识 通过insert 进行添加 构成新的备份文件信息


项目总结

项目名称:云备份系统
项目功能:搭建云备份服务器与客户端,客户端程序运行在客户机上自动将指定目录下的文件备份到服务器,并且能够支持浏览器查看与下载,其中下载支持断点续传功能,并且服务器端对备份的文件进行热点管理,将长时间无访问
文件进行压缩存储。
开发环境: centos7.6/vim、g++、gdb、makefile 以及 windows10/vs2022
技术特点: http 客户端/服务器搭建, json 序列化,文件压缩,热点管理,断点续传,,读写锁(读共享 写互斥),单例模式


项目模块:

服务端:
数据管理模块: 内存中使用hash表存储提高访问效率,持久化使用文件存储管理备份数据
业务处理模块: 搭建 http 服务器与客户端进行通信处理客户端的上传,下载,查看请求,并支持断点续

热点管理模块: 对备份的文件进行热点管理,将长时间无访问文件进行压缩存储,节省磁盘空间。


客户端
数据管理模块: 内存中使用hash表存储提高访问效率,持久化使用文件存储管理备份数据
文件检索模块: 基于 c++17 文件系统库,遍历获取指定文件夹下所有文件。
文件备份模块: 搭建 http 客户端上传备份文件。

整体代码

服务器端(Linux下实现)

util.hpp(实用文件工具模块)


#ifndef _MY_UTIL_
#define _MY_UTIL_
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<sys/stat.h>
#include"bundle.h"
#include<experimental/filesystem>
#include<jsoncpp/json/json.h>
#include<memory>

namespace cloud
{
  namespace fs=std::experimental::filesystem;

 class FileUtil
 {
  private:
          std::string _filename;//文件名称
  public:
          FileUtil(const std::string &filename):_filename(filename)//构造函数 
          {}

          bool Remove()//删除文件
          {
             //若文件不存在 相当于删除成功 则返回true
             if(this->Exists()==false)
             {
                 return true;
             }
              remove(_filename.c_str());
              return true;
          }
 
          int64_t FileSize()//文件大小
          {
            struct stat st;
           if( stat(_filename.c_str(),&st)<0)
           {
              std::cout<<"get file size failed!\n";	
              return -1;
           }
           return st.st_size;           
          }

          time_t  LastMTime()//文件最后一次修改时间
          {
           struct stat st;
           if( stat(_filename.c_str(),&st)<0)
           {
              std::cout<<"get file size failed!\n";
              return -1;
           }  
           return st.st_mtime; 
          }
           
          time_t  LastATime()//文件最后一次访问时间
          {
           struct stat st;
           if( stat(_filename.c_str(),&st)<0)
           {
              std::cout<<"get file size failed!\n";
              return -1;
           }
           return st.st_atime;
          }

          std::string FileName()//文件名称
          {
            // ./abc/test.txt
            size_t pos=_filename.find_last_of("/");
            if(pos==std::string::npos)
            {
             return _filename;
            }
            return _filename.substr(pos+1);
          }
         bool GetPostLen(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<<"read file failed"<<std::endl;
               return false;
             }
             //打开成功,获取文件数据
             size_t fsize=this->FileSize();//获取文件大小
             if(pos+len>fsize)//若pos开始位置超过了文件大小
             {
               std::cout<<"get file len is 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<< "get file content fialed "<<std::endl;
               ifs.close();
               return false;
             }
             ifs.close();

              return true;
          }

          bool GetContent(std::string *body) //获取整体文件数据
          {
              size_t fsize=this->FileSize();//获取文件大小
              return GetPostLen(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<<"write open file failed"<<std::endl;
              return false;
            }
            ofs.write(&body[0], body.size());//将body数据写入到文件中
            if(ofs.good()==false)//写入失败
            {
               std::cout<<"write file content failed"<<std::endl;
               ofs.close();  
               return false;
            }
            
             ofs.close();
             return true;
          }

          bool Compress(const std::string &packname) //压缩
          {
             //读取文件数据
              std::string body;
              if(this->GetContent(&body)==false)
              {
                std::cout<<"compress get file content failed"<<std::endl;
                return false;
              } 
             //对数据进行压缩
             std::string packed=bundle::pack(bundle::LZIP,body);
             

             //将压缩数据存储到压缩包文件中
              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)//解压缩
          {
              //将当前压缩包数据读取出来
              std::string body;
               if(this->GetContent(&body)==false)//获取文件内容
              {
                std::cout<<"compress get file content failed"<<std::endl;
                return false;
              } 
                            
              //对压缩的数据进行解压缩
              std::string unpacked=bundle::unpack(body);

              //将解压缩的数据写入到新文件中
               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 CreateDirectory()//创建目录
          {
            if(this->Exists())
            {
              return true;
            }
             return fs::create_directories(_filename);
          }
 
          bool ScanDirectory(std::vector<std::string> * arry)//浏览目录
          {
             for (auto & p : fs::directory_iterator(_filename))//遍历目录
             {
               if(fs::is_directory(p)==true)//检测遍历到的文件 是一个文件夹 就不进行操作
               {
                  continue;
               }
               //普通文件才进行操作
               //relative_path 表示带有路径的文件名
               arry->push_back(fs::path(p).relative_path().string());
             }
              return true;
          }
  };

  class JsonUtil
  {
    public:
    // 序列化 将结构化数据 转化为字符串
      static bool Serialize(const Json::Value &root, std::string *str)
      {
        Json::StreamWriterBuilder swb;
        std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
        std::stringstream ss;
        sw->write(root,&ss);
        *str=ss.str();

        return true;
      }

      // 反序列化 将字符串 转化为 结构化数据
      static bool UnSerialize(const std::string &str, Json::Value *root)
      {
        Json::CharReaderBuilder crb;
        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
        std::string err;
        bool ret=cr->parse(str.c_str(),str.c_str()+str.size(),root,&err);
        if(ret==false)
        {
          std::cout<<"parse error:"<<err<<std::endl;
          return false;
        }
        return true;
      }
  };
}

#endif
 

service.hpp(业务处理模块)


#ifndef  _MY_SERVICE_//避免头文件重复包含
#define  _MY_SERVICE_
#include "data.hpp"
#include "httplib.h"

extern cloud::DataManager *_data;
namespace cloud
{
   class Service
   {
     private:
          int _server_port;
          std::string _server_ip;
          std::string _download_prefix;
          httplib::Server _server;
   
     private:
   
     static void Upload(const httplib::Request &req,httplib::Response &rsp)//上传请求
     {
             // post /upload 文件数据在正文中
             auto ret=req.has_file("file");//判断有没有上传的文件区域
             if(ret==false)
             {
               rsp.status=400;
               return;
             }
             //若有上传的文件区域 则获取文件包含的各项数据
             const auto& file =req.get_file_value("file");

             //file.filename文件名称   file.content 文件内容
             std::string back_dir= Config::GetInstance()->GetBackDir();
             std::string realpath=back_dir+FileUtil(file.filename).FileName();

             FileUtil fu(realpath);
             fu.SetContent(file.content);//将数据写入文件中

             BackupInfo info;
             info.NewBackupInfo(realpath);//组织备份文件信息
             _data->Insert(info);//向数据管理模块添加备份文件信息
             return;
     }
     
      
      static std::string TimetoStr(time_t  t)
      {
          std::string tmp=std::ctime(&t);
          return tmp;

      }
     static void ListShow(const httplib::Request &req,httplib::Response &rsp)// 获取展示页面处理
     {
         //获取所有文件信息
          std::vector<BackupInfo> arry;
          _data->GetAll(&arry);
          
         //根据所有备份信息 组织html文件数据
          std::stringstream ss;
          ss << "<html><head><title>Download</title><meta charset='UTF-8'></head>";
          ss<<"<body><hl>Download</hl><table>";
          for(auto &a:arry)
          {
             ss<<"<tr>";
             std::string filename=FileUtil(a.real_path).FileName();
             ss<<"<td><a href =' "<<a.url<<" '>"<<filename<< "</a></td>"; 
             ss<<"<td align='right'>"<<TimetoStr(a.mtime)<<"</td>";
             ss<<"<td align ='right'>"<<a.fsize/1024<< "k</td>";
             ss<<"</tr>";
          }
          ss<<"</table></body></html>";
          rsp.body=ss.str();
          rsp.set_header("Content-Type","text/html");//设置头部信息
          rsp.status=200;//状态码设置为200 表示成功响应
          return ;
     }
    
     
    
    static std::string GetETag(const  BackupInfo & info)//获取 ETag字段
    {
         //  etag 文件名 - filename-fsize 文件大小 - mtime 最后一次修改时间 
          FileUtil fu(info.real_path);
          std::string etag =fu.FileName();//获取文件名
          etag+= "-";
          etag+= std::to_string(info.fsize);//获取文件大小(字符串格式)
          etag+="-";
          etag+=std::to_string(info.mtime);//获取最后一次修改时间(字符串格式) 
          return etag; 
    }

     static void Download(const httplib::Request &req,httplib::Response &rsp)//文件下载请求
     {
         // 获取客户端请求的资源路径 req.path
          
         // 根据资源路径  获取文件备份信息
         BackupInfo info;
         _data->GetOneByURL(req.path,&info);//通过URL获取单个数据
      
         //判断文件是否被压缩  如果被压缩 需要先解压缩
         if(info.pack_flag==true)//说明被压缩了
         {
           FileUtil fu(info.pack_path); //使用压缩包路径实例化一个对象
           fu.UnCompress(info.real_path);//解压缩

            // 删除压缩包  修改备份信息
            fu.Remove();//删除文件
            info.pack_flag=false;  
            _data->Update(info);//更新
         }

          // 读取文件数据 放入 rsp.body中
          FileUtil fu(info.real_path);
          fu.GetContent(&rsp.body);//将 real_path中的数据 放入 body中
         
         bool retrans =false;//retrans表示断点续传
         std::string old_etag;
         if(req.has_header("If-Range"))//若存在该头部字段 则说明为断点续传
         {
             old_etag =req.get_header_value("If-Range");//将头部字段对应的value值传给 old_etag
             if(old_etag ==GetETag(info))//若If-Range字段 的值 与请求文件的最新etag一致 则符合断点续传
             {
                retrans =true;
             }
         }

         //若没有 If-Range 字段 则为正常下载
         //如果有这个字段 但是它的值与当前文件的 etag 不一致 则必须返回全部数据
          if(retrans==false)
          {
            //正常下载
            fu.GetContent(&rsp.body);//将 real_path中的数据 放入 body中
            //设置响应头部字段  ETag  Accept-Ranges: bytes
            rsp.set_header("Accept-Ranges","bytes");//建立头部字段
            rsp.set_header("ETag",GetETag(info));
            rsp.set_header("Content-Type","application/octet-stream");
            rsp.status=200;//响应成功
          } 
          else 
          {
            //说明是断点续传 
            //httplib 内部实现对于 断点续传请求的处理
            //只需要用户 将文件所有数据读取到rsp.body中
            //内部会自动根据请求区间 从body中取出指定区间 数据 进行响应
            fu.GetContent(&rsp.body);//将 real_path中的数据 放入 body中
            rsp.set_header("Accept-Ranges","bytes");//建立头部字段
            rsp.set_header("ETag",GetETag(info));
            rsp.set_header("Content-Type","application/octet-stream");
            rsp.status=206;  //区间请求响应  
          }
        }

     public:
     Service()//构造函数
     {
        Config * config=Config::GetInstance();//创建对象
       _server_port=config->GetServerPort();
       _server_ip=config->GetServerIp();
       _download_prefix =config->GetDownloadPrefix();
     }
     bool RunModule()//运行模块
     {
        _server.Post("/upload",Upload);
        _server.Get("/listshow", ListShow);
        _server.Get("/", ListShow);
        std::string download_url =_download_prefix+"(.*)";//下载请求
        _server.Get(download_url,Download);
        
        _server.listen(_server_ip.c_str(),_server_port);
         return true;
     } 
   };
}
#endif 



makefile

.PHONY: cloud
cloud:cloud.cpp  util.hpp 
	g++ -g  $^ -o $@  -L./lib -lpthread  -lstdc++fs -ljsoncpp -lbundle

	

hot.hpp(热点管理模块)


#ifndef _MY_HOT_
#define _MY_HOT_
#include<unistd.h>
#include"data.hpp"


extern cloud::DataManager *_data;
namespace cloud
{
   class HotManager
    {
        private:
           std::string _back_dir;// 备份文件路径
           std::string _pack_dir;// 压缩文件路径
           std::string _pack_suffix;//压缩包后缀名
           int _hot_time; // 热点时间
        
        private:
         bool Hotjudge(const std::string &filename)//热点判断
         {
            FileUtil fu(filename);
             time_t last_atime=fu.LastATime();//文件最后一次访问时间
             time_t cur_time=time(NULL);//当前系统时间
             if(cur_time-last_atime>_hot_time)
             {
                //差值超过设定的值 所以为非热点文件
                 return true;
             }
             //差值小于设定的值 所以为热点文件
             return false;
         }
        
        
        public:
         HotManager() //构造函数
         {
             Config* config=Config::GetInstance();//创建对象
             _back_dir=config->GetBackDir();
             _pack_dir=config->GetPackDir();
             _pack_suffix=config->GetPackFileSuffix();
             _hot_time=config->GetHotTime();
              FileUtil tmp1(_back_dir);
              FileUtil tmp2(_pack_dir);
              //为了防止目录不存在 则创建目录
              tmp1.CreateDirectory();//创建目录
              tmp2.CreateDirectory();

         }

         bool RunModule()//运行模块
         {
            while(1)
            {
             //遍历备份目录 获取所有文件名
             FileUtil fu(_back_dir);
             std::vector<std::string>arry;
             fu.ScanDirectory(&arry);

             //遍历判断文件是否是非热点文件
              for(auto &a :arry)
              {
                  if(Hotjudge(a)==false)//说明为热点文件
                  {
                    //热点文件不需要处理 所以直接跳过
                      continue;
                  }
                
                //获取文件备份信息
                 cloud::BackupInfo bi;
                 //GetOneByRealPat 通过realpath获取单个数据
                 if(_data->GetOneByRealPath(a,&bi)==false)
                 {
                   //有文件存在 但是没有备份信息
                   bi.NewBackupInfo(a);
                 }
                  
                //对非热点文件进行压缩处理
                FileUtil tmp(a);
                tmp.Compress(bi.pack_path);//压缩

                //删除源文件 修改备份信息
                tmp.Remove(); 
                bi.pack_flag=true;//表示压缩成功
                _data->Update(bi);//将bi更新到_table哈希表中
              }
              usleep(1000);
            }
             return true;
         }
    };
     
}

#endif

data.hpp (数据管理模块)


#ifndef _MY_DATA_
#define _MY_DATA_
#include<unordered_map>
#include<pthread.h>
#include"config.hpp"

namespace cloud
{
 
  typedef struct BackupInfo
  {
    bool pack_flag;//压缩标志
    size_t fsize;  //文件大小
    time_t mtime;  //最后一次修改时间
    time_t atime;  //最后一次访问时间
    std::string real_path;//文件实际存储路径
    std::string pack_path;//压缩包存储路径名称
    std::string url; //请求资源路径
    bool  NewBackupInfo(const std::string &realpath)//获取各项属性信息
    {
          FileUtil fu(realpath);
          if(fu.Exists()==false)
          {
            std::cout<<"new backupinfo file not exists" <<std::endl;
            return false;
          }

        Config* config=Config::GetInstance();//创建对象
        std::string packdir=config->GetPackDir();//压缩包存放路径
        std::string packsuffix=config->GetPackFileSuffix();//压缩包后缀名称
        std::string download_prefix =config->GetDownloadPrefix();//URL前缀路径

          this->pack_flag=false;
          this->fsize=fu.FileSize();
          this->mtime=fu.LastMTime();
          this->atime=fu.LastATime();
          this->real_path=realpath; 
          this->pack_path  = packdir+fu.FileName()+packsuffix;
          // ./backdir/a.txt -> ./packdir/a.txt.lz
         
          this->url=download_prefix + fu.FileName();
          //./backdir/a.txt  -> /download/a.txt 
          return true;
    }
  }BackupInfo;

    class DataManager
    {
        private:
          std::string _backup_file;//数据持久化存储文件 
          pthread_rwlock_t  _rwlock;//读写锁 
          std::unordered_map<std::string,BackupInfo> _table;//哈希表
        public:
          DataManager()//构造函数
          { 
            _backup_file=Config::GetInstance()->GetBackupFile();//数据信息存放文件
            pthread_rwlock_init(&_rwlock,NULL);//对读写锁初始化
            InitLoad();

          }
          ~DataManager()//析构函数
          {
             pthread_rwlock_destroy(&_rwlock);//对读写锁进行销毁
          }

          bool Insert(const BackupInfo &info)//新增
          { 
               pthread_rwlock_wrlock(&_rwlock);//加写锁
               _table[info.url]=info;
               pthread_rwlock_unlock(&_rwlock);//解锁
               Storage();
               return true;
          }
          bool Update(const BackupInfo& info)//更新
          {
               pthread_rwlock_wrlock(&_rwlock);//加写锁
               _table[info.url]=info;
               pthread_rwlock_unlock(&_rwlock);//解锁
               Storage();
               return true;
          }
          bool GetOneByURL(const std::string &url,BackupInfo*info)//通过URL获取单个数据
          {
                pthread_rwlock_wrlock(&_rwlock);//加写锁
                //因为url是key值 所以可以直接通过key值来进行查找
                auto it=_table.find(url);
                if(it==_table.end())
                {
                    return false;
                }
                *info= it->second;//获取url对应的info
                pthread_rwlock_unlock(&_rwlock);//解锁
                return true;
          } 
           bool GetOneByRealPath(const std::string &realpath ,BackupInfo*info)//通过realpath获取单个数据
           {
              pthread_rwlock_wrlock(&_rwlock);//加写锁
                 auto it=_table.begin();
                 for(;it!=_table.end();++it)//遍历
                 {
                      if(it->second.real_path==realpath)
                      {
                        *info=it->second;
                        pthread_rwlock_unlock(&_rwlock);//解锁
                        return true;
                      }
                 }
                pthread_rwlock_unlock(&_rwlock);//解锁
                 return false;
           }
           bool GetAll(std::vector<BackupInfo>*arry) //获取所有
           {
                pthread_rwlock_wrlock(&_rwlock);//加写锁
                auto it=_table.begin();
                 for(;it!=_table.end();++it)//遍历
                 {
                    arry->push_back(it->second);
                 }
                 pthread_rwlock_unlock(&_rwlock);//解锁
                 return true;
           }

           bool Storage()//持久化存储实现
           {
              //获取所有数据 
               std::vector<BackupInfo> arry;
               this->GetAll(&arry);//获取所有数据放入arry中
              
               //添加到json::value中
               Json::Value root; 
               for(int i=0;i<arry.size();i++)
               {
                    Json::Value item; 
                    item["pack_flag"]=  arry[i].pack_flag;
                    item["fsize"]=  (Json::Int64)arry[i].fsize;
                    item["atime"]=  (Json::Int64)arry[i].atime;
                    item["mtime"]=  (Json::Int64)arry[i].mtime;
                    item["real_path"]=  arry[i].real_path;
                    item["pack_path"]=  arry[i].pack_path;
                    item["url"]= arry[i].url; 
                    root.append(item); //添加数组元素item       
               }  

               // 对json::value 序列化 
                std::string body;
                JsonUtil::Serialize(root,&body);//序列化 
                 

               //写文件
              FileUtil fu(_backup_file);//数据持久化存储文件 
              fu.SetContent(body);

              return true;
           }
          
          
           bool InitLoad()//初始化加载
           {
              //将数据文件中的数据读取出来
               FileUtil fu(_backup_file);//数据持久化存储文件   
               if(fu.Exists()==false)//若没有数据 则直接返回true 就不用进行加载了
               {
                return true;
               }              
               std::string body;
               fu.GetContent(&body);//将_backup_file文件中的数据 全部读取到body中

               //反序列化
               Json::Value root;
               JsonUtil::UnSerialize(body,&root);//反序列化  将body字符串转化为 root结构化数据
                
               //将反序列化得到的Json::Value中的数据添加到table中
               for(int i=0;i<root.size();i++)
               {
                    BackupInfo info;
                    info.pack_flag =root[i]["pack_flag"].asBool();
                    info.fsize =root[i]["fsize"].asInt64();
                    info.atime =root[i]["atime"].asInt64();
                    info.mtime =root[i]["mtime"].asInt64();
                    info.pack_path =root[i]["pack_path"].asString();
                    info.real_path =root[i]["real_path"].asString();
                    info.url =root[i]["url"].asString();
                    Insert(info);//插入
               }
               return true;
           }
           
    };
}


#endif


config.hpp(配置加载文件模块)


//防止头文件被重复包含
#ifndef _MY_CONFIG_
#define _MY_CONFIG_
#include"util.hpp"
#include<mutex>

namespace cloud
{
 #define CONFIG_FILE "./cloud.conf"
 class Config
 {
   private:
    Config()
    {
       ReadConfigFile();//读取配置文件信息
    }
    static Config*    _instance;
    static std::mutex _mutex;
    
  private:
    int _hot_time;                //热点判断时间
    int _server_port;             //服务器的监听端口
    std::string _server_ip;       //下载的url前缀路径    
    std::string _download_prefix; // 压缩包后缀名称
    std::string _packfile_suffix; //备份文件存放目录
    std::string _pack_dir;        // 压缩包存放目录 
    std::string _back_dir;        // 服务器IP地址
    std::string _backup_file;     // 数据信息存放文件
    bool ReadConfigFile()//读取配置文件
    {
        FileUtil fu(CONFIG_FILE);
        std::string body;
        //获取文件内容到body中
        if(fu.GetContent(&body) ==false)//读取失败
        {
           std::cout<<"load config file failed"<<std::endl;
           return false;
        }
        Json::Value root;
        if(JsonUtil::UnSerialize(body,&root)==false)//反序列化 字符串转化为结构化数据 
        {
            std::cout<<"parse config file failed"<<std::endl;
            return false;
        }   
        _hot_time=root["hot_time"].asInt();
        _server_port=root["server_port"].asInt();
        _server_ip=root["server_ip"].asString();
        _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();
        return true;

    } 

  public:
     static Config *GetInstance()  //创建对象
     {
      if(_instance==NULL)
      {
         _mutex.lock();//加锁
        //若指针为空 则创建对象
        if(_instance==NULL)
        {
          _instance= new Config();//实例化对象
        }
         _mutex.unlock();
      }
       return _instance;
    }
     int  GetHotTime()//获取热点时间
     {
        return _hot_time;
     }
     int  GetServerPort()      //端口号
     {
           return _server_port;
     }
     std::string GetServerIp() //IP地址
     {
        return _server_ip;
     }
     std::string GetDownloadPrefix()//URL前缀路径
     {
         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;
     }

};  
   Config* Config::_instance=NULL;
   std::mutex Config::_mutex;
}


#endif


cloud.conf

{
    "hot_time": 30,  
    "server_port":9090, 
    "server_ip":"10.0.16.6",
    "download_prefix":"/download/",
    "packfile_suffix":".lz",
    "pack_dir": "./packdir/",
    "back_dir": "./backdir/",
    "backup_file":"./cloud.dat"
}


客户端(VS2022下实现)

util.hpp(实用文件工具模块)


#ifndef _MY_UTIL_
#define _MY_UTIL_
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<sys/stat.h>
#include<experimental/filesystem>
#include<memory>

namespace cloud
{
    namespace fs = std::experimental::filesystem;

    class FileUtil
    {
    private:
        std::string _filename;//文件名称
    public:
        FileUtil(const std::string& filename) :_filename(filename)//构造函数 
        {}

        bool Remove()//删除文件
        {
            //若文件不存在 相当于删除成功 则返回true
            if (this->Exists() == false)
            {
                return true;
            }
            remove(_filename.c_str());
            return true;
        }

        size_t FileSize()//文件大小
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get file size failed!\n";
                return 0;
            }
            return st.st_size;
        }

        time_t  LastMTime()//文件最后一次修改时间
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get file size failed!\n";
                return -1;
            }
            return st.st_mtime;
        }

        time_t  LastATime()//文件最后一次访问时间
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get file size failed!\n";
                return -1;
            }
            return st.st_atime;
        }

        std::string FileName()//文件名称
        {
            // ./abc/test.txt
            size_t pos = _filename.find_last_of("\\");
            if (pos == std::string::npos)
            {
                return _filename;
            }
           // return  fs::path(_filename).filename().string();
            return _filename.substr(pos + 1);
        }
        bool GetPostLen(std::string* body, size_t pos, size_t len)//获取文件数据
        {
            //打开成功,获取文件数据
            size_t fsize = this->FileSize();//获取文件大小
            if (pos + len > fsize)//若pos开始位置超过了文件大小
            {
                std::cout << "get file len is error" << std::endl;
                return false;
            }
            std::ifstream ifs;
            ifs.open(_filename, std::ios::binary);//以二进制方式打开文件
            if (ifs.is_open() == false)
            {
                std::cout << "read file failed" << 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 << "get file content fialed " << std::endl;
                ifs.close();
                return false;
            }
            ifs.close();

            return true;
        }

        bool GetContent(std::string* body) //获取整体文件数据
        {
            size_t fsize = this->FileSize();//获取文件大小
            return GetPostLen(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 << "write open file failed" << std::endl;
                return false;
            }
            ofs.write(&body[0], body.size());//将body数据写入到文件中
            if (ofs.good() == false)//写入失败
            {
                std::cout << "write file content failed" << std::endl;
                ofs.close();
                return false;
            }

            ofs.close();
            return true;
        }

     
        bool Exists()//判断文件是否存在
        {
            return fs::exists(_filename);
        }

        bool CreateDirectory()//创建目录
        {
            if (this->Exists())
            {
                return true;
            }
            return fs::create_directories(_filename);
        }

        bool ScanDirectory(std::vector<std::string>* arry)//浏览目录
        {
            this->CreateDirectory();//创建目录
            for (auto& p : fs::directory_iterator(_filename))//遍历目录
            {
                if (fs::is_directory(p) == true)//检测遍历到的文件 是一个文件夹 就不进行操作
                {
                    continue;
                }
                //普通文件才进行操作
                //relative_path 表示带有路径的文件名
                arry->push_back(fs::path(p).relative_path().string());
            }
            return true;
        }
    };
}

#endif



data.hpp (数据管理模块)



#ifndef _MY_DATA_
#define _MY_DATA_
#include<unordered_map>
#include<sstream>
#include"util.hpp"

namespace cloud
{
  class DataManager
  {
   private:
	   std::string _backup_file;//备份信息的持久化存储文件
	   std::unordered_map<std::string, std::string> _table;//哈希表
   public:
	   DataManager(const std::string &backup_file)//构造函数
		   :_backup_file(backup_file)
	   {
		   InitLoad();
	   }

	   bool Storage()//持久化存储
	   {
		   //获取所有的备份信息
		   std::stringstream ss;//字符串流
		   auto it = _table.begin();
		   for (; it != _table.end(); it++)//遍历_table
		   {
			   //将所有信息进行指定持久化格式的组织
			   ss << it->first << " " << it->second << "\n";
		   }
		   //持久化存储
		   FileUtil fu(_backup_file);
		   fu.SetContent(ss.str());//将ss流中的数据写入到fu中
		   return true;
	   }
	   //分割字符串数据
	   int Split(const std::string& str, const std::string& sep, std::vector<std::string>* arry)
	   {
		   int count = 0;
		   size_t pos = 0;//分隔符位置
		   size_t idx = 0;//起始偏移量
		   while (1)
		   {
			   //从起始偏移量开始的 分隔符位置pos
			   pos = str.find(sep, idx);
			   if (pos == std::string::npos)//找不到分隔符就退出循环
			   {
				   break;
			   }
			   if (pos == idx)//若有两个以上分隔符 则跳过分隔符则为起始偏移量
			   {
				   idx = pos + sep.size();
				   continue;
			   }
			   std::string tmp = str.substr(idx, pos - idx);//tmp为获取数据大小
			   arry->push_back(tmp);
			   idx += pos + sep.size();
			   count++;
		   }
		   //最后一个数据也需放入arry数组中
		   if (idx < str.size())
		   {
			   arry->push_back(str.substr(idx));
			   count++;
		   }
		   return count;
	   }
	   bool InitLoad()//初始化加载
	   {
			//从文件中读取所有数据
		   FileUtil fu(_backup_file);
		   std::string body;
		   fu.GetContent(&body);//将数据读取到body中
		    
		    //进行数据解析 添加到表中
		   std::vector<std::string>arry;
		   Split(body,"\n",&arry);//分割字符串数据 放入arry数组中
		   for (auto& a : arry)
		   {
			   //在arry数组中 的每一个元素 分为  文件名 唯一标识
			   std::vector<std::string>tmp;
			   Split(a, " ", &tmp);//分割出文件名和唯一标识
			   if (tmp.size() != 2)
			   {
				   continue;
			   }
			   //tmp[0]文件名  tmp[1]唯一标识
			   _table[tmp[0]] = tmp[1];//使哈希表中文件名和唯一标识一一对应
		   }  
		   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;
	   }
  };
}

#endif

cloud.hpp (文件备份模块)



#ifndef _MY_CLOUD_  //防止头文件重复包含
#define _MY_CLOUD_ 
#include"data.hpp"
#include"httplib.h"
#include<windows.h>


namespace cloud
{
 #define  SERVER_ADDR "192.144.206.100"
 #define   SERVER_PORT 9090
	class Backup
	{
	  private:
		  std::string  _back_dir;
		  DataManager* _data;
      public:
		  Backup(const std::string& back_dir, const std::string& back_file)//构造函数
			  :_back_dir(back_dir)
		  {
			  _data = new DataManager(back_file);
		  }
			
		  std::string GetFileIdentifier(const std::string &filename)//创建文件唯一标识
		  {
			  //a.txt -fsize(文件大小)-mtime(最后一次修改时间)
			  FileUtil fu(filename);
			  std::stringstream ss;
			  ss << fu.FileName() << "-" << fu.FileSize() << "-" << fu.LastMTime();
			  return ss.str();
		  }
		
		  
		  bool Upload(const std::string &filename)//文件上传
		  {
			  //获取文件数据
			  FileUtil fu(filename);
			  std::string body;
			  fu.GetContent(&body);//读取文件数据到body中

			  //搭建http客户端上传 文件数据
			  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 res = client.Post("/upload", items);//发送post请求 URL资源路径upload
			  if (!res || res->status != 200)//res不存在 或者状态码不为200
			  {
				  //失败
				  return false;
			  }
			  return true;
		  } 
		  
	
		  bool IsNeedUpload(const std::string &filename)//判断一个文件是否需要上传
		  {
			  //需要上传的文件的判断条件 1.文件是新增的  2.不是新增的但是被修改过
			  
			  //若文件是新增的: 则只需看有没有历史备份信息 若有则为新增的 
			  std::string id;
			  if (_data->GetOneByKey(filename, &id) != false)//找到了对应的历史信息
			  {
				  std::string new_id = GetFileIdentifier(filename);//获取新的标识信息
				  if (new_id == id)//与历史文件信息一致 则不需要上传
				  {
					  return false;
				  }
			  }
			  //不是新增的但是被修改过:有历史信息	但是历史的唯一标识与当前最新的唯一标识不一致

			  FileUtil fu(filename);
			  //若3秒钟内被修改过 就认为文件还在修改中
			  if (time(NULL) - fu.LastMTime() < 3)
			  {
				  return false;
			  }
			  std::cout << filename << " need upload! \n";
			  return true;
		  }
	
		  bool RunModule()//运行模块
		  {
			  while (1)
			  {
				  //1.遍历指定文件夹中的所有文件
				  FileUtil fu(_back_dir);
				  std::vector<std::string>arry;
				  fu.ScanDirectory(&arry);//将_back_dir中的数据传入arry数组中

				  //2.逐个判断文件是否需要上传
				  for (auto& a : arry)
				  { 
					  if (IsNeedUpload(a) == false)//不需要上传
					  {
						  continue;
					  }
					  //3.如果需要上传 则上传文件
					  if (Upload(a) == true)
					  {
						  //若上传成功 则新增文件备份信息 
						  // 备份信息: 文件名称  唯一标识
						  _data->Insert(a, GetFileIdentifier(a));
						  std::cout << a << "upload success!\n";
					  }
				  }
				  Sleep(1);
				  std::cout << "------loop end -----\n";
			  }
		  } 
	};
}
	
#endif

cloud.cpp


#include"util.hpp"
#include"data.hpp"
#include"cloud.hpp"

#define BACKUP_FILE "./backup.dat"
#define BACKUP_DIR "./backup/"
int main()
{
	cloud::Backup backup(BACKUP_DIR, BACKUP_FILE); \
		backup.RunModule();
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【原创分享】高功率电源PCB设计中变压器下方走线的关键技巧

高功率电源的设计中&#xff0c;变压器起到了电能的传递与转换的重要作用。变压器下方的走线设计不仅涉及到电路的功率传输效率&#xff0c;还与电磁兼容性&#xff08;EMC&#xff09;、热管理以及电路的可靠性密切相关。 1. 走线布局 在进行变压器下方走线设计时&#xff0c…

Vmware虚拟机简介和安装

作者&#xff1a;余小小 常见的虚拟机 vmwarevirtualBox Vmware 运行在win系统上centos运行在Vm上 先安装vm&#xff0c;在安装centos系统 Vmware介绍 不用分区或者重开机&#xff0c;就可以在同一台pc上使用多种操作系统完全隔离&#xff0c;且保护不同的操作系统环境和文…

Kubernetes常用工作负载控制器

文章目录 一、常用负载控制器是什么二、Deployment控制器1.介绍2.使用流程3.应用部署4.应用升级5.滚动升级实现原理&#xff08;replicaset控制器&#xff09;6.滚动升级实现流程7.滚动升级策略8.应用实例扩容和缩容9.应用发布失败回滚10.应用下线 三、DaemonSet控制器四、Job控…

Linux修改时区失败,手动修改localtime无效

有时候改了这个也不行&#xff0c;用命令行修改也不行 解决办法 &#xff1a;cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 或者想改其他时区的直接 ll /usr/share/zoneinfo/ 查看

2023年1月18日 Go生态洞察:开发者的声音与Go的进化

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

WebGL开发交互式艺术品技术方案

开发交互式艺术品需要使用 WebGL 技术&#xff0c;并结合其他前端技术以实现丰富的用户体验。以下是一个可能的技术方案&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.WebGL 框架&#xff1a; 选…

AWS攻略——子网

文章目录 分配子网给Public子网分配互联网网关创建互联网网关附加到VPC 给Public子网创建路由表关联子网 打通Public子网和互联网网关 创建Public子网下的EC2进行测试配置Private子网路由给Private子网创建路由表附加在Private子网 创建Private子网下的EC2进行测试创建实例在跳…

Cannot resolve com.lxz.springcloud:cloud-api-commons:1.0-SNAPSHOT

原因可能是groupId等信息写错了 导入的jar包的groupId要与它自己的坐标匹配

创新、升级丨数据手套FOHEART Pro开启手势识别新篇章!

在人机交互领域&#xff0c;我们始终追求更加自然、逼真的体验。正如现实生活中&#xff0c;我们习惯于通过语言和表情来传达思想和情感&#xff0c;然而&#xff0c;在虚拟世界中&#xff0c;人机交互需要以更加直观、生动的方式进行操作、控制和交互。 为了更好地满足市场的…

Isaac Sim教程07 拓展编程Extension

Isaac Sim 拓展编程Extension 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. The aut…

出海风潮:中国母婴品牌征服国际市场的机遇与挑战!

近年来&#xff0c;中国母婴品牌在国内市场蓬勃发展的同时&#xff0c;也逐渐将目光投向国际市场。这一趋势不仅受益于中国经济的崛起&#xff0c;还得益于全球市场对高质量母婴产品的不断需求。然而&#xff0c;面对国际市场的机遇&#xff0c;中国母婴品牌同样面临着一系列挑…

【稳定检索|投稿优惠】2024年光电信息与机器人发展国际会议(ICOIRD 2024)

2024年光电信息与机器人发展国际会议(ICOIRD 2024) 2024 International Conference on Optoelectronic Information and Robot Development(ICOIRD 2024) 一、【会议简介】 信息技术与人工智能的浪潮正在激荡&#xff0c;不断刷新我们生活的页面&#xff0c;深刻烙印在光电信息…

如何为 3D 模型制作纹理的最佳方法

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 您可以通过不同的方式为 3D 模型创建 3D 纹理。下面我们将介绍为 3D …

Django回顾6

目录 一.Session 1.什么是Session 2.Django中Session相关方法 3.Django中的Session配置 二.中间件 1.什么是中间件 中间件的定义 2.中间件有什么用 3.自定义中间件 process_request和process_reponse &#xff08;1&#xff09;导入 &#xff08;2&#xff09;自定义…

分享116个图片JS特效,总有一款适合您

分享116个图片JS特效&#xff0c;总有一款适合您 116个图片JS特效下载链接&#xff1a;https://pan.baidu.com/s/1WvUvmG1adR2EJG97MiGj3A?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整…

网卡bonding绑定

目录 一、概念 1、概述&#xff1a; 二、实验 1、绑定案例&#xff1a; 一、概念 1、概述&#xff1a; 将多个物理网卡进行排列组合&#xff0c;形成逻辑网卡&#xff0c;网卡的高可用 绑定模式 mode0&#xff08;平衡负载模式&#xff09;&#xff1a;平时两块网卡均工…

安全AI系统开发指南

执行摘要 本文件建议为使用人工智能&#xff08;AI&#xff09;的任何系统的提供商提供指导方针&#xff0c;无论这些系统是从头开始创建的&#xff0c;还是建立在他人提供的工具和服务之上的。实施这些指导方针将有助于提供商构建按预期运行、在需要时可用的人工智能系统&…

“上云”还是“下云”?探云计算的下一站未来!

引言 10 月 27 日&#xff0c;X&#xff08;原Twitter&#xff09;工程技术发布帖子称&#xff0c;在过去的一年里&#xff0c;技术团队优化了 X 的云服务使用方式&#xff0c;着手将更多工作负载迁往本地基础设施。这一转变使 X 每月的云成本降低了 60%。所有媒体、Blob 存储均…

docker搭建xxl-job

使用docker-compose创建并运行xxl-job 查看、下载镜像 docker search xxl-job # 结果&#xff0c;自己指定版本 xuxueli/xxl-job-admin:2.3.1创建文件夹 /usr/local/software/xxl-job/logs编排docker-compose文件 version: 2 networks:wn_docker_net:external: true servic…

springboot -事务管理

事务 概念 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败。 操作 开启事务&#xff1a; start transaction / begin提交事务&#xff1a;commit回滚事务&#xff1a; rollback 注解 Transactional …