elasticserach项目实战

通过优步旅游项目中搜索查询项目,进行elasticsearch项目实战,完成酒店搜索和分页、酒店结果过滤、周边的酒店和酒店竞价排名

一、酒店搜索和分页

案例1:实现优步旅游的酒店搜索功能,完成关键字搜索和分页:

启动项目,访问搜索功能,对应的post类型的接口并没有实现,所以实现该接口即可:

1.1.步骤1:定义类,来接收前端请求参数

由于上面的接口是post请求,参数有四个,所以在pojo包下创建实体类 RequestParam,用于接受前端的参数如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestParam{
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
}

1.2.步骤2:编码实现,接收前端请求

定义一个Hotel Controller,声明查询接口,满足下列要求:

  • 请求方式:Post
  • 请求路径:/hotel/list
  • 请求参数:对象,类型为RequestParam
  • 返回值:PageResult,包含两个属性
    • Long total:总条数
    • List<Hotel Doc>hotels:酒店数据

1.2.1.在pojo下定义PageResult类:

返回结果对象类

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

1.2.2.定义controller

在Controller包下创建类HotelController, 定义searchList方法的参数RequestParams,前端提交的是json,方法参数需要加注解@RequestBody

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

    @Autowired
    private IHotelService iHotelService;

    /**
     * 搜索
     * @param param 通过对象接收参数
     * @return PageResult JSON对象
     * @throws IOException
     */
    @PostMapping("/list")
    public PageResult searchList(@RequestBody RequestParam param) throws IOException {
        return iHotelService.search(param);
    }
}

1.2.3.创建配置类

通过Restclient创建连接对象,通过spring注入到容器中:可以在启动类中创建Bean,在config包下创建RestClientConfig,代码如下:

package cn.itcast.hotel.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RestClientConfig {

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

1.2.4.创建service层

创建 IHotelService 定义接口如下:

public interface IHotelService extends IService<Hotel> {
    PageResult search(RequestParam param) throws IOException;
}

创建HotelService,实现接口IHotelService,如下:

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;

    /**
     * 查询、分页
     * @param param
     */
    @Override
    public PageResult search(RequestParam param) {
        try {
            //1.准备request
            SearchRequest request = new SearchRequest("hotel");

            //2.准备DSL
            //2.1.query
            String key = param.getKey();
            //健壮性判断,如果前端传递的参数为null或者为空字符串,就进行全文检索,else则是按照条件搜索
            if (key == null || "".equals(key)) {
                request.source().query(QueryBuilders.matchAllQuery());
            }
            {
                request.source().query(QueryBuilders.matchQuery("all", param.getKey()));
            }

            //2.2.分页
            Integer page = param.getPage();
            Integer size = param.getSize();
            request.source().from((page - 1) * size).size(size);

            //3.发送请求,得到响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析响应
     * @param response
     */
    private PageResult handleResponse(SearchResponse response){
        //4.解析响应
        SearchHits searchHits = response.getHits();
        //4.1.获取总条数
        long total = searchHits.getTotalHits().value;

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

        ArrayList<HotelDoc> hotelDocs = new ArrayList<>();

        //4.3.遍历
        for (SearchHit hit : hits) {
            //获取文档source
            String json = hit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

            //添加到 集合
            hotelDocs.add(hotelDoc);
            //System.out.println("hotelDoc = "+hotelDoc);
        }
        //封装成PageResult对象
        return new PageResult(total,hotelDocs);
    }
}

1.2.5.创建mapper层

创建 HotelMapper,如下:

package cn.itcast.hotel.mapper;

import cn.itcast.hotel.pojo.Hotel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface HotelMapper extends BaseMapper<Hotel> {
}

1.3.测试

重启项目,然后进行搜索,尝试分页操作,发现已经可以:

二、酒店结果过滤

案例2: 页面是有过滤项的,用户点击这些可以做过滤,添加品牌、城市、星级和价格等过滤功能,如下:

尝试操作前端页面,发现需在之前的请求中添加参数如下:

2.1.步骤一:修改实体类RequestParams,添加brand、city、starName、minPrice、maxPrice等参数

修改后代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestParam{
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String brand;
    private String starName;
    private String city;
    private Integer minPrice;
    private Integer maxPrice;
}

2.2.步骤二:修改search方法,在match查询基础上添加过滤条件

修改search方法的实现,在关键字搜索时,如果brand等参数存在,对其做过滤,过滤条件包括:

  • city精确匹配
  • brand精确匹配
  • starName精确匹配
  • price范围过滤

注意事项:

  • 多个条件之间是AND关系,组合多条件用BooleanQuery
  • 参数存在才需要过滤,做好非空判断

修改HotelService实现类中的search方法实现上面的操作添加过滤条件如下:

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;

    /**
     * 查询、分页
     * @param param
     */
    @Override
    public PageResult search(RequestParam param) {
        try {
            //1.准备request
            SearchRequest request = new SearchRequest("hotel");

            //2.准备DSL
            //2.1.BooleanQuery
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

            //2.2.关键字搜索
            String key = param.getKey();
            //健壮性判断,如果前端传递的参数为null或者为空字符串,就进行全文检索,else则是按照条件搜索
            if (key == null || "".equals(key)) {
                boolQuery.must(QueryBuilders.matchAllQuery());
            }
            {
                boolQuery.must(QueryBuilders.matchQuery("all", param.getKey()));
            }

            //2.3.条件:城市
            String city = param.getCity();
            if(city != null && !"".equals(city)){
                boolQuery.filter(QueryBuilders.termQuery("city",city));
            }

            //2.4.条件:品牌
            String brand = param.getBrand();
            if(brand != null && !"".equals(brand)){
                boolQuery.filter(QueryBuilders.termQuery("brand",brand));
            }

            //2.5.条件:星级
            String starName = param.getStarName();
            if(starName != null && !"".equals(starName)){
                boolQuery.filter(QueryBuilders.termQuery("starName",starName));
            }

            //2.6.条件:价格
            Integer maxPrice = param.getMaxPrice();
            Integer minPrice = param.getMinPrice();
            if(maxPrice != null && minPrice != null){
                boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
            }

            //添加boolQuery
            request.source().query(boolQuery);

            //2.7.分页
            Integer page = param.getPage();
            Integer size = param.getSize();
            request.source().from((page - 1) * size).size(size);

            //3.发送请求,得到响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 解析响应
     * @param response
     */
    private PageResult handleResponse(SearchResponse response){
        //4.解析响应
        SearchHits searchHits = response.getHits();
        //4.1.获取总条数
        long total = searchHits.getTotalHits().value;

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

        ArrayList<HotelDoc> hotelDocs = new ArrayList<>();

        //4.3.遍历
        for (SearchHit hit : hits) {
            //获取文档source
            String json = hit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

            //添加到 集合
            hotelDocs.add(hotelDoc);
            //System.out.println("hotelDoc = "+hotelDoc);
        }
        //封装成PageResult对象
        return new PageResult(total,hotelDocs);
    }
}

2.3.优化代码

上面的DSL查询条件进行抽离封装,代码如下:

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;

    /**
     * 查询、分页
     * @param param
     */
    @Override
    public PageResult search(RequestParam param) {
        try {
            //1.准备request
            SearchRequest request = new SearchRequest("hotel");

            //2.准备DSL
            buildBaiscQuery(param,request);

            //2.7.分页
            Integer page = param.getPage();
            Integer size = param.getSize();
            request.source().from((page - 1) * size).size(size);

            //3.发送请求,得到响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 抽离构建DSL条件的代码封装成方法
     * @param param 前端参数对象
     * @param request
     */
    private void buildBaiscQuery(RequestParam param,SearchRequest request){
        //2.1.BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //2.2.关键字搜索
        String key = param.getKey();
        //健壮性判断,如果前端传递的参数为null或者为空字符串,就进行全文检索,else则是按照条件搜索
        if (key == null || "".equals(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        }
        {
            boolQuery.must(QueryBuilders.matchQuery("all", param.getKey()));
        }

        //2.3.条件:城市
        String city = param.getCity();
        if(city != null && !"".equals(city)){
            boolQuery.filter(QueryBuilders.termQuery("city",city));
        }

        //2.4.条件:品牌
        String brand = param.getBrand();
        if(brand != null && !"".equals(brand)){
            boolQuery.filter(QueryBuilders.termQuery("brand",brand));
        }

        //2.5.条件:星级
        String starName = param.getStarName();
        if(starName != null && !"".equals(starName)){
            boolQuery.filter(QueryBuilders.termQuery("starName",starName));
        }

        //2.6.条件:价格
        Integer maxPrice = param.getMaxPrice();
        Integer minPrice = param.getMinPrice();
        if(maxPrice != null && minPrice != null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
        }

        //添加boolQuery
        request.source().query(boolQuery);
    }

    /**
     * 解析响应
     * @param response SearchRequest对象
     */
    private PageResult handleResponse(SearchResponse response){
        //4.解析响应
        SearchHits searchHits = response.getHits();
        //4.1.获取总条数
        long total = searchHits.getTotalHits().value;

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

        ArrayList<HotelDoc> hotelDocs = new ArrayList<>();

        //4.3.遍历
        for (SearchHit hit : hits) {
            //获取文档source
            String json = hit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

            //添加到 集合
            hotelDocs.add(hotelDoc);
            //System.out.println("hotelDoc = "+hotelDoc);
        }
        //封装成PageResult对象
        return new PageResult(total,hotelDocs);
    }
}

2.4.测试

重启项目,按照添加筛选查询,发现已经可以实现,如下:

三、周边的酒店

案例3:我附近的酒店,前端页面点击定位后,会将你所在的位置发送到后台,如下:

 发送接口信息如下:

要根据上面的坐标,将酒店结果按照到这个点的距离升序排序。实现思路如下:

  • 修改RequestParams参数,接收location字段
  • 修改search方法业务逻辑,如果location有值,添加根据geo_distance排序的功能

距离排序

距离排序与普通字段排序有所差异,API如下:

3.1.添加字段

在实体类RequestParam,添加字段location字段,接受前端传递的经纬度

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestParam{
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String brand;
    private String starName;
    private String city;
    private Integer minPrice;
    private Integer maxPrice;
    //经纬度
    private String location;
}

3.2.修改实现类sercher方法:添加排序的代码

添加排序代码如下:

    /**
     * 查询、分页
     * @param param
     */
    @Override
    public PageResult search(RequestParam param) {
        try {
            //1.准备request
            SearchRequest request = new SearchRequest("hotel");

            //2.准备DSL
            buildBaiscQuery(param,request);

            //2.7.分页
            Integer page = param.getPage();
            Integer size = param.getSize();
            request.source().from((page - 1) * size).size(size);

            //2.8 排序
            String location = param.getLocation();
            if(location != null && !location.equals("")){
                request.source().sort(SortBuilders
                    .geoDistanceSort("location", new GeoPoint(location)) //指定排序的字段和原点位置
                        .order(SortOrder.ASC) //升序排序
                        .unit(DistanceUnit.KILOMETERS) //单位km
                );
            }

            //3.发送请求,得到响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

3.3.排序回显距离信息实现

如下图:

需要把返回的距离定位距离通过 HotelDoc 对象返回,所以这里需要修改HotelDoc添加一个字段保存该值,

 修改响应解析代码,获取距离信息,然后返回,如下:

    /**
     * 解析响应
     * @param response SearchRequest对象
     */
    private PageResult handleResponse(SearchResponse response){
        //4.解析响应
        SearchHits searchHits = response.getHits();
        //4.1.获取总条数
        long total = searchHits.getTotalHits().value;

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

        ArrayList<HotelDoc> hotelDocs = new ArrayList<>();

        //4.3.遍历
        for (SearchHit hit : hits) {
            //获取文档source
            String json = hit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

            //获取排序值
            Object[] sortValues = hit.getSortValues();
            //判断是否为空
            if(sortValues.length > 0){
                Object sortValue = sortValues[0];
                //保存给返回的对象
                hotelDoc.setDistance(sortValue);
            }

            //添加到 集合
            hotelDocs.add(hotelDoc);
            //System.out.println("hotelDoc = "+hotelDoc);
        }
        //封装成PageResult对象
        return new PageResult(total,hotelDocs);
    }

3.4.测试

重启项目,然后点击,获取当前位置,左侧会显示根据距离升序排序查询出来的酒店信息,如下:

四、酒店竞价排名

案例4:让指定的酒店在搜索结果中排名置顶

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

实现步骤分析:

  1. 给HotelDoc类添加isAD字段,Boolean类型
  2. 挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true
  3. 修改search方法,添加function score功能,给isAD值为true的酒店增加权重

组合查询-function score

Function Score查询可以控制文档的相关性算分,使用方式如下:

4.1.步骤一:给HotelDoc类添加isAD字段,Boolean类型

修改后如下:

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

根据文档ID修改酒店信息,添加isAD设置为true,DSL语法如下

# 添加isAD字段
POST /hotel/_update/434082
{
  "doc":{
    "isAD": true
  }
}


# 添加isAD字段
POST /hotel/_update/432335
{
  "doc":{
    "isAD": true
  }
}

# 添加isAD字段
POST /hotel/_update/60487
{
  "doc":{
    "isAD": true
  }
}


# 添加isAD字段
POST /hotel/_update/2359697
{
  "doc":{
    "isAD": true
  }
}

4.3.修改HotelService中对于构建DSL查询代码,添加functionscore功能,给isAD值为true的酒店增加权重

就是在buildBaiscQuery中添加即可

    /**
     * 抽离构建DSL条件的代码封装成方法
     * @param param 前端参数对象
     * @param request
     */
    private void buildBaiscQuery(RequestParam param,SearchRequest request){
        //2.1.BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        //2.2.关键字搜索
        String key = param.getKey();
        //健壮性判断,如果前端传递的参数为null或者为空字符串,就进行全文检索,else则是按照条件搜索
        if (key == null || "".equals(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else {
            boolQuery.must(QueryBuilders.matchQuery("all", param.getKey()));
        }

        //2.3.条件:城市
        String city = param.getCity();
        if(city != null && !"".equals(city)){
            boolQuery.filter(QueryBuilders.termQuery("city",city));
        }

        //2.4.条件:品牌
        String brand = param.getBrand();
        if(brand != null && !"".equals(brand)){
            boolQuery.filter(QueryBuilders.termQuery("brand",brand));
        }

        //2.5.条件:星级
        String starName = param.getStarName();
        if(starName != null && !"".equals(starName)){
            boolQuery.filter(QueryBuilders.termQuery("starName",starName));
        }

        //2.6.条件:价格
        Integer maxPrice = param.getMaxPrice();
        Integer minPrice = param.getMinPrice();
        if(maxPrice != null && minPrice != null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice));
        }

        //TODO 控制算分,先传入boolQuery
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        // 一个function score元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                //算分函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                });

        //添加boolQuery
        request.source().query(functionScoreQuery);
    }

4.4.测试

重启项目,搜索上海的酒店,就会查询出来的信息,之前增加的权重的会显示在最前面,如下:

posted @ 2023-11-11 01:24  酒剑仙*  阅读(40)  评论(0)    收藏  举报