1.Elasticsearch 简介

Elaticsearch,简称为ES,ES是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别(大数据时代)的数据。ES由 Java 语言开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTFULL API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。Logstash 负责数据的采集,处理(丰富数据,数据转型等),Kibana 负责数据展,分析及管理。Elasticsearch 处于最核心的位置,它可以帮我们对数据进行快速地搜索及分析。

2. ES 的下载安装(Linux安装)

2.1 ES 要求:JDK1.8以上!

1.查询要安装jdk的版本:

命令: yum -y list java*

spring连接es集群_spring连接es集群

2.安装jdk1.8

命令:yum install -y java-1.8.0-openjdk.x86_64

3.查询jdk版本

命令: java -version

spring连接es集群_数据_02

这样就安装成功了。默认给安装到usr/lib/jvm/ 还有一种手动的

2.2下载 ES

Linux版本

下载地址: [https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-6-1]: https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-6-1

2.3 安装ES

压缩包上传

Xftp Xshell 个人免费申请

https://www.netsarang.com/zh/free-for-home-school/

通过 XFTP 这个上传到服务器或者虚拟机指定文件夹位置:

spring连接es集群_elasticsearch_03

位置:/usr/local/src/software

XShell 工具连接到服务器或者虚拟机

ES解压

tar -zxvf elasticsearch-7.6.1-linux-x86_64.tar.gz

改文件夹名称,方便:mv elasticsearch-7.6.1 elaticSeach7

spring连接es集群_数据_04

ES相关配置
基础配置

ES 本身相当一个数据库

elasticsearch7 文件夹创建 data 文件夹,用于存放数据:mdkir data

spring连接es集群_IP_05

进入 data文件夹给于文件夹权限,执行 pwd

data /usr/local/src/software/elaticseach7/data

ES目录介绍

bin: ES启动文件 elasticsearch.bat / elasticsearch

config:配置目录

data:数据目录

jdk,lib:java运行环境以及依懒包

logs:日志目录

modules,plugins:模块及插件目录,head插件可以存放在plugins目录下

修改配置文件elasticseach.yml,进入 elasticseach7confing 文件夹,编辑 elasticseach.yml 文件夹: vim elasticseach.yml

spring连接es集群_elasticsearch_06

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2k8mxaTy-1615343315058)(C:\Users\heng\AppData\Roaming\Typora\typora-user-images\image-20210308110744983.png)]

修改完成 按 :wq 保存退出,回到elasticsearch7 文件夹, 进入 logs 文件夹, pwd 查看文件夹路径,复制

重新回到 config 目录编辑 elasticsearch.yml 文件:

  • cd /usr/local/src/software/elasticsearch7/config
  • vim elasticsearch.yml

spring连接es集群_elasticsearch_07

跨域配置

在config目录下的elasticsearch.yml文件末尾添加跨域允许

# 跨域问题
http.cors.enabled: true
http.cors.allow-origin: "*"
http.cors.allow-headers: Authorization,X-Requested-with,Content-Length,Content-Type

配置流程参考:

3. ES 的分词器插件

ES 默认自带有分词器,只支持英文分词,需要安装中文分词插件

3.1 IK 分词器下载

IK 分词器 Github 地址:https://github.com/medcl/elasticsearch-analysis-ik

下载 等应版本:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.6.1

使用 Xshell 上传 原来的位置

3.2 分词器解压

  • 进入 plugins 文件夹, 新建一个 ik 文件夹,用于存放 IK 分词器插件:

spring连接es集群_数据_08

  • 回到 software 文件夹, 将 IK 分词器解压到 ik 文件夹:unzip elasticsearch-analysis-ik-7.6.1.zip -d /usr/local/src/software/elasticsearch7/plugins/ik

spring连接es集群_elasticsearch_09

没有这个指令 先下载 unzip 命令 :yum install unzip zip

重新执行 :unzip elasticsearch-analysis-ik-7.6.1.zip -d /usr/local/src/software/elasticsearch7/plugins/ik

spring连接es集群_elasticsearch_10

3.4 IK 分词器的配置文件

  • 进入 ik 目录下的 config 目录:
  • cd /usr/local/src/software/elasticsearch7/plugins/ik/config
  • 查看:ll
扩展分词配置

如果出现一些网络热词,我们想对其继续分词配置,我们需要到 ik 文件夹中 config 文件夹中的IKAnalyzer.cfg.xml

去配置: vim IKAnalyzer.cfg.xml

spring连接es集群_spring连接es集群_11

如果需要加一些额外的网络新词,可以把这些词放到 自己新建的 ext_dict 文件中,每个词直接都要换行,就像extra_main.doc文件的格式一样就行

4.启动与关闭ES

4.1 ES 服务启动和关闭

  • 进入 bin 目录: cd /usr/local/src/software/elasticsearch7/bin 再执行命令:./elasticsearch
  • 运行ES服务! 后台运行命令:./elasticsearch -d
  • 关闭ES 服务: ps -ef|grep elastic
  • 查看进程,
  • ps -elf
  • 并使用 kill -9 进程id 来结束进程

入坑一:

spring连接es集群_数据_12

不能以 root 用户去启动 ES 服务

解决方案

  • 新建一个用户elasticsearch,命令: adduser elasticsearch
  • 在software 目录下 赋予 elasticsearch7 这个文件夹权限给 elasticsearch 用户,命令: chown -R elasticsearch elasticsearch7
  • 切换 elasticsearch 用户,使用 su elasticsearch 命令,重新回到 bin 目录执行 ES 服务‘

入坑二:

spring连接es集群_数据_13

解决方案

在 elasticsearch 的 config 目录下,修改 elasticsearch.yml 配置文件

spring连接es集群_elasticsearch_14

spring连接es集群_elasticsearch_15

入坑三:

ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

解决方案

  • 修改配置sysctl.conf
  • vim /etc/sysctl.conf
  • 在最后一行添加下面配置:
  • vm.max_map_count=655360
  • 保存退出并执行命令:
  • sysctl -p

重新启动 elasticsearch 服务

访问成功:

spring连接es集群_spring连接es集群_16

还有个 坑 服务器1核2G的 内存小 不能运行 需要修改

看 海贼王的 原章修改 :Linux环境下ElasticSearch的安装与使用

5.ES中基本概念

5.1关系型数据库和ES对比

Relational DB

Elasticsearch

数据库(database)

索引(indices)

表(tables)

类型(types)

行(一条记录)(rows)

文档(documents)

字段(columns)

属性(fields)

5.2 接近实时(NRT Near Real Time)

Elasticsearch 是一个接近实时的搜索平台,从索引一个文档直到这个文档被搜索到有一个轻微的延迟(通知1秒内)

5.3 索引(index)

一个索引就是一个拥有几分相类似特征的文档的集合。比如说,你可以有一个客户数据索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字标识(必须全部是小写字母的),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。索引类似关系型数据库Database的概念。在一个集群中,如果你想,可以定义任意多的索引。

5.4 类型(type)

在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型还有评论数据也可以定义另一个类型。类型类似于关系型数据库中Table的概念

NOTE:在5.X版本以前可以在一个索引中定义多个类型,6.X之后版本也可以使用,不推荐,在7~8.X版本中彻底移除一个索引中创建多个类型

5.5 映射(Mapping)

Mapping是ES中的一个很重要的内容,它类似于传统关系型数据中table的schema,用于定义一个索引(index)中的类型(type)的数据的结构

在ES中,我们可以手动创建type(相当于table)和mapping(相关与schema),也可以采用默认创建方式。在默认配置下,ES可以根据插入的数据自动创建type及其mapping. mapping中主要包括字段名、字段数据类型和字段索引类型

5.6 文档(document)

一个文档是一个可被索引的基础信息单元,类似于表中的一条记录。比如,你可以拥有某一个员工的文档,也可以拥有某个商品的一个文档

文档以采用了轻量级的数据交换格式JSON来表示

6. ES 常用命令

6.1索引操作

使用命令行的方式 curl 发送请求:

查看ES服务健康状态:

curl -X GET "ip地址:9200/_cat/health?v"

spring连接es集群_数据_17

查看ES服务中的节点:

curl -X GET "ip地址:9200/_cat/indices?v"

查看ES服务中的所有索引:

spring连接es集群_数据_18

新增索引:

curl -X PUT “ip地址:9200/myindex”

spring连接es集群_spring连接es集群_19

查看索引:

curl -X GET “ip地址:9200/_cat/indices?v”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SfO5SxBy-1615343315067)(C:\Users\heng\AppData\Roaming\Typora\typora-user-images\image-20210309095222486.png)]

删除索引:

curl -X DELETE “ip地址:9200/myindex”

spring连接es集群_elasticsearch_20

使用服务器 curl 手动输入请求 ES 服务不方便,所有使用Postman 请求方法

新建索引:(使用Postman)

spring连接es集群_spring连接es集群_21

新建文档并指定id:

PUT IP地址:9200/myindex/_doc/1

spring连接es集群_elasticsearch_22

根据id查询数据:

GET IP地址:9200/myindex/_doc/1

spring连接es集群_elasticsearch_23

根据id修改数据:

PUT IP地址:9200/myindex/_doc/1

spring连接es集群_spring连接es集群_24

根据id删除数据:

DELETE IP地址:9200/myindex/_doc/1

spring连接es集群_数据_25

6.2 索引搜索

ES 添加些数据

PUT IP 地址:9200/myindex/_doc/1
{
    "title":"海贼王",
    "content":"船长是路飞,副船长是索隆~"
}

PUT IP 地址:9200/myindex/_doc/2
{
    "title":"火影忍者",
    "content":"七代火影是鸣人,八代火影是木叶丸~"
}

PUT IP 地址:9200/myindex/_doc/3
{
    "title":"一拳超人",
    "content":"主角是埼玉,配角是杰诺斯~"
}

PUT IP 地址:9200/myindex/_doc/4
{
    "title":"进击的巨人",
    "content":"人类最牛的是兵长,巨人最菜的是男主~"
}

PUT IP 地址:9200/myindex/_doc/5
{
    "title":"名侦探柯南",
    "content":"男主是柯南,女主不确定~"
}

PUT IP 地址:9200/myindex/_doc/6
{
    "title":"海贼王路飞",
    "content":"路飞是要成为海贼我的男人~"
}

PUT IP 地址:9200/myindex/_doc/7
{
    "title":"路飞的果实能力",
    "content":"路飞是吃了橡胶果实的男人~"
}
搜索全部

GET IP地址:9200/myindex/_search

spring连接es集群_数据_26

结果:

{
    "took": 100,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 7,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "2",
                "_score": 1.0,
                "_source": {
                    "title": "火影忍者",
                    "content": "七代火影是鸣人,八代火影是木叶丸~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "3",
                "_score": 1.0,
                "_source": {
                    "title": "一拳超人",
                    "content": "主角是埼玉,配角是杰诺斯~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "4",
                "_score": 1.0,
                "_source": {
                    "title": "进击的巨人",
                    "content": "人类最牛的是兵长,巨人最菜的是男主~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "5",
                "_score": 1.0,
                "_source": {
                    "title": "名侦探柯南",
                    "content": "男主是柯南,女主不确定~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "6",
                "_score": 1.0,
                "_source": {
                    "title": "海贼王路飞",
                    "content": "路飞是要成为海贼我的男人~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "7",
                "_score": 1.0,
                "_source": {
                    "title": "路飞的果实能力",
                    "content": "路飞是吃了橡胶果实的男人~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "8",
                "_score": 1.0,
                "_source": {
                    "title": "海贼王",
                    "content": "船长是路飞,副船长是索隆~"
                }
            }
        ]
    }
}
根据单个条件搜索

GET IP地址/myindex/_search?q=title:海贼王

或者

GET IP地址/myindex/_search?q=content:路飞

spring连接es集群_数据_27

结果:

{
    "took": 3,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 3,
            "relation": "eq"
        },
        "max_score": 1.9062529,
        "hits": [
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "8",
                "_score": 1.9062529,
                "_source": {
                    "title": "海贼王",
                    "content": "船长是路飞,副船长是索隆~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "6",
                "_score": 1.8387749,
                "_source": {
                    "title": "海贼王路飞",
                    "content": "路飞是要成为海贼我的男人~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "7",
                "_score": 1.8387749,
                "_source": {
                    "title": "路飞的果实能力",
                    "content": "路飞是吃了橡胶果实的男人~"
                }
            }
        ]
    }
}
根据多个条件搜索

GET IP地址:9200/myindex/_search

{
    "query":{
        //多个匹配
        "multi_match":{
            //查询条件 路飞
            "query":"路飞",
            // 从title 和 content 字段中匹配是否存在 路飞 数据
            "fields":["title","content"]
        }
    }
}

结果:

{
    "took": 6,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 3,
            "relation": "eq"
        },
        "max_score": 2.2700202,
        "hits": [
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "6",
                "_score": 2.2700202,
                "_source": {
                    "title": "海贼王路飞",
                    "content": "路飞是要成为海贼我的男人~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "7",
                "_score": 1.9412584,
                "_source": {
                    "title": "路飞的果实能力",
                    "content": "路飞是吃了橡胶果实的男人~"
                }
            },
            {
                "_index": "myindex",
                "_type": "_doc",
                "_id": "8",
                "_score": 1.9062529,
                "_source": {
                    "title": "海贼王",
                    "content": "船长是路飞,副船长是索隆~"
                }
            }
        ]
    }
}

7. SpringBoot 整合 ES

7.1 引入依赖

<!--elasticsearch 可以中文搜索-->
<dependency>
  <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  <version>2.2.5.RELEASE</version>
</dependency>

7.2 ES 相关配置

application.yml 中配置

spring:
  thymeleaf:
    mode: HTML
  profiles:
    active: dev
  messages:
    basename: i18n/messages
  # elasticsearch 配置
  rest:
    # ip是服务器ip地址
    uris: http://8.xxx.xxx.xxx:9200
    # 账号 有账号密码 就配置 没有不需要
    username: root
    # 密码
    password: @@7750731hua

7.3 相关注解介绍

ES 几个常用注解

  • @Document: 声明索引库配置
  • indexName:索引库名称
  • type:映射类型。如果未设置,则使用小写的类简单名称(版本4.0以上 不推荐使用)
  • shards: 分片数量,默认 5
  • replicas: 副本数量, 默认 1
  • @id:声明实体类的id
  • @Field:声明字段属性
  • type: 字段的数据类型
  • analyzer: 指定在存储时候使用的分词类型
  • searchAnalyzer:指定在搜索时候使用的分词器类型
  • index: 是否创建索引

8.1 SpringBoot 实操

推荐观看海贼王文章

Spring Data 形式:SpringBoot 操作ES增删改

RestHighLevelClient 形式:SpringBoot整合ES7.6的API(RestHighLevelClient)

  • 三种形式 (API使用)
  • 主 RestHighLevelClient 客户端使用 加依赖就 使用
  • 副 SpringData 也 就是 jpa形式 使用ElasticsearchRepository 调用API
  • Jest 也是 调用API 2018年已 停止更新 (不推荐使用
介绍RestHighLevelClient 实操

Maven:

<!--elasticsearch 可以中文搜索-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.32</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

实体类:

@Data
public class DiscussPost {
    private int id;
    private String blogName;
    private String goods;
    
    public DiscussPost(int id, String blogName, String goods) {
        this.id = id;
        this.blogName = blogName;
        this.goods = goods;
    }
}

测试类:

/**
 * @Description: ex7.6.x客户端测试 API
 */

@SpringBootTest
public class ElasticsearchTest {


    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;

    /**
     * 索引创建 Request:PUT xin_index
     */

    @Test
    void testCreateIndex() throws IOException {
        // 1.创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("xin_index");
        // 2.执行创建请求 IndicesClient 请求后获得响应
        CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);

        //输出结果
        System.out.println(createIndexResponse);
    }

    /**
     * 获取索引 GET
     */
    @Test
    void testExisIndex() throws IOException {
        // 1.获取索引库的请求
        GetIndexRequest request = new GetIndexRequest("xin_index");
        // 2.执行获取索引库的请求
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);

        System.out.println(exists);// 索引库存在 输出 true 否则 false
    }

    /**
     * 删除索引 DELETE
     */
    @Test
    void testDeleteIndex() throws IOException {
        // 1.删除索引库的请求
        DeleteIndexRequest request = new DeleteIndexRequest("xin_index");
        // 2.执行删除索引库的请求
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);

        // 输出结果
        System.out.println(delete.isAcknowledged());// 删除成功输出true,否则为false
    }

    /**
     * 添加文档
     */
    @Test
    void testAddDocument() throws IOException {
        // 1.创建对象
        DiscussPost user = new DiscussPost(4,"恒新","书");
//        user.setId(1);
//        user.setBlogName("恒新");
//        user.setGoods("书");
        // 2.创建请求
        IndexRequest request = new IndexRequest("xin_index");

        // 3.构建请求规则:PUT /csp_index/_doc/1
        request.id("4");
        request.timeout(TimeValue.timeValueSeconds(1));
        //request.timeout("1s");

        // 4.将user对象数据放入请求,json 数据: 需要用到fastjson
        request.source(JSON.toJSONString(user), XContentType.JSON);
        // 导下json依赖就用下面的
        //request.source(JSON.toJSONString(user), XContentType.JSON);
        // 5.客户端发送请求,获取响应的结果
        IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);

        // 输出结果
        // IndexResponse[index=csp_index,type=_doc,id=1,version=1,result=created,seqNo=0,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
        System.out.println(indexResponse.toString());
        System.out.println(indexResponse.status());// CREATED:表示创建成功
    }

    /**
     * 获取文档,判断是否存在
     */
    @Test
    void testIsExists() throws IOException {
        // 1.获取文档信息请求
        GetRequest getRequest = new GetRequest("xin_index","1");
        // 2.执行获取文档信息的请求
        GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);

        System.out.println(getResponse.getSourceAsString());// {"DiscussPost{id=1, blogName='恒新', goods='书'}":"JSON"}
        System.out.println(getResponse);
        //{"_index":"xin_index","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"DiscussPost{id=1, blogName='恒新', goods='书'}":"JSON"}}
    }

    /**
     * 更新文档信息
     */
    @Test
    void testUpdateDocument() throws IOException {
        // 1.更新文档请求
        UpdateRequest updateRequest = new UpdateRequest("xin_index", "1");
        // 设置请求超时时间
        updateRequest.timeout("1s");
        // user数据对象封装到json中
        DiscussPost user = new DiscussPost(3,"恒久","羽毛球");
//        user.setBlogName("恒久");
//        user.setGoods("羽毛球");
        updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);

        // 2.执行更新文档请求
        UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);

        // 输出结果
        System.out.println(updateResponse.status());// OK:表示更新成功
    }

    /**
     * 删除文档记录
     */

    @Test
    void testDeleteDocument() throws IOException {
        // 1.删除文档请求
        DeleteRequest deleteRequest = new DeleteRequest("xin_index", "1");
        deleteRequest.timeout("1s");

        // 2.执行删除文档请求
        DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);

        // 输出结果
        System.out.println(deleteResponse.status());// OK:表示删除成功
    }

    /**
     * 批量插入数据
     */
    @Test
    void testBulkRequest() throws IOException{
        // 1.批处理请求
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");

        // user 数据集合
        ArrayList<DiscussPost> list = new ArrayList<>();
        //DiscussPost user = new DiscussPost();
        //DiscussPost user = new DiscussPost();

        list.add(new DiscussPost(0,"路飞","恶魔果实"));
        list.add(new DiscussPost(1,"索隆","剑术"));
        list.add(new DiscussPost(2,"山治","厨师"));
        System.out.println(list);
        for (int i = 0; i < list.size(); i++) {
            // 批量更新,修改,删除 都是在此进行操作
            bulkRequest.add(
                    new IndexRequest("xin_index")
                            // 批量赋值文档id: 如果不在自己赋值文档id,会默认生成随机的文档id
                            .id("" + (i + 1))
                            // ArrayList转换成json
                    .source(JSON.toJSONString(list.get(i)), XContentType.JSON)
            );
        }
        // 2.执行批量插入请求
        BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);

        // 输出结果
        System.out.println(bulkResponse.status());// OK: 表示批量插入成功!
    }

    /**
     * ES中数据搜索
     * SearchRequest 搜索请求
     * SearchSourceBuilder 条件构造
     * HighlightBuilder 构建高亮
     * TermQueryBuilder 精确查询
     * XXXQueryBuilder 构建我们需要用到的命令
     */
    @Test
    void testSearch() throws IOException {
        // 1.创建搜索请求
        SearchRequest searchRequest = new SearchRequest("xin_index");
        // 2.构建搜索条件:条件构造器 SearchSourceBuilder
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //高亮结果的条件构造器
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("blogName");// 要高亮的字段
        highlightBuilder.requireFieldMatch(false);//不需要多个字段高亮,如果需要设置true
        highlightBuilder.preTags("<span style='color:blue'>");
        highlightBuilder.postTags("</span>");
        // 条件构造器,开启搜索结果高亮,并加入高亮结果的条件构造器
        sourceBuilder.highlighter(highlightBuilder);
        /**
         * 查询条件,使用QueryBuilders工具类
         *              QueryBuilders.termQuery() 精确查询
         *              QueryBuilders.matchAllQuery() 匹配所有
         */
        //TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "xin");// 精确查询
        //MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();// 搜索所有数据
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("blogName", "索隆");// 搜索字段name为路飞的数据

        //查询条件(matchQueryBuilder)放入条件构造器
        sourceBuilder.query(matchQueryBuilder);

        // 条件构造器,开启分页条件: 从第1个数据开始,每页展示5条结果数据
        sourceBuilder.from(0);
        sourceBuilder.size(5);

        // 条件构造器,搜索请求超时时间60s
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        // 将条件构造器放入搜索请求
        searchRequest.source(sourceBuilder);

        // 执行搜索请求,并获得searchResponse响应
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

        // 搜索得到的所有结果都封装在hits里面,拿数据从hits里面获取
        SearchHits hits = searchResponse.getHits();
//        System.out.println("-------");
//        System.out.println(JSON.toJSONString(hits));
//        System.out.println("-------");


        // 遍历hits:解析结果,并将结果放入resultList集合
        ArrayList<Map<String, Object>> resultList = new ArrayList<>();
        for (SearchHit documentFields : searchResponse.getHits()) {
            // 获取高亮的字段
            Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
            HighlightField name = highlightFields.get("blogName");// 获取高亮的字段

            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();// 先获取原来未高亮的结果

            // 解析高亮的字段, 将原来未高亮的title字段换成高亮的字段
            if (name != null) {
                Text[] fragments = name.fragments();
                String newName = "";
                for (Text text : fragments) {
                    newName += text;
                }
                // 高亮字段替换原来内容
                sourceAsMap.put("blogName", newName);
            }
            resultList.add(documentFields.getSourceAsMap());
        }
        // 遍历resultList
        resultList.forEach(item -> {
            System.out.println(item);// {name=<span style='color:red'>路</span><span style='color:red'>飞</span>, age=1}
        });
    }
}