ES旅游案例(完整的关键字搜索、条件过滤、附近酒店距离、公告竞价排位案例)

本文主要是介绍ES旅游案例(完整的关键字搜索、条件过滤、附近酒店距离、公告竞价排位案例),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ES旅游案例

下面,我们通过ES旅游的案例来实战演练下之前学习的知识。

我们实现四部分功能:

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

启动我们提供的hotel-demo项目,其默认端口是8089,访问http://localhost:8090,就能看到项目页面了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GDMt90cB-1654329626702)(images/image-20220604131153236.png)]

由于页面内容的确实,所以现在要使用postman软件进行接口的测试,访问的数据页数一样的

点击搜索按钮查看页面的请求数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kR4lRlfs-1654329626703)(images/image-20220604131328458.png)]

由此可以知道,我们这个请求的信息如下:

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

因此,我们实现业务的流程如下:

  • 步骤一:定义实体类,接收请求参数的JSON对象
  • 步骤二:编写controller,接收页面的请求
  • 步骤三:编写业务实现,利用RestHighLevelClient实现搜索、分页

1、定义实体类

实体类有两个,一个是前端的请求参数实体,一个是服务端应该返回的响应结果实体。

1)请求参数

前端请求的json结构如下:

{"key": "搜索关键字","page": 1,"size": 3,"sortBy": "default"
}

因此,我们在cn.itcast.hotel.pojo包下定义一个实体类:

package cn.itcast.hotel.pojo;import lombok.Data;@Data
public class RequestParams {private String key;private Integer page;private Integer size;private String sortBy;
}

2)返回值

分页查询,需要返回分页结果PageResult,包含两个属性:

  • total:总条数
  • List<HotelDoc>:当前页的数据

因此,我们在cn.itcast.hotel.pojo中定义返回结果:

package cn.itcast.hotel.pojo;import lombok.Data;import java.util.List;@Data
public class PageResult {private Long total;private List<HotelDoc> hotels;public PageResult() {}public PageResult(Long total, List<HotelDoc> hotels) {this.total = total;this.hotels = hotels;}
}

2、定义Controller

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

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

因此,我们在cn.itcast.hotel.web中定义HotelController:

package cn.itcast.hotel.web;import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
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;/*** 项目名称:hotel-demo* 描述:请求控制器** @author zhong* @date 2022-06-04 13:20*/
@RestController
@RequestMapping("/hotel")
public class HotelController {/*** 注入业务层*/@Autowiredprivate IHotelService hotelService;/*** 请求查询分页信息并返回* @return*/@PostMapping("/list")public PageResult search(@RequestBody RequestParams params){return hotelService.search(params);}
}

3、创建业务层以及实现方式

我们在controller调用了IHotelService,并没有实现该方法,因此下面我们就在IHotelService中定义方法,并且去实现业务逻辑。

1)在cn.itcast.hotel.service中的IHotelService接口中定义一个方法:

快捷键介绍:创建接口后,需要创建接口的实现类,可以按住键盘的Ctrl+Alt+B进行跳转到实现类上

/*** 根据关键字搜索酒店信息* @param params 请求参数对象,包含用户输入的关键字 * @return 酒店文档列表*/
PageResult search(RequestParams params);

2)实现搜索业务,肯定离不开RestHighLevelClient,我们需要把它注册到Spring中作为一个Bean。在cn.itcast.hotel中的HotelDemoApplication中声明这个Bean:

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

3)在cn.itcast.hotel.service.impl中的HotelService中实现search方法:

package cn.itcast.hotel.service.impl;import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {/*** 注入*/@AutowiredRestHighLevelClient client;/**** @param params* @return*/@Overridepublic PageResult search(RequestParams params) {try {// 1、准备requeueSearchRequest request = new SearchRequest("hotel");// 2、准备DSL// 2.1、关键字搜索String key = params.getKey();if(key==null || "".equals(key)){request.source().query(QueryBuilders.matchAllQuery());}else{request.source().query(QueryBuilders.matchQuery("all",key));}// 2.2、分页搜索Integer page = params.getPage();Integer size = params.getSize();request.source().from((page -1)*size).size(size);// 3、发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4、解析响应return extracted(response);} catch (IOException e) {throw new RuntimeException();}}/*** 封装的提统一使用的重构步骤* @param search*/private PageResult extracted(SearchResponse search) {// 4、解析响应数据SearchHits hits = search.getHits();// 4.1、获取总条数long value = hits.getTotalHits().value;// 4.2、获取文档数组SearchHit[] hitsArray = hits.getHits();List<HotelDoc> hotelDocs = new ArrayList<>();// 4.3、遍历数组for (SearchHit documentFields : hitsArray) {// 获取文档String sourceAsString = documentFields.getSourceAsString();// 将文档放序列化为json对象HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);hotelDocs.add(hotelDoc);}return new PageResult(value,hotelDocs);}
}

4、酒店结果过滤

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

包含的过滤条件有:

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

我们需要做两件事情:

  • 修改请求参数的对象RequestParams,接收上述参数
  • 修改业务逻辑,在搜索条件之外,添加一些过滤条件

4.1、添加实体类属性

修改在cn.itcast.hotel.pojo包下的实体类RequestParams:

@Data
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;
}

4.2、修改搜索业务

在HotelService的search方法中,只有一个地方需要修改:requet.source().query( … )其中的查询条件。

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

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

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

  • 关键字搜索放到must中,参与算分
  • 其它过滤条件放到filter中,不参与算分

因为条件构建的逻辑比较复杂,这里先封装为一个函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xvwk9O6d-1654329626704)(images/image-20220604144139791.png)]

buildBasicQuery的代码如下:

/*** 拼接筛选条件* @param params* @return*/
@Override
public PageResult search(RequestParams params) {try {// 1、准备requeueSearchRequest request = new SearchRequest("hotel");// 2、准备DSLbuildBaicQuery(params, request);// 2.2、分页搜索int page = params.getPage();int size = params.getSize();request.source().from((page -1)*size).size(size);// 3、发送请求,得到响应SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4、解析响应return extracted(response);} catch (IOException e) {throw new RuntimeException();}
}/*** 重构筛选条件* @param params* @param request*/
private void buildBaicQuery(RequestParams params, SearchRequest request) {// 将查询的条件较多,所以封装在一起BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 2.1、关键字搜索String key = params.getKey();if(key==null || "".equals(key)){boolQuery.must(QueryBuilders.matchAllQuery());}else{boolQuery.must(QueryBuilders.matchQuery("all",key));}// 城市条件查询,不要参与算分if(params.getCity() != null && !params.getCity().equals("")){boolQuery.filter(QueryBuilders.matchQuery("city", params.getCity()));}// 匹配条件if(params.getBrand() != null && !params.getBrand().equals("")){boolQuery.filter(QueryBuilders.matchQuery("brand", params.getBrand()));}// 星级条件if(params.getStarName() != null && !params.getStarName().equals("")){boolQuery.filter(QueryBuilders.matchQuery("starName", params.getStarName()));}// 价格判断if(params.getMinPrice() != null && !params.getMaxPrice().equals("")){// 大于等于和小于等于boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));}request.source().query(boolQuery);
}

使用postman软件进行测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6DINiZfJ-1654329626705)(images/image-20220604150609832.png)]

5、附近酒店查询

需求:我附近的酒店

酒店信息的坐标key是location

们要做的事情就是基于这个location坐标,然后按照距离对周围酒店排序。实现思路如下:

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

5.1、修改实体类

在返回封装的实体类上进行一个坐标距离的添加

// 我当前的地理坐标
private String location;

5.2、距离排序API

我们以前学习过排序功能,包括两种:

  • 普通字段排序
  • 地理坐标排序

我们只讲了普通字段排序对应的java写法。地理坐标排序只学过DSL语法,如下:

GET /indexName/_search
{"query": {"match_all": {}},"sort": [{"price": "asc"  },{"_geo_distance" : {"FIELD" : "纬度,经度","order" : "asc","unit" : "km"}}]
}

5.3、添加距离排序

// 坐标范围排序
String location = params.getLocation();
if(location != null && !location.equals("")){request.source().sort(SortBuilders.geoDistanceSort("location",new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));}

5.4、完善解析数据,回显酒店距离

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-icu4uGqa-1654329626706)(images/image-20220604153748352.png)]

6、酒店竞价排名

需求:让指定的酒店在搜索结果中排名置顶

我们之前学习过的function_score查询可以影响算分,算分高了,自然排名也就高了。而function_score包含3个要素:

  • 过滤条件:哪些文档要加分
  • 算分函数:如何计算function score
  • 加权方式:function score 与 query score如何运算

这里的需求是:让指定酒店排名靠前。因此我们需要给这些酒店添加一个标记,这样在过滤条件中就可以根据这个标记来判断,是否要提高算分

比如,我们给酒店添加一个字段:isAD,Boolean类型:

  • true:是广告
  • false:不是广告

这样function_score包含3个要素就很好确定了:

  • 过滤条件:判断isAD 是否为true
  • 算分函数:我们可以用最简单暴力的weight,固定加权值
  • 加权方式:可以用默认的相乘,大大提高算分

因此,业务的实现步骤包括:

  1. 给HotelDoc类添加isAD字段,Boolean类型

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

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

6.1、修改HotelDoc实体

添加多一个isAD布尔字段

private Boolean isAD;

6.3、在Dev Tools编写页面手动的添加这几个字段的属性为公告的

添加广告标记

# 手动添加公告字段
POST /hotel/_update/1514269829
{"doc":{"isAD":true}
}POST /hotel/_update/541619
{"doc":{"isAD":true}
}POST /hotel/_update/485775
{"doc":{"isAD":true}
}

6.4、修改原有的查询方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEbfTIvV-1654329626707)(images/image-20220604155923236.png)]

java对应的api如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G7No1aZJ-1654329626708)(images/image-20220604155942976.png)]

添加一个新的算分方法

我们可以将之前写的boolean查询作为原始查询条件放到query中,接下来就是添加过滤条件算分函数加权模式了。所以原来的代码依然可以沿用。

// 2、算分控制
FunctionScoreQueryBuilder functionScoreQueryBuilder =QueryBuilders.functionScoreQuery(// 原始算分方法boolQuery,// function score的数组new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{// 其中一个function score原始new FunctionScoreQueryBuilder.FilterFunctionBuilder(// 过滤条件QueryBuilders.termQuery("isAD",true),// 算分函数ScoreFunctionBuilders.weightFactorFunction(10))});

完整的业务代码

package cn.itcast.hotel.service.impl;import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {/*** 注入*/@AutowiredRestHighLevelClient client;/*** 拼接筛选条件** @param params* @return*/@Overridepublic PageResult search(RequestParams params) {try {// 1、准备requeueSearchRequest request = new SearchRequest("hotel");// 2、准备DSLbuildBaicQuery(params, request);// 2.2、分页搜索int page = params.getPage();int size = params.getSize();request.source().from((page - 1) * size).size(size);// 坐标范围排序String location = params.getLocation();if (location != null && !location.equals("")) {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 extracted(response);} catch (IOException e) {throw new RuntimeException();}}/*** 重构筛选条件** @param params* @param request*/private void buildBaicQuery(RequestParams params, SearchRequest request) {// 将查询的条件较多,所以封装在一起BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 2.1、关键字搜索String key = params.getKey();if (key == null || "".equals(key)) {boolQuery.must(QueryBuilders.matchAllQuery());} else {boolQuery.must(QueryBuilders.matchQuery("all", key));}// 城市条件查询,不要参与算分if (params.getCity() != null && !params.getCity().equals("")) {boolQuery.filter(QueryBuilders.matchQuery("city", params.getCity()));}// 匹配条件if (params.getBrand() != null && !params.getBrand().equals("")) {boolQuery.filter(QueryBuilders.matchQuery("brand", params.getBrand()));}// 星级条件if (params.getStarName() != null && !params.getStarName().equals("")) {boolQuery.filter(QueryBuilders.matchQuery("starName", params.getStarName()));}// 价格判断if (params.getMinPrice() != null && !params.getMaxPrice().equals("")) {// 大于等于和小于等于boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));}// 2、算分控制FunctionScoreQueryBuilder functionScoreQueryBuilder =QueryBuilders.functionScoreQuery(// 原始算分方法boolQuery,// function score的数组new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{// 其中一个function score原始new FunctionScoreQueryBuilder.FilterFunctionBuilder(// 过滤条件QueryBuilders.termQuery("isAD",true),// 算分函数ScoreFunctionBuilders.weightFactorFunction(10))});request.source().query(boolQuery);}/*** 封装的提统一使用的重构步骤** @param search*/private PageResult extracted(SearchResponse search) {// 4、解析响应数据SearchHits hits = search.getHits();// 4.1、获取总条数long value = hits.getTotalHits().value;// 4.2、获取文档数组SearchHit[] hitsArray = hits.getHits();List<HotelDoc> hotelDocs = new ArrayList<>();// 4.3、遍历数组for (SearchHit documentFields : hitsArray) {// 获取文档String sourceAsString = documentFields.getSourceAsString();// 将文档放序列化为json对象HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);// 获取到距离排序的值Object[] rawSortValues = documentFields.getSortValues();if (rawSortValues.length > 0) {Object sortValue = rawSortValues[0];// 添加距离hotelDoc.setDistance(sortValue);System.out.println("查询返回的公里数:" + sortValue);}hotelDocs.add(hotelDoc);}return new PageResult(value, hotelDocs);}
}

这篇关于ES旅游案例(完整的关键字搜索、条件过滤、附近酒店距离、公告竞价排位案例)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/689777

相关文章

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有

Spring 中的切面与事务结合使用完整示例

《Spring中的切面与事务结合使用完整示例》本文给大家介绍Spring中的切面与事务结合使用完整示例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录 一、前置知识:Spring AOP 与 事务的关系 事务本质上就是一个“切面”二、核心组件三、完

Three.js构建一个 3D 商品展示空间完整实战项目

《Three.js构建一个3D商品展示空间完整实战项目》Three.js是一个强大的JavaScript库,专用于在Web浏览器中创建3D图形,:本文主要介绍Three.js构建一个3D商品展... 目录引言项目核心技术1. 项目架构与资源组织2. 多模型切换、交互热点绑定3. 移动端适配与帧率优化4. 可

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

Python自动化处理PDF文档的操作完整指南

《Python自动化处理PDF文档的操作完整指南》在办公自动化中,PDF文档处理是一项常见需求,本文将介绍如何使用Python实现PDF文档的自动化处理,感兴趣的小伙伴可以跟随小编一起学习一下... 目录使用pymupdf读写PDF文件基本概念安装pymupdf提取文本内容提取图像添加水印使用pdfplum

基于Python实现自动化邮件发送系统的完整指南

《基于Python实现自动化邮件发送系统的完整指南》在现代软件开发和自动化流程中,邮件通知是一个常见且实用的功能,无论是用于发送报告、告警信息还是用户提醒,通过Python实现自动化的邮件发送功能都能... 目录一、前言:二、项目概述三、配置文件 `.env` 解析四、代码结构解析1. 导入模块2. 加载环

Java 正则表达式的使用实战案例

《Java正则表达式的使用实战案例》本文详细介绍了Java正则表达式的使用方法,涵盖语法细节、核心类方法、高级特性及实战案例,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录一、正则表达式语法详解1. 基础字符匹配2. 字符类([]定义)3. 量词(控制匹配次数)4. 边

Python Counter 函数使用案例

《PythonCounter函数使用案例》Counter是collections模块中的一个类,专门用于对可迭代对象中的元素进行计数,接下来通过本文给大家介绍PythonCounter函数使用案例... 目录一、Counter函数概述二、基本使用案例(一)列表元素计数(二)字符串字符计数(三)元组计数三、C

Nginx中配置使用非默认80端口进行服务的完整指南

《Nginx中配置使用非默认80端口进行服务的完整指南》在实际生产环境中,我们经常需要将Nginx配置在其他端口上运行,本文将详细介绍如何在Nginx中配置使用非默认端口进行服务,希望对大家有所帮助... 目录一、为什么需要使用非默认端口二、配置Nginx使用非默认端口的基本方法2.1 修改listen指令