【ElasticSearch】ES案例:旅游酒店搜索

文章目录

  • 一、项目分析
  • 二、需求1:酒店搜索功能
  • 三、需求2:添加过滤功能
  • 四、需求3:我附近的酒店
  • 五、需求4:置顶花广告费的酒店

一、项目分析

启动hotel-demo项目,访问localhost:servicePort,即可访问static下的index.html:

在这里插入图片描述

从页面分析,我们需要实现搜索、分页、排序等功能。点击页面,可以看到list接口的传参为:

在这里插入图片描述

二、需求1:酒店搜索功能

接下来实现酒店搜索功能,完成关键字搜索和分页。

  • 定义接参的Dto类
@Data
public class RequestParam {private String key;private Integer page;  //pageNumprivate Integer size;  //pageSizeprivate String sortBy;
}
  • 定义返回的结果类
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PageResult {private Long total;private List<HotelDoc>  hotelDocList;
}
  • 定义controller接口,接收页面请求
@RestController
@RequestMapping("/hotel")
public class HotelSearchController {@ResourceIHotelService hotelService;@PostMapping("/list")public PageResult searchHotel(@RequestBody RequestParam requestParam){return hotelService.search(requestParam);}}
  • Service层要用到JavaRestHighLevelClient对象,在启动类中定义这个Bean
@SpringBootApplication
public class HotelDemoApplication {public static void main(String[] args) {SpringApplication.run(HotelDemoApplication.class, args);}@Beanpublic RestHighLevelClient client(){return new RestHighLevelClient(RestClient.builder(HttpHost.create("http://10.4.130.220:9200")));}}
  • 完成Service层,利用match查询实现根据关键字搜索酒店信息
public interface IHotelService extends IService<Hotel> {PageResult search(RequestParam requestParam);
}
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {@ResourceRestHighLevelClient client;  //注入客户端操作的bean@Overridepublic PageResult search(RequestParam requestParam) {try {SearchRequest request = new SearchRequest("hotel");//搜索关键字String key = requestParam.getKey();if (StringUtils.isNotEmpty(key)) {   //有key就走全文检索request.source().query(QueryBuilders.matchQuery("all", key));} else {   //没key就走查所有request.source().query(QueryBuilders.matchAllQuery());}//分页request.source().from((requestParam.getPage() - 1) * requestParam.getSize())    //(pageNum-1)*pageSize.size(requestParam.getSize());//发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);//处理响应结果return handleResponse(response);} catch (IOException e) {throw new RuntimeException();}}//处理响应结果的方法private PageResult handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();long total = searchHits.getTotalHits().value;SearchHit[] hits = searchHits.getHits();//Stream流将hits中的每条数据都转为HotelDoc对象List<HotelDoc> hotelDocList = Arrays.stream(hits).map(t -> {String json = t.getSourceAsString();HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);return hotelDoc;}).collect(Collectors.toList());return new PageResult(total, hotelDocList);}
}

重启服务,搜索和分页已实现。

在这里插入图片描述

三、需求2:添加过滤功能

接下来添加品牌、城市、星级、价格的过滤功能。这里参与搜索的条件对应着不同的搜索类型,有全文检索,有精确查找,自然要用复合查询Boolean Search

在这里插入图片描述

  • 修改接参dto类:
@Data
public class RequestParam {private String key;private Integer page;  //pageNumprivate Integer size;  //pageSizeprivate String sortBy;private String brand;  private String starName; private String city;    private Integer minPrice;    private Integer maxPrice;}
  • 修改Service层实现,这里把搜索条件的构建单独抽取成方法,一来方便后面复用,二来让代码看着清爽点
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {@ResourceRestHighLevelClient client;@Overridepublic PageResult search(RequestParam requestParam) {try {//准备requestSearchRequest request = new SearchRequest("hotel");//构建查询条件buildBasicQuery(requestParam, request);//分页request.source().from((requestParam.getPage() - 1) * requestParam.getSize()).size(requestParam.getSize());//发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);//处理响应结果return handleResponse(response);} catch (IOException e) {throw new RuntimeException();}}private void buildBasicQuery(RequestParam requestParam, SearchRequest request) {BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();//关键字String key = requestParam.getKey();if (! isEmpty(key)) {booleanQuery.must(QueryBuilders.matchQuery("all", key));} else {booleanQuery.must(QueryBuilders.matchAllQuery());}//城市if (! isEmpty(requestParam.getCity())) {booleanQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));}//品牌if (! isEmpty(requestParam.getBrand())) {booleanQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));}//星级if (! isEmpty(requestParam.getStarName())) {booleanQuery.filter(QueryBuilders.termQuery("startName", requestParam.getStarName()));}//价格if (requestParam.getMaxPrice() != null && requestParam.getMinPrice() != null) {booleanQuery.filter(QueryBuilders.rangeQuery("price").lte(requestParam.getMaxPrice()).gte(requestParam.getMinPrice()));}request.source().query(booleanQuery);}private static boolean isEmpty(String str){return str == null || "".equals(str);}
}

在这里插入图片描述

四、需求3:我附近的酒店

前端页面点击定位后,会将你所在的位置发送到后台:
在这里插入图片描述
接下来实现根据这个坐标,将酒店结果按照到这个点的距离升序排序。
在这里插入图片描述

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

在这里插入图片描述

开始实现需求:

  • 修改RequestParams参数,接收location字段
@Data
public class RequestParam {private String key;private Integer page;  //pageNumprivate Integer size;  //pageSizeprivate String sortBy;private String brand;  private String starName; private String city;    private Integer minPrice;    private Integer maxPrice;private String location;  //经纬度位置
}
  • 修改Service中,在分页前加排序逻辑
@Override
public PageResult search(RequestParam requestParam) {try {//准备requestSearchRequest request = new SearchRequest("hotel");//构建查询条件buildBasicQuery(requestParam, request);//排序String myLocation = requestParam.getLocation();if(! isEmpty(myLocation)){request.source().sort(SortBuilders.geoDistanceSort("location",new GeoPoint(myLocation)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));}//分页request.source().from((requestParam.getPage() - 1) * requestParam.getSize()).size(requestParam.getSize());//发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);//处理响应结果return handleResponse(response);} catch (IOException e) {throw new RuntimeException();}}

但此时发现返回结果中少了距离你xxx千米的信息:

在这里插入图片描述

查看DSL返回结果,看到距离是在sort字段中:

在这里插入图片描述

因此需要修改结果处理的方法,且最后pageResult中是HotelDoc对象的集合,因此,修改Hoteldoc类,加distance距离字段:

@Data
@NoArgsConstructor
public class HotelDoc {private Long id;private String name;private String address;private Integer price;private Integer score;private String brand;private String city;private String starName;private String business;private String location;private String pic;//距离private Object distance;   //新加字段public HotelDoc(Hotel hotel) {this.id = hotel.getId();this.name = hotel.getName();this.address = hotel.getAddress();this.price = hotel.getPrice();this.score = hotel.getScore();this.brand = hotel.getBrand();this.city = hotel.getCity();this.starName = hotel.getStarName();this.business = hotel.getBusiness();this.location = hotel.getLatitude() + ", " + hotel.getLongitude();this.pic = hotel.getPic();}
}
private PageResult handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();long total = searchHits.getTotalHits().value;SearchHit[] hits = searchHits.getHits();List<HotelDoc> hotelDocList = Arrays.stream(hits).map(t -> {String json = t.getSourceAsString();HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);//开始加入距离Object[] sortValues = t.getSortValues();   //排序字段可能不止一个if(sortValues.length > 0 ){Object sortValue = sortValues[0];hotelDoc.setDistance(sortValue); 拿到sort值赋值给距离}return hotelDoc;}).collect(Collectors.toList());return new PageResult(total, hotelDocList);
}

到此,需求实现:

在这里插入图片描述

五、需求4:置顶花广告费的酒店

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

在这里插入图片描述
实现思路为:

  • HotelDoc类添加标记字段isAD,Boolean类型
  • 对于出广告费的酒店,isAD为true,前端可用这个字段给酒店打广告标签
  • 使用function score给花钱的酒店人为增加权重,干涉排序

代码实现:

  • hotelDoc类:
@Data
@NoArgsConstructor
public class HotelDoc {private Long id;private String name;private String address;private Integer price;private Integer score;private String brand;private String city;private String starName;private String business;private String location;private String pic;//距离private Object distance;   //新加字段//是否有广告费private Boolean isAD;
  • 更新ES数据,模拟某酒店出广告费

在这里插入图片描述

  • 加入function score算分认为控制,给isAD为true的加权
private void buildBasicQuery(RequestParam requestParam, SearchRequest request) {//BoolQuery原始查询条件,原始算分BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();//关键字String key = requestParam.getKey();if (!isEmpty(key)) {booleanQuery.must(QueryBuilders.matchQuery("all", key));} else {booleanQuery.must(QueryBuilders.matchAllQuery());}//城市if (!isEmpty(requestParam.getCity())) {booleanQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));}//品牌if (!isEmpty(requestParam.getBrand())) {booleanQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));}//星级if (!isEmpty(requestParam.getStarName())) {booleanQuery.filter(QueryBuilders.termQuery("startName", requestParam.getStarName()));}//价格if (requestParam.getMaxPrice() != null && requestParam.getMinPrice() != null) {booleanQuery.filter(QueryBuilders.rangeQuery("price").lte(requestParam.getMaxPrice()).gte(requestParam.getMinPrice()));}//function score算分控制FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(booleanQuery,  //第一个参数传入booleanQuery为原始查询,对应原始的相关性算分new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{  //第二个形参,function score数组,里面有个function score元素new FunctionScoreQueryBuilder.FilterFunctionBuilder(  //function score元素对象,第一个参数传入筛选字段QueryBuilders.termQuery("isAD", true),   //不再用酒店品牌筛选,而是isAD字段ScoreFunctionBuilders.weightFactorFunction(10)  //算分函数,用默认的乘法,权重为10)});request.source().query(functionScoreQuery);}

实现效果;

在这里插入图片描述


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

在这里插入图片描述

最后贴上以上四个需求Service层代码:

import cn.itcast.hotel.domain.dto.RequestParam;
import cn.itcast.hotel.domain.pojo.HotelDoc;
import cn.itcast.hotel.domain.vo.PageResult;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.domain.pojo.Hotel;
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.stereotype.Service;import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {@ResourceRestHighLevelClient client;@Overridepublic PageResult search(RequestParam requestParam) {try {//准备requestSearchRequest request = new SearchRequest("hotel");//构建查询条件buildBasicQuery(requestParam, request);//排序String myLocation = requestParam.getLocation();if (!isEmpty(myLocation)) {request.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(myLocation)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));}//分页request.source().from((requestParam.getPage() - 1) * requestParam.getSize()).size(requestParam.getSize());//发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);//处理响应结果return handleResponse(response);} catch (IOException e) {throw new RuntimeException();}}private void buildBasicQuery(RequestParam requestParam, SearchRequest request) {//BoolQuery原始查询条件,原始算分BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();//关键字String key = requestParam.getKey();if (!isEmpty(key)) {booleanQuery.must(QueryBuilders.matchQuery("all", key));} else {booleanQuery.must(QueryBuilders.matchAllQuery());}//城市if (!isEmpty(requestParam.getCity())) {booleanQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));}//品牌if (!isEmpty(requestParam.getBrand())) {booleanQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));}//星级if (!isEmpty(requestParam.getStarName())) {booleanQuery.filter(QueryBuilders.termQuery("startName", requestParam.getStarName()));}//价格if (requestParam.getMaxPrice() != null && requestParam.getMinPrice() != null) {booleanQuery.filter(QueryBuilders.rangeQuery("price").lte(requestParam.getMaxPrice()).gte(requestParam.getMinPrice()));}//function score算分控制FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(booleanQuery,  //第一个参数传入booleanQuery为原始查询,对应原始的相关性算分new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{  //第二个形参,function score数组,里面有个function score元素new FunctionScoreQueryBuilder.FilterFunctionBuilder(  //function score元素对象,第一个参数传入筛选字段QueryBuilders.termQuery("isAD", true),   //不再用酒店品牌筛选,而是isAD字段ScoreFunctionBuilders.weightFactorFunction(10)  //算分函数,用默认的乘法,权重为10)});request.source().query(functionScoreQuery);}private PageResult handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();long total = searchHits.getTotalHits().value;SearchHit[] hits = searchHits.getHits();List<HotelDoc> hotelDocList = Arrays.stream(hits).map(t -> {String json = t.getSourceAsString();HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);//开始加入距离Object[] sortValues = t.getSortValues();if (sortValues.length > 0) {Object sortValue = sortValues[0];hotelDoc.setDistance(sortValue);}return hotelDoc;}).collect(Collectors.toList());return new PageResult(total, hotelDocList);}private static boolean isEmpty(String str) {return str == null || "".equals(str);}
}

最后,页面上其他地方的需求实现思路:

排序:

在这里插入图片描述
前端会传递sortBy参数,就是排序方式,后端需要判断sortBy值是什么:

  • default:相关度算分排序,这个不用管,es的默认排序策略
  • score:根据酒店的score字段排序,也就是用户评价,降序
  • price:根据酒店的price字段排序,就是价格,升序
高亮:

在这里插入图片描述

request.source().query(QueryBuilders.matchQuery("all",requestParam.getKey())).highlighter(new HighlightBuilder().field("name").requireFieldMatch(false).preTags("<strong>").postTags("</strong"));

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

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

相关文章

【CANopen】周立功轻松入门CANopen笔记

前言 想学习些新东西了&#xff0c;原本想直接学学Ethercat&#xff0c;但是简单看了看对象字典啥的概念一头雾水的&#xff0c;决定先从CANopen开始&#xff0c;Ethercat看着头疼。Etehrcat和CANopen有挺多类似的地方。感谢ZLG的这个入门笔记&#xff0c;我似乎是看懂了些&am…

ITIL 4服务连续性管理实践

一、目的和描述 关键信息 服务连续性管理实践的目的是确保灾难发生时&#xff0c;服务的可用性和性能能够保持在足够的水平。本实践提供了一个框架机制&#xff0c;利用产生有效响应的能力来构建组织的弹性&#xff0c;以保障关键利益相关者的利益&#xff0c;还有组织的声誉…

数据库视图与索引经典题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 视图与索引视图&#xff1a;定义视图创建视图删除视图查询视图视图的作用 索引索引的概念索引的类型设计索引 视图与索引 视图&#xff1a; 视图是从一个或几个基…

chatgpt生成pygame opengl实现旋转用图片填充的3d三角形

import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import *def draw_triangle():vertices ((0, 2, 0), # 顶点1(-2, -2, 0), # 顶点2(2, -2, 0) # 顶点3)tex_coords ((1, 2), # 顶点1的纹理坐标(1, 1), # 顶点2的纹理坐标(2, …

HTML期末作业-精仿故宫模板(HTML+CSS+JavaScript)

期末作业完成&#xff01;我仿了故宫官网&#xff0c;老师给了90分。现在分享给大家&#xff01; 首页包含功能&#xff1a; 轮播图&#xff1a;在首页顶部设置一个可自动轮播的图片展示区域&#xff0c;展示多张宣传图片或产品图片&#xff0c;提升页面的视觉效果和吸引力。…

Ajax简介和实例

目录 什么是 AJAX &#xff1f; AJAX实例 ajax-get无参 ajax-get有参 对象和查询字符串的互转 ajax-post ajax-post 表单 AJAX 是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。 什么是 AJAX &#xff1f; 菜鸟教程是这样介绍的&#xff1a…

使用TypeScript实现贪吃蛇小游戏(网页版)

本项目使用webpackts所编写 下边是项目的文件目录 /src下边的index.html页面是入口文件 index.ts是引入所有的ts文件 /modules文件夹是用来存放所有类的 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"…

SpringCloud:微服务技术

一、认识微服务&#xff1a; 首先&#xff0c;微服务架构不等于SpringCloud&#xff0c;微服务架构是一种经过良好架构设计的分布式架构方案&#xff0c; &#xff0c;它将应用构建成一系列按业务领域划分模块的&#xff0c;小的自治服务&#xff0c;并解决服务拆分所产生的各种…

【网络】TCP协议详解

目录 TCP协议格式 感性理解TCP报头 认识报头中的字段 序号和确认序号 4位首部长度 窗口大小 标记位 确认应答机制 超时重传机制 TCP协议格式 感性理解TCP报头 linux内核是用C语言写的&#xff0c;所以报头实际上就是一种结构化的数据对象&#xff0c;用伪代码可表示为…

UNI-APP_subNVue原生子窗口使用,web-view层级问题解决

subNVues文档 app-subnvues文档 subNVues开发指南 需求&#xff1a;在pages/cloud_control/index页面使用subNVue原生子窗口 1.pages文件配置 "app-plus": {"bounce": "none","subNVues":[{"id": "control_popup&qu…

Chapter 3: Conditional | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介Chapter 3: Conditional executionBoolean expressionsLogical operatorsConditional executionAlternative executionChained conditionalsNested conditionalsCatching exceptions using try and exceptShort-circuit evaluation of lo…

idea 启动项目 java: Compilation failed: internal java compiler error

1. 首先查看 项目的 编译的 JDK 版本是否是 匹配了或匹配的 2. 堆分配的内存不足导致&#xff0c;如下图位置 堆 构建程序的 堆大小调大