基于脚手架微服务的视频点播系统-脚手架开发部分(完结)elasticsearch与libcurl的简单使用与二次封装及bug修复

作者:加班敲代码的Plana日期:2025/11/15

基于脚手架微服务的视频点播系统-脚手架开发部分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.3 Kibana访问es进行测试
      • 1.3.1创建索引
        • 1.3.2新增数据
        • 1.3.3查看并搜索数据
        • 1.3.4删除索引
    • 1.4典型操作
      • 1.4.1索引
        • 1.4.2新增数据
          • 1.4.2.1单数据新增
            * 1.4.2.2批量数据新增
            * 1.4.2.3查询所有数据
        • 1.4.3删除
          • 1.4.3.1删除指定id的数据
            * 1.4.3.2批量删除指定id的数据
            * 1.4.3.3根据条件进行数据的删除
        • 1.4.4更新
          • 1.4.4.1单数据更新
            * 1.4.4.2批量数据更新
        • 1.4.5查询
          • 单条件查询
            * 多条件查询
    • 1.5ES客户端SDK
      • 1.5.1接口介绍
          • 1.响应结构
            * 2.典型接口
        • 1.5.2使用样例
    • 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.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, keywordtext会被分词生成索引;keyword不会被分词生成索引,只能精确值搜索
整形integer, long, short, byte
浮点double, float
逻辑booleantrue或false
日期date, date_nanos“2018-01-13” 或 “2018-01-13 12:10:30” 或者时间戳,即1970到现在的秒数/毫秒数
二进制binary二进制通常只存储,不索引
范围range

1.2.4映射(mapping)

映射是在处理数据的⽅式和规则⽅⾯做⼀些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射⾥⾯可以设置的,其它就是处理es⾥⾯数据的⼀些使⽤规则设置也叫做映射,按着最优规则处理数据对性能提⾼很⼤,因此才需要建⽴映射,并且需要思考如何建⽴映射才能对性能更好。

名称数值备注
enabledtrue(默认) / false是否仅作存储,不做搜索和分析
indextrue(默认) / false是否构建倒排索引(决定了是否分词,是否被索引)
index_option
dynamictrue(缺省) / false控制mapping的自动更新
doc_valuetrue(默认) / false是否开启doc_value,用于聚合和排序分析,分词字段不能使用
fielddata“fielddata”: {“format”: “disabled”}是否为text类型启动fielddata,实现排序和聚合分析
storetrue / false(默认)针对分词字段,参与排序或聚合时能提高性能,不分词字段统一建议使用doc_value
coercetrue(默认) / false是否开启自动数据类型转换功能,比如:字符串转数字,浮点转整型
analyzer“analyzer”: “ik”指定分词器,默认分词器为standard analyzer
boost“boost”: 1.23字段级别的分数加权,默认值是1.0
fields“fields”: { “name”: { “type”: “text”, “index”: true }, … }对一个字段提供多种索引模式,同一个字段的值,一个分词,一个不分词
data_detectiontrue(默认) / 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_URLSMTP服务器地址“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_USERNAMESMTP登录用户名[email protected]
CURLOPT_PASSWORDSMTP登录密码
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修复》 是转载文章,点击查看原文


相关推荐


qinkun的缓存机制也有弊端,建议官方个参数控制
石小石Orz2025/11/14

公司前端基于qiankun架构,主应用通过qiankun加载子应用,子应用也可能通过qiankun继续加载子应用,反复套娃。经过测试,不断打开子应用后,会导致内存不断上上。通过快照分析,发现内存升高的元凶是qiankun内置的# import-html-entry。 import-html-entry 的作用是什么 import-html-entry 是 qiankun / single-spa 微前端生态的核心模块之一,用来: 加载远程 HTML 入口文件(entry HTML),并提取出其中


Python 的内置函数 int
IMPYLH2025/11/13

Python 内建函数列表 > Python 的内置函数 int Python 的内置函数 int() 是一个用于将其他数据类型转换为整数类型的重要函数。它具有以下详细特性: 基本功能: 将数字或字符串转换为整数语法:int(x, base=10)示例:int('123') # 返回 123 int(12.34) # 返回 12 参数说明: 第一个参数可以是: 数字(整数或浮点数)字符串(仅包含数字字符)布尔值(True 转为 1,False 转为 0) 可


✍️记录自己的git分支管理实践
你的人类朋友2025/11/11

前言 👋 你好啊,我是你的人类朋友! 因为本人的开发经常涉及各个分支间的同步,这一套同步的流程从刚开始的小心翼翼,到现在相对熟悉了 所以我想记录下自己工作中常用的分支同步的步骤 😆 顺便研究康康有没有可以优化的地方 🍃 正文 先介绍下背景情况吧 首先主分支为 master 其次,因为开发分为多个阶段,比如 phase_1、phase_2、phase_3 等 那就在 master 之后再创建 feature/phase_1、feature/phase_2 这样的分支,作为每一个 phase


草梅 Auth 1.11.0 发布与 GitHub 依赖安全更新 | 2025 年第 45 周草梅周报
草梅友仁2025/11/9

本文在 草梅友仁的博客 发布和更新,并在多个平台同步发布。如有更新,以博客上的版本为准。您也可以通过文末的 原文链接 查看最新版本。 前言 欢迎来到草梅周报!这是一个由草梅友仁基于 AI 整理的周报,旨在为您提供最新的博客更新、GitHub 动态、个人动态和其他周刊文章推荐等内容。 本周依旧在开发 草梅 Auth 中。 你也可以直接访问官网地址:auth.cmyr.dev/ Demo 站:auth-demo.cmyr.dev/ 文档地址:auth-docs.cmyr.dev/ 本周 草梅


理解 LangChain 智能体:create_react_agent 与 create_tool_calling_agent
奇舞精选2025/11/7

本文译者为 360 奇舞团前端开发工程师 原文标题:理解 LangChain 智能体:create_react_agent 与 create_tool_calling_agent 原文作者:Anil Goyal 原文地址:medium.com/@anil.goyal… 当我们使用 LangChain 构建 AI 智能体时,首先要做的是选择正确的智能体架构。 目前常用的2种架构是create_react_agent和create_tool_calling_agent。两者都可以让AI使用外部工具


windows npm打包无问题,但linux npm打包后部分样式缺失
悢七2025/11/4

原因 前端package.json中指定的是依赖版本范围,而linux中使用npm install安装的版本与windows不同。 例如"@ant-design/icons": “^4.0.0” 插入符号^意味着它可以安装最新的兼容版本。如果希望它安装特定版本,可以在版本前面删除^。 详见package.json文档和符号学 插入符号将让它安装一个不改变第一个数字的更高版本。例如,你的package.json为@ant-design/icons指定了^4.0.0,但它安装了4.6.2。由


【SCI二区IEEE复现】基于混合有限集模型预测控制(FCS-MPC)的模块化多电平换流器(MMC)整流电路仿真模型(Simulink仿真实现)
荔枝科研社2025/10/31

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭:行百里者,半于九十。 📋📋📋本文内容如下:🎁🎁🎁  ⛳️赠与读者 👨‍💻做科研,涉及到一个深在的思想系统,需要科研者逻辑缜密,踏实认真,但是不能只是努力,很多时候借力比努力更重要,然后还要有仰望星空的创新点和启发点。建议读者按目录次序逐一浏览,免得骤然跌入幽暗的迷宫找不到来时的路,它


2025年前端菜鸟自救指南:从零搭建专业开发环境
良山有风来2025/10/29

你是不是经常遇到这种情况? 新入职一家公司,面对一堆看不懂的配置文件一脸懵逼。同事说“先npm install一下”,你却在终端里卡了半天。好不容易跑起来项目,想改个代码又不知道从哪下手。 别担心,这几乎是每个前端开发者都会经历的阶段。还记得我刚入行时,光是配置个Webpack就折腾了一整天,最后还是在同事帮助下才搞定。 但现在不一样了。经过这几年的实战积累,我总结出了一套超级简单的前端环境搭建方法。今天就把这套方法论完整分享给你,让你在2025年能够快速上手任何前端项目。 为什么你需要专业的开


两张大图一次性讲清楚k8s调度器工作原理
有态度的下等马2025/10/26

面试常被追问k8s调度器工作原理,一图胜10086句言,收藏 == 学废。😣😣 kube-scheduler负责将k8s pod调度到worker节点上。 当你部署pod时,在manifest文件pod规格上会指定cpu、memory、亲和性affinity、污点taints、优先级、持久盘等。 调度器的主要工作是识别create request然后选择满足要求的最佳节点。 分步解释: Pod Create Request: 外部系统(kubectl、cicd)发出了创建一个新pod的


LeetCode第1710题 - 卡车上的最大单元数
小南家的青蛙2025/10/23

题目 解答 class Solution { public int maximumUnits(int[][] boxTypes, int truckSize) { Arrays.sort(boxTypes, new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { return o2[1] - o1[1]; } });

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0