基于脚手架微服务的视频点播系统-脚手架开发部分elasticsearch与libcurl的简单使用与二次封装及bug修复-完结
- 1.ElasticClient的使用
-
- 1.1ES检索原理
-
- 正排索引
- 倒排索引
- 正排索引
- 1.2ES核心概念
-
- 1.2.1索引(index)
- 1.2.2类型(Field)
- 1.2.3字段(Field)
- 1.2.4映射(mapping)
- 1.2.5文档(document)
- 1.2.1索引(index)
- 1.3 Kibana访问es进行测试
-
- 1.3.1创建索引
- 1.3.2新增数据
- 1.3.3查看并搜索数据
- 1.3.4删除索引
- 1.3.1创建索引
- 1.4典型操作
-
- 1.4.1索引
- 1.4.2新增数据
-
- 1.4.2.1单数据新增
* 1.4.2.2批量数据新增
* 1.4.2.3查询所有数据
- 1.4.2.1单数据新增
- 1.4.3删除
-
- 1.4.3.1删除指定id的数据
* 1.4.3.2批量删除指定id的数据
* 1.4.3.3根据条件进行数据的删除
- 1.4.3.1删除指定id的数据
- 1.4.4更新
-
- 1.4.4.1单数据更新
* 1.4.4.2批量数据更新
- 1.4.4.1单数据更新
- 1.4.5查询
-
- 单条件查询
* 多条件查询
- 单条件查询
- 1.4.1索引
- 1.5ES客户端SDK
-
- 1.5.1接口介绍
-
- 1.响应结构
* 2.典型接口
- 1.响应结构
- 1.5.2使用样例
-
- 1.5.1接口介绍
- 1.6二次封装
-
- 使用样例
- 2.libcurl的使用与封装
-
- 2.1启用邮箱授权码(以163邮箱为例)
- 2.2使用
-
- 2.2.1头文件与链接库
- 2.2.2核心接口
- 2.2.3链表操作
- 2.2.4http选项
- 2.2.5SMTP选项
- 2.2.1头文件与链接库
- 2.3二次封装
-
- 使用样例
- 3.bug修复
-
- 3.1util⼯具类功能补充
- 3.2RPC封装Bug与补充
- 3.3MQ封装Bug
- 4.打包脚手架项目
-
- 4.1在项目根目录创建如下CMakeLists.txt文件
- 4.2在根目录下创建名为cmake的文件夹,并添加两个新文件
- 4.3在根目下创建一个名为build的文件夹,进入并依次执行如下bash命令
1.ElasticClient的使用
Elasticsearch, 简称ES,它是个开源分布式搜索引擎,它的特点有:分布式,零配置,⾃动发现,索引⾃动分⽚,索引副本机制,restful⻛格接⼝,多数据源,⾃动搜索负载等。它可以近乎实时的存储、检索数据;本⾝扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使⽤Java开发并使⽤Lucene作为其核⼼来实现所有索引和搜索的功能,但是它的⽬的是通过简单的RESTful API来隐藏Lucene的复杂性,从⽽让全⽂搜索变得简单。
Elasticsearch是⾯向⽂档(document oriented)的,这意味着它可以存储整个对象或⽂档(document)。然⽽它不仅仅是存储,还会索引(index)每个⽂档的内容使之可以被搜索。在Elasticsearch中,你可以对⽂档(⽽⾮成⾏成列的数据)进⾏索引、搜索、排序、过滤。
1.1ES检索原理
正排索引
正排索引,也称为前向索引,是⼀种将⽂档或数据记录按照某种特定顺序组织的索引机制。在正排索引中,索引的键通常是⽂档的标识符,如⽂档ID,⽽索引的值则包含⽂档的详细信息,例如标题、内容摘要、发布⽇期等。这种结构使得正排索引⾮常适合执⾏基于特定标识符的查找操作。正排索引的优点在于能够直接根据⽂档ID快速访问⽂档,适合于需要按照⽂档顺序进⾏操作的场景。

倒排索引
倒排索引,⼜称反向索引或逆向索引,是按照⽂档中的词汇来组织数据的索引⽅法。在倒排索引中,每个独特的词汇都会有⼀个索引条⽬,该条⽬包含指向包含该词 的所有⽂档的指针或引⽤。这使得倒排索引⾮常适合全⽂搜索,能够快速找到包含特定关键词的⽂档。倒排索引则适合于全⽂搜索,可以快速找到包含特定关键词的所有⽂档,索引的⼤⼩相对较⼩,因为它只记录关键词和⽂档的映射关系。但是,倒排索引不能直接通过索引访问⽂档,需要结合正排索引来获取⽂档的详细信息。

1.2ES核心概念
ES中的一些概念与传统数据库可以做如下类比

这里需要注意的一点是,在es7.x版本之后,类型这个概念已经被废弃,也就是每个索引下只有一个类型就是_doc。我们访问一个索引中的文档时格式如下:/index/_doc/文档id。
1.2.1索引(index)
⼀个索引就是⼀个拥有⼏分相似特征的⽂档的集合。⽐如说,你可以有⼀个客⼾数据的索引,⼀个产品⽬录的索引,还有⼀个订单数据的索引。⼀个索引由⼀个名字来标识(必须全部是⼩写字⺟的),并且当我们要对应于这个索引中的⽂档进⾏索引、搜索、更新和删除的时候,都要使⽤到这个名字。在⼀个集群中,可以定义任意多的索引。
1.2.2类型(Field)
在⼀个索引中,你可以定义⼀种或多种类型。⼀个类型是你的索引的⼀个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有⼀组共同字段的⽂档定义⼀个类型。⽐如说,我们假设你运营⼀个博客平台并且将你所有的数据存储到⼀个索引中。在这个索引中,你可以为⽤⼾数据定义⼀个类型,为博客数据定义另⼀个类型,为评论数据定义另⼀个类型…
1.2.3字段(Field)
字段相当于是数据表的字段,对⽂档数据根据不同属性进⾏的分类标识。
| 分类 | 类型 | 备注 |
|---|---|---|
| 字符串 | text, keyword | text会被分词生成索引;keyword不会被分词生成索引,只能精确值搜索 |
| 整形 | integer, long, short, byte | |
| 浮点 | double, float | |
| 逻辑 | boolean | true或false |
| 日期 | date, date_nanos | “2018-01-13” 或 “2018-01-13 12:10:30” 或者时间戳,即1970到现在的秒数/毫秒数 |
| 二进制 | binary | 二进制通常只存储,不索引 |
| 范围 | range |
1.2.4映射(mapping)
映射是在处理数据的⽅式和规则⽅⾯做⼀些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射⾥⾯可以设置的,其它就是处理es⾥⾯数据的⼀些使⽤规则设置也叫做映射,按着最优规则处理数据对性能提⾼很⼤,因此才需要建⽴映射,并且需要思考如何建⽴映射才能对性能更好。
| 名称 | 数值 | 备注 |
|---|---|---|
| enabled | true(默认) / false | 是否仅作存储,不做搜索和分析 |
| index | true(默认) / false | 是否构建倒排索引(决定了是否分词,是否被索引) |
| index_option | ||
| dynamic | true(缺省) / false | 控制mapping的自动更新 |
| doc_value | true(默认) / false | 是否开启doc_value,用于聚合和排序分析,分词字段不能使用 |
| fielddata | “fielddata”: {“format”: “disabled”} | 是否为text类型启动fielddata,实现排序和聚合分析 |
| store | true / false(默认) | 针对分词字段,参与排序或聚合时能提高性能,不分词字段统一建议使用doc_value |
| coerce | true(默认) / false | 是否开启自动数据类型转换功能,比如:字符串转数字,浮点转整型 |
| analyzer | “analyzer”: “ik” | 指定分词器,默认分词器为standard analyzer |
| boost | “boost”: 1.23 | 字段级别的分数加权,默认值是1.0 |
| fields | “fields”: { “name”: { “type”: “text”, “index”: true }, … } | 对一个字段提供多种索引模式,同一个字段的值,一个分词,一个不分词 |
| data_detection | true(默认) / false | 是否自动识别日期类型 |
1.2.5文档(document)
⼀个⽂档是⼀个可被索引的基础信息单元。⽐如,你可以拥有某⼀个客⼾的⽂档,某⼀个产品的⼀个⽂档或者某个订单的⼀个⽂档。⽂档以JSON(Javascript Object Notation)格式来表⽰,⽽JSON是⼀个到处存在的互联⽹数据交互格式。在⼀个index/type⾥⾯,你可以存储任意多的⽂档。⼀个⽂档必须被索引或者赋予⼀个索引的type。
1.3 Kibana访问es进行测试
通过⽹⻚访问kibana: http://192.168.65.128:5601/ ,注意:将链接中的IP地址改换成为你的主机IP地址。
默认账户密码为:elastic:123456

在开发工具中进行以下实验即可:

1.3.1创建索引
1PUT /user 2 { 3 "settings": { 4 "analysis": { 5 "analyzer": { 6 "ikmax": { 7 "type": "custom", 8 "tokenizer": "ik_max_word" 9 } 10 } 11 } 12 }, 13 "mappings": { 14 "dynamic": false, 15 "properties": { 16 "name": { 17 "type": "text", 18 "boost": 3, 19 "analyzer": "ikmax" 20 }, 21 "age": { 22 "type": "integer" 23 }, 24 "phone": { 25 "type": "keyword", 26 "boost": 1 27 }, 28 "skills": { 29 "type": "text" 30 }, 31 "birth": { 32 "type": "date", 33 "index": false, 34 "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" 35 } 36 } 37 } 38 } 39
1.3.2新增数据
1POST /user/_doc/_bulk 2{"index":{"_id":"1"}} 3{"user_id" : "USER4b862aaa-2df8654a-7eb4bb65-e3507f66","nickname" : "昵称 41","phone" : "⼿机号1","description" : "签名1","avatar_id" : "头像1"} 5{"index":{"_id":"2"}} 6{"user_id" : "USER14eeeaa5-442771b9-0262e455-e4663d1d","nickname" : "昵称 72","phone" : "⼿机号2","description" : "签名2","avatar_id" : "头像2"} 8{"index":{"_id":"3"}} 9{"user_id" : "USER484a6734-03a124f0-996c169d-d05c1869","nickname" : "昵称 103","phone" : "⼿机号3","description" : "签名3","avatar_id" : "头像3"} 11{"index":{"_id":"4"}} 12{"user_id" : "USER186ade83-4460d4a6-8c08068f-83127b5d","nickname" : "昵称 134","phone" : "⼿机号4","description" : "签名4","avatar_id" : "头像4"} 14{"index":{"_id":"5"}} 15{"user_id" : "USER6f19d074-c33891cf-23bf5a83-57189a19","nickname" : "昵称 165","phone" : "⼿机号5","description" : "签名5","avatar_id" : "头像5"} 17{"index":{"_id":"6"}} 18{"user_id" : "USER97605c64-9833ebb7-d0455353-35a59195","nickname" : "昵称 196","phone" : "⼿机号6","description" : "签名6","avatar_id" : "头像6"} 20
1.3.3查看并搜索数据
1GET /user/_doc/_search?pretty 2{ 3 "query" : { 4 "bool" : { 5 "must_not" : [ 6 { 7 "terms" : { 8 "user_id.keyword" : [ 9 "USER4b862aaa-2df8654a-7eb4bb65-e3507f66", 10 "USER14eeeaa5-442771b9-0262e455-e4663d1d", 11 "USER484a6734-03a124f0-996c169d-d05c1869" 12 ] 13 } 14 } 15 ], 16 "should" : [ 17 { 18 "match" : { 19 "user_id" : "昵称" 20 } 21 }, 22 { 23 "match" : { 24 "nickname" : "昵称" 25 } 26 }, 27 { 28 "match" : { 29 "phone" : "昵称" 30 } 31 } 32 ] 33 } 34 } 35} 36
1.3.4删除索引
1DELETE /user 2
1.4典型操作
ES中的操作,是基于Restful⻛格的接⼝,使⽤HTTP协议进⾏通信,通信的时候正⽂采⽤JSON格式进⾏序列化。
因此,在组织请求的过程中,更多关注的⽅⾯:
- 请求⽅法
- 请求的URI路径
- 请求正⽂中的各个关键字段
1.4.1索引
创建索引
1PUT /student 2{ 3 "settings": { 4 "analysis": { 5 "analyzer": { 6 "ikmax": { 7 "type": "custom", 8 "tokenizer": "ik_max_word" 9 } 10 } 11 } 12 }, 13 "mappings": { 14 "dynamic": false, 15 "properties": { 16 "name": { 17 "type": "text", 18 "boost": 3, 19 "analyzer": "ikmax" 20 }, 21 "age": { 22 "type": "integer" 23 }, 24 "phone": { 25 "type": "keyword", 26 "boost": 1 27 }, 28 "skills": { 29 "type": "text" 30 }, 31 "birth": { 32 "type": "date", 33 "index": false, 34 "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" 35 } 36 } 37 } 38 } 39
删除索引
1DELETE /student 2
1.4.2新增数据
1.4.2.1单数据新增
URI组成: /索引名称/⽂档类型名称[/当前⽂档ID]
1POST /student/_doc/2 2{ 3 "name":"lisi", 4 "age": 22, 5 "phone":"15522222222", 6 "skills":[ 7 "C++", 8 "Python" 9 ], 10 "birth":"2018-08-19 22:22:33" 11} 12
1.4.2.2批量数据新增
正⽂格式要求: 每个⽂档包含两⾏内容:1. 索引信息; 2. ⽂档信息
1POST /student/_doc/_bulk 2{"index":{"_id":"1"}} 3{"name":"zhangsan","age":18,"phone":"15511111111","skills":["C++", 4"Java"],"birth":"2017-08-19 22:22:22"} 5{"index":{"_id":"6"}} 6{"name":"lisi","age":19,"phone":"15522222222","skills":["C++", 7"Python"],"birth":"2018-08-19 22:22:33"} 8{"index":{"_id":"3"}} 9{"name":"wangwu","age":30,"phone":"15533333333","skills":["Php", 10"Python"],"birth":"2028-08-19 22:22:44"} 11{"index":{"_id":"4"}} 12{"name":"lisi w","age":20,"phone":"15544444444","skills":["Php", 13"Java"],"birth":"2038-08-19 22:22:55"} 14{"index":{"_id":"5"}} 15{"name":"lisi z","age":10,"phone":"15555555555","skills":["YYY", 16"BBBB"],"birth":"2048-08-19 22:22:11"} 17
1.4.2.3查询所有数据
1POST /student/_doc/_search 2{ 3 "query": { 4 "match_all": {} 5 } 6} 7
1.4.3删除
1.4.3.1删除指定id的数据
1DELETE /student/_doc/1 2
1.4.3.2批量删除指定id的数据
1POST /student/_doc/_bulk 2{ "delete": { "_id": "2" } } 3{ "delete": { "_id": "3" } } 4
1.4.3.3根据条件进行数据的删除
1POST /student/_doc/_delete_by_query 2{ 3 "query": { 4 "match": { 5 "name": "lisi" 6 } 7 } 8} 9
1.4.4更新
1.4.4.1单数据更新
1POST /student/_update/1 2{ 3 "doc": { 4 "phone": "18888888888", 5 "age": 19 6 } 7} 8
1.4.4.2批量数据更新
1POST /student/_doc/_bulk 2{"update":{"_id":"1"}} 3{ "doc": { "phone": "11111111111" } } 4{"update":{"_id":"2"}} 5{ "doc": { "phone": "22222222222" } } 6{"update":{"_id":"3"}} 7{ "doc": { "phone": "33333333333" } } 8
除了以上两种⽅式进⾏更新,也可以以新增单个数据的⽅式进⾏全字段更新。
1.4.5查询
单条件查询
1.term查询,单字段精确匹配(如果字段没有keyword属性时比如下方例子去掉.keyword,便是一种模糊匹配,有则都是精确匹配)
1POST /student/_doc/_search 2{ 3 "query" : { 4 "term" : { 5 "name.keyword" : "lisi" 6 } 7 } 8} 9
2.terms单字段多值精确匹配
1{ 2 "query" : { 3 "terms" : { 4 "phone" : [ 5 "15511111111", 6 "15522222222" 7 ] 8 } 9 } 10} 11
3.match单字段模糊匹配
1{ 2 "query" : { 3 "match" : { 4 "name" : "z" 5 } 6 } 7} 8
4.数字类型区间匹配
1{ 2 "query" : { 3 "range" : { 4 "age" : { 5 "gte" : 10, 6 "lte" : 20 7 } 8 } 9} 10
5.多字段模糊匹配
1{ 2 "query" : { 3 "multi_match" : { 4 "fields" : [ 5 "name", 6 "phone" 7 ], 8 "query" : "zhangsan" 9 } 10 } 11} 12
多条件查询
bool检索 • must:必须匹配的条件 • must_not : 必须过滤掉的条件 • should: 匹配⼀个或多个都可以 minimum_should_match:should中⾄少应该匹配n个条件
1{ 2 "query" : { 3 "bool" : { 4 "must" : [ 5 { 6 "match" : 7 { 8 "skills" : "C++" 9 } 10 }, 11 ], 12 "must_not" : [ 13 { 14 "terms" : { 15 "phone" : [ 16 "15522222222", 17 "15555555555" 18 ] 19 } 20 } 21 ], 22 "should" : [ 23 { 24 "match" : { 25 "name" : "lisi" 26 } 27 }, 28 { 29 "terms" : { 30 "phone" : [ 31 "15511111111", 32 "15555555555" 33 ] 34 } 35 } 36 ] 37 "minimum_should_match" : 1, 38 } 39 } 40} 41
sort检索
1{ 2 "query" : { 3 "bool" : { 4 "minimum_should_match" : 1, 5 "should" : [ 6 { 7 "match" : { 8 "name" : "lisi" 9 } 10 }, 11 { 12 "terms" : { 13 "phone" : 14 [ 15 "15511111111", 16 "15533333333" 17 ] 18 } 19 } 20 ] 21 } 22 }, 23 "sort" : [ 24 { 25 "name.keyword" : "desc" 26 }, 27 { 28 "age" : "asc" 29 } 30 ] 31} 32#结果⽰例 33[ 34{ 35 "age" : 18, 36 "birth" : "2017-08-19 22:22:22", 37 "name" : "zhangsan", 38 "phone" : "15511111111", 39 "skills" : [ 40 "C++", 41 "Java" 42 ] 43}, 44{ 45 "age" : 10, 46 "birth" : "2048-08-19 22:22:11", 47 "name" : "lisi z", 48 "phone" : "15555555555", 49 "skills" : [ 50 "YYY", 51 "BBBB" 52 ] 53}, 54{ 55 "age" : 20, 56 "birth" : "2038-08-19 22:22:55", 57 "name" : "lisi w", 58 "phone" : "15544444444", 59 "skills" : [ 60 "Php", 61 "Java" 62 ] 63}, 64{ 65 "age" : 19, 66 "birth" : "2018-08-19 22:22:33", 67 "name" : "lisi", 68 "phone" : "15522222222", 69 "skills" : [ 70 "C++", 71 "Python" 72 ] 73} 74] 75
分⻚查找 对检索按指定⽅式进⾏排序,并限制获取结果数量,以及偏移量
1{ 2 "query" : 3 { 4 "bool" : 5 { 6 "minimum_should_match" : 1, 7 "should" : 8 [ 9 { 10 "match" : 11 { 12 "name" : "lisi" 13 } 14 }, 15 { 16 "terms" : 17 { 18 "phone" : 19 [ 20 "15511111111", 21 "15533333333" 22 ] 23 } 24 } 25 ] 26 } 27 }, 28 "size" : 10, 29 "from" : 2, 30 "sort" : 31 [ 32 { 33 "name.keyword" : "desc" 34 }, 35 { 36 "age" : "asc" 37 } 38 ] 39} [ 40{ 41 "age" : 18, 42 "birth" : "2017-08-19 22:22:22", 43 "name" : "zhangsan", 44 "phone" : "15511111111", 45 "skills" : 46 [ 47 "C++", 48 "Java" 49 ] 50}, 51{ 52 "age" : 10, 53 "birth" : "2048-08-19 22:22:11", 54 "name" : "lisi z", 55 "phone" : "15555555555", 56 "skills" : 57 [ 58 "YYY", 59 "BBBB" 60 ] 61} 62
1.5ES客户端SDK
代码
官网
ES C++的客⼾端选择并不多, 我们这⾥使⽤elasticlient库, 下⾯进⾏安装。
1.5.1接口介绍
1.响应结构
1namespace cpr { 2 class Response { 3 public: 4 long status_code{}; 5 std::string text{}; 6 Header header{}; 7 Url url{}; 8 double elapsed{}; 9 Cookies cookies{}; 10 Error error{}; 11 std::string raw_header{}; 12 std::string status_line{}; 13 std::string reason{}; 14 }; 15} 16
2.典型接口
1namespace elasticlient { 2 class Client { 3 // http://user:password@localhost:9200/ 4 Client(const std::vector<std::string> &hostUrlList, 5 std::int32_t timeout = 6000); 6 /** 7* Perform search on nodes until it is successful. Throws exception if all 8nodes 9* has failed to respond. 10* \param indexName specification of an Elasticsearch index. 11* \param docType specification of an Elasticsearch document type. 12* \param body Elasticsearch request body. 13* \param routing Elasticsearch routing. If empty, no routing has been used. 14* \return cpr::Response if any of node responds to request. 15* \throws ConnectionException if all hosts in cluster failed to respond. 16*/ 17 cpr::Response search(const std::string &indexName, 18 const std::string &docType, 19 const std::string &body, 20 const std::string &routing = std::string()); 21 /** 22* Get document with specified id from cluster. Throws exception if all nodes 23* has failed to respond. 24* \param indexName specification of an Elasticsearch index. 25* \param docType specification of an Elasticsearch document type. 26* \param id Id of document which should be retrieved. 27* \param routing Elasticsearch routing. If empty, no routing has been used. 28* * 29\return cpr::Response if any of node responds to request. 30* \throws ConnectionException if all hosts in cluster failed to respond. 31*/ 32 cpr::Response get(const std::string &indexName, 33 const std::string &docType, 34 const std::string &id = std::string(), 35 const std::string &routing = std::string()); 36 /** 37* Index new document to cluster. Throws exception if all nodes has failed to 38respond. 39* \param indexName specification of an Elasticsearch index. 40* \param docType specification of an Elasticsearch document type. 41* \param body Elasticsearch request body. 42* \param id Id of document which should be indexed. If empty, id will be 43generated 44* automatically by Elasticsearch cluster. 45* \param routing Elasticsearch routing. If empty, no routing has been used. 46* * 47\return cpr::Response if any of node responds to request. 48* \throws ConnectionException if all hosts in cluster failed to respond. 49*/ 50 cpr::Response index(const std::string &indexName, 51 const std::string &docType, 52 const std::string &id, 53 const std::string &body, 54 const std::string &routing = std::string()); 55 /** 56* Delete document with specified id from cluster. Throws exception if all 57nodes 58* has failed to respond. 59* \param indexName specification of an Elasticsearch index. 60* \param docType specification of an Elasticsearch document type. 61* \param id Id of document which should be deleted. 62* \param routing Elasticsearch routing. If empty, no routing has been used. 63* * 64\return cpr::Response if any of node responds to request. 65* \throws ConnectionException if all hosts in cluster failed to respond. 66*/ 67 cpr::Response remove(const std::string &indexName, 68 const std::string &docType, 69 const std::string &id, 70 const std::string &routing = std::string()); 71 } } 72
1.5.2使用样例
1#include <cpr/response.h> 2#include <elasticlient/client.h> 3#include <iostream> 4 5const std::string index_name = "student"; 6const std::string doc_type = "_doc"; 7const std::string doc_id = "default_id"; 8 9void add_index(elasticlient::Client& client){ 10 std::string body = R"( 11 { 12 "settings": { 13 "analysis": { 14 "analyzer": { 15 "ikmax": { 16 "type": "custom", 17 "tokenizer": "ik_max_word" 18 } 19 } 20 } 21 }, 22 "mappings": { 23 "dynamic": false, 24 "properties": { 25 "name": { 26 "type": "text", 27 "boost": 3, 28 "analyzer": "ikmax" 29 }, 30 "age": { 31 "type": "integer" 32 }, 33 "phone": { 34 "type": "keyword", 35 "boost": 1 36 }, 37 "skills": { 38 "type": "text" 39 }, 40 "birth": { 41 "type": "date", 42 "index": false, 43 "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" 44 } 45 } 46 } 47 })"; 48 auto resp = client.performRequest(elasticlient::Client::HTTPMethod::PUT,index_name,body); 49 if(resp.status_code < 200 || resp.status_code >= 300){ 50 std::cerr << "创建索引失败,响应码:" << resp.status_code << ",响应信息:" << resp.text << std::endl; 51 } 52} 53 54void add_data(elasticlient::Client& client){ 55 std::string body = R"({ 56 "name": "张三", 57 "age": 19, 58 "phone": "13888888888", 59 "skills": ["java","php","go"], 60 "birth": "2007-05-21 12:35:32"})"; 61 auto resp = client.index(index_name,doc_type,"1",body); 62 if(resp.status_code < 200 || resp.status_code >= 300){ 63 std::cerr << "添加数据失败,响应码:" << resp.status_code << ",响应信息:" << resp.text << std::endl; 64 } 65} 66 67void mod_data(elasticlient::Client& client){ 68 //注意必须是全量插入,否则会导致除了更新字段其他字段全部被删除 69 std::string body = R"({ 70 "name": "张三", 71 "age": 19, 72 "phone": "13333333333", 73 "skills": ["java","php","go"], 74 "birth": "2007-05-21 12:35:32"})"; 75 //插入进行数据修改,也就是先删除原有数据再进行新增,一般不使用update方法对数据进行更新,此三方库中也并不提供update方法 76 auto resp = client.index(index_name,doc_type,"1",body); 77 if(resp.status_code < 200 || resp.status_code >= 300){ 78 std::cerr << "修改数据失败,响应码:" << resp.status_code << ",响应信息:" << resp.text << std::endl; 79 } 80} 81 82void search_data(elasticlient::Client& client){ 83 //按照测试函数的顺序执行这几个测试函数,会因为请求过快而查不到对应数据 84 std::string query = R"({"query": {"match_all": {}}})"; 85 auto resp = client.search(index_name,doc_type,query); 86 if(resp.status_code < 200 || resp.status_code >= 300){ 87 std::cerr << "搜索数据失败,响应码:" << resp.status_code << ",响应信息:" << resp.text << std::endl; 88 }else{ 89 std::cout << "搜索结果:" << resp.text << std::endl; 90 } 91} 92 93void remove_data(elasticlient::Client& client){ 94 auto resp = client.remove(index_name,doc_type,"1"); 95 if(resp.status_code < 200 || resp.status_code >= 300){ 96 std::cerr << "删除数据失败,响应码:" << resp.status_code << ",响应信息:" << resp.text << std::endl; 97 } 98} 99 100void remove_index(elasticlient::Client& client){ 101 auto resp = client.performRequest(elasticlient::Client::HTTPMethod::DELETE,index_name,""); 102 if(resp.status_code < 200 || resp.status_code >= 300){ 103 std::cerr << "删除索引失败,响应码:" << resp.status_code << ",响应信息:" << resp.text << std::endl; 104 } 105} 106 107int main(){ 108 //设置服务端访问url 109 const std::string url = "http://elastic:[email protected]:9200/";//记得一定要加上最后的/不然会报错 110 //创建elasticlient客户端 111 elasticlient::Client client({url}); 112 //测试 113 add_index(client);//如果存在会创建失败 114 add_data(client); 115 mod_data(client); 116 search_data(client); 117 remove_data(client); 118 remove_index(client); 119 return 0; 120} 121
编译运行:
1source:source.cc 2 g++ -std=c++17 $^ -o $@ -lcpr -ljsoncpp -lelasticlient 3clean: 4 rm -f source 5
1.6二次封装
对于ES的操作其实都是通过resful⻛格API实现的,⽽在代码中我们更多是组织出合适的json格式的正⽂,⽽如果使⽤json来进⾏组织,则要求使⽤⼈员对ES各项请求的格式与字段都有深⼊了解才⾏,为了简化使⽤要求,我们的⽬标就是封装出⼀套类能够直接序列化出指定功能的json正⽂。
ES中的操作⽆⾮也就是数据的增删改查操作,我们要做的就是将这些操作中所⽤到的json结构定义出来。
具体要搞出来的结构如下:

其实就是按照我们上面的使用样例的格式走的,因为我们index的分词器设置也就只有设置一个中文分词器而已,所以我们就不搞那么复杂了,具体封装如下:
1#pragma once 2#include "limeutil.h" 3#include "limelog.h" 4#include <unordered_map> 5#include <cpr/response.h> 6#include <elasticlient/client.h> 7#include <iostream> 8#include <optional> 9 10namespace limeelastic { 11 class Base 12 { 13 public: 14 using Ptr = std::shared_ptr<Base>; 15 Base(const std::string& key); 16 template<typename T> 17 void add(const std::string& key, const T& value)//键值对类型数据 18 { 19 _value[key] = value; 20 } 21 template<typename T> 22 void append(const std::string& key,const T& value)//数组类型数据 23 { 24 _value[key].append(value); 25 } 26 virtual std::string to_string();//将对象转化为json字符串 27 const std::string& get_key() const;//获取当前对象存储值的名称 28 virtual const Json::Value get_value() const;//获取当前对象存储的值 29 protected: 30 std::string _key;//当前对象存储值的名称 31 Json::Value _value;//当前对象存储的值 32 }; 33 34 class Array; 35 class Object : public Base 36 { 37 public: 38 using Ptr = std::shared_ptr<Object>; 39 Object(const std::string& key); 40 void addElement(const Base::Ptr& element); 41 std::shared_ptr<Object> newObject(const std::string& key); 42 std::shared_ptr<Array> newArray(const std::string& key); 43 template<typename T> 44 std::shared_ptr<T> getElement(const std::string& key) 45 { 46 auto it = _elements.find(key); 47 if (it!= _elements.end()) { 48 //使用dynamic_pointer_cast向下安全转换类型,仅适用于shared_ptr与weak_ptr 49 return std::dynamic_pointer_cast<T>(it->second); 50 } 51 return nullptr; 52 } 53 const Json::Value get_value() const; 54 std::string to_string() const; 55 private: 56 std::unordered_map<std::string, Base::Ptr> _elements; 57 }; 58 59 class Array : public Base 60 { 61 public: 62 using Ptr = std::shared_ptr<Array>; 63 Array(const std::string& key); 64 void addElement(const Base::Ptr& element); 65 std::shared_ptr<Object> newObject(const std::string& key); 66 std::shared_ptr<Array> newArray(const std::string& key); 67 const Json::Value get_value() const; 68 std::string to_string() const; 69 private: 70 std::vector<Base::Ptr> _elements; 71 }; 72 73 class Tokenizer : public Object{ 74 public: 75 using Ptr = std::shared_ptr<Tokenizer>; 76 Tokenizer(const std::string& key); 77 void setTokenizer(const std::string& tokenizer);//设定分词器 78 void setType(const std::string& type);//设定分词类型 79 }; 80 81 class Analyzer : public Object{ 82 public: 83 using Ptr = std::shared_ptr<Analyzer>; 84 Analyzer(); 85 Tokenizer::Ptr setTokenizer(const std::string& tokenizer); 86 }; 87 88 class Analysis : public Object{ 89 public: 90 using Ptr = std::shared_ptr<Analysis>; 91 Analysis(); 92 Analyzer::Ptr setAnalyzer(); 93 }; 94 95 class Settings : public Object{ 96 public: 97 using Ptr = std::shared_ptr<Settings>; 98 Settings(); 99 Analysis::Ptr setAnalysis(); 100 }; 101 102 class Field : public Object{ 103 public: 104 using Ptr = std::shared_ptr<Field>; 105 Field(const std::string& key); 106 void setType(const std::string& type);//设置字段类型 107 void setIndex(bool index);//设置是否索引 108 void setBoost(double boost);//设置权重 109 void setAnalyzer(const std::string& analyzer);//设置分词器 110 void setFormat(const std::string& format);//设置格式化器-对于日期这类的特殊字段 111 }; 112 113 class Properties : public Object{ 114 public: 115 using Ptr = std::shared_ptr<Properties>; 116 Properties(); 117 Field::Ptr addField(const std::string& key); 118 }; 119 120 class Mappings : public Object{ 121 public: 122 using Ptr = std::shared_ptr<Mappings>; 123 Mappings(); 124 void setDynamic(bool dynamic);//设置是否动态映射 125 Properties::Ptr setProperties(); 126 }; 127 128 class Term : public Object{ 129 public: 130 using Ptr = std::shared_ptr<Term>; 131 Term(const std::string& field_name); 132 template<typename T> 133 void setValue(const T& value) 134 { 135 this->add(_field_name, value); 136 }//设置查询值 137 private: 138 std::string _field_name; 139 }; 140 141 class Terms : public Object{ 142 public: 143 using Ptr = std::shared_ptr<Terms>; 144 Terms(const std::string& field_name); 145 void addValue(const std::string& value) 146 { 147 this->append(_field_name, value); 148 }//添加查询值 149 private: 150 std::string _field_name; 151 }; 152 153 class Match : public Object{ 154 public: 155 using Ptr = std::shared_ptr<Match>; 156 Match(const std::string& field_name); 157 template<typename T> 158 void setValue(const T& value) 159 { 160 this->add(_field_name, value); 161 }//设置查询值 162 private: 163 std::string _field_name; 164 }; 165 166 class MultiMatch : public Object{ 167 public: 168 using Ptr = std::shared_ptr<MultiMatch>; 169 MultiMatch(); 170 void addField(const std::string& field_name);//追加查询字段 171 template<typename T> 172 void setQuery(const T& query) 173 { 174 this->add("query", query); 175 }//设置查询语句 176 }; 177 178 class Range : public Object{ 179 public: 180 using Ptr = std::shared_ptr<Range>; 181 Range(const std::string& field_name); 182 template<typename T> 183 void setRange(const T& gte, const T& lte) 184 { 185 _sub->add("gte", gte); 186 _sub->add("lte", lte); 187 }//设置区间 188 private: 189 Object::Ptr _sub; 190 }; 191 192 //查询操作的基础类 193 class QObject : public Object{ 194 public: 195 using Ptr = std::shared_ptr<QObject>; 196 QObject(const std::string& key); 197 Term::Ptr term(const std::string& field_name);//单字段精确匹配 198 Terms::Ptr terms(const std::string& field_name);//多字段精确匹配 199 Match::Ptr match(const std::string& field_name);//单字段模糊匹配 200 MultiMatch::Ptr multi_match();//多字段模糊匹配 201 Range::Ptr range(const std::string& field_name);//数字字段区间匹配 202 }; 203 204 class QArray : public Array{ 205 public: 206 using Ptr = std::shared_ptr<QArray>; 207 QArray(const std::string& key); 208 Term::Ptr term(const std::string& field_name);//单字段精确匹配 209 Terms::Ptr terms(const std::string& field_name);//多字段精确匹配 210 Match::Ptr match(const std::string& field_name);//单字段模糊匹配 211 MultiMatch::Ptr multi_match();//多字段模糊匹配 212 Range::Ptr range(const std::string& field_name);//数字字段区间匹配 213 private: 214 std::string _key; 215 }; 216 217 class Must : public QArray{ 218 public: 219 using Ptr = std::shared_ptr<Must>; 220 Must(); 221 }; 222 223 class Should : public QArray{ 224 public: 225 using Ptr = std::shared_ptr<Should>; 226 Should(); 227 }; 228 229 class MustNot : public QArray{ 230 public: 231 using Ptr = std::shared_ptr<MustNot>; 232 MustNot(); 233 }; 234 235 class BoolQuery : public QObject{ 236 public: 237 using Ptr = std::shared_ptr<BoolQuery>; 238 BoolQuery(); 239 void minimum_should_match(int num); 240 Must::Ptr setMust(); 241 Should::Ptr setShould(); 242 MustNot::Ptr setMustNot(); 243 }; 244 245 class Query : public QObject{ 246 public: 247 using Ptr = std::shared_ptr<Query>; 248 Query(); 249 void match_all(); 250 BoolQuery::Ptr setBoolQuery(); 251 Must::Ptr setMust(); 252 Should::Ptr setShould(); 253 MustNot::Ptr setMustNot(); 254 }; 255 256 class Request { 257 public: 258 Request(const std::string &index, const std::string &type, const std::string &op, const std::string &id); 259 void set_index(const std::string &index); 260 void set_type(const std::string &type); 261 void set_op(const std::string &op); 262 void set_id(const std::string &id); 263 const std::string& get_index() const; 264 const std::string& get_type() const; 265 const std::string& get_op() const; 266 const std::string& get_id() const; 267 protected: 268 std::string _index; 269 std::string _type; 270 std::string _op; 271 std::string _id; 272 }; 273 274 class Indexer : public Object,public Request{ 275 public: 276 using Ptr = std::shared_ptr<Indexer>; 277 Indexer(const std::string &index); 278 Settings::Ptr setSettings(); 279 Tokenizer::Ptr setTokenizer(const std::string& tokenizer);//设定分词器 280 Mappings::Ptr setMappings(); 281 Field::Ptr addField(const std::string& key);//添加字段 282 }; 283 284 class Inserter : public Object, public Request { 285 public: 286 using ptr = std::shared_ptr<Inserter>; 287 Inserter(const std::string &index, const std::string &id); 288 }; 289 290 class Updater : public Object, public Request { 291 public: 292 using ptr = std::shared_ptr<Updater>; 293 Updater(const std::string &index, const std::string &id); 294 Object::Ptr doc(); 295 }; 296 297 class Deleter : public Object, public Request { 298 public: 299 using ptr = std::shared_ptr<Deleter>; 300 Deleter(const std::string &index, const std::string &id); 301 }; 302 303 class Searcher: public QObject, public Request{ 304 public: 305 using Ptr = std::shared_ptr<Searcher>; 306 Searcher(const std::string &index); 307 Query::Ptr setQuery(); 308 void size(size_t count); 309 void from(size_t offest); 310 void addSort(const std::string& field_name, bool asc = true); 311 }; 312 313 class BaseClinet{ 314 public: 315 using Ptr = std::shared_ptr<BaseClinet>; 316 BaseClinet() = default; 317 virtual ~BaseClinet() = default; 318 virtual bool addIndex(const Indexer& indexer) = 0; 319 virtual bool addData(const Inserter& inserter) = 0; 320 virtual bool updateData(const Updater& updater) = 0; 321 virtual bool deleteData(const Deleter& deleter) = 0; 322 virtual bool deleteIndex(const std::string& index) = 0; 323 virtual std::optional<Json::Value> searchData(const Searcher& searcher) = 0; 324 }; 325 326 class EscClient : public BaseClinet{ 327 public: 328 using Ptr = std::shared_ptr<EscClient>; 329 EscClient(const std::vector<std::string>& hosts); 330 bool addIndex(const Indexer& indexer) override; 331 bool addData(const Inserter& inserter) override; 332 bool updateData(const Updater& updater) override; 333 bool deleteData(const Deleter& deleter) override; 334 bool deleteIndex(const std::string& index) override; 335 std::optional<Json::Value> searchData(const Searcher& searcher) override; 336 private: 337 std::shared_ptr<elasticlient::Client> _client; 338 }; 339}// namespace limeelastic 340
1#include "limeelastic.h" 2 3namespace limeelastic { 4 Base::Base(const std::string& key) :_key(key) {} 5 6 std::string Base::to_string() 7 { 8 Json::Value root; 9 root[_key] = _value; 10 return *limeutil::LimeJson::serialize(root,true); 11 } 12 13 const std::string& Base::get_key() const 14 { 15 return _key; 16 } 17 18 const Json::Value Base::get_value() const 19 { 20 return _value; 21 } 22 23 Object::Object(const std::string& key) : Base(key) {} 24 25 void Object::addElement(const Base::Ptr& element) 26 { 27 _elements[element->get_key()] = element; 28 } 29 30 std::shared_ptr<Object> Object::newObject(const std::string& key) 31 { 32 auto is_exist = this->getElement<Object>(key); 33 if (is_exist) { 34 return is_exist; 35 } 36 auto obj = std::make_shared<Object>(key); 37 this->addElement(obj); 38 return obj; 39 } 40 41 std::shared_ptr<Array> Object::newArray(const std::string& key) 42 { 43 auto is_exist = this->getElement<Array>(key); 44 if (is_exist) { 45 return is_exist; 46 } 47 auto arr = std::make_shared<Array>(key); 48 this->addElement(arr); 49 return arr; 50 } 51 52 const Json::Value Object::get_value() const 53 { 54 Json::Value root = _value; 55 for (auto& element : _elements) { 56 root[element.first] = element.second->get_value(); 57 } 58 return root; 59 } 60 61 std::string Object::to_string() const 62 { 63 Json::Value root = _value;//当前对象存储的值 64 for (auto& element : _elements) {//子对象存储的值 65 root[element.first] = element.second->get_value(); 66 } 67 return *limeutil::LimeJson::serialize(root,true); 68 } 69 70 Array::Array(const std::string& key) : Base(key) {} 71 72 void Array::addElement(const Base::Ptr& element) 73 { 74 _elements.push_back(element); 75 } 76 77 std::shared_ptr<Object> Array::newObject(const std::string& key) 78 { 79 //这里不管原本是否存在目标元素,添加重复了是使用者的问题 80 auto obj = std::make_shared<Object>(key); 81 this->addElement(obj); 82 return obj; 83 } 84 85 std::shared_ptr<Array> Array::newArray(const std::string& key) 86 { 87 //这里不管原本是否存在目标元素,添加重复了是使用者的问题 88 auto arr = std::make_shared<Array>(key); 89 this->addElement(arr); 90 return arr; 91 } 92 93 const Json::Value Array::get_value() const 94 { 95 Json::Value root = _value; 96 for (auto& element : _elements) { 97 root.append(element->get_value()); 98 } 99 return root; 100 } 101 102 std::string Array::to_string() const 103 { 104 Json::Value root = _value; 105 for (auto& element : _elements) { 106 root.append(element->get_value()); 107 } 108 return *limeutil::LimeJson::serialize(root,true); 109 } 110 111 Tokenizer::Tokenizer(const std::string& key) : Object(key) { 112 this->setTokenizer("ik_max_word");//默认设置 113 this->setType("custom"); 114 } 115 116 void Tokenizer::setTokenizer(const std::string& tokenizer) 117 { 118 this->add("tokenizer", tokenizer); 119 } 120 121 void Tokenizer::setType(const std::string& type) 122 { 123 this->add("type", type); 124 } 125 126 Analyzer::Analyzer() : Object("analyzer") {} 127 128 Tokenizer::Ptr Analyzer::setTokenizer(const std::string& tokenizer) 129 { 130 auto is_exist = this->getElement<Tokenizer>("tokenizer"); 131 if (is_exist) { 132 return is_exist; 133 } 134 auto tokenizer_obj = std::make_shared<Tokenizer>(tokenizer); 135 this->addElement(tokenizer_obj); 136 return tokenizer_obj; 137 } 138 139 Analysis::Analysis() : Object("analysis") {} 140 141 Analyzer::Ptr Analysis::setAnalyzer() 142 { 143 auto is_exist = this->getElement<Analyzer>("analyzer"); 144 if (is_exist) { 145 return is_exist; 146 } 147 auto analyzer_obj = std::make_shared<Analyzer>(); 148 this->addElement(analyzer_obj); 149 return analyzer_obj; 150 } 151 152 Settings::Settings() : Object("settings") {} 153 154 Analysis::Ptr Settings::setAnalysis() 155 { 156 auto is_exist = this->getElement<Analysis>("analysis"); 157 if (is_exist) { 158 return is_exist; 159 } 160 auto analysis_obj = std::make_shared<Analysis>(); 161 this->addElement(analysis_obj); 162 return analysis_obj; 163 } 164 165 Field::Field(const std::string& key) : Object(key) { 166 this->setType("text");//默认设置字段类型为text 167 } 168 169 void Field::setType(const std::string& type) 170 { 171 this->add("type", type); 172 } 173 174 void Field::setIndex(bool index) 175 { 176 this->add("index", index); 177 } 178 179 void Field::setBoost(double boost) 180 { 181 this->add("boost", boost); 182 } 183 184 void Field::setAnalyzer(const std::string& analyzer) 185 { 186 this->add("analyzer", analyzer); 187 } 188 189 void Field::setFormat(const std::string& format) 190 { 191 this->add("format", format); 192 } 193 194 Properties::Properties() : Object("properties") {} 195 196 Field::Ptr Properties::addField(const std::string& key) 197 { 198 //对字段进行查找,如果存在则不添加 199 auto is_exist = this->getElement<Field>(key); 200 if (is_exist) { 201 return is_exist; 202 } 203 auto field_obj = std::make_shared<Field>(key); 204 this->addElement(field_obj); 205 return field_obj; 206 } 207 208 Mappings::Mappings() : Object("mappings") {} 209 210 void Mappings::setDynamic(bool dynamic) 211 { 212 this->add("dynamic", dynamic); 213 } 214 215 Properties::Ptr Mappings::setProperties() 216 { 217 auto is_exist = this->getElement<Properties>("properties"); 218 if (is_exist) { 219 return is_exist; 220 } 221 auto properties_obj = std::make_shared<Properties>(); 222 this->addElement(properties_obj); 223 return properties_obj; 224 } 225 226 Term::Term(const std::string& field_name) 227 : Object("term") 228 , _field_name(field_name) 229 {} 230 231 Terms::Terms(const std::string& field_name) 232 : Object("terms") 233 , _field_name(field_name) 234 {} 235 236 Match::Match(const std::string& field_name) 237 : Object("match") 238 , _field_name(field_name) 239 {} 240 241 MultiMatch::MultiMatch() 242 : Object("multi_match") 243 {} 244 245 void MultiMatch::addField(const std::string& field_name) 246 { 247 this->append("fields", field_name); 248 } 249 250 Range::Range(const std::string& field_name) 251 : Object("range") 252 { 253 _sub = std::make_shared<Object>(field_name); 254 this->addElement(_sub); 255 } 256 257 QObject::QObject(const std::string& key) : Object(key) {} 258 259 Term::Ptr QObject::term(const std::string& field_name) 260 { 261 auto is_exist = this->getElement<Term>("term"); 262 if (is_exist) { 263 return is_exist; 264 } 265 auto term_obj = std::make_shared<Term>(field_name); 266 this->addElement(term_obj); 267 return term_obj; 268 } 269 270 Terms::Ptr QObject::terms(const std::string& field_name) 271 { 272 auto is_exist = this->getElement<Terms>("terms"); 273 if (is_exist) { 274 return is_exist; 275 } 276 auto terms_obj = std::make_shared<Terms>(field_name); 277 this->addElement(terms_obj); 278 return terms_obj; 279 } 280 281 Match::Ptr QObject::match(const std::string& field_name) 282 { 283 auto is_exist = this->getElement<Match>("match"); 284 if (is_exist) { 285 return is_exist; 286 } 287 auto match_obj = std::make_shared<Match>(field_name); 288 this->addElement(match_obj); 289 return match_obj; 290 } 291 292 MultiMatch::Ptr QObject::multi_match() 293 { 294 auto is_exist = this->getElement<MultiMatch>("multi_match"); 295 if (is_exist) { 296 return is_exist; 297 } 298 auto multi_match_obj = std::make_shared<MultiMatch>(); 299 this->addElement(multi_match_obj); 300 return multi_match_obj; 301 } 302 303 Range::Ptr QObject::range(const std::string& field_name) 304 { 305 auto is_exist = this->getElement<Range>("range"); 306 if (is_exist) { 307 return is_exist; 308 } 309 auto range_obj = std::make_shared<Range>(field_name); 310 this->addElement(range_obj); 311 return range_obj; 312 } 313 314 QArray::QArray(const std::string& key) 315 : Array(key) 316 , _key(key) 317 {} 318 319 Term::Ptr QArray::term(const std::string& field_name) 320 { 321 //不考虑重复添加 322 auto term_obj = std::make_shared<Term>(field_name); 323 auto obj = this->newObject(""); 324 obj->addElement(term_obj); 325 return term_obj; 326 } 327 328 Terms::Ptr QArray::terms(const std::string& field_name) 329 { 330 //不考虑重复添加 331 auto terms_obj = std::make_shared<Terms>(field_name); 332 auto obj = this->newObject(""); 333 obj->addElement(terms_obj); 334 return terms_obj; 335 } 336 337 Match::Ptr QArray::match(const std::string& field_name) 338 { 339 //不考虑重复添加 340 auto match_obj = std::make_shared<Match>(field_name); 341 auto obj = this->newObject(""); 342 obj->addElement(match_obj); 343 return match_obj; 344 } 345 346 MultiMatch::Ptr QArray::multi_match() 347 { 348 //不考虑重复添加 349 auto multi_match_obj = std::make_shared<MultiMatch>(); 350 auto obj = this->newObject(""); 351 obj->addElement(multi_match_obj); 352 return multi_match_obj; 353 } 354 355 Range::Ptr QArray::range(const std::string& field_name) 356 { 357 //不考虑重复添加 358 auto range_obj = std::make_shared<Range>(field_name); 359 auto obj = this->newObject(""); 360 obj->addElement(range_obj); 361 return range_obj; 362 } 363 364 Must::Must() : QArray("must") {} 365 366 Should::Should() : QArray("should") {} 367 368 MustNot::MustNot() : QArray("must_not") {} 369 370 BoolQuery::BoolQuery() : QObject("bool") {} 371 372 void BoolQuery::minimum_should_match(int num) 373 { 374 this->add("minimum_should_match", num); 375 } 376 377 Must::Ptr BoolQuery::setMust() 378 { 379 auto is_exist = this->getElement<Must>("must"); 380 if (is_exist) { 381 return is_exist; 382 } 383 auto must_obj = std::make_shared<Must>(); 384 this->addElement(must_obj); 385 return must_obj; 386 } 387 388 Should::Ptr BoolQuery::setShould() 389 { 390 auto is_exist = this->getElement<Should>("should"); 391 if (is_exist) { 392 return is_exist; 393 } 394 auto should_obj = std::make_shared<Should>(); 395 this->addElement(should_obj); 396 return should_obj; 397 } 398 399 MustNot::Ptr BoolQuery::setMustNot() 400 { 401 auto is_exist = this->getElement<MustNot>("must_not"); 402 if (is_exist) { 403 return is_exist; 404 } 405 auto must_not_obj = std::make_shared<MustNot>(); 406 this->addElement(must_not_obj); 407 return must_not_obj; 408 } 409 410 Query::Query() : QObject("query") {} 411 412 void Query::match_all() 413 { 414 this->add("match_all",Json::Value(Json::ValueType::objectValue)); 415 } 416 417 BoolQuery::Ptr Query::setBoolQuery() 418 { 419 auto is_exist = this->getElement<BoolQuery>("bool"); 420 if (is_exist) { 421 return is_exist; 422 } 423 auto bool_obj = std::make_shared<BoolQuery>(); 424 this->addElement(bool_obj); 425 return bool_obj; 426 } 427 428 Must::Ptr Query::setMust() 429 { 430 auto is_exist = this->getElement<Must>("must"); 431 if (is_exist) { 432 return is_exist; 433 } 434 auto must_obj = std::make_shared<Must>(); 435 this->addElement(must_obj); 436 return must_obj; 437 } 438 439 Should::Ptr Query::setShould() 440 { 441 auto is_exist = this->getElement<Should>("should"); 442 if (is_exist) { 443 return is_exist; 444 } 445 auto should_obj = std::make_shared<Should>(); 446 this->addElement(should_obj); 447 return should_obj; 448 } 449 450 MustNot::Ptr Query::setMustNot() 451 { 452 auto is_exist = this->getElement<MustNot>("must_not"); 453 if (is_exist) { 454 return is_exist; 455 } 456 auto must_not_obj = std::make_shared<MustNot>(); 457 this->addElement(must_not_obj); 458 return must_not_obj; 459 } 460 461 Request::Request(const std::string &index, const std::string &type, const std::string &op, const std::string &id) 462 : _index(index) 463 , _type(type) 464 , _op(op) 465 , _id(id) 466 {} 467 468 void Request::set_index(const std::string &index) { _index = index; }; 469 470 void Request::set_type(const std::string &type) { _type = type; }; 471 472 void Request::set_op(const std::string &op) { _op = op; }; 473 474 void Request::set_id(const std::string &id) { _id = id; }; 475 476 const std::string& Request::get_index() const { return _index; }; 477 478 const std::string& Request::get_type() const { return _type; }; 479 480 const std::string& Request::get_op() const { return _op; }; 481 482 const std::string& Request::get_id() const { return _id; }; 483 484 Indexer::Indexer(const std::string &index) 485 : Object("") 486 , Request(index, "_doc", "_index", index) 487 {} 488 489 Settings::Ptr Indexer::setSettings() 490 { 491 auto is_exist = this->getElement<Settings>("settings"); 492 if (is_exist) { 493 return is_exist; 494 } 495 auto settings_obj = std::make_shared<Settings>(); 496 this->addElement(settings_obj); 497 return settings_obj; 498 } 499 500 Tokenizer::Ptr Indexer::setTokenizer(const std::string& tokenizer) 501 { 502 return this->setSettings()->setAnalysis()->setAnalyzer()->setTokenizer(tokenizer); 503 } 504 505 Mappings::Ptr Indexer::setMappings() 506 { 507 auto is_exist = this->getElement<Mappings>("mappings"); 508 if (is_exist) { 509 return is_exist; 510 } 511 auto mappings_obj = std::make_shared<Mappings>(); 512 this->addElement(mappings_obj); 513 return mappings_obj; 514 } 515 516 Field::Ptr Indexer::addField(const std::string& key) 517 { 518 return this->setMappings()->setProperties()->addField(key); 519 } 520 521 Inserter::Inserter(const std::string &index, const std::string &id) 522 : Object("") 523 , Request(index, "_doc", "_insert", id) 524 {} 525 526 Updater::Updater(const std::string &index, const std::string &id) 527 : Object("") 528 , Request(index, "_doc", "_update", id) 529 {} 530 531 Object::Ptr Updater::doc() 532 { 533 auto is_exist = this->getElement<Object>("doc"); 534 if (is_exist) { 535 return is_exist; 536 } 537 auto doc_obj = std::make_shared<Object>("doc"); 538 this->addElement(doc_obj); 539 return doc_obj; 540 } 541 542 Deleter::Deleter(const std::string &index, const std::string &id) 543 : Object("") 544 , Request(index, "_doc", "_delete", id) 545 {} 546 547 Searcher::Searcher(const std::string &index) 548 : QObject("") 549 , Request(index, "_doc", "_search", "") 550 {} 551 552 Query::Ptr Searcher::setQuery() 553 { 554 auto is_exist = this->getElement<Query>("query"); 555 if (is_exist) { 556 return is_exist; 557 } 558 auto query_obj = std::make_shared<Query>(); 559 this->addElement(query_obj); 560 return query_obj; 561 } 562 563 void Searcher::size(size_t count) { 564 this->add("size", count); 565 } 566 567 void Searcher::from(size_t offset) { 568 this->add("from", offset); 569 } 570 571 void Searcher::addSort(const std::string& field_name, bool asc) { 572 Json::Value sort_obj; 573 sort_obj[field_name] = asc ? "asc" : "desc"; 574 this->append("sort", sort_obj);//重复添加排序规则是使用者的问题 575 } 576 577 EscClient::EscClient(const std::vector<std::string>& hosts) 578 : _client(std::make_shared<elasticlient::Client>(hosts)) 579 {} 580 581 bool EscClient::addIndex(const Indexer& indexer) 582 { 583 std::string index_name = indexer.get_index(); 584 std::string body = indexer.to_string(); 585 auto resp = _client->performRequest(elasticlient::Client::HTTPMethod::PUT,index_name,body); 586 if(resp.status_code < 200 || resp.status_code >= 300){ 587 ERR("创建索引失败,响应码:{},响应信息:{}", resp.status_code, resp.text); 588 return false; 589 } 590 return true; 591 } 592 593 bool EscClient::addData(const Inserter& inserter) 594 { 595 std::string index_name = inserter.get_index(); 596 std::string doc_type = inserter.get_type(); 597 std::string id = inserter.get_id(); 598 std::string body = inserter.to_string(); 599 auto resp = _client->index(index_name,doc_type,id,body); 600 if(resp.status_code < 200 || resp.status_code >= 300){ 601 ERR("添加数据失败,响应码:{},响应信息:{}", resp.status_code, resp.text); 602 return false; 603 } 604 return true; 605 } 606 607 bool EscClient::updateData(const Updater& updater) 608 { 609 std::string index_name = updater.get_index(); 610 std::string op = updater.get_op(); 611 std::string id = updater.get_id(); 612 std::string body = updater.to_string(); 613 std::string url = index_name + "/" + op + "/" + id; 614 auto resp = _client->performRequest(elasticlient::Client::HTTPMethod::POST,url,body); 615 if(resp.status_code < 200 || resp.status_code >= 300){ 616 ERR("更新数据失败,响应码:{},响应信息:{}", resp.status_code, resp.text); 617 return false; 618 } 619 return true; 620 } 621 622 bool EscClient::deleteData(const Deleter& deleter) 623 { 624 std::string index_name = deleter.get_index(); 625 std::string doc_type = deleter.get_type(); 626 std::string id = deleter.get_id(); 627 auto resp = _client->remove(index_name,doc_type,id); 628 if(resp.status_code < 200 || resp.status_code >= 300){ 629 ERR("删除数据失败,响应码:{},响应信息:{}", resp.status_code, resp.text); 630 return false; 631 } 632 return true; 633 } 634 635 bool EscClient::deleteIndex(const std::string& index) 636 { 637 std::string index_name = index; 638 auto resp = _client->performRequest(elasticlient::Client::HTTPMethod::DELETE,index_name,""); 639 if(resp.status_code < 200 || resp.status_code >= 300){ 640 ERR("删除索引失败,响应码:{},响应信息:{}", resp.status_code, resp.text); 641 return false; 642 } 643 return true; 644 } 645 646 std::optional<Json::Value> EscClient::searchData(const Searcher& searcher) 647 { 648 std::string index_name = searcher.get_index(); 649 std::string doc_type = searcher.get_type(); 650 std::string body = searcher.to_string(); 651 auto resp = _client->search(index_name,doc_type,body); 652 if(resp.status_code < 200 || resp.status_code >= 300){ 653 ERR("搜索数据失败,响应码:{},响应信息:{},请求正文:{}", resp.status_code, resp.text,body); 654 return std::nullopt; 655 } 656 //解析响应数据 657 auto json_resp = limeutil::LimeJson::deserialize(resp.text); 658 if(!json_resp) 659 { 660 ERR("解析响应数据失败:{}", resp.text); 661 return std::nullopt; 662 } 663 //获取查询结果 664 if(json_resp->isNull() || (*json_resp)["hits"].isNull() || (*json_resp)["hits"]["hits"].isNull()) 665 { 666 ERR("无法正确解析结果:{}", resp.text); 667 return std::nullopt; 668 } 669 Json::Value result; 670 int sz = (*json_resp)["hits"]["hits"].size(); 671 for(int i=0;i<sz;i++) 672 { 673 result.append((*json_resp)["hits"]["hits"][i]["_source"]); 674 } 675 return result; 676 } 677} // namespace limeelastic 678
使用样例
1#include "../../source/limeelastic.h" 2 3limeelastic::Indexer test_indexer(){ 4 limeelastic::Indexer indexer("student"); 5 //设置字段是否动态映射 6 indexer.setMappings()->setDynamic(false); 7 //添加字段 8 auto field = indexer.addField("name"); 9 field->setType("text"); 10 field->setBoost(3.0); 11 field->setAnalyzer("ikmax"); 12 auto field2 = indexer.addField("age"); 13 field2->setType("integer"); 14 auto field3 = indexer.addField("phone"); 15 field3->setType("keyword"); 16 field3->setBoost(1.0); 17 auto field4 = indexer.addField("birthday"); 18 field4->setType("date"); 19 field4->setIndex(false); 20 field4->setFormat("yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"); 21 auto filed5 = indexer.addField("skills"); 22 filed5->setType("text"); 23 //设置分词器 24 auto tokenizer = indexer.setTokenizer("ikmax"); 25 //打印索引配置 26 //std::cout << indexer.to_string() << std::endl; 27 return indexer; 28} 29 30void test_query() 31{ 32 auto searchQuery = std::make_shared<limeelastic::Searcher>("student"); 33 auto query = searchQuery->setQuery(); 34 //设置match_all查询 35 query->match_all(); 36 //设置term查询 37 auto term = query->term("name.keyword"); 38 term->setValue("lisi"); 39 //设置terms查询 40 auto terms = query->terms("phone"); 41 terms->addValue("15511111111"); 42 terms->addValue("15522222222"); 43 //设置match查询 44 auto match = query->match("name"); 45 match->setValue("z"); 46 //设置multi_match查询 47 auto multi_match = query->multi_match(); 48 multi_match->setQuery("zhangsan"); 49 multi_match->addField("name"); 50 multi_match->addField("phone"); 51 //设置range查询 52 auto range = query->range("age"); 53 range->setRange(10,20); 54 //打印查询配置 55 std::cout << searchQuery->to_string() << std::endl; 56} 57 58void test_bool() 59{ 60 auto searchQuery = std::make_shared<limeelastic::Searcher>("student"); 61 auto boolQuery = searchQuery->setQuery()->setBoolQuery(); 62 boolQuery->minimum_should_match(1); 63 //设置bool查询 64 auto must = boolQuery->setMust(); 65 auto match = must->match("skills"); 66 match->setValue("c++"); 67 auto should = boolQuery->setShould(); 68 auto match2 = should->match("name"); 69 match2->setValue("lisi"); 70 auto terms1 = should->terms("phone"); 71 terms1->addValue("15511111111"); 72 terms1->addValue("15555555555"); 73 auto must_not = boolQuery->setMustNot(); 74 auto terms = must_not->terms("phone"); 75 terms->addValue("15522222222"); 76 terms->addValue("15555555555"); 77 //设置sort 78 searchQuery->addSort("age", true); 79 searchQuery->addSort("birthday", false); 80 //打印查询配置 81 std::cout << searchQuery->to_string() << std::endl; 82} 83 84int main(){ 85 limelog::limelog_init(); 86 limeelastic::EscClient esc_client({"http://elastic:[email protected]:9200/"}); 87 // esc_client.addIndex(test_indexer()); 88 // limeelastic::Inserter inserter("student","1"); 89 // inserter.add("name","张三"); 90 // inserter.add("age",20); 91 // inserter.add("phone","13888888888"); 92 // inserter.append("skills","c++"); 93 // inserter.append("skills","php"); 94 // inserter.append("skills","go"); 95 // inserter.add("birthday","2007-05-21 12:35:32"); 96 // esc_client.addData(inserter); 97 // limeelastic::Updater updater("student","1"); 98 // updater.doc()->add("phone","13333333333"); 99 // esc_client.updateData(updater); 100 limeelastic::Searcher searcher("student"); 101 searcher.setQuery()->match("name")->setValue("lisi"); 102 searcher.size(2); 103 searcher.from(2); 104 auto result = esc_client.searchData(searcher); 105 if(result) 106 { 107 INF("查询到的目标数据为:{}",*limeutil::LimeJson::serialize(*result,true)); 108 // //进行数据删除 109 // limeelastic::Deleter deleter("student","1"); 110 // esc_client.deleteData(deleter); 111 // //进行索引删除 112 // esc_client.deleteIndex("student"); 113 } 114 115 return 0; 116} 117
编译构建:
1test:test.cc ../../source/limeelastic.cc ../../source/limeutil.cc ../../source/limelog.cc 2 g++ $^ -o $@ -std=c++17 -lcpr -ljsoncpp -lelasticlient -lspdlog -lpthread -lfmt 3clean: 4 rm test 5
这样一来就不需要我们自己再去一个一个的组织格式然后发给elastic服务端了,方便很多。
2.libcurl的使用与封装
libcurl 是⼀个跨平台、开源的客⼾端⽹络传输库,⽀持多种协议(如 HTTP、HTTPS、FTP、SMTP 等),⼴泛应⽤于⽹络通信、数据抓取、API 交互等场景。
安装命令如下:
1sudo apt install libcurl4-openssl-dev 2
2.1启用邮箱授权码(以163邮箱为例)
因为我们是通过libcurl实现邮件推送客⼾端,因此先在这⾥启⽤邮箱客⼾端授权码,这样才能便于使⽤。
163邮箱官网地址:https://mail.163.com/




2.2使用
2.2.1头文件与链接库
1#include <curl/curl.h> 2
1-lcurl -lssl -lcrypto 2
2.2.2核心接口
1#define CURL_GLOBAL_SSL (1<<0) /* no purpose since since 7.57.0 */ 2#define CURL_GLOBAL_WIN32 (1<<1) 3#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32) 4#define CURL_GLOBAL_NOTHING 0 5#define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL 6#define CURL_GLOBAL_ACK_EINTR (1<<2) 7// 初始化全局配置 8CURLcode curl_global_init(long flags); 9// 销毁全局配置 10void curl_global_cleanup(void); 11// 创建并初始化curl操作句柄 12CURL *curl_easy_init(void); 13// 设置curl操作选项 14CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); 15// 同步阻塞函数,会⼀次性执⾏所有通过curl_easy_setopt 设置的选项 16// 如: URL,请求⽅法,回调函数等 17CURLcode curl_easy_perform(CURL *curl); 18// 仅清除通过 curl_easy_setopt 设置的选项,恢复为初始状态 19void curl_easy_reset(CURL *curl); 20// 关闭所有与该句柄相关的连接,释放所有资源 21void curl_easy_cleanup(CURL *curl); 22// 获取上次函数执⾏错误信息 23const char *curl_easy_strerror(CURLcode); 24
2.2.3链表操作
1struct curl_slist { 2 char *data; 3 struct curl_slist *next; 4}; 5struct curl_slist *curl_slist_append(struct curl_slist *, const char *); 6void curl_slist_free_all(struct curl_slist *); 7
2.2.4http选项
| 选项 | 功能 | 备注 |
|---|---|---|
| CURLOPT_URL | 设置请求 URL | 所有请求的基础配置 |
| CURLOPT_READFUNCTION | 处理请求数据的回调函数 | 添加请求正文 |
| CURLOPT_WRITEDATA | 设置给请求回调函数的用户数据 | 文件句柄或数据对象 |
| CURLOPT_WRITEFUNCTION | 处理响应数据的回调函数 | 下载文件或保存 API 响应 |
| CURLOPT_POSTFIELDS | 指定 POST 请求正文 | 提交表单或 JSON 数据 |
| CURLOPT_HTTPHEADER | 自定义 HTTP 头部 | 设置认证头或内容类型 |
| CURLOPT_FOLLOWLOCATION | 自动跟随重定向 | 处理短链接或跳转页面 |
| CURLOPT_VERBOSE | 输出调试信息 | 开发阶段问题排查 |
| CURLOPT_SSL_VERIFYPEER | 控制是否验证对端证书 | 生产环境不推荐使用 |
| CURLOPT_TIMEOUT | 设置传输超时时间 | 请求到完成 |
| CURLOPT_CONNECTTIMEOUT | 设置连接超时时间 | 握手超时时间 |
2.2.5SMTP选项
| 选项 | 功能 | 典型值示例 |
|---|---|---|
| CURLOPT_URL | SMTP服务器地址 | “smtps://smtp.example.com:465” |
| CURLOPT_MAIL_FROM | 发件人地址 | “[email protected]” |
| CURLOPT_MAIL_RCPT | 收件人列表(链表) | curl_slist.append(recipients, “[email protected]”) |
| CURLOPT_READFUNCTION | 处理请求数据的回调函数 | 添加请求正文 |
| CURLOPT_READDATA | 通信正文内容 | 代码块1 From: [email protected]\r\n2 To: [email protected]\r\n3 Subject: title\r\n4 Content-Type: text/html\r\n5 \r\n6 Body\r\n |
| CURLOPT_SSL_VERIFYPEER | 启用服务器证书验证 | 1L(启用)或 0L(禁用) |
| CURLOPT_USERNAME | SMTP登录用户名 | “[email protected]” |
| CURLOPT_PASSWORD | SMTP登录密码 | |
| CURLOPT_USE_SSL | 控制SSL/TLS加密行为 | |
| CURLOPT_UPLOAD | 启用上传模式 | 1L(启用)或 0L(禁用),与CURLOPT_READDATA & CURLOPT_READFUNCTION搭配使用 |
2.3二次封装
因为这里的使用基本是按照流程走的,所以使用样例我们也放到二次封装处即可。我们的二次封装主要也就以下几个内容:
验证码发送客户端类:
- 发送邮件函数sendCode
- 设置邮件正文函数
具体实现如下:
1#pragma once 2#include <curl/curl.h> 3#include <iostream> 4#include <sstream> 5#include <memory> 6 7namespace limemail { 8 struct EmailCodeSettings{ 9 std::string username; 10 std::string password; 11 std::string url; 12 std::string from; 13 }; 14 15 class CodeClient{ 16 public: 17 CodeClient() = default; 18 virtual ~CodeClient() = default; 19 virtual bool sendCode(const std::string& to, const std::string& code) = 0; 20 }; 21 22 class EmailClient : public CodeClient{ 23 public: 24 using Ptr = std::shared_ptr<EmailClient>; 25 EmailClient(const EmailCodeSettings& settings); 26 bool sendCode(const std::string& to, const std::string& code) override; 27 ~EmailClient(); 28 private: 29 std::stringstream requestBody(const std::string& to, const std::string& code); 30 static size_t callback(char *buffer, size_t size, size_t nitems, void *userdata); 31 private: 32 EmailCodeSettings _settings;//不要保存curl句柄,因为他是线程不安全的,最好是每次使用时直接获取与释放 33 std::string _subject = "limeplayer登录/注册验证码通知"; 34 }; 35} // namespace limemail 36
1#include "limemail.h" 2#include "limelog.h" 3 4namespace limemail { 5 const std::string htmlTemplate = R"(<!DOCTYPE html> 6<html> 7<head> 8 <meta charset="UTF-8"> 9 <title>Limeplayer登录/注册验证码通知</title> 10 <style> 11 body { 12 font-family: 'Arial', 'Helvetica Neue', sans-serif; 13 margin: 0; 14 padding: 20px; 15 background: linear-gradient(135deg, #fffdbf 0%, #c9f7c9 100%); 16 min-height: 100vh; 17 } 18 .container { 19 max-width: 600px; 20 margin: 0 auto; 21 background: white; 22 padding: 40px; 23 border-radius: 16px; 24 box-shadow: 0 10px 30px rgba(0,0,0,0.1); 25 position: relative; 26 overflow: hidden; 27 } 28 /* 夏日装饰元素 */ 29 .container::before { 30 content: ''; 31 position: absolute; 32 top: 0; 33 right: 0; 34 width: 120px; 35 height: 120px; 36 background: linear-gradient(45deg, #ffeb3b 0%, #ff9800 100%); 37 border-radius: 0 0 0 100px; 38 z-index: 0; 39 } 40 .lemon { 41 position: absolute; 42 width: 40px; 43 height: 40px; 44 background: #fff176; 45 border-radius: 50%; 46 top: 25px; 47 right: 25px; 48 z-index: 1; 49 box-shadow: 0 0 10px rgba(255, 193, 7, 0.5); 50 } 51 .lemon::before { 52 content: ''; 53 position: absolute; 54 width: 5px; 55 height: 15px; 56 background: #388e3c; 57 left: 18px; 58 top: -10px; 59 border-radius: 3px; 60 } 61 .lemon::after { 62 content: ''; 63 position: absolute; 64 width: 30px; 65 height: 30px; 66 border-radius: 50%; 67 border: 2px dashed rgba(255, 255, 255, 0.7); 68 top: 5px; 69 left: 5px; 70 } 71 .content { 72 position: relative; 73 z-index: 2; 74 } 75 h2 { 76 color: #388e3c; 77 margin-top: 0; 78 font-size: 24px; 79 border-left: 4px solid #ff9800; 80 padding-left: 15px; 81 } 82 p { 83 line-height: 1.6; 84 color: #424242; 85 } 86 .code-container { 87 text-align: center; 88 margin: 30px 0; 89 padding: 20px; 90 background: #f9fbe7; 91 border-radius: 10px; 92 border: 2px dashed #ff9800; 93 } 94 .code { 95 font-size: 40px; 96 color: #ff6f00; 97 font-weight: bold; 98 letter-spacing: 8px; 99 margin: 10px 0; 100 text-shadow: 1px 1px 3px rgba(0,0,0,0.1); 101 } 102 .expiry { 103 color: #f44336; 104 font-size: 14px; 105 font-weight: bold; 106 display: flex; 107 align-items: center; 108 justify-content: center; 109 } 110 .expiry::before { 111 content: '⏰'; 112 margin-right: 5px; 113 font-size: 16px; 114 } 115 .footer { 116 margin-top: 40px; 117 padding-top: 20px; 118 border-top: 1px solid #e0e0e0; 119 color: #757575; 120 font-size: 12px; 121 text-align: center; 122 } 123 .lemon-slice { 124 display: inline-block; 125 font-size: 18px; 126 margin: 0 3px; 127 } 128 </style> 129</head> 130<body> 131 <div class="container"> 132 <div class="lemon"></div> 133 <div class="content"> 134 <h2>尊敬的用户{{USER_NAME}},您好!</h2> 135 <p>感谢您使用Limeplayer!您正在进行的操作需要验证身份,请输入以下验证码:</p> 136 137 <div class="code-container"> 138 <div class="code">{{VERIFICATION_CODE}}</div> 139 </div> 140 141 <p class="expiry">此验证码将在 5 分钟内失效,请尽快使用</p> 142 143 <p>如果这不是您本人的操作,请立即忽略此邮件。</p> 144 145 <div class="footer"> 146 <p> 147 <span class="lemon-slice">🍋</span> 148 系统自动发送,请勿回复 149 <span class="lemon-slice">🍋</span> 150 </p> 151 </div> 152 </div> 153 </div> 154</body> 155</html>)"; 156 157 EmailClient::EmailClient(const EmailCodeSettings& settings) 158 :_settings(settings) 159 { 160 //初始化全局配置 161 auto ret = curl_global_init(CURL_GLOBAL_DEFAULT); 162 if (ret != CURLE_OK) { 163 ERR("curl全局初始化失败, 错误信息: {}", curl_easy_strerror(ret)); 164 exit(-1);//直接退出程序 165 } 166 } 167 168 bool EmailClient::sendCode(const std::string& to, const std::string& code) 169 { 170 //构造操作句柄 171 auto curl = curl_easy_init(); 172 if (curl == nullptr) { 173 ERR("获取curl操作句柄失败"); 174 return false; 175 } 176 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); // 连接超时15秒 177 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 传输超时30秒 178 //设置请求参数:url,username,password,body, from, to 179 auto ret = curl_easy_setopt(curl, CURLOPT_URL, _settings.url.c_str()); 180 if(ret != CURLE_OK) 181 { 182 ERR("设置请求url失败, 错误信息: {}", curl_easy_strerror(ret)); 183 return false; 184 } 185 ret = curl_easy_setopt(curl, CURLOPT_USERNAME, _settings.username.c_str()); 186 if(ret != CURLE_OK) 187 { 188 ERR("设置用户名失败, 错误信息: {}", curl_easy_strerror(ret)); 189 return false; 190 } 191 ret = curl_easy_setopt(curl, CURLOPT_PASSWORD, _settings.password.c_str()); 192 if(ret != CURLE_OK) 193 { 194 ERR("设置密码失败, 错误信息: {}", curl_easy_strerror(ret)); 195 return false; 196 } 197 ret = curl_easy_setopt(curl, CURLOPT_MAIL_FROM, _settings.from.c_str()); 198 if(ret != CURLE_OK) 199 { 200 ERR("设置发件人失败, 错误信息: {}", curl_easy_strerror(ret)); 201 return false; 202 } 203 struct curl_slist *cs = nullptr; 204 cs = curl_slist_append(cs, to.c_str()); 205 ret = curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, cs); 206 if(ret != CURLE_OK) 207 { 208 ERR("设置收件人失败, 错误信息: {}", curl_easy_strerror(ret)); 209 return false; 210 } 211 std::stringstream ss = requestBody(to, code); 212 ret = curl_easy_setopt(curl, CURLOPT_READDATA, (void*)&ss); 213 if(ret != CURLE_OK) 214 { 215 ERR("设置请求体失败, 错误信息: {}", curl_easy_strerror(ret)); 216 return false; 217 } 218 //设置回调函数 219 ret = curl_easy_setopt(curl, CURLOPT_READFUNCTION, EmailClient::callback); 220 if(ret != CURLE_OK) 221 { 222 ERR("设置回调函数失败, 错误信息: {}", curl_easy_strerror(ret)); 223 return false; 224 } 225 ret = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); 226 if(ret != CURLE_OK) 227 { 228 ERR("设置上传模式失败, 错误信息: {}", curl_easy_strerror(ret)); 229 return false; 230 } 231 //执行请求 232 ret = curl_easy_perform(curl); 233 if(ret != CURLE_OK) 234 { 235 ERR("执行请求失败, 错误信息: {}", curl_easy_strerror(ret)); 236 return false; 237 } 238 //释放资源 239 curl_slist_free_all(cs); 240 curl_easy_cleanup(curl); 241 DBG("发送验证码成功:{}-{}", to, code); 242 return true; 243 } 244 245 EmailClient::~EmailClient() 246 { 247 //释放全局资源 248 curl_global_cleanup(); 249 } 250 251 std::stringstream EmailClient::requestBody(const std::string& to, const std::string& code) 252 { 253 std::stringstream ss; 254 ss << "From:"<< "LimePlayer" << "\r\n" 255 << "To:" << to << "\r\n" 256 << "Subject:"<< _subject <<"\r\n" 257 << "Content-Type: text/html; charset=utf-8\r\n" 258 << "\r\n"; 259 260 std::string html = htmlTemplate; 261 size_t pos = html.find("{{VERIFICATION_CODE}}"); 262 if (pos != std::string::npos) { 263 html.replace(pos, std::string("{{VERIFICATION_CODE}}").length(), code); 264 } 265 // 用户名替换 266 pos = html.find("{{USER_NAME}}"); 267 if (pos != std::string::npos) { 268 html.replace(pos, std::string("{{USER_NAME}}").length(), to); 269 } 270 ss << html << "\r\n"; 271 return ss; 272 } 273 274 size_t EmailClient::callback(char *buffer, size_t size, size_t nitems, void *userdata) 275 { 276 std::stringstream *ss = (std::stringstream*)userdata; 277 ss->read(buffer, size * nitems); 278 return ss->gcount(); 279 } 280}// namespace limemail 281
使用样例
1#include "../../source/limemail.h" 2#include "../../source/limelog.h" 3 4int main() 5{ 6 limelog::limelog_init(); 7 limemail::EmailCodeSettings settings{ 8 .username = "[email protected]", 9 .password = "111",//输入你开启smtp的授权码 10 .url = "smtps://smtp.163.com:465", 11 .from = "[email protected]" 12 }; 13 auto client = std::make_unique<limemail::EmailClient>(settings); 14 client->sendCode("[email protected]","114514"); 15 return 0; 16} 17
编译运行:
1mail_test:mail_test.cc ../../source/limelog.cc ../../source/limemail.cc 2 g++ $^ -o $@ -std=c++17 -lpthread -lcurl -lssl -lcrypto -lgflags -lfmt -lpthread -lspdlog 3clean: 4 rm -f mail_test 5
运行成功之后你应该会在你的目标邮箱中收到如下一封邮件:

3.bug修复
3.1util⼯具类功能补充
描述:在后面设置缓存过期时间的时候需要⽣成⼀个指定范围的随机数,但是当前没有接⼝提供
解决:在Random类中新增⼀个⽣成随机数的接⼝
1 size_t LimeRandom::number(size_t min, size_t max) { 2 std::random_device rd; 3 std::mt19937 generator(rd()); 4 std::uniform_int_distribution<int> distribution(min, max); 5 return distribution(generator); 6 } 7
3.2RPC封装Bug与补充
功能补充
描述:⽬前封装信道管理,是提前创建好信道使⽤时直接获取,信道的使⽤协议是固定的,但是在有
些情况下固定协议的信道⽆法⽀持更加灵活的操作。
解决:在信道管理的同时也增加节点地址管理,并且提供⼀个获取节点地址的接口
bug修复
描述:当前代码中rpc信道的默认超时时间有些短,稍有处理时间有些⻓的接⼝就会导致请求超时失败。
解决:创建信道时将信道的请求超时时间设置的⻓⼀些
1#pragma once 2#include <brpc/channel.h> 3#include <brpc/server.h> 4#include <mutex> 5#include <unordered_map> 6#include <optional> 7#include "limelog.h" 8 9namespace limerpc 10{ 11 using ChannelPtr = std::shared_ptr<brpc::Channel>; 12 // 服务信道管理类 13 class RpcChannels 14 { 15 public: 16 using ptr = std::shared_ptr<RpcChannels>; 17 RpcChannels(); 18 // 获取服务信道 19 ChannelPtr get_channel(); 20 // 增加服务信道 21 void add_channel(const std::string &addr); 22 // 删除服务信道 23 void remove_channel(const std::string &addr); 24 std::optional<std::string> selectAddr(); 25 private: 26 std::mutex _mtx; // 互斥锁 27 uint32_t _idx; // 服务信道索引-轮询下标 28 std::vector<std::pair<std::string, ChannelPtr>> _channels; // 服务信道列表 29 }; 30 // 服务管理类 31 class SvcRpcChannels 32 { 33 public: 34 using ptr = std::shared_ptr<SvcRpcChannels>; 35 SvcRpcChannels() = default; 36 // 设置服务关心 37 void set_match(const std::string &service_name); 38 // 新增结点 39 void add_node(const std::string &service_name, const std::string &node_addr); 40 // 删除结点 41 void remove_node(const std::string &service_name, const std::string &node_addr); 42 // 获取服务信道 43 ChannelPtr get_channel(const std::string &service_name); 44 45 private: 46 std::mutex _mtx; 47 std::unordered_map<std::string, RpcChannels::ptr> _svc_channels_map; // 服务名称-服务信道管理映射表 48 }; 49 // 异步回调⼯⼚类 50 class ClosureFactory 51 { 52 public: 53 using callback_t = std::function<void()>; 54 static google::protobuf::Closure *create(callback_t &&cb); 55 private: 56 struct Object 57 { 58 using ptr = std::shared_ptr<Object>; 59 callback_t callback; 60 }; 61 static void asyncCallback(const Object::ptr obj); 62 }; 63 // 服务器⼯⼚类 64 class RpcServer{ 65 public: 66 // 默认svc是堆上new出来的对象,将管理权移交给rpc服务器进⾏管理 67 static std::shared_ptr<brpc::Server> create(int port,google::protobuf::Service *svc); 68 }; 69} // namespace limerpc 70
1#include "limerpc.h" 2 3namespace limerpc{ 4 RpcChannels::RpcChannels():_idx(0){ 5 } 6 // 轮询获取服务信道 7 ChannelPtr RpcChannels::get_channel(){ 8 std::unique_lock<std::mutex> lock(_mtx); 9 if(_channels.size() == 0){ 10 return ChannelPtr(); 11 } 12 size_t index = _idx % _channels.size(); 13 _idx++; 14 return _channels[index].second; 15 } 16 // 增加服务信道 17 void RpcChannels::add_channel(const std::string &addr){ 18 std::unique_lock<std::mutex> lock(_mtx); 19 for(const auto &item : _channels){ 20 if(item.first == addr){ 21 //信道已存在,直接返回 22 return; 23 } 24 } 25 //1.定义并设置channel的配置 26 brpc::ChannelOptions options; 27 options.protocol = "baidu_std"; //使用baidu_std协议 28 options.timeout_ms = 30000; //设置超时时间为30秒 29 //2.创建并初始化channel-channel可以理解为客⼾端到服务器的⼀条通信线路 30 ChannelPtr channel = std::make_shared<brpc::Channel>(); 31 channel->Init(addr.c_str(), &options); 32 //3.将channel添加到_channels中 33 _channels.push_back(std::make_pair(addr, channel)); 34 } 35 36 // 删除服务信道 37 void RpcChannels::remove_channel(const std::string &addr){ 38 std::unique_lock<std::mutex> lock(_mtx); 39 //找到后从_channels中删除 40 for(auto it = _channels.begin(); it != _channels.end(); it++){ 41 if(it->first == addr){ 42 _channels.erase(it); 43 return; 44 } 45 } 46 } 47 48 std::optional<std::string> RpcChannels::selectAddr(){ 49 std::unique_lock<std::mutex> lock(_mtx); 50 if(_channels.size() == 0){ 51 return std::nullopt; 52 } 53 size_t index = _idx % _channels.size(); 54 _idx++; 55 return _channels[index].first; 56 } 57 58 //设置服务关心 59 void SvcRpcChannels::set_match(const std::string &service_name){ 60 std::unique_lock<std::mutex> lock(_mtx); 61 _svc_channels_map[service_name] = std::make_shared<RpcChannels>(); 62 } 63 //新增结点 64 void SvcRpcChannels::add_node(const std::string &service_name, const std::string &node_addr){ 65 //判断是否为关心的服务 66 auto it = _svc_channels_map.find(service_name); 67 if(it == _svc_channels_map.end()){ 68 DBG("服务:{} 未设置关注,忽略添加", service_name); 69 return; 70 } 71 //增加服务信道 72 it->second->add_channel(node_addr); 73 } 74 //删除结点 75 void SvcRpcChannels::remove_node(const std::string &service_name, const std::string &node_addr){ 76 //判断是否为关心的服务 77 auto it = _svc_channels_map.find(service_name); 78 if(it == _svc_channels_map.end()){ 79 DBG("服务:{} 未设置关注,忽略删除", service_name); 80 return; 81 } 82 //删除服务信道 83 it->second->remove_channel(node_addr); 84 } 85 //获取服务信道 86 ChannelPtr SvcRpcChannels::get_channel(const std::string &service_name){ 87 //判断是否为关心的服务 88 auto it = _svc_channels_map.find(service_name); 89 if(it == _svc_channels_map.end()){ 90 DBG("服务:{} 未设置关注,无法获取信道", service_name); 91 return ChannelPtr(); 92 } 93 //获取服务信道 94 return it->second->get_channel(); 95 } 96 97 //因为google::protobuf::Closure的回调函数不允许传入多个参数,所以需要定义一个Object类来包装回调函数和参数 98 google::protobuf::Closure* ClosureFactory::create(callback_t &&cb){ 99 ClosureFactory::Object::ptr obj = std::make_shared<ClosureFactory::Object>(); 100 obj->callback = std::move(cb); 101 return google::protobuf::NewCallback(ClosureFactory::asyncCallback, obj); 102 } 103 //异步回调函数执行 104 void ClosureFactory::asyncCallback(const ClosureFactory::Object::ptr obj){ 105 obj->callback(); 106 } 107 108 //服务端创建 109 std::shared_ptr<brpc::Server> RpcServer::create(int port,google::protobuf::Service *svc){ 110 //1.定义服务器配置对象ServerOptions 111 brpc::ServerOptions options; 112 options.idle_timeout_sec = -1;//设置超时时间为-1,表示不超时 113 //2.创建服务器对象 114 std::shared_ptr<brpc::Server> server = std::make_shared<brpc::Server>(); 115 //3.注册服务 116 if (server->AddService(svc, brpc::SERVER_OWNS_SERVICE) != 0) { 117 ERR("服务注册失败"); 118 exit(-1);//直接退出 119 } 120 //4.启动服务器 121 if (server->Start(port, &options) != 0) { 122 ERR("服务启动失败"); 123 exit(-1);//直接退出 124 } 125 return server; 126 } 127} 128
3.3MQ封装Bug
描述:条件控制问题,⽐如在交换机队列还没有创建绑定完毕,就开始订阅队列消息
解决后修正的代码如下:
1#pragma once 2#include <amqpcpp.h> 3#include <ev.h> 4#include <amqpcpp/libev.h> 5#include <iostream> 6#include <thread> 7#include <mutex> 8#include <condition_variable> 9#include <functional> 10 11namespace limemq { 12 const std::string DIRECT = "direct"; 13 const std::string FANOUT = "fanout"; 14 const std::string TOPIC = "topic"; 15 const std::string HEADERS = "headers"; 16 const std::string DELAYED = "delayed"; 17 const std::string DLX_PREFIX = "dlx-"; 18 const std::string DEAD_LETTER_EXCHANGE = "x-dead-letter-exchange"; 19 const std::string DEAD_LETTER_BINDING_KEY = "x-dead-letter-routing-key"; 20 const std::string MESSAGE_TTL = "x-message-ttl"; 21 struct declare_settings { 22 std::string exchange; 23 std::string exchange_type;// 交换机类型: direct、fanout、topic、headers、delayed 24 std::string queue; 25 std::string binding_key; 26 size_t delayed_ttl = 0; 27 28 std::string dlx_exchange() const; 29 std::string dlx_queue() const; 30 std::string dlx_binding_key() const; 31 }; 32 extern AMQP::ExchangeType exchange_type(const std::string &type); 33 using MessageCallback = std::function<void(const char*, size_t)>; 34 class MQClient { 35 public: 36 using ptr = std::shared_ptr<MQClient>; 37 MQClient(const std::string &url); // 构造成员,启动事件循环 38 ~MQClient(); // 发送异步请求,结束事件循环,等待异步线程结束 39 //声明交换机,声明队列,并绑定交换机和队列,如果是延时队列,则还要创建配套的死信交换机和队列 40 //声明交换机和队列以及绑定成功后,需要等待,等待实际交换机和队列声明成功,再返回 41 void declare(const declare_settings &settings); 42 bool publish(const std::string &exchange, const std::string &routing_key, const std::string &body); 43 void consume(const std::string &queue, const MessageCallback &callback); 44 void wait(); 45 private: 46 static void callback(struct ev_loop *loop, ev_async *watcher, int32_t revents); 47 // 声明常规交换机和队列,并进行绑定 48 void _declared(const declare_settings &settings, AMQP::Table &args, bool is_dlx = false); 49 private: 50 std::mutex _declared_mtx; 51 std::mutex _mtx; 52 std::condition_variable _cv; 53 struct ev_loop *_ev_loop; 54 struct ev_async _ev_async; 55 AMQP::LibEvHandler _handler; 56 AMQP::TcpConnection _connection; 57 AMQP::TcpChannel _channel; 58 std::thread _async_thread; 59 }; 60 61 class Publisher { 62 public: 63 using ptr = std::shared_ptr<Publisher>; 64 //对成员进行初始化,并声明套件内的交换机和队列 65 Publisher(const MQClient::ptr &mq_client, const declare_settings &settings); 66 bool publish(const std::string &body); 67 private: 68 MQClient::ptr _mq_client; 69 declare_settings _settings; 70 }; 71 72 class Subscriber { 73 public: 74 using ptr = std::shared_ptr<Subscriber>; 75 //对成员进行初始化,并声明套件内的交换机和队列 76 Subscriber(const MQClient::ptr &mq_client, const declare_settings &settings); 77 //如果是延时队列,则实际订阅的是配套的死信队列 78 void consume(MessageCallback &&callback); 79 private: 80 MQClient::ptr _mq_client; 81 declare_settings _settings; 82 MessageCallback _callback; 83 }; 84} 85
1#include "limemq.h" 2#include "limelog.h" 3 4namespace limemq { 5 std::string declare_settings::dlx_exchange() const { 6 return DLX_PREFIX + exchange; 7 } 8 9 std::string declare_settings::dlx_queue() const { 10 return DLX_PREFIX + queue; 11 } 12 13 std::string declare_settings::dlx_binding_key() const { 14 return DLX_PREFIX + binding_key; 15 } 16 17 AMQP::ExchangeType exchange_type(const std::string &type) { 18 if(type == DIRECT) 19 { 20 //直接匹配 21 return AMQP::direct; 22 } 23 else if(type == FANOUT) 24 { 25 //广播类型 26 return AMQP::fanout; 27 } 28 else if(type == TOPIC) 29 { 30 //主题类型 31 return AMQP::topic; 32 } 33 else if(type == HEADERS) 34 { 35 //头部匹配 36 return AMQP::headers; 37 } 38 else if(type == DELAYED) 39 { 40 return AMQP::direct; 41 } 42 else 43 { 44 WRN("未知交换机类型:{}, 使用默认类型direct", type); 45 return AMQP::direct; 46 } 47 } 48 49 MQClient::MQClient(const std::string &url) 50 :_ev_loop(EV_DEFAULT), 51 _handler(_ev_loop), 52 _connection(&_handler, AMQP::Address(url)), 53 _channel(&_connection), 54 _async_thread(std::thread([this](){ ev_run(_ev_loop); })) 55 {} 56 57 void MQClient::declare(const declare_settings &settings) 58 { 59 AMQP::Table args; 60 if(settings.exchange_type == DELAYED) 61 { 62 //声明死信交换机和死信队列 63 _declared(settings, args, true); 64 args["x-dead-letter-exchange"] = settings.dlx_exchange(); 65 args["x-dead-letter-routing-key"] = settings.dlx_binding_key(); 66 args["x-message-ttl"] = settings.delayed_ttl; 67 } 68 //声明延时队列与交换机 69 _declared(settings, args, false); 70 } 71 72 void MQClient::_declared(const declare_settings &settings, AMQP::Table &args, bool is_dlx) 73 { 74 //用于互斥 75 std::unique_lock<std::mutex> declared_lock(_declared_mtx); 76 //用于条件控制 77 std::unique_lock<std::mutex> lock(_mtx); 78 //定义交换机名,队列名,绑定键 79 std::string exchange = is_dlx ? settings.dlx_exchange() : settings.exchange; 80 std::string queue = is_dlx ? settings.dlx_queue() : settings.queue; 81 std::string binding_key = is_dlx ? settings.dlx_binding_key() : settings.binding_key; 82 //5.声明exchange(直接交换)和queue 83 _channel.declareExchange(exchange,exchange_type(settings.exchange_type)) 84 .onSuccess([=](){ 85 //交换机声明成功,声明队列 86 _channel.declareQueue(queue,args) 87 .onSuccess([=](const std::string &name,uint32_t messagecount,uint32_t consumercount){ 88 std::cout<<"队列声明成功: "<< name<<", 消息数: "<< messagecount<<", 消费者数: "<< consumercount<<std::endl; 89 //队列声明成功,绑定交换机和队列 90 _channel.bindQueue(exchange,queue,binding_key) 91 .onSuccess([=](){ 92 //成功绑定后通知等待线程 93 _cv.notify_all(); 94 }) 95 .onError([=](const char* message){ 96 std::cerr<<"队列绑定失败: "<< message <<std::endl; 97 exit(-1);//绑定失败直接退出 98 }); 99 }) 100 .onError([=](const char* message){ 101 std::cerr<<"队列声明失败: "<< message <<std::endl; 102 exit(-1);//声明失败直接退出 103 }); 104 }) 105 .onError([=](const char* message){ 106 std::cerr<<"交换机声明失败: "<< message <<std::endl; 107 exit(-1);//声明失败直接退出 108 }); 109 _cv.wait(lock); 110 } 111 112 bool MQClient::publish(const std::string &exchange, const std::string &routing_key, const std::string &body) 113 { 114 return _channel.publish(exchange, routing_key, body); 115 } 116 117 void MQClient::consume(const std::string &queue, const MessageCallback &callback) 118 { 119 std::unique_lock<std::mutex> declared_lock(_declared_mtx); 120 std::unique_lock<std::mutex> lock(_mtx); 121 _channel.consume(queue) 122 .onReceived([=](const AMQP::Message &message,uint64_t deliveryTag,bool redelivered){ 123 callback(message.body(), message.bodySize()); 124 //确认消息 125 _channel.ack(deliveryTag); 126 }) 127 .onError([=](const char* message){ 128 std::cerr<<"订阅消息失败: "<< message <<std::endl; 129 exit(-1);//订阅失败直接退出 130 }) 131 .onSuccess([=](){ 132 std::cout<<"订阅成功"<<std::endl; 133 _cv.notify_all(); 134 }); 135 _cv.wait(lock); 136 } 137 138 void MQClient::callback(struct ev_loop *loop, ev_async *watcher, int32_t revents) 139 { 140 //此接口官方规定不能跨线程调用 141 ev_break(loop, EVBREAK_ALL); 142 } 143 144 void MQClient::wait() 145 { 146 //等待异步线程结束,主线程无事可干时等待异步线程结束 147 _async_thread.join(); 148 } 149 150 MQClient::~MQClient(){ 151 //当mqclient对象被销毁时,关闭异步线程 152 ev_async_init(&_ev_async, MQClient::callback); 153 ev_async_start(_ev_loop, &_ev_async); 154 ev_async_send(_ev_loop, &_ev_async); 155 _async_thread.join();// 等待异步线程结束 156 } 157 158 Publisher::Publisher(const MQClient::ptr &mq_client, const declare_settings &settings) 159 :_mq_client(mq_client), 160 _settings(settings) 161 { 162 _mq_client->declare(_settings); 163 } 164 165 bool Publisher::publish(const std::string &body) 166 { 167 return _mq_client->publish(_settings.exchange, _settings.binding_key, body); 168 } 169 170 Subscriber::Subscriber(const MQClient::ptr &mq_client, const declare_settings &settings) 171 :_mq_client(mq_client), 172 _settings(settings) 173 { 174 _mq_client->declare(_settings); 175 } 176 177 void Subscriber::consume(MessageCallback &&callback) 178 { 179 _callback = callback; 180 //如果是延时队列,顶订阅的是死信队列消息,否则订阅的是常规队列消息 181 if (_settings.exchange_type == DELAYED) { 182 _mq_client->consume(_settings.dlx_queue(), _callback); 183 }else { 184 _mq_client->consume(_settings.queue, _callback); 185 } 186 } 187} // namespace limemq 188
4.打包脚手架项目
因为我们并没有深入了解学习过cmake,所以我们这里直接按照下面的步骤对我们的项目仅打包:
4.1在项目根目录创建如下CMakeLists.txt文件
1# 1. 设置cmake所需版本 2cmake_minimum_required(VERSION 3.1.3) 3# 2. 设置工程项目名称(内部会生成一系列的内置变量) 4project(lime_scaffold VERSION 1.0) 5# 3. 添加生成库目标(说明通过哪些文件生成哪个库) 6file(GLOB_RECURSE BASE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cc) # CMAKE_CURRENT_SOURCE_DIR cmakelists.txt文件在哪里,就指向哪个路径 7add_library(${PROJECT_NAME} STATIC ${BASE_FILES}) 8# 4. 设置编译特性 9target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) 10# 5. 设置生成库属性:默认生成位置,版本信息 11set_target_properties(${PROJECT_NAME} PROPERTIES 12 ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) # CMAKE_CURRENT_BINARY_DIR 在哪里执行cmake,就指向哪个路径 13set_target_properties(${PROJECT_NAME} PROPERTIES 14 VERSION ${PROJECT_VERSION} # 完整版本号(1.0) 15 SOVERSION ${PROJECT_VERSION_MAJOR} # 主版本号(1) 16) 17# 6. 依赖检查 18list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 声明依赖检测子模块的所在路径 19include(Finddepends) 20# 1. 编写依赖检查子模块(查找系统中的指定库,并将库添加到链接选项中) 21find_my_depends() 22# 7. 设置安装头文件到指定位置 23install( 24 DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/source/ 25 DESTINATION include/${PROJECT_NAME} 26 FILES_MATCHING PATTERN "*.h" 27) 28# 8. 设置安装库文件到指定位置 29install( 30 TARGETS ${PROJECT_NAME} 31 EXPORT ${PROJECT_NAME}Targets # 声明目标名称 32 ARCHIVE DESTINATION lib #静态库安装路径 33 INCLUDES DESTINATION include/${PROJECT_NAME} #头文件安装路径 34) 35# 9. 设置安装子模块到指定位置 36install( 37 DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ 38 DESTINATION lib/cmake/${PROJECT_NAME}/modules 39 FILES_MATCHING PATTERN "Find*.cmake" 40) 41# 10. 设置导出目标信息 42install( 43 EXPORT ${PROJECT_NAME}Targets 44 FILE ${PROJECT_NAME}Targets.cmake 45 DESTINATION lib/cmake/${PROJECT_NAME} 46 NAMESPACE ${PROJECT_NAME}:: 47) 48# 11. 处理 Config.cmake.in 模板 49# 1. 需要提前编写好模板内容 50include(CMakePackageConfigHelpers) 51# 处理 Config.cmake.in 模板,生成最终的配置文件,解析 @PACKAGE_INIT@ 变量 52configure_package_config_file( 53 ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in 54 ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 55 INSTALL_DESTINATION lib/cmake/${PROJECT_NAME} 56) 57# 12. 设置版本配置文件 58write_basic_package_version_file( 59 ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 60 VERSION ${PROJECT_VERSION} 61 COMPATIBILITY SameMajorVersion 62) 63# 13. 安装配置文件 64install( 65 FILES 66 ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 67 ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 68 DESTINATION lib/cmake/${PROJECT_NAME} 69) 70
4.2在根目录下创建名为cmake的文件夹,并添加两个新文件
1///home/dev/workspace/cpp-microservice-scaffold/cmake/Finddepends.cmake 2# 定义变量,保存所有需要查找的库的名称 3set(libs 4 amqpcpp 5 ev 6 brpc 7 leveldb 8 protobuf 9 dl 10 ssl 11 crypto 12 gflags 13 pthread 14 cpr 15 jsoncpp 16 elasticlient 17 cpprest 18 etcd-cpp-api 19 fdfsclient 20 fastcommon 21 avcodec 22 avformat 23 avutil 24 gtest 25 curl 26 odb-mysql 27 odb 28 odb-boost 29 hiredis 30 redis++ 31 spdlog 32 fmt 33 boost_system 34) 35# 编写辅助宏:查找库 36macro(find_libraries lib) 37 find_library(${lib}_LIBRARY ${lib} PATHS /usr/lib /usr/lib/x86_64-linux-gnu /usr/local/lib) 38 if(${lib}_LIBRARY) 39 set(${lib}_FOUND TRUE) 40 if(NOT TARGET lime_scaffold::${lib}) 41 add_library(lime_scaffold::${lib} INTERFACE IMPORTED) 42 set_target_properties(lime_scaffold::${lib} PROPERTIES 43 INTERFACE_LINK_LIBRARIES "${${lib}_LIBRARY}" 44 ) 45 message(STATUS "找到依赖库: " ${lib} " - " ${${lib}_LIBRARY}) 46 endif() 47 endif() 48endmacro() 49 50function(find_my_depends) 51 foreach(lib ${libs}) 52 find_libraries(${lib}) 53 if(NOT ${lib}_FOUND) 54 message(FATAL_ERROR "丢失关键依赖库: ${lib}") 55 endif() 56 endforeach() 57 target_link_libraries(${PROJECT_NAME} PUBLIC ${libs}) 58endfunction() 59
1///home/dev/workspace/cpp-microservice-scaffold/cmake/lime_scaffoldConfig.cmake.in 2@PACKAGE_INIT@ # CMake 提供的初始化宏 3 4# 添加自定义Find模块路径 5list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/modules) 6 7# 声明所有传递依赖(自动触发find_package) 8include("${CMAKE_CURRENT_LIST_DIR}/modules/Finddepends.cmake") 9find_my_depends() 10 11# 包含目标导出文件 12include(${CMAKE_CURRENT_LIST_DIR}/lime_scaffoldTargets.cmake) 13# 可选:验证组件或其他配置 14check_required_components(lime_scaffold) 15
4.3在根目下创建一个名为build的文件夹,进入并依次执行如下bash命令
1cmake .. -DCMAKE_INSTALL_PREFIX=/usr 2make && sudo make install 3
执行完毕后,查看对应文件与库是否安装到对应位置:
1dev@ca01086a6c5d:~/workspace/cpp-videoplayer-server$ ls /usr/include/lime_scaffold/ 2limeelastic.h limeetcd.h limefds.h limeffmpeg.h limelog.h limemail.h limemq.h limeodb.h limeredis.h limerpc.h limeutil.h 3dev@ca01086a6c5d:~/workspace/cpp-videoplayer-server$ ls /usr/lib/liblime_scaffold.a 4/usr/lib/liblime_scaffold.a 5
出现如上结果则说明我们项目已经打包完毕。至此我们脚手架的编写到此结束。开始最后一部分服务端的编写。
《基于脚手架微服务的视频点播系统-脚手架开发部分(完结)elasticsearch与libcurl的简单使用与二次封装及bug修复》 是转载文章,点击查看原文。