ElasticSearch的学习

介绍

ElasticSearch(简称ES)是一个开源的分布式搜索和数据分析引擎,是用Java开发并且是当前最流行的开源的企业级搜索引擎,能够达到近实时搜索,它专门设计用于处理大规模的文本数据和实现高性能的全文检索。

ElasticSearch是基于Restfull风格进行数据操作

应用场景:全文检索、日志分析、商业智能决策 等

ElasticStack生态介绍

ElasticSearch:核心搜索和分析引擎,提供存储、索引和分布式搜索功能

Logstach:数据处理管道,负责数据收集、处理和传输

Beats:边缘数据采集器,负责从各种来源采集数据并发送到Logstash或者ElasticSearch

Kibana:可视化和管理工具,提供数据展示和交互式查询

分词器

分词器可以让搜索的时候,能将指定的搜索词分成几个不同部分的搜索词来进行分别搜索。官方称之为文本分析器,顾名思义,是对文本进行分析处理的一种手段,基本处理逻辑为按照预先制定的分词规则,把原始文档分割成若干更小粒度的词项,粒度大小取决于分词器规则。

一般中文使用ik分词器,使用"analyzer":"ik_max_word","search_analyzer":"ik_smart"的方案。

ElasticSearch的核心概念

全文检索(Full-Text-Serach):全文检索是一种从大量文本数据中快速检索出包含指定词汇或者词语的信息的技术。

倒排索引:在一个文档集合中,每个文档都可以视为一个词语的集合,倒排索引则是将词语映射到包含这个词语的文档的数据结构

如何实现倒排索引:

  1. 文档预处理
  2. 构建词典
  3. 创建倒排列表
  4. 存储索引文件
  5. 查询处理

索引:类似于MySQL中的表,用于存储不同的数据

映射:类似于表中的schema,用于表示索引中的数据的结构

文档:索引中的一个数据实体,类似于MySQL中的一行记录

索引操作

创建索引

PUT /index_name

index_name代表索引名

可以直接用上述的语法去创建一个空的索引,也可以直接在创建的时候直接初始化好表的信息,例如:

PUT /index_name
{"settings":{//索引设置},"mapping":{//字段映射}
}

必要的参数:

  • 索引名称(index_name):索引名必须是小写字母,可以包含数字和下划线

  • 索引设置(settings)

    • 分片数量(numberofshards):一个索引的分片数量决定了索引的并行度和数据分布,默认是一个
    • 副本数量(numberofreplicas):副本提高了数据的可用性和容错能力,默认是一个
  • 映射(mappings)

    • 字段属性(properties):定义索引中文档的字段及其类型。常用的字段包括:text,keyword,integer,float,date等。示例:
    {"properties":{"field_name1":{"type":"text",……},"field_name2":{"type":"integer",……}}
    }
    

删除索引

DELETE /index_name

修改索引

PUT /index_name_settings

修改的时候,带上要修改的跟设置相关的参数,例如:

PUT /index_name_settings
{"index": {"number_of_replicas": 2}
}

PUT /index_name/_mapping

修改的时候(添加新字段),带上要修改的跟映射相关的参数,例如:

PUT /index_name/_mapping
{"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}}
}

索引库和mapping一旦创建就无法更改,但是可以添加新的字段。

索引别名

aliases是一个和setting和mapping同级的参数,用于指定索引的别名。例如:

PUT /index2/_mapping
{"aliases":{"index_alias":{}}"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}}
}

如此,就创建了一个索引index2,index2这个索引就有了index_alias这个别名

也可以给现有的索引增加一个别名:

POST /_aliases
{"actions":[{"add":{"index":"my_index","aliases":"my_index_alias"}}]
}

如此,现有的my_index索引就有了一个别名叫做my_index_alias

索引别名有什么作用呢?

如果要多索引检索,即同时检索多个索引,有两种方案:

  • 使用逗号对多个索引名称进程分隔

    POST index1,index2,index3,index4/_search
    
  • 使用通配符的方式

    POST index*/_search
    

这种方式会有很大的局限性,建议使用别名的方式

使用别名的方式:

  • 使用别名关联已有索引

    POST /_aliases
    {"actions":[{"add":{"index":"my_index1","aliases":"my_index_alias"}},{"add":{"index":"my_index2","aliases":"my_index_alias"}},{"add":{"index":"my_index3","aliases":"my_index_alias"}}]
    }
    

查询的时候,GET my_index_alias 就会查询关联这个别名的所有的表

若索引和和别名指向相同,则在相同检索条件下的检索效率是一致的,因为索引别名只是物理索引的软链接的名称而已。

对相同索引别名的物理索引建议由一致的映射,以提升检索效率

推荐充分发挥索引别名在检索方面的优势,但是在写入和更新的时候还要使用物理索引。

mapping的字段的属性

mapping是对索引库中文档的约束,常见的mapping的字段的属性包括:

  • type:字段数据类型,创建的类型有:
    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址,不可分词,是一个不可分割的整体)
    • 数值:long、integer、short、byte、double、float
    • 布尔:boolean
    • 日期:date
    • 对象:object

如果要使用数组的形式,type直接指定数组元素的类型即可,数据值也是以[xxx,xxx,xxx,……]数组的形式。

  • index:是否创建索引,给了值为true,就会为其创建倒排索引
  • analyzer:使用那种分词器,结合text类型的字段使用
  • properties:该字段的子字段
  • copy_to:可以将该字段的属性值拼接到指定哪个属性中

文档操作

新增文档

新增文档的DSL语法:

POST /索引库名/_doc/文档id
{"字段1":"值1","字段2":{"子属性1":"值2","子属性2":"值3"}
}

如果不指定新建的文档id,就会自动随机生成一个id,这种情况显然不好。建议在新增文档的时候,加上指定的文档id。

查看文档

GET /索引库名/_doc/文档id

删除文档

DELETE /索引库名/_doc/文档id

更新文档

方式一:全量修改,会删除旧文档,添加新文档

PUT /索引库名/_doc/文档id
{"字段1":"值1","字段2":{"子属性1":"值2","子属性2":"值3"}
}

如果文档id对应的文档存在,就删除旧文档,添加新文档。如果不存在,就直接增加一个新文档。

方式二:增量修改,修改指定字段值。

POST /索引库名/_update/文档id
{"doc":{"字段1":"值1","字段2":{"子属性1":"值2","子属性2":"值3"}}
}

这种方式只会修改一条文档中的指定的字段值

DSL查询文档

DSL Query的分类

ElasticSearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据。match_all
  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引中匹配。
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型的字段。
  • 地理(geo)查询:根据经纬度查询。
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。

DSL Query查询语法

查询的基本语法如下:

GET /index_name/_search
{"query":{"查询类型":{"查询条件":"条件值"}}
}

全文检索查询

全文检索查询,会对用户的输入内容进行分词,常用于搜索框搜索

  • match查询:全文检索查询中的一种,会对用户输入内容进行分词,然后去倒排索引库检索。

    GET /index_name/_search
    {"query":{"match":{"字段名":"搜索内容"}}
    }
    
  • multi_match:与match查询类似,只不过允许同时查询多个字段

    GET /hotel/_search
    {"query":{"multi_match":{"query":"搜索词","fields":["字段1","字段2","字段3",……]}}
    }
    

推荐使用copy_to将要共同查询的字段汇总到一个总字段之后,使用match查询。使用multi_match查询的时候,参与查询的字段越多,查询性能越差。

精确查询

精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的方式有:

  • term:根据词条精确值查询

    GET /index_name/_search
    {"query":{"term":{"字段名":{"value":"搜索值"}}}
    }
    
  • range:根据值的范围查询

    GET /index_name/_search
    {"query":{"range":{"字段名":{"gte":"最小值","lte":"最大值"}}}
    }
    
    • gte是表示大于等于,如果只想表示大于,使用gt
    • lte是表示小于等于,如果只想表示小于,使用lt

地理查询

根据经纬度查询。常见方式有:

  • geo_bounding_box:查询geo_point值落在某个矩形范围内的所有文档

    GET /index_name/_search
    {"query":{"geo_bounding_box":{"字段名":{"top_left":{ //左上角"lat":"纬度值","lon":"经度值"},"bottom_right":{ //右下角"lat":"纬度值","lon":"经度值"}}}}
    }
    
    • top_left属性就是矩形左上角的点的经纬度
    • bottom_right属性就是矩形右下角的点的经纬度
  • geo_distance:查询到指定中心点小于某个距离值的所有文档

    GET /index_name/_search
    {"query":{"geo_distance":{"distance":"距离(直接以m、km等等为距离单位即可)""字段名":"字段值"}}
    }
    

复合查询

符合查询可以将其他简单查询组合起来,实现更复杂的搜索逻辑。

function score:算分函数查询,可以控制文档相关性算分,控制文档排名

相关性算分:当我利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果的时候按照分值降序排列。TF(词条频率)= 词条出现次数/文档中词条总数。还有一个TF-IDF算法,即也考虑逆文档频率,逆文档频率 = log(文档总数/包含词条的文档总数)。但是目前使用的是更高级的BM25算法,这种算法不会受词频影响较大,得分增长比较平缓,后面趋于水平。

Function Score Query

使用function score query,可以修改文档的相关性算分(query score),根据新得到的算分排序。

GET /index_name/_search
{"query":{"function_score":{//原始查询条件,搜索文档并根据相关性打分(query_score)"query":{"查询类型":{"字段名":"搜索内容"}},"function":[{"filter":{"查询类型":{"字段名":"字段值"}},"算分函数":……}],"boost_mode":"multiply" //加权模式}}
}

算分函数,算分函数的结果称为function score,将来会与query score运算,得到新的算分,常见的有:

  • weight:给一个常量值,作为函数结果(function score)
  • field_value_factor:用文档中的某个字段值作为函数结果
  • random_score:随机生成一个值,作为函数结果
  • script_score:自定义计算公式,公式结果作为函数结果

加权模式,定义function score与query score的运算方式,包括:

  • multiply:两者相乘。默认就是这个运算方式。
  • replace:用function score替换这个query score
  • 其他:sum、avg、max、min

Boolean Query

布尔查询是一个或者多个查询子句的组合,子查询的组合方式有:

  • must:必须匹配每个子查询,类似于“与”
  • should:选择性匹配子查询,类似于“或”
  • must_not:必须不匹配,不参与算分,类似于“非”
  • filter:必须匹配,不参与算分

语法:

GET /hotel/_search
{"query":{"bool":{"must":[{"查询方式":{"字段名":"字段值"}},{"查询方式":{"字段名":"字段值"}},……],"should":[{"查询方式":{"字段名":"字段值"}},{"查询方式":{"字段名":"字段值"}},……],"must_not":[{"查询方式":{"字段名":"字段值"}},……],"filter":[{"查询方式":{"字段名":"字段值"}},……]}}
}

搜索结果处理

排序

elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序的字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

GET /index_name/_search
{//正常搜索内容部分"query":{"搜索方式":{"字段名":"搜索内容"}},"sort":[{"字段名":"排序方式(desc/asc)"},{"字段名":"排序方式(desc/asc)"},……]
}

sort数组中的一个元素代表根据一个字段排序及其排序方式,排序的优先级是按数组中的顺序,从前到后,依次递减。

如果是地理坐标类型的数据,sort数组里面的元素的写法要有些区别:

"sort":[{"_geo_distance":{"字段名":"经度值,纬度值",  //坐标"order":"asc",  //排序方式"unit":"km"  //单位}},……]

分页

elasticsearch默认情况下只返回top10的数据,如果要查询更多数据,就需要修改分页参数了。

elasticsearch中通过修改from、size参数来控制要返回的分页结果:

GET /hotel/_search
{"query":{"搜索方式":{"字段名":"搜索内容"}},"from":60,  //分页开始的位置如果想第n页,值为(n-1)*size"size":20
}

深度分页问题

es分页是选取从第一个到指定分页中最后一个的所有数据,再把前面的不要的数据丢掉,在集群的时候,就会面临问题。

es一般是分布式的,所以会面临深度分页问题。比如要搜数据中的前一千条,就是从每个集群中搜索指定的一千条数据,再汇总这些每个一千条,从汇总中取出新的前一千条。

深度分页,es提供了俩种解决方案:

search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。

scroll:将排序数据形成快照,保存在内存中。官方已经不推荐使用。

高亮

高亮就是在搜索结果中把搜索关键字突出显示。

语法:

GET /hotel/_search
{"query":{"搜索方式":{"字段名":"搜索内容"}},"highlight":{"fields":{"字段名":{"pre_tags":"<em>",  //用来标记高亮字段的前置标签"post_tags":"</em>" //用来标记高亮字段的后置标签 },"字段名2":{"pre_tags":"<em>",   //用来标记高亮字段的前置标签"post_tags":"</em>", //用来标记高亮字段的后置标签 "require_field_match":"false"}}}
}

但是默认情况下,es搜索字段必须要和高亮字段一致,如果不是一致的字段,要加上require_field_match配置项

数据聚合

聚合(aggregations)可以实现对文档数据的统计、分析、运算。常见的聚合有三类:

  • 桶(Bucket)聚合:用来对文档做分组。常见的有以下两种聚合类型

    • TermAggregation:按照文档字段值进行分组。
    • DateHistogram:按照日期阶梯进行分组,例如一周为一组,或者一个月为一组。

    DSL语法:

    GET /hotel/_search
    {"size":0, //定义size为零,结果中不包含文档,只包含聚合结果"aggs":{"聚合名":{  //给聚合自定义一个名字 "聚合类型":{"field":"字段名","size":20 //希望聚合的结果的数量}}}
    }
    

    默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。

    可以加一个order字段,进行修改结果排序方式:

    GET /hotel/_search
    {"size":0, //定义size为零,结果中不包含文档,只包含聚合结果"aggs":{"聚合名":{  //给聚合自定义一个名字 "聚合类型(同查询类型)":{"field":"字段名","order":{"字段名":"asc/desc"  //排序方式},"size":20 //希望聚合的结果的数量}}}
    }
    

    默认情况下,Bucket聚合是对索引库中的所有的文档做聚合,我们可以限定文档要聚合的文档范围,只要添加query条件即可:

    GET /hotel/_search
    {"query":{"搜索方式":{"字段名":"搜索内容"}},"size":0, //定义size为零,结果中不包含文档,只包含聚合结果"aggs":{"聚合名":{  //给聚合自定义一个名字 "聚合类型(同查询类型)":{"field":"字段名","order":{"字段名":"asc/desc"  //排序方式},"size":20 //希望聚合的结果的数量}}}
    }
    
  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等。

    • Avg:求平均值
    • Max:求最大值
    • Min:求最小值
    • Stats:同时求Max、Min、Avg、Sum等。
    GET /hotel/_search
    {"size":0, //定义size为零,结果中不包含文档,只包含聚合结果"aggs":{"聚合名":{  //给聚合自定义一个名字 "聚合类型(同查询类型)":{"field":"字段名","size":20 //希望聚合的结果的数量}"aggs":{  //是上一层聚合的子聚合,也就是分组后对每组进行计算"聚合名称":{  //子聚合名称"stats":{  //metric聚合类型"field":"字段名"  //要聚合的字段}}}}}
    }
    

    排序的时候,就可以使用子聚合中的结果来排序,直接将子聚合的聚合名作为字段名即可,使用stats的时候,就使用 子聚合名.avg 、 子聚合名.min 等等作为字段名即可。

  • 管道(pipeline)聚合:其他聚合的结果为基础再做聚合。

自动补全

自定义分词器

elasticsearch中分词器(analyzer)的组成包含三部分:

  • character filter:在tokenizer之前对文本进行处理。例如删除字符、替换字符。
  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等。

要实现在拼音分词的之前,能先进行ik分词,可以在创建索引的时候,通过settings来配置自定义的analyzer分词器,而且,也要设置一些拼音分词器的参数,让其能达到更好的拼音分词效果:

PUT /test
{"settings":{"analysis":{"analyzer":{ //用于创建自定义分词"自定义分词器名称":{"tokenizer":"ik_max_word","filter":"pinyin"}},"filter": { // 自定义tokenizer filter"py": { // 自定义过滤器名称"type": "pinyin", // 过滤器类型,这里是pinyin"keep_full_pinyin": false,"keep_joined_full_pinyin": true,"keep_original": true,"limit_first_letter_length": 16,"remove_duplicated_term": true,"none_chinese_pinyin_tokenize": false}}}},"mappings":{"properties":{"字段名":{"type":"字段类型","analyzer":"my_analyzer","search_analyzer":"ik_smart"}}}
}

因为不同的同音中文对应的拼音会一致,为了防止搜出同音词,应该要在倒排索引的时候使用拼音分词器,而搜索的时候不应该使用拼音分词器。要再添加一个search_analyzer,设置为ik_smart,如此搜索的时候,就会使用search_analyzer配置的分词器而不会走analyzer的分词器。

自动补全查询

completion suggester查询

这个查询会匹配用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中的字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。
  • 字段的内容一般是用来补全的多个词条形成的数组。

查询语法如下:

GET /test/_search
{"suggest":{"title_suggest":{"text":"搜索内容","completion":{"field":"title",  //补全查询的字段"skip_duplicates":true,"size":10}}}
}

可以在后端的业务里,将要进行自动补全查询的字段,加到一个list里面,再将这个list赋值给这个自动补全查询字段

数据同步

当关于某个实体的数据更新或者增加的时候,当然在es和数据库中的数据都要做相应的数据更新。

  • 方案一:同步调用
    • 即先更新数据库,数据库中更新完数据之后,再调用更新索引中的文档的接口,更新es中的数据。
    • 优点:简单
    • 缺点:耦合度高
  • 方案二:异步调用
    • 更新数据库的操作时候,发送一条消息给mq,mq监听消息之后,调用更新索引的文档的接口,更新es中的数据。
    • 优点:低耦合
    • 缺点:依赖mq的可靠性
  • 方案三:监听binlog
    • 使用一些中间件,比如canal,监听binlog,来监听mysql中的增删改操作,当相应的数据发生改变的时候,触发相应的更新es中的操作。
    • 优点:完全解除服务间的耦合
    • 缺点:增加数据库负担

SpringBoot整合ElasticSearch(Elasticsearch Java API Client操作索引库)

初始化

maven导入依赖:

<!--        elasticsearch的导入--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency>

配置类配置基本的es连接信息(记得Java这里的import不要导入错了依赖)

yaml:

elasticsearch:host: xxx.xxx.xxx.xxx #主机地址port: 9200 #es端口scheme: http #协议

连接信息配置类:

@ConfigurationProperties(prefix = "elasticsearch")
@Component
@Data
public class EsProperties {private String host;private Integer port;private String scheme;
}

es操作的组件的配置类:

builder中的new的HttpHost对象就是在配置好client的连接信息。

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import jakarta.annotation.Resource;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ElasticSearchConfig {@Resourceprivate EsProperties esProperties;@Beanpublic ElasticsearchClient esClient() {RestClient restClient = RestClient.builder(new HttpHost(esProperties.getHost(),esProperties.getPort(),esProperties.getScheme())).build();  ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());return new ElasticsearchClient(transport);  }
}

索引操作

由于我感觉,索引操作好像一般不会放在springboot中进行,一般进行的应该都是文档操作之类的,就简单展示一下索引操作的Java代码:

// 索引名字  
String indexName = "student"; // 索引是否存在  
BooleanResponse books = esClient.indices().exists(e -> e.index(indexName));  
System.out.println("索引是否存在:" + books.value());  // 创建索引  
esClient.indices().create(c -> c  .index(indexName)  .mappings(mappings -> mappings  // 映射  .properties("name", p -> p  .text(t -> t // text类型,index=false  .index(false)  )  )  .properties("age", p -> p  .long_(t -> t) // long类型 )  )  
); // 删除索引  
esClient.indices().delete(d -> d.index(indexName));

esClient就是上面的配置文件中配置的ioc容器中的ElasticSearchClient类型的组件,直接使用autowired或者resource注解注入即可。

文档操作

新增:

// 新增
CreateResponse createResponse = esClient.create(c -> c  .index(indexName) // 索引名字  .id(account.getId()) // id  .document(account) // 实体类  
);

我使用的是es8的方式,使用ElasticsearchClient进行操作,这种方式中有着较多的lambda的写法。且会有多层调用,可以像我这种写法,能将参数分析清楚。

删除:

DeleteResponse deleteResp = esClient.delete(d -> d.index(indexName).id("1"));

批量新增:

List<Account> accountList = ...
BulkRequest.Builder br = new BulkRequest.Builder();  
for (Account acc : accountList) {  br.operations(op -> op  .create(c -> c  .index(indexName)  .id(acc.getId())  .document(acc)  )  );  
}  
BulkResponse bulkResp = esClient.bulk(br.build());

批量新增操作要使用到Bulk。

根据id查找:

      	GetResponse<ArticleVo> getResp = esClient.get(g ->g.index("article_index1").id("17770146669-1732611392477"), ArticleVo.class);if (getResp.found()) {ArticleVo source = getResp.source();  // 这就是得到的实体类source.setArticleId(getResp.id());System.out.println(source);}

高亮、分页、排序查找:

三个知识点我就放在一起了,其实和普通的DSL的写法大致,只要DSL掌握的好,再按这个结果写,写起来会很简单。

    public ResultData<List<ArticleESVo>> searchArticlePage(String message, Integer pageSize, Integer pageNum) throws IOException {SearchResponse<ArticleESVo> search = esClient.search(s -> s.index("article_index1")  //指明索引.from((pageNum-1) * pageSize)  //分页开始数.size(pageSize)  //每页的页大小.sort(so ->so  //排序配置.field(f -> f.field("publicTimeView") //排序字段.order(SortOrder.Desc).field("likes")  //排序字段.order(SortOrder.Desc))).query(q -> q  //查询配置.match(t -> t.field("all")  //查询字段,这里直接查all.query(message)      //用户的查询内容)).highlight(h ->h  //高光配置.preTags("<span color='red'>")  //高光部分的前置标签.postTags("</span>")  //高光部分的后置标签.fields("title",hi ->hi)    //要高光的字段.fields("mainContent",hi2->hi2)   //要高光的字段.requireFieldMatch(false)   //设置为不需要匹配查询字段也行), ArticleESVo.class);List<ArticleESVo> articleESVoList = new ArrayList<>();System.out.println(search + "es给回来的数据数据是这样子的");List<Hit<ArticleESVo>> hits = search.hits().hits();  //目标实体的数据都在这里面,但是还是存在一个Hit对象里面,高光和普通数据,都要从这个hit里面获取。for (Hit<ArticleESVo> hit : hits) {List<String> listTitleHighLight = hit.highlight().get("title");  //获取高光部分的内容List<String> listMainContentHighLight = hit.highlight().get("mainContent");  //获取高光部分的内容ArticleESVo articleESVo = hit.source();if (listTitleHighLight != null){articleESVo.setTitle(listTitleHighLight.get(0));  //如果标题有高光,就替换掉}if (listMainContentHighLight != null){articleESVo.setMainContent(listMainContentHighLight.get(0));  //如果文章摘要有高光,就替换掉}articleESVoList.add(articleESVo);  //将目标数据加到要返回的集合中}System.out.println(articleESVoList + " 最终结果");return ResultData.success(articleESVoList);}

高光字段是存在hit参数里面的highlight参数里面的,通过get方法指定高光处理后的字段值,再覆盖掉原来source中获取的实体类的相应字段(source中的实体对象中的字段信息是没有经过高光处理的),如果没有指定的高光信息,就不能赋值过去的,不然会报空指针异常,所以要先进行一个判断。

自动补全:

    public ResultData<List<String>> suggestSearch(String message) throws IOException {SearchResponse<ArticleESVo> search = esClient.search(s -> s.index("article_index").suggest(sug -> sug.suggesters("suggest_article", fs -> fs.text(message).completion(te -> te.field("suggestion").skipDuplicates(true).size(10)))), ArticleESVo.class);List<String> list = new ArrayList<>();System.out.println(search + "推荐结果");Suggestion<ArticleESVo> suggest_article = search.suggest().get("suggest_article").get(0);List<CompletionSuggestOption<ArticleESVo>> options = suggest_article.completion().options();for (CompletionSuggestOption<ArticleESVo> option : options) {String text = option.text();list.add(text);}return ResultData.success(list);}

可以看出,自动补全的Elasticsearch Java API Client的写法看起来和DSL的写法非常相似,可见,掌握好DSL的语法,对使用Elasticsearch Java API Client非常有帮助。我这里的写法是用一个String类型的集合,存储所有的自动补全词条,再将其返回给前端。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/62576.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Y20030018基于Java+Springboot+mysql+jsp+layui的家政服务系统的设计与实现 源代码 文档

家政服务系统的设计与实现 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 随着人们生活水平的提高&#xff0c;老龄化、少子化等多重因素影响&#xff0c;我国对家政服务人群的需求与日俱增。家政服务行业对我国的就业和社会效益贡献也与日俱增&#…

南京仁品耳鼻喉专科医院:12月启动公益义诊月

专业医疗资源送至“家门口”&#xff01;南京仁品耳鼻喉专科医院启动公益义诊月 随着2024年即将步入尾声&#xff0c;南京仁品耳鼻喉医院为回馈社会&#xff0c;提升公众健康福祉&#xff0c;将于12月隆重推出“三甲专家公益义诊月”活动。此次活动旨在通过汇聚众多耳鼻喉领域…

ospf协议(动态路由协议)

ospf基本概念 定义 OSPF 是典型的链路状态路由协议&#xff0c;是目前业内使用非常广泛的 IGP 协议之一。 目前针对 IPv4 协议使用的是 OSPF Version 2 &#xff08; RFC2328 &#xff09;&#xff1b;针对 IPv6 协议使用 OSPF Version 3 &#xff08; RFC2740 &#xff09;。…

Linux - nfs服务器

五、nfs服务器 1、基础 NFS服务器可以让PC将网络中的NFS服务器共享的目录挂载到本地端的文件系统中&#xff0c;而在本地端的系统 中看来&#xff0c;那个远程主机的目录就好像是自己的一个磁盘分区一样。 由于NFS支持的功能比较多&#xff0c;而不同的功能都会使用不同的程…

现代网络架构PCI DSS合规范围确定和网络分割措施实施探讨

本文为atsec和作者技术共享类文章&#xff0c;旨在共同探讨信息安全业界的相关话题。未经许可&#xff0c;任何单位及个人不得以任何方式或理由对本文的任何内容进行修改。转载请注明&#xff1a;atsec信息安全和作者名称 1 引言 支付卡行业数据安全标准 &#xff08;P…

第二讲:C++基础语法与程序结构

课程目标&#xff1a; 使学生掌握C的基本语法规则。教授学生如何编写简单的C程序&#xff0c;并理解程序的基本结构。通过实践&#xff0c;加深学生对C编程的理解和兴趣。 课程内容&#xff1a; 一、引言 回顾上一讲关于C的概述&#xff0c;强调学习基础语法的重要性。简要…

鸿蒙开发:自定义一个任意位置弹出的Dialog

前言 鸿蒙开发中&#xff0c;一直有个问题困扰着自己&#xff0c;想必也困扰着大多数开发者&#xff0c;那就是&#xff0c;系统提供的dialog自定义弹窗&#xff0c;无法实现在任意位置进行弹出&#xff0c;仅限于CustomDialog和Component struct的成员变量&#xff0c;这就导致…

深入浅出:开发者如何快速上手Web3生态系统

Web3作为互联网的未来发展方向&#xff0c;正在逐步改变传统互联网架构&#xff0c;推动去中心化技术的发展。对于开发者而言&#xff0c;Web3代表着一个充满机遇与挑战的新领域&#xff0c;学习和掌握Web3的基本技术和工具&#xff0c;将为未来的项目开发提供强大的支持。那么…

Q-2A型金相试样切割机

产品概述 在金相试样制备过程中&#xff0c;试样材料的切割是试样制备的首道重要工序,本机利用高速旋转的薄片砂轮来截取试样&#xff0c;适直切割较硬的金属材料&#xff0c;本机有冷却装置&#xff0c;用来带走切割时所产生的热量&#xff0c;避免试样过热而改变组织。 主要…

十二、Pod的扩缩容-手动/自动-HPA

在实际生产系统中,经常会遇到某个服务需要扩容的场景,也可能会遇到由于资源紧张或者工作负载降低而需要减少服务实例数量的场景。此时可以利用Deployment/RC的Scale机制来完成这些工作。 Kubernetes对Pod的扩缩容操作提供了手动和自动两种模式,手动模式通过运行kubectl sca…

Ubuntu环境中RocketMQ安装教程

参考教程 https://blog.csdn.net/weixin_56219549/article/details/126143231 1、安装JDK&#xff0c;并配置环境变量&#xff08;略&#xff09; 2、下载RocketMQ安装包 RocketMQ下载地址&#xff0c;选择二进制包下载 unzip rocketmq-all-5.0.0-ALPHA-bin-release.zip 使…

传输控制协议(TCP)

传输控制协议是Internet一个重要的传输层协议。TCP提供面向连接、可靠、有序、字节流传输服务。 1、TCP报文段结构 注&#xff1a;TCP默认采用累积确认机制。 2、三次握手、四次挥手 &#xff08;1&#xff09;当客户向服务器发送完最后一个数据段后&#xff0c;发送一个FIN段…

我们来学mysql -- 事务之概念(原理篇)

事务的概念 题记一个例子一致性隔离性原子性持久性 题记 在漫长的编程岁月中&#xff0c;存在一如既往地贯穿着工作&#xff0c;面试的概念这类知识点&#xff0c;事不关己当然高高挂起&#xff0c;精准踩坑时那心情也的却是日了&#x1f436;请原谅我的粗俗&#xff0c;遇到B…

2024 ccpc 辽宁省赛 E(构造 思维?)L(二分+一点点数论知识?)

E 题意&#xff1a; 可以注意到&#xff1a; 我的两种方格都四个方格的大小。 所以 如果存在一种摆放方式 那么 4|nm。 再考虑一种特殊的情况 22 &#xff0c;此时虽然我的积是4 但是无法摆放的。 1>对于 4 | n,或者 4 | m.我直接摆放第二种方格就可以了。 如果我n 是4 的…

自定义类型: 结构体、枚举 、联合

目录 结构体 结构体类型的声明 匿名结构体 结构的自引用 结构体变量的定义和初始化 结构体成员变量的访问 结构体内存对齐 结构体传参 位段 位段类型的声明 位段的内存分配 位段的跨平台问题 位段的应用 枚举 枚举类型的定义 枚举的优点 联合体(共用体) 联合…

道可云人工智能元宇宙每日资讯|第三届京西地区发展论坛成功召开

道可云元宇宙每日简报&#xff08;2024年11月27日&#xff09;讯&#xff0c;今日元宇宙新鲜事有&#xff1a; 工信部等十二部门印发《5G规模化应用“扬帆”行动升级方案》 11月25日&#xff0c;工业和信息化部等十二部门印发《5G规模化应用“扬帆”行动升级方案》。《方案》…

更多开源创新 挑战OpenAI-o1的模型出现和AI个体模拟突破

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

说说Elasticsearch查询语句如何提升权重?

大家好&#xff0c;我是锋哥。今天分享关于【说说Elasticsearch查询语句如何提升权重&#xff1f;】面试题。希望对大家有帮助&#xff1b; 说说Elasticsearch查询语句如何提升权重&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Elasticsearch 中&…

基于协同推荐的黔醉酒业白酒销售系统

文末获取源码和万字论文 摘 要 基于协同推荐的黔醉酒业白酒销售系统主要针对黔醉酒业的具体业务需求所设计&#xff0c;现阶段阶段我国大型企业都会有自己的电商平台以及销售管理系统&#xff0c;其功能对于中小型过于冗长复杂&#xff0c;成本也不是中小型企业能够承受的&…

【Redis】—0.1、Ubuntu20.04源码编译部署redis6.2.7

1、Redis下载 创建redis的目录&#xff1a;mkdir -p /data/db/redis 下载redis&#xff1a;https://redis.io/download/ 2、上传文件到目录后解压 tar xvf redis-6.2.7.tar.gz 3、安装redis的依赖软件更新gcc&#xff0c;装一系列软件包&#xff0c;gcc&#xff0c;g和make。 s…