『 实战项目 』Cloud Backup System - 云备份
文章目录
- 云备份项目
- 服务端功能
- 服务端功能模块划分
- 客户端功能
- 客户端模块划分
- 项目条件
- Jsoncpp第三方库
- Bundle第三方库
- httplib第三方库
- Request类
- Response类
- Server类
- Client类
- 搭建简单服务器
- 搭建简单客户端
- 服务端工具类实现 - 文件实用工具类
- 服务器配置信息模块实现- 系统配置信息
- 服务端配置信息模块实现 - 单例文件配置类设计
- 服务端管理模块实现 - 需要管理的数据(后期需要用到的数据)
- 服务端管理模块设计
- 热点管理模块
- 网络通信模块和业务处理模块
- 网络通信接口设计
- 服务端业务处理类的设计
- 断点续传
- 客户端
- 数据管理模块
- 数据管理类设计
- 文件备份类设计
- 项目源码
云备份项目
将本地指定目录中需要备份的文件进行上传,将文件上传至服务器当中;
且随时能通过浏览器进行查看与下载;
下载过程中支持断点续传(暂停后继续下载);
服务器对所上传文件进行热点管理:
-
判定文件是否为热点文件
热点文件的判定为在一定时间或者条件内访问该文件的此处或者是文件最后一次访问的时间来判断是否为热点文件;
-
为热点文件
不处理
-
为非热点文件
将非热点文件进行压缩处理以节省云服务器的磁盘空间;
-
服务端功能
-
支持客户端文件的备份
-
支持客户端浏览器的查看与下载
-
支持客户端文件下载功能
断点续传;
-
热点文件管理
对服务器中长时间无访问的文件进行压缩存储存储;
服务端功能模块划分
-
数据管理模块
管理的备份文件信息,以便于随时获取;
-
网络通信模块
实现与客户端的网络通信;
-
业务处理模块
负责上传,展示列表,下载(断点续传)功能;
-
热点文件管理模块
对长时间无访问文件进行压缩存储;
客户端功能
-
指定文件夹中的文件检测
获取文件夹中的文件;
-
判断文件是否需要备份
新增的文件,备份在服务端但被修改过的文件;
除此之外间隔一段时间进行判断是否需要备份(如五分钟,十分钟);
-
将需要备份的文件上传到服务器上
客户端模块划分
-
数据管理模块
管理备份文件信息;
-
文件检测模块
检测/监控指定的目录/文件夹;
-
例:
当一个文件被加载至文件夹中, 而数据管理模块中备份文件信息并没有该文件的信息则表示该文件为新加入的文件,需要进行备份;
检测一个文件的信息与备份的文件信息是否一致,检测文件是否又被修改,若是文件被修改则需要进行备份;
-
-
文件备份模块(网络通信模块)
上传需要备份的文件数据,使得服务端存储所上传的数据;
项目条件
环境要求编译器gcc-c++ 7.3.1 以上支持;
-
环境搭建:
bundle数据压缩库,jsoncpp库,httplib第三方网络库;
Jsoncpp第三方库
数据交换格式,数据序列化与反序列化;
数据类型: 对象,数组,字符串,数字
-
对象
使用花括号
{}
括起来的表示为一个对象; -
数组
使用中括号
[]
括起来的表示一个数组; -
字符串
使用
""
常规双引号括起来表示一个字符串; -
数字
直接使用,包括整形和浮点型;
假设一组数据以cpp
的形式为:
/* Cpp */
char* name1 = "张三";
char* name2 = "李四";
int age1 = 18;
int age2 = 22;
float score1[3] = {88,89,90};
float score2[3] = {99,82.5,100}
对应json
的形式则为:
[{"姓名" : "张三","年龄" : 18,"成绩" : [88,89,90]},{"姓名" : "李四","年龄" : 22,"成绩" : [99,82.5,100]}
]
Bundle第三方库
对文件进行压缩与解压缩;
为嵌入式第三方库,嵌入式表示需要将头文件源文件嵌入至项目内(静态库.h
文件);
/* 使用例 */#include <cassert>
#include "bundle.h"int main() {using namespace bundle;using namespace std;// 23 mb datasetstring original( "There's a lady who's sure all that glitters is gold" );for (int i = 0; i < 18; ++i) original += original + string( i + 1, 32 + i );// pack, unpack & verify all encodersvector<unsigned> libs { RAW, SHOCO, LZ4F, MINIZ, LZIP, LZMA20,ZPAQ, LZ4, BROTLI9, ZSTD, LZMA25,BSC, BROTLI11, SHRINKER, CSC20, BCM,ZLING, MCM, TANGELO, ZMOLLY, CRUSH, LZJB};for( auto &lib : libs ) {string packed = pack(lib, original);string unpacked = unpack(packed);cout << original.size() << " -> " << packed.size() << " bytes (" << name_of(lib) << ")" << endl;assert( original == unpacked );}cout << "All ok." << endl;
}
其中主要的操作为pack()
与unpack
;
共有三个等级的API接口:
namespace bundle
{// low level API (raw pointers)bool is_packed( *ptr, len );bool is_unpacked( *ptr, len );unsigned type_of( *ptr, len );size_t len( *ptr, len );size_t zlen( *ptr, len );const void *zptr( *ptr, len );bool pack( unsigned Q, *in, len, *out, &zlen );bool unpack( unsigned Q, *in, len, *out, &zlen );// medium level API, templates (in-place)bool is_packed( T );bool is_unpacked( T );unsigned type_of( T );size_t len( T );size_t zlen( T );const void *zptr( T );bool unpack( T &, T );bool pack( unsigned Q, T &, T );// high level API, templates (copy)T pack( unsigned Q, T );T unpack( T );
}
主要的操作流程为:
- 压缩
- 创建两个文件流(读文件流和写文件流)
- 用读文件流打开原文件(以二进制形式即
std::ios::binary
) - 用
seekg()
将文件流跳转至末尾并使用tellg()
获取当前文件流位置(本意是获取偏移量来计算文件大小)最后再次使用seekg()
将文件流跳回远处 - 创建一个
std::string
字符串对象并使用文件大小来初始化字符串大小 - 读文件流调用
read()
将文件数据写至字符串中 - 字符串调用
bundle::pack()
传入字符串进行压缩同时将返回一个字符串std::string
(Hight level API) - 用写文件流打开文件(以二进制形式即
std::ios::binary
) - 调用
write()
将压缩后的数据写入文件中作为压缩文件 - 关闭两个文件流
- 解压过程一致,只是调用接口不同,不进行赘述
httplib第三方库
第三方的网络库,一个C++11
单头文件的跨平台HTTP/HTTPS
库;
使用时包含httplib.h
头文件即可;
使用第三方库能够使得更加轻松的搭建一个http
服务器或者客户端从而提高开发效率;
Request类
Http请求格式通常为:
Request类为如下:
struct MultipartFormData { std::string name; // 字段名称std::string content; // 文件内容std::string filename; // 文件名称std::string content_type; // 正文类型};
using MultipartFormDataItems = std::vector<MultipartFormData>;struct Request {// HTTP method and pathstd::string method; // 请求方法std::string path; // 资源路径// Query parameters and headersParams params; // 查询字符串Headers headers; // 头部字段// Request bodystd::string body; // 正文部分// Remote and local address informationstd::string remote_addr;int remote_port = -1;std::string local_addr;int local_port = -1;// Server-specific attributesstd::string version; // 协议版本MultipartFormDataMap files; // 所保存的客户端上传文件信息(见首行)Ranges ranges; // 用于实现断点续传的请求文件区间// 存在一个开始位置和结束位置// Header-related methodsbool has_header(const std::string &key) const; // 查询header中是否有哪个头部字段std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; // 获取头部字段的值void set_header(const std::string &key, const std::string &val); // 设置头部字段的值bool has_file(const std::string &key) const; // 是否包含某个文件MultipartFormData get_file_value(const std::string &key) const; // 获取文件信息
private:
// ...
};
客户端保存的所有http
请求相关信息,将其组织成http
请求报文并发送给服务器;
服务器收到http
请求后进行解析,将解析的数据保存在Request
结构体中;
Response类
http响应格式为:
Response类通常存储响应信息;
Response类为如下:
struct Response {std::string version; // 协议版本 - 不需要填充int status = -1; // 响应状态码Headers headers; // 头部字段std::string body; // 响应给客户端的正文void set_header(const std::string &key, const std::string &val); // 设置头部字段void set_content(const std::string &s, const std::string &content_type); // 设置正文// ...
};
-
功能:
用户将响应数据放到结构体中,httplib将会其中的数据按照httpResopnse格式组织成为一条响应,并发送给客户端;
Server类
Server类用于搭建http服务器:
class Server {
public:using Handler = std::function<void(const Request &, Response &)>; // 函数指针类型 - 定义了一个http请求处理回调函数格式// httplib搭建的服务器收到请求后进行解析,得到一个Request结构体,其中包含请求数据// 根据请求数据从而可以处理这个请求 处理函数定义的格式就是Handler// Request参数保存请求数据 让用户能够根据请求数据进行业务处理// Response参数需要用户在业务处理中填充数据,最终将数据通过响应的方式返回给客户端// -----------using Handlers = std::vector<std::pair<std::regex, Handler>>;// Handlers 表示一个请求路由数组(请求与处理函数映射表)// 其中regex 是一个正则表达式 用于匹配http请求资源路径// Handler 为请求处理函数指针// 可以理解为 Handlers 是一张表,映射了一个客户端请求的资源路径和一个处理函数(用户自定义的函数)// 当服务器接收到了对应的请求后将会根据资源路径以及请求方法到这张表中查找是否有匹配的处理函数 若是没有匹配的处理函数则返回 404// -----------std::function<TaskQueue *(void)> new_task_queue;// 线程池 - 用于处理请求// 当httplib收到一个新的连接时 这个客户端连接将会被扔进线程池中;/* 线程池中线程的工作:1. 接收请求,解析请求,得到Request结构体2. 在Handlers映射表中,根据请求信息查找处理函数,如果有则调用处理函数 void(const Request &, Response &)3. 当处理函数调用完毕,根据函数返回的Response结构体中的数据组织http响应发回给客户端*/// -----------bool listen(const std::string &host, int port, int socket_flags = 0);// 搭建并启动 http 服务器// -----------// 下列函数为针对某种请求方法的// 其中 handler 可调用对象参数为需要传入的函数指针// 其中 pattern 表示资源路径 正则表达式的格式 Server &Get(const std::string &pattern, Handler handler);Server &Post(const std::string &pattern, Handler handler);Server &Put(const std::string &pattern, Handler handler);Server &Patch(const std::string &pattern, Handler handler);Server &Delete(const std::string &pattern, Handler handler);Server &Options(const std::string &pattern, Handler handler);};
Client类
struct MultipartFormData { std::string name; // 字段名称std::string content; // 文件内容std::string filename; // 文件名称std::string content_type; // 正文类型};
using MultipartFormDataItems = std::vector<MultipartFormData>;
class Client {
public:// Universal interfaceexplicit Client(const std::string &scheme_host_port); // 传入服务器的IP地址和端口// 下列接口都为客户端向服务端发送对应请求Result Get(const std::string &path, const Headers &headers); // 向服务端发送GET请求 参数分别传递请求路径和头部字段Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type); // 向服务端发送 POST 请求, path 表示提交给哪个资源路径, body 表示正文数据, content_length 表示正文长度, content_type 表正文类型Result Post(const std::string &path, const MultipartFormDataItems &items); // 向服务端发送 POST 请求, path 表示资源提交路径, items 的类型为 MultipartFormDataItems, 实际上为一个vector数组;// POST 请求提交多区域数据, 常用于多文件上传
};
搭建简单服务器
搭建服务器类最简单的方式就是使用httplib
第三方库实例化出一个对应的Server
类服务器对象server
;
通过httplib::Server::Get()
,httplib::Server::Post()
等成员函数将对应的请求与处理函数注册进服务器当中;
最后通过httplib::Server::listen()
函数传入需要监听的IP
和端口号,实例化并启动服务器;
#include <iostream>#include <regex.h>#include "httplib.h"void Hello(const httplib::Request &req,httplib::Response &rsp){rsp.set_content("Hello world","text/plain"); // 设置响应正文 其中数据类型为正文类型 "text/plain"rsp.status = 200; // 设置状态码为 200
}void Numbers(const httplib::Request &req, httplib::Response &rsp){auto num = req.matches[1]; // 位置[0]中保存的是整体path,往后的下标中保存的都是捕捉的数据rsp.set_content(num,"text/plain"); // 此处 num 获取上来仍为一个字符串 并未进行类型转移 因此直接以文本类型输出即可rsp.status = 200;
}void Multipart(const httplib::Request &req, httplib::Response &rsp){// 来打酱油的 混个眼熟 在客户端中再配auto ret = req.has_file("file"); // 判断传入的请求中是否传有名为 "file" 的文件if(ret == false) {// 表示不是文件上传std::cout<<"not file upload\n";rsp.status = 400;return ;}const auto &file = req.get_file_value("file"); // 获取文件区域数据信息rsp.body.clear();rsp.body = file.filename; // 文件名称rsp.body += "\n";rsp.body += file.content; // 文件内容rsp.set_header("Content-Type", "text/plain"); // 设置头部字段rsp.status = 200; // 设置状态码return ;}int main(){// std::cout<<"hello world"<<std::endl;httplib::Server server; // 实例化一个Server对象server.Get("/hi",Hello); // 注册一个针对"hi"的GET请求的处理函数映射关系// server.Get(R"(/number/(\d+))",Numbers); // 这两行内容大致相同 其中采用 R 来去除字符串中特殊含义 如转义字符 server.Get("/number/(\\d+)",Numbers); // vimplus 中的报错可以不用卵// ()用来捕捉数据// 这里是注册一个针对"/number/[具体数字]"的GET请求的处理函数映射关系// \d 是正则表达式 表示[0-9]的字符 // + 表示子表达式一次或多次 (>=1) server.Post("/multipart",Multipart);server.listen("0.0.0.0",8121); // 创建并使用服务器return 0;
}
运行服务器后浏览器可以正常访问(防火墙已经开启);
搭建简单客户端
客户端的搭建与服务器的搭建别无二致;
客户端只需要实例化出一个httplib::Client
类型的客户端类并传入需要访问的服务器IP地址与端口号即可;
无需调用listen
,当程序运行时即表示对服务器进行访问;
在客户端中可以直接调用对应的成员函数,如httplib::Client::Get()
,httplib::Client::Post()
函数来进行对应的操作,如上传某个资源或者获取某个资源;
#include <iostream>
#include "httplib.h" // 引入 httplib 库的头文件,用于 HTTP 请求处理
#include <vector> // 引入 vector 容器,用于存储 multipart 文件条目// 定义服务器地址和端口号
static const char* SERVER_IP = "114.55.52.91"; // 目标服务器的 IP 地址
static const uint16_t SERVER_PORT = 8121; // 目标服务器对应的端口号int main() {// 创建一个 HTTP 客户端,设置目标服务器的 IP 和端口httplib::Client client(SERVER_IP, SERVER_PORT);// 定义一个 MultipartFormData 对象,用于存储文件字段信息httplib::MultipartFormData item;item.name = "file"; // 字段名称为 file,对应表单中的 key 值item.filename = "hello.txt"; // 设置文件名称为 hello.txtitem.content = "Hello world"; // 设置文件内容为 Hello worlditem.content_type = "text/plain"; // 指定文件的 MIME 类型为纯文本类型 (text/plain)// 创建一个列表(容器),用于存放多个 MultipartFormData 对象httplib::MultipartFormDataItems items;items.push_back(item); // 将上述文件数据结构添加到列表中// 向服务器发送 POST 请求到路径 /multipart,并附加 multipart 文件数据auto res = client.Post("/multipart", items);// 打印服务器返回的 HTTP 状态码和响应内容if (res) { // 检查请求是否成功 (res 是否为 nullptr)std::cout << "HTTP Status Code: "<< res->status << std::endl; // 输出状态码std::cout << "Response Body: "<< res->body << std::endl; // 输出响应主体} else { // 如果请求失败std::cerr << "Error: Failed to connect to server! "<< std::endl;}return 0;
}
该案例中使用了httplib::MultipartFormData
来定义一个文件数据结构并上传对应的文件信息;
结合上文服务器构建;
运行程序结果如下:
服务端工具类实现 - 文件实用工具类
设计一个封装文件操作类,用于简化后续任意模块对文件的操作;
-
结构大致如下:
class {private:std::string _filename; // 文件名(包含路径)public:size_t FileSize(); // 获取文件大小time_t LastMTime(); // 获取文件最后一次修改时间time_t LastATime(); // 获取文件最后一次访问时间 (通过访问时间判断是否为热点文件)std::string FileName(); // 获取文件路径名中的文件名称bool SetContent(const std::string &body); // 向文件内写数据(write)bool GetContent(std::string *body); // 从文件中读取数据(read)bool GetPoslen(std::string *body, size_t pos, size_t len); // 获取文件指定位置 指定长度的数据 (断点续传)bool Exists(); // 判断文件(或目录)是否存在bool CreateDirectory(); // 创建目录bool GetDirectory(std::vector<std::string> *arry); // 遍历目录(目录也是文件) 获取目录中所有文件的文件名(包括路径)bool Compress(const std::string &packname); // 进行热点管理时的压缩bool UnCompress(const std::string &packname); // 解压缩 };
服务器配置信息模块实现- 系统配置信息
-
热点判断时间
判断多长时间没有访问的文件为非热点文件;
-
文件下载的URL前缀路径
采用Web根目录的方式,如
wwwroot
的方式配置web
根目录使得避免客户端可通过路径直接访问到服务器的其他非web
资源;同时通过区别前缀路径判别不同请求,如:
http://127.0.0.1:8080/download/downloadfile.txt
其中
/download
这个前置路径表示这是一个下载请求,下载的文件是downloadfile.txt
文件; -
压缩包后缀名称
在对一个文件进行压缩后,这个文件压缩后的文件名为原文件名+对应的压缩格式;
如在压缩模块中采用
LZIP
形式进行压缩;假设对一个为
downloadfile.txt
文件进行压缩,压缩后的文件名则为downloadfile.txt.lz
; -
上传文件存放路径
一个文件上传后在云服务器中的对应位置;
在上面提到以
wwwroot
作为web
根目录,上传文件放在哪一个区域; -
压缩文件存放路径
与上传文件相似,当服务器检测到一个文件由热点文件变为非热点文件状态后将要对文件进行压缩,压缩后将要与未压缩文件进行区分,将其单独放在一个列表中进行管理;
-
服务端备份信息存放文件
当客户端上传一个文件后服务端需要对上传的文件信息进行备份,这些信息包括文件大小,文件名,最后修改时间,最后访问时间等等;
这些信息将单独存放在一个文件中方便进行查找;
-
服务器的访问IP地址
-
服务端的访问端口
服务端配置信息模块实现 - 单例文件配置类设计
class Config{
public:static COnfig *GetInstance(); // 获取单例
private:Config(){} // 构造函数私有化 并且在构造函数中读取配置文件static std::mutex _mutex; // 互斥锁 - 搞定同步问题static Config *_instance; // 单例实例
private:int _hot_time; // 热点时间判断int _server_port; // 服务器监听端口std::string _download_prefix; // 下载url前缀路径std::string _packfile_suffix; // 压缩包后缀名称std::string _back_dir; // 备份文件存放目录std::string _pack_dir; // 压缩包文件存放目录std::string _server_ip; // IP地址std::string _backup_file; // 数据信息存放文件
public:int GetHotTime(); // 获取热点时间int GetServerPort(); // 获取服务器监听端口std::string GetDownloadPrefix(); // 获取前置路径std::string GetPackFileSuffix(); // 获取压缩包std::string GetBackDir(); // 获取备份文件目录std::string GetPackDir(); // 获取压缩包文件目录std::string GetServerIP(); // 获取IP地址std::string GetBackupFile(); // 获取数据信息存放文件};
服务端管理模块实现 - 需要管理的数据(后期需要用到的数据)
-
下载相关
-
文件的实际存储路径
当客户端需要下载文件时需要从哪个路径哪个文件中读取数据响应下载;
-
文件压缩包存放路径
被判为非热点文件的文件将被压缩存储,届时被查看后需要进行解压,解压位置在哪个位置;
对应的当客户端进行下载时也同样要把文件进行解压并传输,压缩文件路径在哪;
-
文件是否被压缩标志位
判断文件是否被压缩;
-
-
文件属性相关
-
文件大小
-
文件最后一次修改时间
-
文件最后一次访问时间
-
文件访问URL中资源路径
path
如:
/download/a.txt
-
-
如何管理数据
-
用于数据信息访问
使用哈希表在内存中进行数据管理,以
url
的path
作为key
值; -
持久化存存储管理
使用
json
序列化将所有数据信息保存在文件中;
-
服务端管理模块设计
-
数据信息结构体
typedef struct BackupInfo_t{bool pack_flag;// 判断文件是否被压缩size_t fsize; // 文件大小time_t atime; // 最后一次访问时间time_t mtime; // 最后一次修改时间std::string real_path; // 文件实际存储路径名称std::string pack_path; // 压缩包存储路径名称std::string url_path; // 请求资源路径 }BackupInfo;
-
数据管理类
class DataManager{private:DataManager();std::string _backup_file; // 持久化存储文件std::unordered_map<std::string, BackupInfo> _table; // 数据信息结构体在内存中以 hash 表存储pthread_rwlock_t _rwlock; // 读写锁 引入线程池后使得多个线程能够一起读 写时互斥bool Storage(); // 持久化存储 - 每当数据新增或者修改时都要重新持久化存储以避免数据丢失bool InitLoad(); // 初始化加载, 在每次系统重启后都要加载以前的数据bool Insert(const BackupInfo &info); // 新增 - 将BackupInfo存储进哈希表中 以 k-v 的形式存储bool Update(const BackupInfo &info); // 当一个文件被压缩后可能需要修改某些信息 如:bool pack_flag;bool GetOneByUrl(const std::string &url, BackupInfo *info); // 当客户端发起下载请求时需要获取对应的文件信息bool GetOneByRealpath(const std::string &path, BackupInfo *info); // 根据真实路径来获取文件信息 判断其是否为一个非热点文件等信息bool GetAll(std::vector<BackupInfo> *arry); // 获取所有信息 };
-
获取信息函数
void NewBackupInfo(const std::string realpath, BackupInfo *info); // 可作为 BackupInfo 的成员函数
通过所传入的路径,将路径对应的文件中的信息在函数中填充至
info
中;
热点管理模块
对服务器上备份的文件进行检测,检测哪些文件长时间没有被访问则认为是非热点文件进行压缩存储从而节省磁盘空间;
实现思路为:
遍历所有的文件,检测文件最后一次访问时间,与当前时间进行相减得到差值;
这个差值如果大于设定好的非热点判断时间则认为是非热点文件;
-
遍历所有文件
- 从数据管理模块中遍历所有备份文件信息
- 遍历备份文件夹,获取所有文件进行属性获取从而判断
选择遍历备份文件夹从而获取最新的数据;
数据管理模块中的数据不一定是最新的,并且遍历文件夹同时可以解决数据信息缺漏的问题;
- 遍历备份目录 获取所有文件路径名称
- 逐个文件获取最后一次访问时间与当前系统时间进行比较判断
- 对非热点文件进行压缩处理并删除源文件
- 修改数据管理模块对应的文件信息(压缩标志 ->
true
)
流程为如下:
- 获取备份目录下的所有文件
- 逐个判断文件是否为非热点文件
- 非热点文件压缩处理
- 删除源文件 修改备份信息
extern DataManager* _data; // 数据管理模块 class HotManager{
private:std::string _back_dir; // 备份文件路径std::string _pack_dir; // 压缩文件路径std::string _pack_suffix; //压缩包后缀名int _hot_time; // 热点判断时间
public:HotManager();bool RunModule(); // 模块启动
};
网络通信模块和业务处理模块
网络模块可以直接使用httplib
实现,因此具体只需要处理业务处理即可;
-
搭建网络通信服务器
借助
httplib
完成; -
业务请求处理
-
文件上传请求
备份客户端上传的文件,响应上传成功;
-
文件列表请求:
客户端浏览器请求一个备份文件的展示页面,响应页面;
-
文件下载请求
通过展示页面,点击下载,响应客户端要下载的文件数据;
-
网络通信接口设计
约定好客户端发送什么样的请求,服务端返回什么样的响应;
-
请求
- 文件上传
- 展示页面
- 文件下载
-
接口设计
接口设计为上述三种请求对应的响应接口;
-
文件上传
当客户端发送了一个
POST
方法的"/upload"
请求时表示是一个上传请求;解析请求得到文件,将数据写入到文件中;
并返回一个对应的响应:
上传成功:
HTTP/1.0 200 OK
上传失败例:
HTTP/1.1 500 Bad Request
-
页面展示
当客户端发送了一个
GET
方法的"/listshow"
请求时表示是一个页面请求;对应的响应可能为:
HTTP/1.1 200 OK Content-Length: 头部信息...<html>...</html>正文信息 <!-- 页面信息 -->
-
文件下载
当客户端发送了一个
GET
方法的"/download/filename"
请求时表示为下载一个名为filename
文件;对应的响应可能为:
HTTP/1.1 200 OK Content-Length: 文件长度等头部信息正文数据(文件数据)
-
服务端业务处理类的设计
class Service{
// 搭建一个http服务器并且进行业务处理
private:int _server_port; // 服务器端口 - 可从配置文件获取std::string _server_ip; // 服务器IP - 可从配置文件获取std::string _download_prefix; // 下载前缀 - 可从配置文件获取httplib:Server _server; // 构建服务器
private:void Upload(const httplib::Request &req, httplib::Response &rsp); // 上传业务处理void ListShow(const httplib::Request &req, httplib::Response &rsp); // 页面展示业务处理void Download(const httplib::Request &req, httplib::Response &rsp); // 下载请求业务处理
public:Server(); // 构造函数 初始化服务器所需资源bool RunModule(); // 运行服务器 - 业务处理
};
-
文件上传
在进行文件上传时对应的客户端将以约定好的表单字段
"file"
判断是否为一个文件;将文件使用
FileUtil
工具写入至back_dir
备份路径中;并调用
NewBackupInfo()
填充BackupInfo
信息插入至_data
中; -
页面展示
需要展示一个
html
界面;获取所有的文件信息
BackupInfo
,组织前端信息为字符串;返回字符串的前端信息;
-
文件下载
文件下载的思路为采用
http
协议的ETag
字段(存储一个资源的唯一标识);客户端第一次下载的时候会收到这个响应信息,第二次下载的时候将会把信息发送给服务器,想要让服务器根据这个唯一标识判断资源是否又被修改,如果未被修改则直接使用原先缓存的数据,无需重新下载;
此处的
ETag
使用"文件名-文件大小-最后修改时间"组成(ETag
字段信息是什么http
协议并不关心,服务端能够自己标识即可);
断点续传
当文件下载过程中因为某种异常导致中断,如果再次从头下载将会降低效率;
因为已经传输过的数据需要再传输一次;
断点续传是在上次下载断开的位置继续下载,已经传输过的数据将不需要重新传输;
-
目的
提高文件重新传输效率;
-
思想
客户端在下载文件时需要时刻记录当前下载数据量;
当异常下载中断时下次断点续传需要将重新下载的数据区间(起始位置与当前位置)发送给服务器;
服务器接收后仅回传客户端剩余数据;
异常下载中断后的重新下载需要判断源文件是否有被修改而判断是否需要重新下载;
如果源文件已经被修改那就需要重新下载,因为已经下载下来的数据可能已经被修改了是作废数据;
在客户端发起第一次下载请求时,服务端向客户端返回响应时需要返回对应的ETag字段;
当出现异常后客户端再次向服务端发起下载请求,此时的下载请求将会携带 If-Range
头部字段;
If-Range
头部字段为服务端在下载时响应的etag
字段;
同样断点续传的下载请求还会携带头部字段Range: bytes [N]-[M]
;
这个表示客户端需要的数据区间为[N]-[M]
;
-
服务端动作
判断
If-Range
和ETag
字段是否相同,不同则从头将文件进行传输;相同则返回
[N]-[M]
的数据;当进行断点续传时,即
If-Range
和ETag
字段相同时对应的返回状态码不再是200
,而应该是206
,表示服务端处理了部分GET
请求(Partial Content
);-
响应
HTTP/1.1 206 Partial Content ETag: "xxxxxxxxxx" Content-Range: bytes [N]-[M]/文件大小[数据正文]正文即为对应区间数据
其中
Range:
字段有几种形式:bytes start-end # 从头到结束 bytes 100-200 # 从100byte-200byte bytes 100- # 从100byte到结束
-
客户端
自动对指定文件夹进行备份;
-
模块划分
-
数据管理模块
管理备份文件信息;
-
目录遍历
获取指定文件夹中的所有文件路径名;
-
文件备份模块
将需要备份的文件上传至服务器;
-
开发环境为Windows11
,采用2017
以上版本的VisualStudio
作为集成开发环境(支持C++17
);
-
同样采用与服务器相同的
FileUtil
将服务端中设计的
util.hpp
文件进行拷贝至当前项目目录中;由于在客户端中未使用
jsoncpp
第三方库,因此可以将对应JsonUtil
所封装的内容进行删除;同时客户端无需操作压缩
Compress
和解压UnCompress
,应对应功能进行删除;所使用的
VisualStudio
可能提示已经在C++17
中摒弃了#include <experimental/filesystem>
需要将该头文件改成#include <filesystem>
;可以使用下列
#define
进行定义以防止编译时的报错:#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1
数据管理模块
数据管理模块用于管理备份文件信息;
判断一个文件是否需要进行备份(或重新备份);
- 文件是否为新增
- 若是文件不为新增则判断该文件是否为已备份文件的修改版
主要管理的数据为 “文件路径名” 与 “文件唯一标识” ;
-
实现思想
-
内存存储
高效率访问,使用哈希表(
unordered_map
); -
持久化存储(文件存储)
文件存储涉及到数据序列化与反序列化(不采用
jsoncpp
,自定义序列化格式);key value\n
key
为文件路径名,value
为文件唯一标识(判断文件上传后是否被修改);
-
数据管理类设计
客户端的数据管理类与服务端大致相同(本质上都为数据管理);
class DataManager {private:std::unordered_map<std::string, std::string> _table; // 用于快速访问std::string _backup_file; // 备份信息文件private:bool InitLoad(); // 读取已有备份信息public:DataManager(std::string);bool Insert(const std::string& , const std::string&); // 插入新的备份信息至哈希表bool Update(const std::string&, const std::string&); // 更新哈希表内的某个文件备份信息bool Storage(); // 持久化存储bool GetOneByFname(const std::string&, std::string*); // 获取一个文件的唯一标识符int Split(const std::string&, const std::string&, std::vector<std::string>*); // 用于进行反序列化的字符串分割}; // calss DataManager
文件备份类设计
自动将指定文件夹中的文件备份到服务器;
- 遍历指定文件夹获取文件信息
- 注意判断文件是否需要被备份
- 需要备份的文件进行上传至服务器备份
#define SERVER_ADDR "xxx.xxx.xxx.xxx" /* IP地址 */
#define SERVER_PORT 8888 /* 端口号 */
class Backup{
private:std::string _back_dir; // 需要备份的文件夹DataManager *_data; // 用于获取文件信息 判断文件是否需要被备份std::string GetFileIDentifier(const std::string &filename); // 计算获取文件唯一标识bool Upload(); // 当判断文件需要进行备份上传时则调用Upload进行备份上传public:Backup(std::string &backdir, std::string &backup_file); //构造函数(需要传入指定文件夹作为备份文件夹)bool RunModule(); // 用来运行文件备份模块的主要功能(上述功能)bool IsNeedUpload(const std::string &filename);// 判断文件是否需要被上传 (判断文件是否有被修改/文件上一次的修改时间是否过近 如果修改时间过近则可能表示文件正在实时拷贝当中 不适合上传)
};
项目源码
-
gitee
Gitee - 半介莽夫/CloudSystem
相关文章:
『 实战项目 』Cloud Backup System - 云备份
文章目录 云备份项目服务端功能服务端功能模块划分客户端功能客户端模块划分 项目条件Jsoncpp第三方库Bundle第三方库httplib第三方库Request类Response类Server类Client类搭建简单服务器搭建简单客户端 服务端工具类实现 - 文件实用工具类服务器配置信息模块实现- 系统配置信息…...
【机器学习实战入门】使用OpenCV和Keras的驾驶员疲劳检测系统
嗜睡驾驶者警报系统 防止司机疲劳驾驶警报系统 中级 Python 项目 - 司机疲劳检测系统 疲劳检测是一种安全技术,能够预防因司机在驾驶过程中入睡而造成的事故。 本中级 Python 项目的目标是建立一个疲劳检测系统,该系统将检测到一个人的眼睛闭合了一段时…...
使用 spring boot 2.5.6 版本时缺少 jvm 配置项
2.5.6我正在使用带有版本和springfox-boot-starter版本的Spring Boot 项目3.0.0。我的项目还包括一个WebSecurityConfig扩展WebSecurityConfigurerAdapter并实现WebMvcConfigurer的类。但是,我面临的问题是指标在端点jvm_memory_usage_after_gc_percent中不可见/act…...
【强化学习】Soft Actor-Critic (SAC) 算法
📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅…...
2024年博客之星主题创作|Android 开发:前沿技术、跨领域融合与就业技能展望
目录 引言 一、推动 Android 应用创新的核心力量 1.1 人工智能与机器学习的崛起 1.2 增强现实(AR)与虚拟现实(VR)的应用扩展 1.3 5G技术的推动 1.4 跨平台开发技术的成熟 1.4.1 React Native 1.4.2 Flutter 1.4.3 Taro …...
Spring Boot--@PathVariable、@RequestParam、@RequestBody
目录 声明!! 什么是RESTful? RESTful 的基本原则 无状态性(Stateless) 统一接口(Uniform Interface) 分层系统(Layered System) 缓存(Cacheable&#…...
网站HTTP改成HTTPS
您不仅需要知道如何将HTTP转换为HTTPS,还必须在不妨碍您的网站自成立以来建立的任何搜索排名权限的情况下进行切换。 为什么应该从HTTP转换为HTTPS? 与非安全HTTP于不同,安全域使用SSL(安全套接字层)服务器上的加密代…...
Spring Boot + Netty + WebSocket 实现消息推送
1、关于Netty Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。 2、Maven依赖 <dependencies><!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><gr…...
AI之HardWare:英伟达NvidiaGPU性价比排名(消费级/专业级/中高端企业级)以及据传英伟达Nvidia2025年将推出RTX 5090/5080、华为2025年推出910C/910D
AI之HardWare:英伟达NvidiaGPU性价比排名(消费级/专业级/中高端企业级)以及据传英伟达Nvidia2025年将推出RTX 5090/5080、华为2025年推出910C/910D 目录 英伟达NvidiaGPU性能排名(消费级/专业级/中高端企业级) NVIDIA中消费级和专业级 GPU NVIDIA中高端企业GPU …...
ESP32云开发二( http + led + lcd)
文章目录 前言先上效果图platformio.iniwokwi.tomldiagram.json源代码编译编译成功上传云端完结撒花⭐⭐⭐⭐⭐ 前言 阅读此篇前建议先看 此片熟悉下wokwi https://blog.csdn.net/qq_20330595/article/details/144289986 先上效果图 Column 1Column 2 platformio.ini wokwi…...
JavaScript语言的软件工程
JavaScript语言的软件工程 引言 在当今软件开发的浪潮中,JavaScript已不仅仅是一个简单的前端脚本语言。它的位置已经升华为全栈开发的重要语言之一,借助Node.js等技术,JavaScript不仅可以用于浏览器环境,还可以在后端服务器中运…...
【Qt】04-Lambda表达式
前言一、概念引入二、使用方法2.1 基本用法代码示例2.2 捕获外部变量2.3 参数列表 三、完整代码mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h 总结 前言 一、概念引入 Lambda表达式(Lambda Expressions)是C11标准引入的一种匿名函数对象&…...
Golang 生态学习
1. Go 语言基础 在深入 Go 语言的生态之前,首先需要掌握 Go 语言本身的核心特性。 • Go 语言官方文档:https://golang.org/doc/ Go 官方文档是学习语言基础和标准库的首选资源。 • 学习内容: • 基础语法:数据类型、控制流…...
Arcgis Pro安装完成后启动失败的解决办法
场景 之前安装的Arcgis Pro 今天突然不能使用了,之前是可以使用的,自从系统更新了以后就出现了这个问题。 环境描述 Arcgis Pro 3.0 Windows 10 问题描述 打开Arcgis Pro,页面也不弹出来,打开任务管理器可以看到进程创建之后&…...
支持向量机SVM的应用案例
支持向量机(Support Vector Machine,SVM)是一种强大的监督学习算法,广泛应用于分类和回归任务。 基本原理 SVM的主要目标是周到一个最优的超平面,该超平面能够将不同类别的数据点尽可能分开,并且使离该超平面最近的数…...
Linux中的Iptables介绍
文章目录 iptables1. 概述2. **工作原理**3. 数据包处理流程与规则匹配顺序4. 常用的匹配条件5. 动作类型6. 基本命令7. 高级功能 iptables 1. 概述 Iptables 是一个用于配置 Linux 内核防火墙的用户空间工具。它能够对进出服务器的网络数据包进行过滤、修改和转发等操作&…...
14天学习微服务-->第2天:Spring Cloud深入与实践
第2天:Spring Cloud深入与实践 一、Spring Cloud核心组件深入 在微服务架构中,Spring Cloud 提供了一系列核心组件来支持服务的注册与发现、配置管理、负载均衡等功能。今天我们将深入学习其中的三个关键组件:Eureka/Nacos(服务…...
使用 Box2D 库开发愤怒的小鸟游戏
使用 Box2D 库开发愤怒的小鸟游戏 Box2D 是一个开源的 2D 物理引擎,广泛应用于游戏开发中,特别是在模拟物体的运动、碰撞、重力等方面。在本文中,我们将利用 Box2D 库开发一个简化版的 愤怒的小鸟 游戏。我们将一步步展示如何实现物理引擎的…...
C# ComboBox 控件属性
ComboBox 的基本属性 在C#中,ComboBox控件具有多种属性,这些属性可以帮助开发者更好地控制和管理控件的各个方面。以下是一些基本的ComboBox属性及其功能: 公共属性 AccessibilityObject:获取分配给该控件的AccessibleObject。 Ac…...
《keras 3 内卷神经网络》
keras 3 内卷神经网络 作者:Aritra Roy Gosthipaty 创建日期:2021/07/25 最后修改时间:2021/07/25 描述:深入研究特定于位置和通道无关的“内卷”内核。 (i) 此示例使用 Keras 3 在 Colab 中查看 GitHub …...
Linux:文件描述符fd、系统调用open
目录 一、文件基础认识 二、C语言操作文件的接口 1.> 和 >> 2.理解“当前路径” 三、相关系统调用 1.open 2.文件描述符 3.一切皆文件 4.再次理解重定向 一、文件基础认识 文件 内容 属性。换句话说,如果在电脑上新建了一个空白文档࿰…...
ToDesk设置临时密码和安全密码都可以当做连接密码使用
ToDesk 在各领域办公都已经是非常常见了 为了安全 ToDesk 设置了连接密码,想连接 需要输入远程码和连接密码 我们刚打开 系统默认给我们用的是临时密码,安全性确实很强 和定时Tokey一样,固定时间切换。 但是 如果我们要经常连接这个电脑&a…...
C#防止重复提交
C#防止重复提交 文章目录 C#防止重复提交前言防止重复提交的思路Web API 防止重复提交代码实现代码讲解使用方法 MVC防止重复提交总结 前言 当用户在前端进行提交数据时,如果网络出现卡顿和前端没有给出响应的话顾客通常都会狂点提交按钮,这样就很容易导…...
递归算法学习v2.2
46. 全排列 class Solution {List<List<Integer>> ret;List<Integer> path;boolean[] check;public List<List<Integer>> permute(int[] nums) {ret new ArrayList<>();path new ArrayList<>();check new boolean[nums.length…...
unity插件Excel转换Proto插件-ExcelToProtobufferTool
unity插件Excel转换Proto插件-ExcelToProtobufferTool **ExcelToProtobufTool 插件文档****1. 插件概述****2. 默认配置类:DefaultIProtoPathConfig****属性说明** **3. 自定义配置类****定义规则****示例代码** **4. 使用方式****4.1 默认路径****4.2 自定义路径**…...
manim(manimgl)安装教学-win11(2024-08)
manim 目前的两种版本:★★ 稍微捋一捋【项目中的 readme.md 十分重要】 manimgl 是 Grant Sanderson(YouTube频道 3Blue1Brown的作者)等人开发。 现在为 manimgl,在维护中。 manimCE 是2020年后的 manim 分支 manim community e…...
【语言处理和机器学习】概述篇(基础小白入门篇)
前言 自学笔记,分享给语言学/语言教育学方向的,但对语言数据处理感兴趣但是尚未入门,却需要在论文中用到的小伙伴,欢迎大佬们补充或绕道。ps:本文不涉及公式讲解(文科生小白友好体质)ÿ…...
脚本工具:PYTHON
Python 是一种高级编程语言,以其简洁清晰的语法和强大的功能被广泛应用于各种领域,包括自动化脚本编写、数据分析、机器学习、Web开发等。以下是一些关于使用 Python 编写脚本工具的基本介绍、常用库以及一些实用技巧总结。 这里写目录标题 基础知识安装…...
一文讲解Redis常见使用方式
1. 单机模式部署 适用场景: • 开发和测试环境,或者对高可用性要求不高的小型项目。 部署步骤: 1. 拉取 Redis 镜像: docker pull redis:latest 2. 运行 Redis 容器: docker run -d --name redis-single -p 637…...
Gin 源码概览 - 路由
本文基于gin 1.1 源码解读 https://github.com/gin-gonic/gin/archive/refs/tags/v1.1.zip 1. 注册路由 我们先来看一段gin代码,来看看最终得到的一颗路由树长啥样 func TestGinDocExp(t *testing.T) {engine : gin.Default()engine.GET("/api/user", f…...
【计算机网络】传输层协议TCP与UDP
传输层 传输层位于OSI七层网络模型的第四层,主要负责端到端通信,可靠性保障(TCP),流量控制(TCP),拥塞控制(TCP),数据分段与分组,多路复用与解复用等,通过TCP与UDP协议实现…...
iOS UIScrollView的一个特性
1如果UIScrollView 的contentSize.height > scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom , 则scrollView就可以滚动,否则无法滚动 并且最大的滚动范围就是 contentSize.height - ( s…...
Docker 实现MySQL 主从复制
一、拉取镜像 docker pull mysql:5.7相关命令: 查看镜像:docker images 二、启动镜像 启动mysql01、02容器: docker run -d -p 3310:3306 -v /root/mysql/node-1/config:/etc/mysql/ -v /root/mysql/node-1/data:/var/lib/mysql -e MYS…...
python爬虫入门(实践)
python爬虫入门(实践) 一、对目标网站进行分析 二、博客爬取 获取博客所有h2标题的路由 确定目标,查看源码 代码实现 """ 获取博客所有h2标题的路由 """url "http://www.crazyant.net"import re…...
通过Ukey或者OTP动态口令实现windows安全登录
通过 安当SLA(System Login Agent)实现Windows安全登录认证,是一种基于双因素认证(2FA)的解决方案,旨在提升 Windows 系统的登录安全性。以下是详细的实现方法和步骤: 1. 安当SLA的核心功能 安…...
C 语言雏启:擘画代码乾坤,谛观编程奥宇之初瞰
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。* 这一课主要是让大家初步了解C语言,了解我们的开发环境,main函数,库…...
【Linux系统编程】—— 进程替换及其在操作系统中的应用与实现
文章目录 什么是进程替换?进程替换当中的接口单进程替换多进程的替换详解exec接口execlexeclpexecv 前言: 本篇博客将深入探讨进程替换的概念及其在操作系统中的作用。我们将介绍进程替换的基本原理,探讨操作系统如何通过进程的切换来实现任务…...
“裸奔”时代下该如何保护网络隐私
网络隐私的保护之道 引言 在这个信息爆炸的时代,网络已经深入到我们生活的每一个角落。你是否曾想过,在享受这些便利时,你的个人隐私正面临着严峻的挑战?网络隐私的现状警示着我们,信息泄露的事件屡见不鲜࿰…...
分类问题(二元,多元逻辑回归,费歇尔判别分析)spss实操
分类模型: 二分类和多分类: 对于二分类模型 ,我们将介绍逻辑回归和Fisher线性判别分析两种分类算法; 对于多分类模型,我们将简单介绍Spss中的多分类线性判别分析和多分类逻辑回归的操作步骤 二分类: 基于广义线性模型&#x…...
推荐一个开源的轻量级任务调度器!TaskScheduler!
大家好,我是麦鸽。 这次推荐一款轻量级的嵌入式任务调度器,目前已经有1.4K的star,这个项目比较轻量化,只有5个源文件,可以作为学习的一个开源项目。 核心文件 项目概述: 这是一个轻量级的协作式多任务处理&…...
Spring 核心技术解析【纯干货版】- IV:Spring 切面编程模块 Spring-Aop 模块精讲
随着软件开发技术的不断进步,面向切面编程(AOP)作为一种重要的编程思想,已经在现代开发中占据了重要地位。它通过将横切逻辑从业务逻辑中分离出来,使得代码更加清晰、易于维护。Spring AOP 作为 Spring 框架的核心模块…...
STM32之FreeRTOS开发介绍(十九)
STM32F407 系列文章 - freertos(十九) 目录 前言 一、简述 二、开源网址 三、原理及功能特性 1.原理简介 2.功能介绍 1.任务调度 2.任务管理 3.中断管理 4.消息队列 3.特点说明 4.优缺点 四、参考书籍 五、实现方式 总结 前言 FreeRTOS是…...
2024年美赛C题评委文章及O奖论文解读 | AI工具如何影响数学建模?从评委和O奖论文出发-O奖论文做对了什么?
模型假设仅仅是简单陈述吗?允许AI的使用是否降低了比赛难度?还在依赖机器学习的模型吗?处理题目的方法有哪些?O奖论文的优点在哪里? 本文调研了当年赛题的评委文章和O奖论文,这些问题都会在文章中一一解答…...
第14篇:从入门到精通:掌握python上下文管理器
第14篇:上下文管理器 内容简介 本篇文章将深入探讨Python中的上下文管理器(Context Manager)。您将了解上下文管理器的概念与用途,学习如何实现自定义的上下文管理器,以及如何使用contextlib模块来简化上下文管理器的…...
cuda从零开始手搓PB神经网络
cuda实现PB神经网络 基于上一篇的矩阵点乘,实现了矩阵的加减乘除、函数调用等。并且复用之前元编程里面写的梯度下降、Adam、NAdam优化方法。实现PB神经网络如下: #ifndef __BP_NETWORK_HPP__ #define __BP_NETWORK_HPP__ #include "matrix.hpp&quo…...
Java 大视界 -- Java 大数据物联网应用:数据处理与设备管理(八)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...
大模型之三十三- 开源Melo 语音合成
大模型之三十三- 开源Melo 语音合成 文本到语音(TTS)系统从基于基础音素的模型演变成复杂的端到端神经方法,这种方法可以直接将文本转换为语音。这一变革得益于深度学习的进步和计算能力的提升,已经在语音的自然度、韵律控制和跨语言能力方面取得了重大进展 。现代TTS系统…...
全同态加密理论、生态现状与未来展望(上)
《全同态加密理论、生态现状与未来展望》系列由lynndell2010gmail.com和mutourend2010gmail.com整理原创发布,分为上中下三个系列: 全同态加密理论、生态现状与未来展望(上):专注于介绍全同态加密理论知识。全同态加密…...
cursor重构谷粒商城02——30分钟构建图书管理系统【cursor使用教程番外篇】
前言:这个系列将使用最前沿的cursor作为辅助编程工具,来快速开发一些基础的编程项目。目的是为了在真实项目中,帮助初级程序员快速进阶,以最快的速度,效率,快速进阶到中高阶程序员。 本项目将基于谷粒商城…...
提升大语言模型的三大策略
1.概述 随着大语言模型(LLMs)在技术和应用上的不断发展,它们已经深刻地改变了我们与计算机的互动方式。从文本生成到语言理解,LLMs的应用几乎涵盖了各个行业。然而,尽管这些模型已展现出令人印象深刻的能力,…...