【EslaticSearch】--RestAPI案例详解

matchAllQuery 查询所有

在这里插入图片描述

    //查询
    @Test
    public void testMatchAll() throws IOException {
        //1. 构建查询请求
        SearchRequest request = new SearchRequest("hotel");

        //2. 设置查询条件
        request.source().query(
                QueryBuilders.matchAllQuery() //match_all
        );

        //3. 执行请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //4. 解析结果
        System.out.println("结果数量:" + response.getHits().getTotalHits().value);//hits.total.value
        SearchHit[] searchHitArr = response.getHits().getHits();
        for (SearchHit searchHit : searchHitArr) {
            String source = searchHit.getSourceAsString();//hits.hits.get(n).source
            System.out.println(JSON.parseObject(source, HotelDoc.class));
        }
    }

elasticsearch返回的结果是一个JSON字符串,结构包含:

  • hits:命中的结果
  • total:总条数,其中的value是具体的总条数值
  • max_score:所有结果中得分最高的文档的相关性算分
  • hits:搜索结果的文档数组,其中的每个文档都是一个json对象
    • _source:文档中的原始数据,也是json对象

因此,我们解析响应结果,就是逐层解析JSON字符串,流程如下:

  • SearchHits:通过response.getHits()获取,就是JSON中的最外层的hits,代表命中的结果
  • SearchHits.getTotalHits().value:获取总条数信息
  • SearchHits.getHits():获取SearchHit数组,也就是文档数组
    • SearchHit.getSourceAsString():获取文档结果中的_source,也就是原始的json文档数据

matchQuery&multiMatchQuery全文检索

在这里插入图片描述

    //查询 match
   //2. 设置查询条件
        request.source().query(
                QueryBuilders.matchQuery("all", "外滩如家") //match
        );
        
    //查询 multi_match
        //2. 设置查询条件
        request.source().query(
                QueryBuilders.multiMatchQuery("外滩如家", "brand", "business", "name")
        );

termQuery 精确查询

词条查询
在这里插入图片描述

    //词条查询 term
       //2. 设置查询条件
        request.source().query(
                QueryBuilders.termQuery("city", "北京")
        );

rangeQuery 范围查询

在这里插入图片描述

    //2. 构建条件,并添加到查询请求
    request.source().query(
    rangeQuery("price")
    .gte(1000)
    .lte(2000));

地理查询

geoBoundingBoxQuery 矩形范围

在这里插入图片描述

      //2. 设置查询条件
        request.source().query(
                QueryBuilders.geoBoundingBoxQuery("location")
                        .setCorners(new GeoPoint(40.08, 116.47), new GeoPoint(39.9, 116.405))
        );

geoDistanceQuery 圆形范围

在这里插入图片描述

        //2. 设置查询条件
        request.source().query(
                QueryBuilders.geoDistanceQuery("location")
                        .point(39.9, 116.405)//设置圆心坐标
                        .distance("15km")//设置半径
        );

functionScoreQuery 算法函数

在这里插入图片描述

        //2. 设置查询条件
        request.source().query(
                QueryBuilders.functionScoreQuery(
                        QueryBuilders.matchQuery("all", "北京"),//设置查询条件
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ //设置算分函数
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        QueryBuilders.termQuery("brand", "凯悦"),
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )

                        }
                ).boostMode(CombineFunction.MULTIPLY) //设置算法策略
        );

boolQuery 复合查询

在这里插入图片描述

        //2. 设置查询条件
        request.source().query(
                QueryBuilders.boolQuery().must(
                        QueryBuilders.termQuery("name", "如家")
                ).mustNot(
                        QueryBuilders.rangeQuery("price").gte(400)
                ).filter(
                        QueryBuilders.geoDistanceQuery("location")
                               1 .point(39.9, 116.405)//设置圆心坐标
                                .distance("10km")//设置半径
                )
        );

排序 分页

在这里插入图片描述

        //2. 设置查询条件
        request.source().query(QueryBuilders.matchAllQuery());
        request.source().sort("price", SortOrder.ASC);//排序,按照价格正序排
        request.source().from(0).size(10);//分页,从0查,查10个;

高亮

在这里插入图片描述
高亮的结果与查询的文档结果默认是分离的,并不在一起,因此解析高亮的代码需要额外处理:
在这里插入图片描述

    //高亮
    @Test
    public void testHighLight() throws IOException {
        //1. 构建查询请求
        SearchRequest request = new SearchRequest("hotel");

        //2. 设置查询条件
        request.source().query(QueryBuilders.matchQuery("all", "如家"));

        //3. 设置高亮效果
        request.source().highlighter(
                new HighlightBuilder()
                        .field("name")
                        .preTags("<em>")
                        .postTags("</em>").
                        requireFieldMatch(false)
        );

        //4. 执行请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //5. 解析结果
        SearchHit[] searchHits = response.getHits().getHits();
        for (SearchHit searchHit : searchHits) {
            //没有效果的对象
            HotelDoc hotelDoc = JSON.parseObject(searchHit.getSourceAsString(), HotelDoc.class);
            //解析效果,替换上面对象中的title的值
            String name = (searchHit.getHighlightFields().get("name").getFragments())[0].toString();
            hotelDoc.setName(name);

            System.out.println(hotelDoc);
        }
    }

案例:酒店搜索

下面,我们通过一个旅游网站的搜索案例来实战演练下之前学习的知识,我们实现四部分功能:

  • 酒店分页搜索
  • 酒店结果过滤
  • 我周边的酒店
  • 酒店竞价排名

启动我们提供的hotel-demo项目,其默认端口是8089,访问http://localhost:8089,就能看到项目页面了
在这里插入图片描述
请求的信息如下:

  • 请求方式:POST
  • 请求路径:/hotel/list
  • 请求参数:JSON对象,包含4个字段:
    • key:搜索关键字
    • page:页码
    • size:每页大小
    • sortBy:排序,目前暂不实现
  • 返回值:分页查询,需要返回分页结果PageResult,包含两个属性:
    • total:总条数
    • List<HotelDoc>:当前页的数据

定义实体类

实体类有两个,一个是前端的请求参数实体,一个是服务端应该返回的响应结果实体。我们在com.itheima.hotel.pojo包下创建这两个实体类:

package com.itheima.hotel.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RequestParams {
    // 基础查询
    private String key; // 关键字
    private Integer page; // 当前页
    private Integer size; // 每页个数
    private String sortBy; // 排序

    // 过滤条件查询
    private String city; // 城市
    private String brand; // 品牌
    private String starName; // 星级
    private Integer minPrice; // 最低价
    private Integer maxPrice; // 最高价

    // 我当前的地理坐标
    private String location;
}
package com.itheima.hotel.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult {
    private Long total;
    private List<HotelDoc> hotels;
}

添加启动类

实现搜索业务,肯定离不开RestHighLevelClient,我们需要把它注册到Spring中作为一个Bean。

@Bean
public RestHighLevelClient client(){
    return new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://192.168.136.134:9200")
    ));
}

HotelController

package com.itheima.hotel.controller;

import com.itheima.hotel.pojo.PageResult;
import com.itheima.hotel.pojo.RequestParams;
import com.itheima.hotel.service.HotelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
@RequestMapping("/hotel")
public class HotelController {

    @Autowired
    private HotelService hotelService;

    @PostMapping("/list")
    public PageResult search(@RequestBody RequestParams requestParams) throws IOException {
        return hotelService.search(requestParams);
    }
}

HotelService

package com.itheima.hotel.service;

import com.itheima.hotel.pojo.PageResult;
import com.itheima.hotel.pojo.RequestParams;

import java.io.IOException;

public interface HotelService {
    PageResult search(RequestParams requestParams) throws IOException;
}


HotelServiceImpl

package com.itheima.hotel.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.itheima.hotel.pojo.HotelDoc;
import com.itheima.hotel.pojo.PageResult;
import com.itheima.hotel.pojo.RequestParams;
import com.itheima.hotel.service.HotelService;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class HotelServiceImpl implements HotelService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult search(RequestParams requestParams) throws IOException {

        //1. 构建查询请求
        SearchRequest request = new SearchRequest("hotel");

        //2. 设置查询条件
        //2-1创建复合查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //2-2 获取搜索关键字, 设置为查询条件
        String key = requestParams.getKey();
        if (StringUtils.isEmpty(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }

        request.source().query(boolQuery);

        //3. 设置分页
        Integer pageNum = requestParams.getPage();
        Integer pageSize = requestParams.getSize();
        request.source().from((pageNum - 1) * pageSize).size(pageSize);

        //4. 设置排序
        
        //5. 设置高亮
        request.source().highlighter(
                new HighlightBuilder()
                        .field("name")
                        .preTags("<em>").postTags("</em>")
                        .requireFieldMatch(false)
        );

        //6. 发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //7. 处理结果
        return handleResponse(response);
    }

    //处理结果
    private PageResult handleResponse(SearchResponse response) {
        //准备结果集合
        List<HotelDoc> hotels = new ArrayList<>();

        SearchHits searchHits = response.getHits();

        //获取总记录数
        long total = searchHits.getTotalHits().value;

        //获取文档数组
        SearchHit[] hits = searchHits.getHits();

        //遍历文档, 获取结果, 处理高亮, 并且添加到返回结果结合
        for (SearchHit hit : hits) {
            // 获取文档source
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            // 处理高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (CollectionUtil.isNotEmpty(highlightFields)) {
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    String name = highlightField.getFragments()[0].string();
                    hotelDoc.setName(name);
                }
            }
            hotels.add(hotelDoc);
        }

        //组装返回结果
        return new PageResult(total, hotels);
    }
}

酒店结果过滤

需求:添加品牌、城市、星级、价格等过滤功能

需求分析:
在页面搜索框下面,会有一些过滤项:
在这里插入图片描述
在这里插入图片描述
包含的过滤条件有:

  • brand:品牌值
  • city:城市
  • minPrice~maxPrice:价格范围
  • starName:星级

修改搜索业务

修改业务逻辑,在搜索条件之外,添加一些过滤条件

在之前的业务中,只有match查询,根据关键字搜索,现在要添加条件过滤,包括:

- 城市过滤:是keyword类型,用term查询
- 星级过滤:是keyword类型,用term查询
- 品牌过滤:是keyword类型,用term查询
- 价格过滤:是数值类型,用range查询

多个查询条件组合,肯定是boolean查询来组合:

- 关键字搜索放到must中,参与算分
- 其它过滤条件放到filter中,不参与算分
    @Override
    public PageResult list(RequestParams requestParams) throws IOException {

        //1. 构建查询请求
        SearchRequest request = new SearchRequest("hotel");

        //2. 设置查询条件
        //2-1创建复合查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //2-2 获取搜索关键字, 设置为查询条件
        String key = requestParams.getKey();
        if (StringUtils.isEmpty(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }

        //2-3 获取城市、星级、品牌、价格,使用过滤语法筛选
        // 城市
        if (StrUtil.isNotEmpty(requestParams.getCity())) {
            boolQuery.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
        }
        // 星级
        if (StrUtil.isNotEmpty(requestParams.getStarName())) {
            boolQuery.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
        }
        // 品牌
        if (StrUtil.isNotEmpty(requestParams.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
        }
        // 价格
        if (requestParams.getMinPrice() != null && requestParams.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
        }

        request.source().query(boolQuery);

        //3. 设置分页
        Integer pageNum = requestParams.getPage();
        Integer pageSize = requestParams.getSize();
        request.source().from((pageNum - 1) * pageSize).size(pageSize);

        //4. 设置排序
        
        
        //5. 设置高亮
        request.source().highlighter(
                new HighlightBuilder()
                        .field("name")
                        .preTags("<em>").postTags("</em>")
                        .requireFieldMatch(false)
        );

        //6. 发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //7. 处理结果
        return handleResponse(response);
    }

我周边的酒店

需求:让检索到的酒店根据我所处的地理位置进行排序,优先显示附近的酒店
需求分析
在酒店列表页的右侧,有一个小地图,点击地图的定位按钮,地图会找到你所在的位置:
在这里插入图片描述
并且,在前端会发起查询请求,将你的坐标发送到服务端:
在这里插入图片描述
注意: Chrome和FireFox浏览器有时候不会发生查询请求,所有此功能使用Windows自带的Edge浏览器就行测试

添加具体排序

注意:距离的排序跟普通字段的排序在api上有些区别
在这里插入图片描述
修改search方法

    @Override
    public PageResult list(RequestParams requestParams) throws IOException {

        //1. 构建查询请求
        SearchRequest request = new SearchRequest("hotel");

        //2. 设置查询条件
        //2-1创建复合查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //2-2 获取搜索关键字, 设置为查询条件
        String key = requestParams.getKey();
        if (StringUtils.isEmpty(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }

        //2-3 获取城市、星级、品牌、价格,使用过滤语法筛选
        // 城市
        if (StrUtil.isNotEmpty(requestParams.getCity())) {
            boolQuery.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
        }
        // 星级
        if (StrUtil.isNotEmpty(requestParams.getStarName())) {
            boolQuery.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
        }
        // 品牌
        if (StrUtil.isNotEmpty(requestParams.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
        }
        // 价格
        if (requestParams.getMinPrice() != null && requestParams.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
        }

        request.source().query(boolQuery);

        //3. 设置分页
        Integer pageNum = requestParams.getPage();
        Integer pageSize = requestParams.getSize();
        request.source().from((pageNum - 1) * pageSize).size(pageSize);

        //4. 设置排序
        String location = requestParams.getLocation();
        if (StrUtil.isNotEmpty(location)) {
            request.source().sort(
                    SortBuilders.geoDistanceSort("location", new GeoPoint(location))//设置核心坐标位置
                            .order(SortOrder.ASC) //设置排序方式
                            .unit(DistanceUnit.KILOMETERS)// 单位
            );
        }

        //5. 设置高亮
        request.source().highlighter(
                new HighlightBuilder()
                        .field("name")
                        .preTags("<em>").postTags("</em>")
                        .requireFieldMatch(false)
        );

        //6. 发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //7. 处理结果
        return handleResponse(response);
    }

排序距离显示

按照距离排序后,还需要显示具体的距离值:
修改handleResponse方法

   //处理结果
    private PageResult handleResponse(SearchResponse response) {
        //准备结果集合
        List<HotelDoc> hotels = new ArrayList<>();

        SearchHits searchHits = response.getHits();

        //获取总记录数
        long total = searchHits.getTotalHits().value;

        //获取文档数组
        SearchHit[] hits = searchHits.getHits();

        //遍历文档, 获取结果, 处理高亮, 并且添加到返回结果结合
        for (SearchHit hit : hits) {
            // 获取文档source
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            // 处理高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (CollectionUtil.isNotEmpty(highlightFields)) {
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    String name = highlightField.getFragments()[0].string();
                    hotelDoc.setName(name);
                }
            }

            // 处理距离显示
            Object[] sortValues = hit.getSortValues();
            if (ArrayUtil.isNotEmpty(sortValues)) {
                Object sortValue = sortValues[0];
                hotelDoc.setDistance(sortValue);
            }

            hotels.add(hotelDoc);
        }
        
        //组装返回结果
        return new PageResult(total, hotels);
    }

酒店竞价排名

需求:让指定的酒店(广告)在搜索结果中排名置顶
要让指定酒店在搜索结果中排名置顶,效果如图:
在这里插入图片描述

我们给需要置顶的酒店文档添加一个标记。然后利用function_score给带有标记的文档增加权重。

实现步骤分析:

1. 挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true

2. 修改search方法,添加function_score功能,给isAD值为true的酒店增加权重

添加广告标记

接下来,我们挑几个酒店,添加isAD字段,设置为true:

POST /hotel/_update/2056126831
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/1989806195
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/2056105938
{
    "doc": {
        "isAD": true
    }
}

添加算分函数

    @Override
    public PageResult list(RequestParams requestParams) throws IOException {

        //1. 构建查询请求
        SearchRequest request = new SearchRequest("hotel");

        //2. 设置查询条件
        //2-1创建复合查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //2-2 获取搜索关键字, 设置为查询条件
        String key = requestParams.getKey();
        if (StringUtils.isEmpty(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }

        //2-3 获取城市、星级、品牌、价格,使用过滤语法筛选
        // 城市
        if (StrUtil.isNotEmpty(requestParams.getCity())) {
            boolQuery.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
        }
        // 星级
        if (StrUtil.isNotEmpty(requestParams.getStarName())) {
            boolQuery.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
        }
        // 品牌
        if (StrUtil.isNotEmpty(requestParams.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
        }
        // 价格
        if (requestParams.getMinPrice() != null && requestParams.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
        }

        //2-4 使用算分函数 提供广告酒店的排名
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(5))
                }
        );

        request.source().query(functionScoreQuery);

        //3. 设置分页
        Integer pageNum = requestParams.getPage();
        Integer pageSize = requestParams.getSize();
        request.source().from((pageNum - 1) * pageSize).size(pageSize);

        //4. 设置排序
        String location = requestParams.getLocation();
        if (StrUtil.isNotEmpty(location)) {
            request.source().sort(
                    SortBuilders.geoDistanceSort("location", new GeoPoint(location))//设置核心坐标位置
                            .order(SortOrder.ASC) //设置排序方式
                            .unit(DistanceUnit.KILOMETERS)// 单位
            );
        }

        //5. 设置高亮
        request.source().highlighter(
                new HighlightBuilder().field("name").requireFieldMatch(false)
        );

        //6. 发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //7. 处理结果
        return handleResponse(response);
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

上官玺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值