Bootstrap

【后端学习】ElasticSearch

ElasticSearch

01-ElasticSearch概述

ElasticSearch 是基于 Lucene 做了一些封装和增强

Elasticsearch是一个基于Apache Lucene™的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进性能最好的、功能最全的搜索引擎库。

但是,Lucene只是一个库。想要使用它,你必须使用ava来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

Elasticsearch也使用/ava开发并使用Luene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏Lucene的复杂性,从而让全文搜索变得简单。

Elaticsearch,简称为es,es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好可以扩展到上百台服务器,处理PB级别(大数据时代)的数据。es也使用ava开发并使用Luene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

ELK技术:elasticsearch+logstash+kibana

ElasticSearch vs Solr 总结

1、es基本是开箱即用( 解压就可以用!),非常简单。Solr安装略微复杂一丢丢!

2、Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能。

3、Solr 支持更多格式的数据,比如SON、XML、CSV,而 Elasticsearch 仅支持json文件格式.

4、Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要kibana友好支撑

5、Solr 查询快,但更新索引时慢( 即插入删除慢 ),用于电商等查询多的应用 ;

  1. ES建立索引快( 即查询慢),即实时性查询快,用于facebook新浪等搜索
  2. Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用。

6、Solr比较成熟,有一个更大,更成熟的用户、开发和贡献者社区,而 Elasticsearch相对开发维护者较少,更新太快,学习使用成本较高。、

02-ElasticSearch安装

下载地址:

ElasticSearch: https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D

logstash: https://mirrors.huaweicloud.com/logstash/?C=N&O=D

kibana: https://mirrors.huaweicloud.com/kibana/?C=N&O=D

elasticsearch-analysis-ik: https://github.com/medcl/elasticsearch-analysis-ik/releases

cerebro: https://github.com/lmenezes/cerebro/releases

head插件: https://github.com/mobz/elasticsearch-head/archive/master.zip

目录组成

  1. bin 启动文件
  2. config 配置文件
  3. 1og4j2:日志配置文件
  4. jvm.options:java虚拟机相关的配置
  5. elasticsearch.yml:elasticsearch 的配置文件:默认 9200 端口,跨域
  6. lib 相关jar包
  7. logs 日志
  8. modules功能模块
  9. plugins插件

可视化界面 es head插件,需要把前端基本的环境安装完毕

1、下载地址: https://github.com/mobz/elasticsearch-head/

2、启动:
npm install
npm run start

3、连接测试发现,存在跨域问题:配置es
http.cors.enabled: true
http.cors.a11ow-origin: "*"

4、重启es服务器,然后再次连接

03-Kibana

Kibana /kiːˈbɑːnə/ 是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交与存储在Elasticsearch索引中的数据。

使用Kibana可以通过各种图表进行高级数据分析及展示。

Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard )实时显示Elasticsearch查询动态。设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监测。

官网:https://www.elastic.co/cn/kibana

注意点:Kibana 版本要和 Es 一致

04-ElasticSearch基础

elasticsearch是面向文档的:索引和搜索数据的最小单位是文档

关系行数据库和elasticsearch 客观的对比

RelationalDBElasticsearch
数据库(database)索引(indices)
表(tables)types
行(rows)documents
字段(columns)fields

elasticsearch(集群)中可以包含多个索引[数据库),每个索引中可以包含多个类型(表),每个类型下又包含多个文档[行),每个文档中又包含多个字段(列)。

elasticsearch在后台把每个索引划分成多个分片,每个分片可以在集群中的不同服务器间迁移

一个索引类型中,包含多个文档,比如说文档1,文档2。当我们索引一篇文档时,可以通过这样的一个顺序找到它:索引->类型文档->ID,通过这个组合就能索引到某个具体的文档。 注意:ID不必是整数,实际上它是个字符串。

文档的属性

  1. 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含 key:value
  2. 层次型的,一个文档中包含自文档,复杂的逻辑实体是一个json对象,fastison进行自动转换
  3. 灵活的结构,文档不依赖预先定义的模式,对于字段是非常灵活的,有时候,可以忽略该字段,或者动态的添加一个新的字段

类型

  1. 类型是文档的逻辑容器。
  2. 类型中对于字段的定义称为映射,比如 name 映射为字符串类型。文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段。elasticsearch会自动的将新字段加入映射,但是这个字段的不确定它是什么类型,elasticsearch就开始猜,如果这个值是18,那elasticsearch会认为它是整型。但是elasticsearch也可能猜不对,所以最安全的方式就是提前定义好所需要的映射

索引

  1. 索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。
  2. 索引存储了映射类型的字段和其他设置,被存储到了各个分片上。

一个集群至少有一个节点,而一个节点就是一个elasricsearch进程,节点可以有多个索引默认的。

如果创建索引,那么索引将会有个5个分片(primary shard,又称主分片)构成的,每一个主分片会有一个副本(replica shard ,又称复制分片)

一个分片是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字。

倒排索引

elasticsearch使用的是一种称为倒排索引的结构,采用Luene倒排索引作为底层。这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。

例如,现在有两个文档,每个文档包含如下内容:

study every day,good good up to forever # 文档1包含的内容

To forever,study every day,good good up # 文档2包含的内容

为了创建倒排索引,首先要将每个文档拆分成独立的词(或称为词条或者tokens),然后创建一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档:

termdoc_1doc_2
Study×
To××
every
forever
day
study×
good
every
to×
up

两个文档都匹配,但是第一个文档比第二个匹配程度更高。如果没有别的条件,现在,这两个包含关键字的文档都将返回

在elasticsearch中,索引被分为多个分片,每个分片是一个Lucene /'lusi:n/ 的索引。所以一个elasticsearch索引是由多个Lucene索引组成的。

05-IK分词器插件

分词概念:

把一段中文或者别的划分成一个个的关键字,在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作

默认的中文分词是将每个字看成一个词,在不符合使用场景时可以安装中文分词器ik来解决这个问题。

IK提供了两个分词算法:

  1. ik smart:最少切分
  2. ik max word:最细度划分

IK的安装:

  1. 地址:https://github.com/medcl/elasticsearch-analysis-ik
  2. 下载完毕之后,放入到elasticsearch 插件即可,目录:…/…/plugins/ik
  3. 重启观察ES,输入cmd命令:elasticsearch-plugin list,查看ik是否被加载

查看不同的分词效果:

Image.png

设置分词词典:IKAnalyzer.cfg.xml

<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext dict">kuang.dic</entry>
    <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext stopwords"></entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <!-- <entry key="remote ext dict">words location</entry> -->
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote ext stopwords">words location</entry> -->
</properties>

06-ElasticSearch操作

Rest风格说明:

一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。

它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

基本Rest命令说明:

methodurl地址描述
PUTlocalhost:9200/索引名称/类型名称/文档id创建文档( 指定文档id )
POSTlocalhost:9200/索引名称/类型名称创建文档( 随机文档id )
POSTlocalhost:9200/索引名称/类型名称/文档id/_update修改文档
DELETElocalhost:9200/索引名称/类型名称/文档id删除文档
GETlocalhost:9200/索引名称/类型名称/文档id查询文档通过文档id
POSTlocalhost:9200/索引名称/类型名称/ search查询所有数据

关于索引的基本操作:

  1. 创建一个索引: PUT /索引名/类型名/文档id {请求体}
  2. 指定字段的类型:PUT/索引名 {“mappings”:{“properties”:{“key字段”:{“type”:“字段類型”}}}
  3. 获取具体規則/信息:GET/索引名
  4. 查看默认的信息:PUT/索引名/_doc/文档id {请求体}
  5. 扩展: get _cat/ 可以获得es的当前的很多信息
  6. 删除索引:通过DELETE 命令实现删除,索引/文档记录

如果文档字段没有指定,那么es 就会默认配置字段类型,最好自定义!

关于文档的基本操作

1. 添加数据
PUT /kuangshen/user/1
{
    "name":"狂神说"
    "age" : 23 ,
    "desc":“一顿操作猛如虎,一看工资2500"tags": ["技术宅","温暖","直男”]
}
2. 获取数据 GET
GET kuangshen/user/1

3. 更新数据 PUT(如果不传递值,就会被覆盖原数据为空)
PUT /kuangshen/user/3
{
    "name":“李四233",
    "age": 30,
    "desc":"mmp,不知道如何形容",
    "tags": ["靓女","旅游","唱歌"]
}

返回——"version":2,result:"updated",

4. 更新数据 POST _update(推荐的更新方式)
POST kuangshen/user/1/_update
{
    "doc":{
        "name":"狂神说Java"
    }
}
5. 通过文档id查询
GET kuangshen/user/1

6. 简单的条件查询,可以根据默认的映射规则,产生基本的查询
GET kuangshen/user/_search?q=name:狂神说

7. 复杂操作搜索select(排序,分页,高亮,模糊查询,精准查询),查询的参数体使用Json构建
GET kuangshen/user/_search
{
    "query":{
        "match":{
            "name":"狂神"
        }
    }
}

返回的hit对象:
1.索引和文栏的信息
2.查询的结果总数
3.查询出来的具体的文档(遍历数据)
4.分数: 可以通过分数来判断谁更加符合结果
8. 过滤输出的结果字段
GET kuangshen/user/_search
{
    "query":{
        "match":{
            "name":"狂神"
        }
    },
    "source": ["name" ,"desc"]
}

返回——
"source":
{
    "name”:"狂神说Java",
    "desc”:"-顿操作墅如虎,一看工资2500"
}
9. 排序
GET kuangshen/user/_search
{
    "query":{
        "match":{
            "name":"狂神"
        }
    },
    "sort": [
        {
            "age":{
                "order":"asc"
            }
        } 
    ]
}
10. 分页(from:从第几个数据开始,size:返回多少条数据( 单页面的数据 )GET kuangshen/user/_search
{
    "query":{
        "match":{
            "name":"狂神"
        }
    },
    "sort": [
        {
            "age":{
                "order":"asc"
            }
        }
    ]
    "from":0,
    "size":1
}
11. 多条件查询(bool)
GET kuangshen/user/_search
{
    "query":{
        "bool":{
            "must":[ ---相当于and
                {
                    "match":{
                        "name":"狂神说"
                    }
                },
                {
                    "match":{
                        "age": 23
                    }
                }
            ],
            "should":[ ---相当于or
                {
                    "match":{
                        "name":"狂神说"
                    }
                },
                {
                    "match":{
                        "age": 23
                    }
                }
            ],
            "must_not":[ ---相当于<>
                {
                    "match":{
                        "name":"狂神说"
                    }
                },
                {
                    "match":{
                        "age": 23
                    }
                }
            ],
            "filter":{ --可以使用filter 进行数据过滤
                "range":{
                    "age":{
                        "lt": 10,
                        "gte": 1 --可以使用多个条件进行过滤
                    }
                }
            }
        }
    }
}
gt大于
gte大于等于
lt小于
lte小于等于

term 查询是直接通过倒排索引指定的词条进程精确查找的

关于分词 :

  1. term,直接查询精确的
  2. match,会使用分词器解析( 先分析文档,然后在通过分析的文档进行查询)
PUT testdb/_doc/2
{
    "name":"狂神说Java name",
    "desc":"狂神说Java desc2"
}

GET_analyze
{
    "analyzer":"keyword",    --keyword字段类型不会被分词器解析
    "text":"狂神说Java name"
}

GET _analyze
{
    "analyzer":"standard",
    "text":"狂神说Java name"
}

GET testdb/_search
{
    "query":{
        "term":{
            "name":“狂"
        }
    }
}

GET testdb/_search
{
    "query":{
        "term":{
            "desc":“狂神说Java desc"
        }
    }
}
多个值匹配精确查询
GET testdb/_search
{
    "query":
"bool":
"should":[
"term":
"t1":"22"
"term":(
"t1":"33"

高亮查询(搜索相关的结果可以高亮显示)
GET kuangshen/user/ search
{
    "query":{
        "match":{
            "name":"狂神说"
        }
    }
    "highlight":{
        "pre tags": "<p class='key' style='color:red'>", -- 自定义搜索高亮条件
        "post tags":"</p>", -- 自定义搜索高亮条件
        "fields":{
            "name":{}
        }
    }
}
  • 07-ElasticSearch API

    07-ElasticSearch API
原生依赖
<dependency>
    <groupId>org.easticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-leve1-client</artifactId>
    <version>7.6.2</version>
</dependency>

pom文件修改es版本
<properties>
    <java.version>1.8</java.version>
    <!-- 自己定义es 版本依赖,保证和本地一致 -->
    <elasticsearch,version>7.6.1</elasticsearch.version>
</properties>

springboot增加elasticSearch依赖

@Configuration
public class ElasticSearchClientConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("127.0.0.1", 9299, "http")));
        return client;
    }
}
// 面向对象来操作
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client:

// 测试索引的创建
Request PUT kuang_index
@Test
void testCreateIndex() throws IOException {
    // 1、创建索引请求
    CreateIndexRequest request = new CreateIndexRequest("kuang index");

    // 2、客户端执行请 IndicesCLient,请求后获得啊应
    CreateIndexResponse createIndexResponse =client.indices().create(request, RequestOptions.DEFAULT);

    System.out.printIn(createIndexResponse);
}

// 测试获取索引,判断其是否存在
@Test
void testExistIndex() throws IOException {
    GetIndexRequest request = new GetIndexRequest("kuang index2");
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.printIn(exists);
}

// 测试删除索引
@Test
void testDeleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest();
    //删除
    AcknowledgedResponse delete = client.indices().delete(request, ReguestOptions.DEFAULT);
    System.out.printIn(delete.isAcknowledged());
}

// 测试添加文档
@SneakyThrows
void testAddDocument() throws IOException {
    // 创建对象
    User user = new User("狂神说" ,3);
    // 创建请求
    IndexRequest request = new IndexRequest("kuang index");

    // 规则 put /kuang_index/_doc/1
    request.id("1");
    request.timeout(TimeValue.timeValueSeconds(1));
    request.timeout("1s");

    // 将数据放入请求Json
    request.source(JSON.toJSONString(user), XContentType.JSON);

    // 客户端发送请求 ,获取响应的结果
    IndexResponse indexResponse = client.index(request, RequestOptions,DEFAULT);

    System.out.printIn(indexResponse.toString());
    System.out.printIn(indexResponse.status());
}

// 获取文档,判断是否存在 get /index/doc/1
void testIsExists() throws IOException {
    GetRequest getRequest = new GetRequest("kuang index", "1");
    // 不获取返回的_source 的上下文
    getRequest.fetchSourceContext(new FetchSourceContext(false));
    getRequest.storedFields("_none_");
    
    boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
    System.out.println(exists);
}

// 获得文档的信息
@Test
void testGetDocument() throws IOException {
    GetRequest getRequest = new GetRequest("kuang index","1");
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
    // 打印文档的内容
    System.out.printIn(getResponse.getSourceAsString()); 
    // 返回的全部内容和命令是一样的
    System.out.printIn(getResponse); 
}


// 更新文档的信息
@Test
void testUpdateRequest() throws IOException {
    UpdateRequest updateRequest = new UpdateRequest( index: "kuang index", id: "1");
    updateRequest.timeout("1s");

    User user = new User("狂神说Java",18);
    updateRequest.doc(JSON.toJSONString(user),XContentType.JSON);

    UpdateResponse updateResponse = client.update(updateRequest, RequestOptions ,DEFAULT);
    System.out.println(updateResponse.status());
}

// 删除文档记录
@Test
void testDeleteRequest() throws IOException {
    DeleteRequest request = new DeleteRequest("kuang index", "3");
    request.timeout("1s");

    DeleteResponse deleteResponse = client,delete(request, RequestOptions,DEFAULT);
    System.out.printIn(deleteResponse.status());
}


// 特殊的,真的项目一般都会批量插入数据
@Test
void testBulkRequest() throws IOException {
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");

    ArrayList<User> userList = new ArrayList<>();
    userList.add(new User("kuangshen1",3));
    userList.add(new User("kuangshen2",3);
    userList.add(new User("kuangshen3",3));
    userList.add(new User("ginjiang1", 3));
    userList.add(new User("qinjiang2",3));
    userList.add(new User("ginjiang3",3)) ;
    
    // 批处理请求
    for (int i = 0; i < userList.size() i++) {
        // 批量更新和批量删除,修改对应的请求即可
        bulkRequest.add(
            new IndexRequest("kuang_index").id(""+(i+1)).source(JSON.toJSONString(userList.get(i)),XContentType.JSON));
    }

    BulkResponse bulkResponse = client.bulk(bulkRequest, ReguestOptions,DEFAULT);
    System.out.printIn(bulkResponse.hasFailures());
}


//查询
//SearchRequest 搜索请求
//SearchSourceBuilder 条件构造
//HighlightBuilder 构建高亮
//TermQueryBuilder 精确查询 
//MatchAllOueryBuilder
//XXX QueryBuilder 对应命令集
@Test
void testSearch() throws IOException {
    SearchRequest searchRequest = new SearchRequest("kuang index");

    // 构建搜索条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    // 查询条件,可以使用 QueryBuilders 工具实现
    // QueryBuilders.termQuery 精确
    // QueryBuilders.matchALLQuery() 匹配所有
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name","qinjiang1");
    //MatchAllOueryBuilder matchAllOueryBuilder = QueryBuilders,matchAllOuery();
    sourceBuilder.query(termQueryBuilder);
    sourceBuilder.timeout(new TimeValue(60,TimeUnit.SECONDS));
    searchRequest.source(sourceBuilder);

    SearchResponse searchResponse = client.search(searchRequest, RequestOptions,DEFAULT);
    System.out.println(JSON.toJSONString(searchResponse.getHits()));
    System.out.println("=================================") ;

    for (SearchHit documentFields : searchResponse.getHits().getHits()){
        System.out.println(documentFields.getSourceAsMap());
    }
}
  • 08-ElasticSearch 实战

    08-ElasticSearch 实战

爬虫依赖及工具类

爬虫依赖:
<!-- jSoup解析网页 -->
<!-- 解析网页jSoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.2</version>
</dependency>


爬虫工具类
public class HtmlParseUtil{
    public static void main(String[] args) throws IOException {
        // 获取请求 https://search.jd.com/Search?keyword=java
        // 前提,需要联网

        String url = "https://search,jd.com/Search?keyword=java";
        // 解析网页。(Jsoup返回Document就是浏觉器Document对象)
        Document document = Jsoup.parse(new URL(ur1), 3000);
    
        // 所有在JS 中可以使用的方法,都能用
        Element element = document.getElementById("J_goodsList");

        // 获取所有的元素
        Elements elements = element,getElementsByTag("li");
        // 获取元素中的内容,这里el 是指每一个Li标签
        for (Element el : elements){
            // 关于这种图片特别多的网站,所有的图片都是延迟加裁的!
            // source-data-Lazy-img
            String img = el.getElementsByTag("img").eg(0),attr("source-data-lazy-img");
            //String img = el.getElementsByTag("img"),eq(0),attr("src");
            String price = el.getElementsByClass("p-price").eq(0).text();
            String title = el.getElementsByClass("p-name").eq(0).text();

            System,out,println("=============================");
            System.out.println(img);
            System.out.println(price);
            System.out.printIn(title);
        }
    }
}


public class HtmlParseUtil {
    public static void main(String[] args) throws Exception {
        new HtmlParseUtil().parseJD( keywords: "java").forEach(System.out::println);
    }
}

public List<Content> parseJD(String keywords) throws Exception {
    String url ="https://search,jd,com/Search?keyword="+keywords;
    Document document = Jsoup.parse(new URL(ur1), 3000);
    Element element = document.getElementById("J_goodsList");

    Elements elements = element.getElementsByTag("li");

    ArrayList<Content> goodsList = new ArrayList<>();
    // 取元素中的内容,这里el 就是每一个Li标签
    for (Element el : elements) {
        // 关于这种图片特别多的网站,所有的图片都是延迟加裁的
        // source-data-Lazy-img
        String img = el.getElementsByTag("img").eg(0),attr("source-data-lazy-img");
        //String img = el.getElementsByTag("img"),eq(0),attr("src");
        String price = el.getElementsByClass("p-price").eq(0).text();
        String title = el.getElementsByClass("p-name").eq(0).text();
        Content content = new Content();
        content.setTitle(title);
        content.setPrice(price);
        content.setImg(img);
        goodsList.add(content);
    }
    return goodslist;
}

前后端分离项目

// 业务编写
@Service
public class ContentService{
    @Autowired
    private RestHighLevelClient restHighLevelclient;

    public static void main(String[] args) throws Exception {
        new ContentService().parseContent("java");
    }
    // 1、解析数据放入 es 索引中
    public Boolean parseContent(String keywords) throws Exception{
        List<Content> contents = new HtmlparseUtil().parseJD(keywords);
        
        // 把查询到的数据放入 es 中
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("2m");
        for(int i = @; i <contents.size() ; i++) {
            bulkRequest.add(new IndexRequest("jd_goods").source(JSON.toJsONString(contents.get(i)),XContentType.JsoN));
        }
        BulkResponse bulk = restHighLevelclientbulk(bulkRequest, Requestoptions.DEFAULT);
        return bulk.hasFailures();
    }

    // 2、获取这些数据实现搜索功能
    public List<Map<String,Object>> searchPage(String keyword,int pageNo,int pageSize){
        if(pageNo<=1){
            pageNo = 1;
        }

        // 条件搜索
        SearchRequest searchRequest = new SearchRequest("jd goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);

        // 精准匹配
        TermQueryBuilder termOueryBuilder = QueryBuilders.termQuery("title", keyword);
        sourceBuilder.query(termOueryBuilder);
        sourceBuilder.timeout(new TimeValue(60,TimeUnit.SECONDS));

        // 高亮

        // 执行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = restHighlevelclient,search(searchRequest, RequestOptions,DEFAULT);

        // 解析结果
        ArrayList<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit documentFields : searchResponse.getHits().getHits()) {
            list.add(documentFields.getSourceAsMap());
        }
        return list;
    }
}

// 编写
@RestController
public class ContentController{
    @Autowired
    private ContentService contentService;
    
    @GetMapping("/parse/{keyword}")
    public Boolean parse(@PathVariable("keyword") String keywords) throws Exception {
        return contentService.parseContent(keywords);
    }

    @GetMapping("/search/(keyword}/(pageNo}/{pageSize}")
    public List<Map<String,0bject>> search(@PathVariable("keyword") String keyword,@PathVariable("pageNo") int pageNo,@PathVariable("pageSize") int pageSize) throws IOException{
        return contentService.searchPage(keyword,pageNo,pageSize);
    }
}

//vue
<script>
new Vue({
    el:'#app',
    data:{
        keyword:// 搜索的关键字
        results:[] // 搜索的结果
    },
    methods: {
        searchKey(){
            var keyword = this.keyword;
            console.log(keyword);
            //对接后端的接口
            axios.get('search/'+keyword+"/1/10").then(response=>{
                console.log(response);
                this.results = response.data; // 绑定数据
            })
        }
    }
})
</script>

关键字高亮

// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.requireFieldMatch(false); // 多个高亮显示
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
sourceBuilder.highlighter(highlightBuilder);

// 执行搜索
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = restHighlevelclient,search(searchRequest, RequestOptions,DEFAULT);

// 解析结果
ArrayList<Map<String,Object>> list = new ArrayList<>();
for(SearchHit hit : searchResponse.getHits().getHits()) {
    Map<String,HighlightField> highlightFields = hit,getHighlightFields();
    HighlightField title = highlightFields.get("title");

    // 原来的结果
    Map<String,Object> sourceAsMap = hitgetSourceAsMap();     
    // 解析高亮的字段,将原来的字段换为高亮的字即可
    if (title!=null){
        Text[] fragments = title.fragments();
        String n_title = "";

        for (Text text : fragments) {
            n_title += text;
        }
        sourceAsMap.put("title",n_title); // 高亮字段替换原来的内容即可
    }
    list.add(sourceAsMap);
}

<!--标题-->
<p class="productTitle">
    <a v-html="result.title">
    </a>
</p>

;