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

news2025/4/18 3:02:23

文章目录

  • 客户端
    • 客户端实现思想
    • 客户端文件操作类的设计与拷贝
      • 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;
}

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

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

相关文章

正点原子linux应用编程——提高篇5

这篇笔记记一下网络应用编程以及CAN总线的应用编程。 网络基础知识 这个在学习lwIP的时候已经接触过了&#xff0c;这边再过一下&#xff0c;我自己觉得没什么意思的我就跳过了。 网络通信概述 网络通信本质上是一种进程间通信&#xff0c;是位于网络中不同主机上的进程之间…

麒麟linux将图片批量生成PDF的方法

笔者手里有一批国产linu系统&#xff0c;目前开始用在日常的工作生产环境中&#xff0c;我这个老程序猿勉为其难的充当运维的或网管的角色。 国产linux系统常见的为麒麟Linux&#xff0c;统信UOS等&#xff0c;基本都是基于debian再开发的linux。 问题描述&#xff1a; wind…

冬天来了,波司登的高端化“春天”不远了?

最近&#xff0c;羽绒服频繁“贵”上热搜。 在众多热搜词条中&#xff0c;一条“国产羽绒服卖到7000元”的话题一度将波司登推上了舆论的风口浪尖。 对此&#xff0c;波司登在最新的业绩说明会上进行了回应&#xff0c;公司表示&#xff1a;“波司登旗下主品牌及子品牌将形成差…

律所信息化建设成为趋势,Alpha系统助力律所数字化升级

近些年来&#xff0c;越来越多的律所借助数字化技术进行信息化建设&#xff0c;围绕“智慧律所”建设做了大量的努力。为尽快完成这一目标&#xff0c;经过深入研判&#xff0c;多数律所决定引进“Alpha法律智能操作系统”。该系统以其强大功能为律所智慧化建设注入催化剂。 据…

2023年AI工具排行榜:最全工具汇总!

如今&#xff0c;人工智能技术正在快速崛起,AI助手、语音识别、机器翻译等工具深深渗透到我们的工作和生活中。这些智能工具极大地提高了我们的工作效率,使我们能更加专注于创造性的任务。 本文将为读者推荐一些实用的AI神器,只要掌握其中一个,就能极大地提升你的工作能力,事半…

使用Python的PyQt实现财务综合计算

背景&#xff1a; 考核内容 使用 Python 编写程序代码&#xff0c;设计一个带交互界面的财务分析软件&#xff0c;并满足以下要求: PART1:《财务软件设计思路报告》 (30分) (1)编写《财务软件设计思路报告》&#xff0c;描述你编制这个财务软件的设计目标、应用场景、设计思路…

Mysql进阶-事务锁

前置知识-事务 事务简介 事务 是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 就比如: 张三给李四转账1000块钱&#xff0…

基于AT89C51单片机四位加法计算器的设计

1&#xff0e;设计任务 利用AT89C51单片机为核心控制元件,设计一个四位加法计算器&#xff0c;设计的系统实用性强、操作简单&#xff0c;实现了智能化、数字化。 1&#xff09;、通过4*4矩阵键盘输入数字及运算符&#xff1b; 2&#xff09;、可以进行4位十进制数以内的加法…

线程池(Linux +C)

参考 手写线程池 - C语言版 | 爱编程的大丙 (subingwen.cn) 目录 1.为什么需要线程池&#xff1f; 1&#xff09;线程问题&#xff1a; 2&#xff09;如何解决线程问题&#xff08;线程池的优势&#xff09;&#xff1a; 2.线程池是什么&#xff1f; 1&#xff09;线程的…

某夕夕商家告诉你:这样寄快递居然这么省钱(便宜寄全国)

在当下很多时候寄快递成为了困扰很多人的问题&#xff0c;比如很多时候都会面临运费贵的问题&#xff0c;而且寄快递的效率也得不到保障&#xff0c;即使投诉快递员最终也是无济于事。其实在目前来看寄快递并没有这么难&#xff0c;闪侠惠递就能够有效的寄快递&#xff0c;而且…

试验数字化平台WDP 助力车企数据管理加速度

一 现状 随着现代测控技术的提高&#xff0c;数据结构变得越来越复杂多样&#xff0c;数据量也在日益增大。又因试验条件的限制&#xff0c;大多数企业的数据管理方式主要是通过各类电子文档将试验数据保存在每个工程师的移动电脑中&#xff0c;再进行汇总存储和共享。这种落后…

OpenHarmony 设备启动Logo和启动视频替换指南

前言 OpenHarmony源码版本&#xff1a;4.0release 开发板&#xff1a;DAYU / rk3568 一、Logo替换 替换其中的logo.bmp 和 logo_kernel.bmp文件 注意事项&#xff1a; 1、图片的分辨率需要和设备匹配 2、如果是非首次编译&#xff08;存在缓存&#xff09;需要将out目录删…

【Backbone】TransNeXt:最新ViT模型(原理+常用神经网络汇总)

文章目录 一、近几年神经网络 Backbone 回顾1.Densenet 与 Resnet2.CBP3.SENet4.GCNet5.DANet6.PANet 与 FPN7.ASPP8.SPP-net9.PSP-net10.ECA-Net 二、TransNeXt&#xff08;2023&#xff09;1.提出问题2.Aggregated Pixel-focused Attention2.1 Pixel-focused Attention&#…

如何一个月内发表一篇中文核心 干货分享

发论文经验教学 干货分享&#xff1a;如何在一个月内发表一篇中文核心 经验分享 干货分享_哔哩哔哩_bilibili

元宇宙红色展厅VR虚拟展馆提高受训者的参与感

生活在和平年代的新一代青少年&#xff0c;可能对革命先烈英勇事迹难以有很深的体会&#xff0c;无法切实感受到中国共产党无畏牺牲、誓死保家卫国的红色精神&#xff0c;因此借助VR虚拟现实制作技术&#xff0c;让参观者们走近革命先烈中&#xff0c;感受老一辈无产阶级革命家…

C语言-字符串变量

字符串变量 char* s “Hello, world!”&#xff1b; s是一个指针&#xff0c;初始化为指向一个字符串常量 由于这个常量所在的地方&#xff0c;所以实际上s是const char* s&#xff0c;但是由于历史的原因&#xff0c;编译器接受不带const的写法但是试图对s所指的字符串做写…

龙迅#LT8311X3 USB中继器应用描述!

1. 概述 LT8311X3是一款USB 2.0高速信号中继器&#xff0c;用于补偿ISI引起的高速信号衰减。通过外部下拉电阻器选择的编程补偿增益有助于提高 USB 2.0 高速信号质量并通过 CTS 测试。 2. 特点 • 兼容 USB 2.0、OTG 2.0 和 BC 1.2• 支持 HS、FS、LS 信令 • 自动检测和补偿 U…

【MATLAB源码-第95期】基于matlab的协作通信中(AF模式)中继选择算法对比。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 最大最小中继选择 (Max-Min Relay Selection)&#xff1a;这种算法选择能够提供最大最小信号强度的中继。它首先计算所有可用中继的信号强度&#xff0c;然后选择那些在最差信道条件下仍能保持最高信号强度的中继。其目的…

Vue3引入markdown编辑器--Bytemd

字节跳动开源了一款markdown编辑器&#xff0c;bytemd&#xff0c;项目地址&#xff1a;GitHub - bytedance/bytemd: ByteMD v1 repository 安装 npm i bytemd/vue-next 引入方式如下&#xff0c;再main.js中引入样式 import bytemd/dist/index.css 直接封装一个Markdown编…

1.vue学习笔记(vue简介+API风格+开发前的准备)

1.介绍 1.一款用于构建用户页面的JavaScript框架 2.基于HTML、CSS、JavaScript 3.官方文档&#xff1a;cn.vuejs.org2.渐进式框架 1.注重灵活性/可被逐步集成 根据需求场景&#xff1a;1.无需构建步骤&#xff0c;渐进式增强静态的HTML2.在任何页面中作为Web Components嵌入&…