这里写目录标题

  • json的认识
    • jsoncpp的认识
    • jsoncpp实现序列化
    • jsoncpp实现反序列化
  • bundle文件压缩库认识
    • bundle库实现文件压缩与解压缩
  • httplib库的认识
    • httplib库搭建简单服务器
    • httplib库搭建简单客户端

json的认识

1.当要实现网络数据传输或持久化存储的时候:需要按照指定的数据格式组织,这样才能在使用数据的时候能更好的解析出来。

2.json的格式:json是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。

具体格式内容我们用下面一个例子来解释:

例如:这是同学小明的信息如下:

char* name = "小明";
int age = 18;
char* sex = "男";
float score[] = {60.5,99,68};

那么用json这种数据交换格式是将这多种数据对象组织成为一个文本字符串格式,如下:

[{"姓名":"小明","年龄":18,"性别":"男","成绩":[60.5,99,68]}
]

组织成这种格式,例如上例,如果有多位同学,那么每个同学都会将自己的信息在{}中以键值对的形式(名称:数值)进行显示,并且不同的同学在[]中以逗号间隔。
其中:

  • json数据类型:对象,数组,字符串,数字。
  • 对象:使用{}括起来的表示一个对象。
  • 数组:使用[]括起来的表示一个数组。
  • 字符串:使用常规""括起来的是一个字符串。
  • 数字:包括整形和浮点型,都是直接使用的。

jsoncpp的认识

1.jsoncpp库就是用于实现json格式的序列化和反序列化的,完成多个数据对象组织成为json格式字符串,以及将json格式字符串解析得到多个数据对象的功能。

2.其中这是josn的类,这些类成员函数都可以在jsoncpp库中找到,如下:

//json数据对象类
class Json::Value
{Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过Value& operator[](const std::string& key);//简单的方式完成 val["姓名"] = "小明";Value& operator[](const char* key);Value removeMember(const char* key);//移除元素const Value& operator[](ArrayIndex index) const; //val["成绩"][0]Value& append(const Value& value);//添加数组元素val["成绩"].append(88),就是由于数组成员都是一个一个的,所以要用append进行加入;ArrayIndex size() const;//获取数组元素个数 val["成绩"].size();std::string asString() const;//转string string name = val["name"].asString();const char* asCString() const;//转char*  char *name = val["name"].asCString();Int asInt() const;//转int int age = val["age"].asInt();float asFloat() const;//转floatbool asBool() const;//转 bool
};

这只是一个json的类,这个类主要的作用就是用来对数据进行保存的,但是对数据进行序列化和反序列化必须使用与其对应的类,如下:

jsoncpp实现序列化

1.对于jsoncpp实现序列化,要使用的是如下的类(不同的版本使用的类也是不同的,有些低版本不支持一些内容):

//json序列化类,低版本用这个更简单
class JSON_API Writer
{virtual std::string write(const Value& root) = 0;
}
class JSON_API FastWriter : public Writer
{virtual std::string write(const Value& root);
}
class JSON_API StyledWriter : public Writer
{virtual std::string write(const Value& root);
}
//json序列化类,高版本推荐,如果用低版本的接口可能会有警告
class JSON_API StreamWriter
{virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {virtual StreamWriter* newStreamWriter() const;
}

(这里我们实现的时候是用高版本实现的)

我们可以看出来,上面的库函数代码参数都是Value对象类型的,所以说在进行序列化的时候,我们是将已经保存好数据的Value对象传入该类函数中,然后这个函数对其进行序列化。

注意

①:由上面的代码我们可以看出来,StreamWriter类是一个父类,而StreamWriterBuilder类是其子类。

②:使用的时候,我们不是直接使用父类对象然后去调用其write函数(因为write函数是一个虚函数,并且该类为一个抽象类,是无法直接实例化出对象的),进行操作的时候,是先实例化出其子类StreamWriterBuilder类的对象,然后再用父类对象的指针指向该子类的对象,通过父类指针调用子类的newStreamWriter函数,去是实现父类的对象。

③:write函数的参数:

  • root:要进行序列化的json对象。
  • sout:序列化后的内容保存地方。(所以我们直接查看这个参数的内容,就可以看到序列化后的样子了)

2.对于上面的例子进行简单的实现:

  1 #include<iostream>                                                                            2 #include<jsoncpp/json/json.h>  3 #include<string>              4 #include<sstream> 5 using namespace std; 6 int main()          7 {                   8   //这是我们要进行序列化操作的数据9   const char* name = "小明";   10   int age = 18;                11   const char* sex = "男"; 12   float score[] = {60.5,99,68};                                13   //1.先将数据保存在json对象中  14   Json::Value val;                               15   val["姓名"] = name;16   val["年龄"] = age; 17   val["性别"] = sex;          18   val["分数"].append(score[0]);19   val["分数"].append(score[1]);20   val["分数"].append(score[2]);21   //2.使用StreamWriter对象进行序列化(注意:必须要用StreamWriter的指针去调用StreamWriterBuilder    对象的newStreamWriter函数进行实例化出对象后,才能进行使用write)22   Json::StreamWriterBuilder swb;23   Json::StreamWriter* sw = swb.newStreamWriter();24   stringstream ss;25   sw->write(val,&ss);26   //3.将序列化后的数据打印出来27   cout<<ss.str()<<endl;28   delete sw;29   return 0;30 }

然后运行结果如下:

注意:由于是第三方库,所以必须在后面链接一下库。

jsoncpp实现反序列化

1.对于jsoncpp实现反序列化,要使用如下类:

//json反序列化类,低版本用起来更简单
class JSON_API Reader
{bool parse(const std::string& document, Value& root, bool collectComments = true);
}
//json反序列化类,高版本更推荐
class JSON_API CharReader
{virtual bool parse(char const* beginDoc, char const* endDoc,Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory
{virtual CharReader* newCharReader() const;
}

(实现的时候是用高版本实现的)

注意:反序列化和序列化的实现步骤差不多都是要先实例化出子类对象,然后用父类对象的指针去指向子类对象调用newCharReader函数返回的对象,然后再用父类对象去调用parse函数,完成操作。

其中,parse函数的参数如下:

  • beginDoc:要反序列化字符串的起始位置。
  • endDoc:要反序列化字符串的末尾位置。
  • root:要进行反序列化的josn对象。
  • errs:反序列化出现错误的时候,错误保留信息。

2.对上面的例子进行简单实现如下:

  1 #include<iostream>                                                                            2 #include<jsoncpp/json/json.h>3 #include<string>4 #include<sstream>5 using namespace std;6 int main()7 {8   //这是我们要进行序列化操作的数据9   const char* name = "小明";10   int age = 18;11   const char* sex = "男";12   float score[] = {60.5,99,68};13   //1.先将数据保存在json对象中14   Json::Value val;15   val["姓名"] = name;16   val["年龄"] = age;17   val["性别"] = sex;18   val["分数"].append(score[0]);19   val["分数"].append(score[1]);20   val["分数"].append(score[2]);21   //2.使用StreamWriter对象进行序列化(注意:必须要用StreamWriter的指针去调用StreamWriterBuilder    对象的newStreamWriter函数进行实例化出对象后,才能进行使用write)22   Json::StreamWriterBuilder swb;23   Json::StreamWriter* sw = swb.newStreamWriter();24   stringstream ss;25   sw->write(val,&ss);26   //3.将序列化后的数据打印出来27   cout<<ss.str()<<endl;28   //4.进行反序列化29   Json::CharReaderBuilder crb;30   Json::CharReader* cr = crb.newCharReader();31   Json::Value Val;32   string err;33   string str = ss.str();34   bool res = cr->parse(str.c_str(),str.c_str()+str.size(),&Val,&err);                         35   if(res == false)36   {37     cout<<"error:"<<err<<endl;38     delete cr;39     return 0;40   }41   //5.查看反序列化后的内容42   cout<<Val["姓名"].asString()<<endl;43   cout<<Val["年龄"].asInt()<<endl;44   cout<<Val["性别"].asString()<<endl;45   int sz = Val["分数"].size();46   for(int i = 0;i < sz;++i)47   {48     cout<<Val["分数"][i].asFloat()<<endl;49   }50   delete sw;51   delete cr;52   return 0;53 }

然后执行结果如下:

反序列化后,数据还是可以打印出来的。

bundle文件压缩库认识

1.bundle库:bundle是一个嵌入式压缩库,支持23中压缩算法和2种存档格式。使用的时候只需要加入两个文件bundle.h和bundle.cpp即可。

其中:

①:2种存档格式如下:

  • 将所有的文件加入压缩类,然后一起压缩。(压缩完成后的文件名后缀变为.zip)
  • 将每个文件压缩后再加入压缩类,然后打包在一起。(压缩完成后文件名后缀为.bun)

特点:

  • 存档支持:.zip和.bun两种压缩保存方式。
  • 流支持:DEFLATE, LZMA, LZIP, ZPAQ, LZ4, ZSTD, BROTLI, BSC, CSC, BCM, MCM, ZMOLLY, ZLING, TANGELO, SHRINKER, CRUSH, LZJB, BZIP2 and SHOCO(流是从支持数据处理操作的源生成的元素序列)
  • 最优化压缩率
  • 最优化压缩速度
  • 支持配置、封装、字包含、混合、跨平台
  • 可选基础结构
  • ZLIB/LibPNG版权协议

②:23种压缩算法以及效率如下:


压缩速率和压缩效率是不可兼得的,所以使用的时候根据需求进行使用即可。

2.常用压缩数据的程序,如下:

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 );
}

这是库文件中的一部分,这些一般是使用这个库的接口,如果要看所有代码,可以去库中去查看。

注意:上述代码中有高级中级和低级的模板,我们在实现的时候使用的是高级一点的接口,就是上面最后两行的两个函数。

bundle库实现文件压缩与解压缩

1.首先,我们使用bundle库的时候,只使用的是bundle.h这里面的函数,但是bundle.h使用的代码都在bundle.cpp中,由于所以链接的时候将其bundle.cpp要一起链接,但是这个里面的代码非常多,大概有12万多行,所以在链接的时候会很难,所以我们先将其生成静态库文件,然后链接的时候效率就快了,如下:

2.实现文件压缩,请看如下程序:

  1 #include<iostream>                                                                            2 #include<string> 3 #include<fstream>4 #include"bundle.h"5 using namespace std;6 bool Read(const string &name,string *body)7 {                                                           8   ifstream ifs;//文件指针9   ifs.open(name,std::ios::binary);//第一个参数为要打开文件名,第二个参数为要使用什么方式打开,    本次使用的方式为以二进制的方式打开。10   if(ifs.is_open() == false)//是用来查看是否打开文件成功的接口. 11   {               12     cout<<"read open false"<<endl;                       13     return false;14   }                                                           15   ifs.seekg(0,std::ios::end);//表示从末尾位置开始偏移,偏移0个大小16   size_t fsize = ifs.tellg();//获取当前位置相对于文件起始位置的偏移量。(这也是为什么有上一步>    的原因,找到文件的大小)                                 17   ifs.seekg(0,std::ios::beg);//表示从起始位置开始偏移,偏移0个大小 18   body->resize(fsize);19   ifs.read(&(*body)[0],fsize);//由于string.c_str()的返回值是一个const char*的类型的,但是第一>    个参数我们要的是要读入文件的起始位置,所以用一步取地址的方法得到。20   if(ifs.good() == false)//用来查看文件读取数据是否成功.21   {22     cout<<"read read fasle"<<endl;23     return false;24   }25   ifs.close();26   return true;27 }28 bool Write(const string &name,const string &body)29 {30   ofstream ofs;//文件指针.31   ofs.open(name,std::ios::binary);32   if(ofs.is_open() == false)                                                                  33   {34     cout<<"write open false"<<endl;35     return false;36   }37   ofs.write(body.c_str(),body.size());38   if(ofs.good() == false)39   {40     cout<<"write write false"<<endl;41     return false;42   }43   ofs.close();44   return true;45 }46 void compress(const string &filename,const string &packname)47 {48   string body;49   Read(filename,&body);//将文件中的内容放入body中。50   string packed = bundle::pack(bundle::LZIP,body);//这个函数第一个参数是要压缩的类型,第二个参    数是要压缩的文件。51   Write(packname,packed);//将压缩后的内容放入在packname中                                     52 }53 void uncompress(const string &filename,const string &packname)54 {55   string packed;56   Read(packname,&packed);//从压缩包中将数据读取到packed中57   string body = bundle::unpack(packed);//对要压缩的数据进行解压缩,并将解压缩后的数据放在body>    中。58   Write(filename,body);//从body中将数据放在新文件中。59 }60 61 int main()62 {63   compress("./hello.cpp","./hello.zip");64   uncompress("./hi.txt","./hello.zip");65   return 0;66 }

然后运行程序如下:

实现了将100M的文件压缩到了15k。

注意:dd if=/dev/zero of=./hello.txt bs=100M count=1:是在当前文件下创建一个大小为100M的文件,并且文件名为hello.txt。

3.查看压缩和解压缩后文件是否有错。

①:要验证两个文件是否相同,那么就计算两个文件的MD5值,如果MD5值相同,那么这两个文件就相同,要是不同,就出现差错了。

②:MD5:是一种散列算法,会根据数据进行大量的运算,终止得到一个结果,而这个结果是字符串,但是只要这两个文件有一点不同,产生的MD5值是完全不一样的。

③:操作:md5sum 文件名

就上面的压缩和解压缩的文件我们进行操作:

两个文件对应的md5的值一模一样,证明压缩和解压缩是完美的。

httplib库的认识

1.httplib库:一个c++11单文件的跨平台HTTP/HTTPS库。安装起来非常容易。只需要包含httplib.h引入代码中即可。

2.优点:作用于搭建一个简单的http服务器或者客户端的库,而这种第三方的库,可以免去我们在搭建服务器或客户端的时间,把更多的精力投入到具体的业务处理中,提高开发效率。

3.httplib库中代码的认识:

httplib库中有两个结构体分别为请求与响应,如下:

①:请求结构体:

struct Request
{std::string method;//请求方法std::string path;//请求的资源路径Headers headers;//存放头部字段的地方,由于头部字段一般是以键值对来进行存储,所以这个存储方式是容map来进行的std::string body;//存放正文// for serverstd::string version;//协议版本Params params;//存放URL中的查询字符串,也是以键值对的方式存储MultipartFormDataMap files;//用于存放上传时正文的数据信息Ranges ranges; //用于实现断点续传的数据请求范围区间,也是键值对。bool has_header(const char *key) const;std::string get_header_value(const char *key, size_t id = 0)const;void set_header(const char *key, const char *val);bool has_file(const char *key) const;MultipartFormData get_file_value(const char *key) const;
};

②:响应结构体:

struct Response
{std::string version;//协议版本int status = -1; //响应状态码std::string reason;//状态信息Headers headers;//用哈希表的方式存储头部字段std::string body;//响应正文信息std::string location; //源地址 void set_header(const char *key, const char *val);//设置头部字段void set_content(const std::string &s, const char *content_type);//设置正文
};

响应结构体和请求结构体中保存的大多都是http协议的格式。(在使用的时候,我们只需要上传其结构体的对象就好了,具体进行使用的是下面的客户端程序和服务端程序)

③:服务端程序

class Server
{using Handler = std::function<void(const Request &, Response &)>;//函数指针类型,就是对于不同的请求,我们要使用不同的函数对请求进行处理using Handlers = std::vector<std::pair<std::regex, Handler>>;//请求-处理函数映射表,一个请求对应一个映射,用键值对的方式存储std::function<TaskQueue *(void)> new_task_queue;//线程池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);bool listen(const char *host, int port, int socket_flags = 0);//启动服务器,开始监听。
};

④:客户端程序

class Client
{Client(const std::string &host, int port);  //传入服务端信息(构造函数)Result Get(const char *path, const Headers &headers);//向服务器发送get请求Result Post(const char *path, const char *body, size_t content_length,const char *content_type);//向服务器发送post请求(数据提交)Result Post(const char *path, const MultipartFormDataItems &items);//向服务器发送post请求(文件上传)
}

⑤:MultipartFormDataItems结构体,主要保存有一些数据的信息

struct MultipartFormData
{std::string name;           //区域名称std::string content;        //区域正文std::string filename;       //文件名称std::string content_type;   //正文类型
};

httplib库搭建简单服务器

1.搭建简单服务器,如下:

    1 #include<iostream>                                                                          2 #include"httplib.h" 3 using namespace std;4 void Upload(const httplib::Request &req,const httplib::Response &rsp)5 {                          6   if(req.has_file("file"))//判断有没有name字段是file的标识区,请求的这个文件名存在不7   {                                         8     httplib::MultipartFormData data = req.get_file_value("file");//返回这个文件一些内容,有d      ata保存 9     std::cout << data.name << std::endl;     //区域字段的标识名10     std::cout << data.filename << std::endl; //如果是文件上传,则是文件名11     std::cout << data.content << std::endl;  //区域正文数据,如果是文件上传,就是文件的内容12   }   13 }        14 void Numbers(const httplib::Request &req,httplib::Response &rsp)   15 {16   //这就是业务处理函数       17   rsp.body = req.path;       18   rsp.body += "-----------------";19   rsp.body += req.matches[1];20   rsp.status = 200;21   rsp.set_header("Content-Type","text/plain");22 }23 int main()24 {25   //1.实例化出Serve对象26   httplib::Server serve;27   //2.添加映关,告诉服务器,对于客户端的什么请求,用什么函数进行处理,如下添加了两个处理方法      ,为Get和Post28   serve.Get("/numbers/(\\d+)",Numbers);//第一个参数为正则表达式也就是请求的资源路径,第二个参数为Numbers,是对应      的处理函数29   serve.Post("/upload",Upload);30   serve.listen("0.0.0.0",9090);31   return 0;32 }                                                                                                       

运行结果后,然后用netstat命令查看如下:

展现出这个服务器已经处于监听状态了。

然后我们将服务端ip地址和端口号和请求资源路径(就是请求的第一个参数),然后函数会利用回调函数进行处理,然后在网页上会显示如下:

httplib库搭建简单客户端

1.搭建简单客户端如下:

  1 #include<iostream>                                                                            2 #include"httplib.h"                                   3 int main()                                      4 {                              5   httplib::Client client("192.168.136.130",9090);//其中,这里填写的是服务端的ip地址和端口号6   //就是Result Get(const char* path,const Headers& headers);7   httplib::Headers headers = {{"connection","Close"}};8   auto res = client.Get("/number/1234",headers);         9   if(res && res->status == 200)                                       10   {                                     11     std::cout<< res->body << std::endl;12   }                                    13   ///14   //Result post(const char* path,const MultipartFormDataItems &items);15   httplib::MultipartFormDataItems items;                     16   httplib::MultipartFormData item;17   item.name = "file";          //文件域18   item.filename = "zhang.txt";//文件名19   item.content = "hello";   //正文数据20   item.content_type = "application/octet-stream";//以二进制流21   items.push_back(item);22 23   res = client.Post("/upload",items);24   return 0;25 }

与服务端配合,使用如下:

第三方库的认识与使用相关推荐

  1. Go 学习笔记(60)— Go 第三方库之 go-redis(初始化 redis、操作 string、操作 list、操作 set、操作 hset)

    1. 第三方库 go-redis 因为 Go 标准库中是没提供 redis 的库,所以我们选择用 go-redis 这个第三方库.源码地址为 https://github.com/go-redis/r ...

  2. Go 学习笔记(56)— Go 第三方库 sqlx (操作数据库)

    1. 安装数据库 在 Go 标准库中是没有数据库驱动,只提供了驱动接口,有很多第三方实现了驱动,以下两种选择我们都可以进行操作,在本文中选择 sqlx . 第三方库 MySQL 驱动库: go-sql ...

  3. flutter导入第三方库

    在pubspec.yaml 文件中找到 dependencies 在里面填写 第三方库即可 例如图中我写了fluttertoast库 特别注意:导入的位置要不dependencies下面的flutte ...

  4. android studio导入第三方库和demo

    导demo,导第三方库,都可以用这个方法,别想太复杂了, file - new - import module

  5. Go 学习笔记(54)— Go 第三方库之 uber-go/zap/lumberjack(记录日志到文件、支持自动分割日志、支持日志级别、打印调用文件、函数和行号)

    1. 简要说明 zap 是 uber 开源的 Go 高性能日志库,支持不同的日志级别, 能够打印基本信息等,但不支持日志的分割,这里我们可以使用 lumberjack 也是 zap 官方推荐用于日志分 ...

  6. 4行指令解决pip下载Python第三方库太慢问题(pip更换国内下载源)

     问题由来: 之前在写一篇项目博客时,pip下载Python第三方库:graphic-verification-code,实在太慢了,于是使用Python库官网下载,还是很慢,而且不断失败,下载慢且不 ...

  7. 安装需要的第三方库时,命令行输入pip提示不是内部或外部命令

    简介 在做Python开发时,安装需要的第三方库时,大多数人喜欢选择在命令行用pip进行安装. 然而有时敲入pip命令会提示'pip'不是内部或外部命令..如图: 解决办法 1.在python安装目录 ...

  8. ISP 【一】————boost标准库使用——批量读取保存文件 /boost第三方库的使用及其cmake添加,图像gramma

    CMakeLists.txt文件中需要添加第三方库,并企鹅在CMakeLists.txt中添加 include_directories(${PROJECT_SOURCE_DIR}/../3party/ ...

  9. Sqlite3数据库之第三方库FMDB学习心得

    很早之前就接触Sqlite数据库,但是之前对数据库操作未使用任何第三方库,只是实现基本的增.删.改.查功能,自己对着一本iPhone开发入门级的书籍写了一个类,基本能实现上述四个功能.最近在开发一个软 ...

  10. Swift项目引入第三方库的方法

     分类: iOS(55)  目录(?)[+] Swift项目引入第三方库的方法 转自 http://blog.shiqichan.com/How-To-Import-3rd-Lib-Into-Swif ...

最新文章

  1. 哪些人不适合去做科研(转)
  2. C指针原理(12)-C指针基础
  3. QT的QGeoAreaMonitorSource类的使用
  4. 深入理解javascript原型和闭包(5)——instanceof
  5. 【洛谷 - P1507 】NASA的食物计划(二维费用背包,dp)
  6. Unity3D游戏开发之开发游戏带来的问题
  7. scala map,foreach,flatMap等方法对比
  8. golang之旅--接口 (interface)
  9. 封装Selenium2Library
  10. Linux学习基础一 【安装 目录 系统命令 常用vim操作】
  11. BKMGT你懂吗?那么RPWT你懂吗?
  12. 拷贝temp文件下的麦客疯临时文件到当前目录 .
  13. Typora MarkDown语法
  14. 自动生产线实训考核装备
  15. SAP MM 采购申请后台配置
  16. SEO技巧:Meta标签详解
  17. WebRTC RTCP XR
  18. iOS ProductspecificationsTree 自定义cell 采用MVVM实现:【选择多级商品规格信息(树形,多选)】应用场景: 发布商品-添加多规格信息
  19. 浅谈标签传播算法LPA
  20. 人工智能及其应用——第二章学习笔记(上)

热门文章

  1. 20230304英语学习
  2. 【iOS开发系列】地图与定位
  3. 趣味学习c语言,趣味学习C语言.ppt
  4. HTML中标签和元素的区别
  5. 用最酷的方法学习R语言
  6. spark之kryo 序列化
  7. UserWarning: Argument interpolation should be of type InterpolationMode instead of int.
  8. 设置PL/SQL Developer关键字自动变成大写
  9. 怎样才能做好SNS社区网站
  10. geetest文件夹什么意思_极验geetest的使用