es检索学习笔记第一篇

概念

倒排索引

想象一下mysql正序索引,从id出发,由B+树作为结构组织索引,最后查到某条记录(文档)

而倒排索引是由文档开始检索,倒序找到的id,符合关键字查询的设计

分词器ik

es自带的standard分词不智能,推荐使用ik分词器

IK分词器包含两种模式:

  • ik_smart:最少切分

  • ik_max_word:最细切分

# 测试分词
POST /_analyze
{
  "text":"好好学习,天天向上学java",
  "analyzer": "standard"
}

创建mapping

mapping——索引的约束,表结构

doc——文档,一条记录

# 创建索引库(索引约束)
PUT /lan
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email": {
        "type": "keyword",
        "index": false
      },
      "name": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword"
          },
          "lastName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

查看索引,修改,删除

# 查看索引
GET /lan/_mapping

# 修改索引库,添加新字段
PUT /lan/_mapping
{
  "properties":{
    "age":{
      "type":"long"
    }
  }
}

# 删除
DELETE /lan

添加文档,查看文档,修改文档,查看文档

不指定id,那么分配一个随机id

# 添加文档,指定了id为1,不指定则随机分配一串字符串
POST /lan/_doc/1
{
  "info":"奥利给点赞大王",
  "email":"agreeKing@aoligei.com",
  "age":"11",
  "name":{
    "firstName":"无名",
    "lastName":"陈"
  }
}

# 全量修改-全量覆盖原来的文档,完全匹配,没有该id则是添加记录,少了字段相当于删除
PUT /lan/_doc/2
{
  "info":"奥利给点赞大王",
  "email":"agreeKing@aoligei.com",
  "age":"10086"
}

# 增量修改,对该id下的某个字段尽行修改
POST /lan/_update/3
{
  "doc": {
    "name": {
      "firstName": "无名",
      "lastName": "张"
    }
  }
}

# 查看文档
GET /lan/_doc/1

RestClient

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。

java-RestClient官网:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html

一、映射分析

创建索引库,最关键的是mapping映射,而mapping映射要考虑的信息包括:

  • 字段名
  • 字段数据类型
  • 是否参与搜索
  • 是否需要分词
  • 如果分词,分词器是什么?

其中:

  • 字段名、字段数据类型,可以参考数据表结构的名称和类型
  • 是否参与搜索要分析业务来判断,例如图片地址,就无需参与搜索
  • 是否分词呢要看内容,内容如果是一个整体就无需分词,反之则要分词
  • 分词器,我们可以统一使用ik_max_word
# 酒店的mapping
PUT /hotel
{
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword",
        "index": false
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "address":{
        "type":"keyword",
        "index": false
      },
      "price":{
        "type":"integer"
      },
      "sorce":{
        "type":"integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword",
        "copy_to": "all"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

几个特殊字段说明:

  • location:地理坐标,里面包含精度、纬度
  • all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索

“copy_to”: “all”,逻辑上的视图,可将分词的文档逻辑上复制到该字段,方便检索,用户检索可通过这个字段

二、使用client创建索引等

构造请求,添加参数,发送请求

基本上所有api请求都是围绕着client.indices()restfulIndexRequest

创建RestClient
private RestHighLevelClient client;

@BeforeEach
void createClient() {
    client = new RestHighLevelClient(
        RestClient.builder(
            HttpHost.create("http://192.168.234.129:9200")
        ));
}
创建索引
@Test
public void testCreateIndex() throws IOException {
    // 1、构造请求
    CreateIndexRequest request = new CreateIndexRequest("hotel");
    // 2、添加参数,DSL语句
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // 3、发送请求
    client.indices().create(request, RequestOptions.DEFAULT);
}
删除索引,判断是否有该索引
@Test
public void testDeleteIndex() throws IOException {
    String index = "hotel";
    // 1、构造请求
    DeleteIndexRequest deleteRequest = new DeleteIndexRequest(index);
    // 2、判断是否有该索引
    GetIndexRequest getIndexRequest = new GetIndexRequest(index);
    boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
    // 3、发送请求
    if (exists){
        client.indices().delete(deleteRequest, RequestOptions.DEFAULT);
    }else {
        System.out.println("索引:" + index + "不存在");
    }
}
总结

JavaRestClient操作elasticsearch的流程基本类似。核心是client.indices()方法来获取索引库的操作对象。

索引库操作的基本步骤:

  • 初始化RestHighLevelClient
  • 创建XxxIndexRequest。XXX是Create、Get、Delete
  • 准备DSL( Create时需要,其它是无参)
  • 发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete

三、使用client操作数据等

添加数据
@Test
public void testAddDocumentById() throws IOException {
    String index = "hotel";
    // 1、查找数据库
    Hotel hotel = hotelService.getById(61075);
    // 2、转换成es的数据,有可能数据库查出来的数据是需要处理的,这里处理的是坐标
    HotelDoc hotelDoc = new HotelDoc(hotel);
    // 3、转换成json字符创,构造请求,添加请求参数,顺便带上id,是数据库的id
    IndexRequest request = new IndexRequest(index).id(hotel.getId().toString());
    request.source(JSONUtil.toJsonStr(hotelDoc),XContentType.JSON);
    client.index(request,RequestOptions.DEFAULT);
}
  • 1)创建Request对象
  • 2)准备请求参数,也就是DSL中的JSON文档
  • 3)发送请求

变化的地方在于,这里直接使用client.xxx()的API,不再需要client.indices()了。

查找数据

在这里插入图片描述

@Test
public void testGetDocumentById() throws IOException {
    String index = "hotel";
    // 1、创建索引
    GetRequest request = new GetRequest(index, "61075");
    // 2、发送请求
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    // 3、获取
    String sourceAsString = response.getSourceAsString();
    HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
    System.out.println(hotelDoc);
}

  • 1)准备Request对象。这次是查询,所以是GetRequest
  • 2)发送请求,得到结果。因为是查询,这里调用client.get()方法
  • 3)解析结果,就是对JSON做反序列化
删除数据
@Test
void testDeleteDocument() throws IOException {
    // 1.准备Request
    DeleteRequest request = new DeleteRequest("hotel", "61083");
    // 2.发送请求
    client.delete(request, RequestOptions.DEFAULT);
}
  • 1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id
  • 2)准备参数,无参
  • 3)发送请求。因为是删除,所以是client.delete()方法
修改数据
@Test
public void testUpdateDocumentById() throws IOException {
    String index = "hotel";
    // 1、创建索引
    UpdateRequest request = new UpdateRequest(index, "61075");
    // 增量修改
    request.doc("price", "952",
                "starName", "四钻");
    // 2、发送请求
    client.update(request, RequestOptions.DEFAULT);
}

有增量修改和全量修改,这里是增量修改

  • 1)准备Request对象。这次是修改,所以是UpdateRequest
  • 2)准备参数。也就是JSON文档,里面包含要修改的字段
  • 3)更新文档。这里调用client.update()方法
bulk批量操作
@Test
public void testBulkDocumentById() throws IOException {
    String index = "hotel";
    // 1、查询数据库
    List<Hotel> hotelList = hotelService.list();
    // 2、构造请求
    BulkRequest bulkRequest = new BulkRequest(index);
    for (Hotel hotel:hotelList){
        HotelDoc hotelDoc = new HotelDoc(hotel);
        IndexRequest request = new IndexRequest(index).id(hotel.getId().toString());
        request.source(JSONUtil.toJsonStr(hotelDoc),XContentType.JSON);
        bulkRequest.add(request);
    }
    // 发送请求
    client.bulk(bulkRequest,RequestOptions.DEFAULT);
}
总结
  • 初始化RestHighLevelClient
  • 创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
  • 准备参数(Index、Update、Bulk时需要)
  • 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
  • 解析结果(Get时需要)

四、DSL查询语法

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

  • 查询所有:查询出所有数据,一般测试用。例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match_query
    • multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:

    • ids
    • range
    • term
  • 地理(geo)查询:根据经纬度查询。例如:

    • geo_distance
    • geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

    • bool
    • function_score
4.1、查询的语法
GET /indexName/_search
{
  "query": {
    "查询类型": {
      "查询条件": "条件值"
    }
  }
}
4.2、全文查找
GET /hotel/_search
{
  "query": {
    "match_all": {}
  }, 
  "size": 10
}

# 因为all逻辑上包含了copy_to的所有分词或keyword关键词,所以单找all是可以的
GET /hotel/_search
{
  "query": {
    "match": {
      "all":"外滩如家"
    }
  },
  "size": 10
}

# 多值查询,但是更推荐copy_to,因为多值查询性能不好
GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "外滩如家",
      "fields": ["brand","name","city"]
    }
  },
  "size": 10
}
总结

match和multi_match的区别是什么?

  • match:根据一个字段查询
  • multi_match:根据多个字段查询,参与查询字段越多,查询性能越差
4.3、精确查找

term:完全匹配,必须不分词

# 精确查询
GET /hotel/_search
{
  "query": {
    "term": {
      "city":"深圳"
    }
  },
  "size": 10
}

range:范围查找,一般对是数值类型

# 范围查找
GET /hotel/_search
{
  "query": {
    "range": {
      "score":{
        "gte": 40,
        "lte": 60
      }
    }
  },
  "size": 10
}
总结

精确查询常见的有哪些?

  • term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
  • range查询:根据数值范围查询,可以是数值、日期的范围
4.4、地理位置查找

所谓的地理坐标查询,其实就是根据经纬度查询,官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-queries.html

常见的使用场景包括:

  • 携程:搜索我附近的酒店
  • 滴滴:搜索我附近的出租车
  • 微信:搜索我附近的人

附近的酒店:

在这里插入图片描述

附近的车:

在这里插入图片描述

4.4.1.矩形范围查询

矩形范围查询,也就是geo_bounding_box查询,查询坐标落在某个矩形范围的所有文档:

在这里插入图片描述

查询时,需要指定矩形的左上右下两个点的坐标,然后画出一个矩形,落在该矩形内的都是符合条件的点。

语法如下:

// geo_bounding_box查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上点
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

这种并不符合“附近的人”这样的需求,所以我们就不做了。

4.4.2附近查询

附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。

换句话来说,在地图上找一个点作为圆心,以指定距离为半径,画一个圆,落在圆内的坐标都算符合条件:

在这里插入图片描述

语法说明:

// geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "FIELD": "31.21,121.5" // 圆心
    }
  }
}

示例:

我们先搜索陆家嘴附近15km的酒店:

在这里插入图片描述

发现共有47家酒店。

然后把半径缩短到3公里:

在这里插入图片描述

可以发现,搜索到的酒店数量减少到了5家。

4.5、复合查询

可以将简单的查询组合起来,实现一些复杂一点的逻辑

4.5.1、算分查询
相关性算分

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。

例如,我们搜索 “虹桥如家”,结果如下:

[
  {
    "_score" : 17.850193,
    "_source" : {
      "name" : "虹桥如家酒店真不错",
    }
  },
  {
    "_score" : 12.259849,
    "_source" : {
      "name" : "外滩如家酒店真不错",
    }
  },
  {
    "_score" : 11.91091,
    "_source" : {
      "name" : "迪士尼如家酒店真不错",
    }
  }
]

在elasticsearch中,早期使用的打分算法是TF-IDF算法,公式如下:

在这里插入图片描述

在后来的5.1版本升级中,elasticsearch将算法改进为BM25算法,公式如下:

在这里插入图片描述

TF-IDF算法有一各缺陷,就是词条频率越高,文档得分也会越高,单个词条对文档影响较大。而BM25则会让单个词条的算分有一个上限,曲线更加平滑:

在这里插入图片描述

小结:elasticsearch会根据词条和文档的相关度做打分,算法由两种:

  • TF-IDF算法
  • BM25算法,elasticsearch5.1版本后采用的算法

当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "multiply"
    }
  }
}

在这里插入图片描述

4.5.2、布尔查询

bool查询有几种逻辑关系?

  • must:必须匹配的条件,可以理解为“与”
  • should:选择性匹配的条件,可以理解为“或”
  • must_not:必须不匹配的条件,不参与打分
  • filter:必须匹配的条件,不参与打分

差别在于算不算分,有些字段是不参与算分,不要用算分的条件,不然算分是会影响性能和结果的,像价格地理坐标

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "all": "如家"
          }
        },
        {
          "geo_distance": {
            "distance": "10km",
            "location": {
              "lat": 31.21,
              "lon": 121.5
            }
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "gte": 400
            }
          }
        }
      ]
    }
  }
}

五、结果处理

5.1、排序

可以排序的字段类型有:keyword,数值,地理坐标,日期

基本语法

排序的sort和查询的query是同级的

普通排序
# 普通类型
GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
    }
  ]
地理坐标排序
# 地理坐标类型
GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance" : {
          "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
          "order" : "asc", // 排序方式
          "unit" : "km" // 排序的距离单位
      }
    }
  ]
}
5.2、分页

elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:

  • from:从第几个文档开始
  • size:总共查询几个文档

类似于mysql中的limit ?, ?

from和size是和query同级的

基本分页
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}
深度分页

对于es来说,一定是会搭建集群的,到时候每个分片的数据都不一样

例如需要9900-10000,排序分页必须是每个分片先选好各自的9900-10000,再一起比较,选出其中的100条

在这里插入图片描述

显然这及其影响内存和性能,所以es限定最多分页10000条,超出则报错

在这里插入图片描述

对于深度分页,es有两种方案

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  • scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。
5.3、高亮

把搜索关键字在查询结果高亮显示,一般是查询结果的文本会用到html标签或者css样式

高亮的语法

GET /hotel/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": { // 指定要高亮的字段
      "FIELD": {
        "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
        "post_tags": "</em>" // 用来标记高亮字段的后置标签
      }
    }
  }
}

注意:

  • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false

示例

在这里插入图片描述

总结

查询的DSL是一个大的JSON对象,包含下列属性:

  • query:查询条件
  • from和size:分页条件
  • sort:排序条件
  • highlight:高亮条件

示例:

在这里插入图片描述

六、java发起查询请求

在这里插入图片描述

想象source是DSL查询语句的外层大括号,那么之后的方法就是大括号里面的结构了,这里关键的API有两个,一个是request.source(),其中包含了查询、排序、分页、高亮等所有功能:

在这里插入图片描述

另一个是QueryBuilders,其中包含match、term、function_score、bool等各种查询:

在这里插入图片描述

/**
 * 查询所有
 */
@Test
public void testMatchAll() throws IOException {
    String index = "hotel";
    // 1、创建查询请求
    SearchRequest request = new SearchRequest(index);
    // 2、添加查询参数
    request.source().query(QueryBuilders.matchAllQuery());
    // 3、发送查询请求
    SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
    // 解析响应结果
    SearchHits hits = searchResponse.getHits();
    long count = hits.getTotalHits().value;
    // 遍历查询到的结果
    for (SearchHit hit : hits.getHits()) {
        // 转换json字符串成实体
        String sourceAsString = hit.getSourceAsString();
        HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
        System.out.println(hotelDoc);
    }
}

查询的基本步骤是:

  1. 创建SearchRequest对象

  2. 准备Request.source(),也就是DSL。

    ① QueryBuilders来构建查询条件

    ② 传入Request.source() 的 query() 方法

  3. 发送请求,得到结果

  4. 解析结果(参考JSON结果,从外到内,逐层解析)

在这里插入图片描述

match查询
private void handleSearchResponse(SearchResponse searchResponse) {
    SearchHits hits = searchResponse.getHits();
    long count = hits.getTotalHits().value;
    // 遍历查询到的结果
    for (SearchHit hit : hits.getHits()) {
        // 转换json字符串成实体
        String sourceAsString = hit.getSourceAsString();
        HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
        System.out.println(hotelDoc);
    }
}

@Test
public void testMatchQuery() throws IOException {
    String index = "hotel";
    // 1、创建查询请求
    SearchRequest request = new SearchRequest(index);
    // 2、添加查询参数
    MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("all", "如家");
    request.source().query(matchQueryBuilder);
    // 3、发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4、解析结果
    handleSearchResponse(response);
}

其他的精确匹配term还是范围range都差不多

分页

分页、排序和查询是同级的

request.source().query(matchQueryBuilder)

.from(0).size(10).sort("price",SortOrder.ASC);

高亮

在这里插入图片描述

高亮结果解析是更需要处理的,highlight和查询query是同级的

@Test
public void testMatchQuery() throws IOException {
    String index = "hotel";
    // 1、创建查询请求
    SearchRequest request = new SearchRequest(index);
    // 2、添加查询参数
    MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("all", "如家");
    request.source().query(matchQueryBuilder).from(0).size(10).sort("price",SortOrder.ASC);
    // 高亮
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("name").requireFieldMatch(false);
    highlightBuilder.field("brand").requireFieldMatch(false);
    request.source().highlighter(highlightBuilder);
    // 3、发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4、解析结果
    handleSearchResponse(response);
}

/**
 * 处理响应结果
 */
private void handleSearchResponse(SearchResponse searchResponse) {
    SearchHits hits = searchResponse.getHits();
    long count = hits.getTotalHits().value;
    // 遍历查询到的结果
    for (SearchHit hit : hits.getHits()) {
        // 转换json字符串成实体
        String sourceAsString = hit.getSourceAsString();
        Map<String, HighlightField> highlightFieldMap = hit.getHighlightFields();
        HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
        if (CollectionUtil.isNotEmpty(highlightFieldMap)){
            HighlightField nameField = highlightFieldMap.get("name");
            if (nameField != null){
                String name = nameField.getFragments()[0].toString();
                hotelDoc.setName(name);
            }
        }
        System.out.println(hotelDoc);
    }
}

七、旅游案例

7.1、搜索、分页、地理位置距离排序,广告算分查询

处理以及发送请求,处理和query不同级的地理位置排序、分页

/**
 * 处理以及发送请求,处理和query不同级的地理位置排序、分页
 * @param requestParam
 * @return
 */
@Override
public PageResult selectHotelList(RequestParams requestParam) {
    try {
        String index = "hotel";
        // 1、创建请求
        SearchRequest request = new SearchRequest(index);
        // 2、添加参数
        // 2.1、query
        this.buildBasicQuery(requestParam,request);
        // 2.2、分页
        int page = requestParam.getPage();
        int size = requestParam.getSize();
        request.source().from((page - 1) * size).size(size);
        // 2.3、地理位置排序,由近及远,升序
        String location = requestParam.getLocation();
        if (StrUtil.isNotBlank(location)) {
            request.source().sort(SortBuilders
                                  .geoDistanceSort("location", new GeoPoint(location))
                                  .order(SortOrder.ASC)
                                  .unit(DistanceUnit.KILOMETERS));
        }
        // 3、发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4、结果解析
        return this.handleSearchResponse(response);
    } catch (IOException e) {
        throw new RuntimeException();
    }
}

处理请求参数(品牌、城市、星级等)以及算分

/**
 * 处理请求参数(品牌、城市、星级等)以及算分
 * @param requestParam
 * @param request
 */
private void buildBasicQuery(RequestParams requestParam, SearchRequest request) {
    // 构建BooleanQuery
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 1、城市,不用算分
    if (StrUtil.isNotBlank(requestParam.getCity())) {
        boolQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));
    }
    // 2、关键字
    if (StrUtil.isNotBlank(requestParam.getKey())) {
        boolQuery.filter(QueryBuilders.matchQuery("all", requestParam.getKey()));
    } else {
        boolQuery.filter(QueryBuilders.matchAllQuery());
    }
    // 3、品牌
    if (StrUtil.isNotBlank(requestParam.getBrand())) {
        boolQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));
    }
    // 4、星级
    if (StrUtil.isNotBlank(requestParam.getStarName())) {
        boolQuery.filter(QueryBuilders.termQuery("starName", requestParam.getStarName()));
    }
    // 5、价格
    if (requestParam.getMinPrice() != null && requestParam.getMaxPrice() != null) {
        boolQuery.filter(QueryBuilders
                         .rangeQuery("price")
                         .gte(requestParam.getMinPrice())
                         .lte(requestParam.getMaxPrice()));
    }
    // 6、算分查询,这里判断是不是广告
    FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
        boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                // 过滤条件
                QueryBuilders.termQuery("isAD", true),
                ScoreFunctionBuilders.weightFactorFunction(10)
            )
        }).boostMode(CombineFunction.MULTIPLY);
    // 7、添加参数
    request.source().query(functionScoreQueryBuilder);
}

处理查询到的数据,封装成分页的结果,处理地理位置距离排序,反序列化

/**
 * 处理查询到的数据,封装成分页的结果,处理地理位置距离排序,反序列化
 *
 * @param searchResponse
 * @return
 */
private PageResult handleSearchResponse(SearchResponse searchResponse) {
    SearchHits searchHits = searchResponse.getHits();
    long total = searchHits.getTotalHits().value;
    // 遍历查询到的结果
    SearchHit[] hits = searchHits.getHits();
    List<HotelDoc> hotelDocList = new ArrayList<>(hits.length);
    for (SearchHit hit : hits) {
        // 转换json字符串成实体
        String sourceAsString = hit.getSourceAsString();
        HotelDoc hotelDoc = JSONUtil.toBean(sourceAsString, HotelDoc.class);
        // 获取排序值,地理位置的排序值就是相差的距离
        Object[] sortValues = hit.getSortValues();
        if (sortValues.length > 0) {
            hotelDoc.setDistance(sortValues[0]);
        }
        hotelDocList.add(hotelDoc);
    }
    return new PageResult(total, hotelDocList);
}

像高亮、排序等,有可能不只是只有一个字段,所以返回的是个数组,不同字段的下标不同

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值