ES练习项目-酒店搜索

目录

  • 1 需求分析
  • 2 酒店搜索和分页
    • 2.1 请求和响应分析
    • 2.2 定义实体类,接收请求参数的JSON对象
    • 2.3 编写controller,接收页面的请求
    • 2.4 编写业务实现,利用RestHighLevelClient实现搜索、分页
  • 3. 酒店结果过滤
    • 3.1 请求和响应分析
    • 3.2 修改请求参数的对象RequestParams
    • 3.2 修改业务逻辑,在搜索条件之外,添加一些过滤条件
  • 4.实现 我周边的酒店
    • 4.1 请求和响应分析
    • 4.2 修改RequestParams参数,接收location字段
    • 4.3 修改search方法,完成距离排序
    • 4.4 排序距离显示
  • 5 酒店竞价排名
    • 5.1 请求和响应分析
    • 5.2 修改Hoteldoc实体类 以及 es添加doc属性
    • 5.3 修改业务层代码
  • 6 实现品牌城市星级价格的聚合
    • 6.1 什么意思
    • 6.2 请求和响应分析
    • 6.3 Controller层实现
    • 6.4 业务层实现

代码请见: https://gitee.com/lhwebsite/es_practice_hotels

1 需求分析

实现四部分功能:

  • 酒店搜索和分页
  • 酒店结果过滤
  • 我周边的酒店
  • 酒店竞价排名
  • 实现品牌城市星级价格的聚合
    在这里插入图片描述

2 酒店搜索和分页

2.1 请求和响应分析

在这里插入图片描述
在这里插入图片描述
由上我们可知:

  • 请求方式:POST
  • 请求路径:/hotel/list
  • 请求参数:JSON对象,包含4个字段:
    • key:搜索关键字
    • page:页码
    • size:每页大小
    • sortBy:排序,目前暂不实现
      再分析下响应信息:
      在这里插入图片描述
      返回值:分页查询,需要返回分页结果PageResult,包含两个属性:
  • total:总条数
  • hotels:当前页的数据

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

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

2.2 定义实体类,接收请求参数的JSON对象

request请求pojo类:

@Data
public class requestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
}

reponse响应pojo类:

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

2.3 编写controller,接收页面的请求

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

    @Autowired
    private IHotelService hotelService;

    @PostMapping("list")
    public PageResult search(@RequestBody RequestParams params) {
        return hotelService.search(params);
    }
}

2.4 编写业务实现,利用RestHighLevelClient实现搜索、分页

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public PageResult searchPageInfo(RequestParams params) throws IOException {
        //1 构建搜索请求对象
        SearchRequest request = new SearchRequest("hotel");
        //2 构建查询条件
        if(params.getKey() == null){
            //如果关键字为空 则无条件查询
            request.source().query(QueryBuilders.matchAllQuery());
        }else{
            request.source().query(QueryBuilders.matchQuery("all",params.getKey()));
        }
        // 3 构建分页
        Integer page = params.getPage()==null?1:params.getPage();
        Integer pageSize = params.getSize()==null?1:params.getSize();
        request.source().from((page - 1)*pageSize).size(pageSize);
        //4 发起请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //5 解析response
        return handerResult(response);
    }

    private PageResult handerResult(SearchResponse response) {
        //1 判断是不是null
        if(response == null){
            return null;
        }
        //2 解析数据
        SearchHits hits = response.getHits();
        //3 获取命中的文档数
        long total = hits.getTotalHits().value;
        //4 获取命中查询的内容
        SearchHit[] hitsArray = hits.getHits();
        List<HotelDoc> docs = new ArrayList<>();
        if(hitsArray.length > 0){
            for (SearchHit hit : hitsArray) {
                String jsonData = hit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(jsonData, HotelDoc.class);
                docs.add(hotelDoc);
            }
        }
        //5 组装pageResult
        return new PageResult(total, docs);
    }

}

3. 酒店结果过滤

3.1 请求和响应分析

需求:添加品牌、城市、星级、价格等过滤功能
在页面搜索框下面,会有一些过滤项:
在这里插入图片描述
前端进入f12查看request和response
在这里插入图片描述
包含的过滤条件有:

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

我们需要做两件事情:

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

3.2 修改请求参数的对象RequestParams

在这里插入图片描述

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

对业务层进行修改,使用bool查询进行组合查询条件:

  • 品牌过滤:是keyword类型,用term查询
  • 星级过滤:是keyword类型,用term查询
  • 价格过滤:是数值类型,用range查询
  • 城市过滤:是keyword类型,用term查询
  • 关键字搜索放到must中,参与算分
  • 其它过滤条件放到filter中,不参与算分

为了提高代码可阅读性,我把bool过滤查询封装到了一个函数:

    /**
     * bool多条件过滤查询构建方法
     * @param request
     * @param params
     */
    private void buildBasicSearch(SearchRequest request, RequestParams params) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //1 设置must全文过滤条件
        if(params.getKey() == null){
            //如果关键字为空 则无条件查询
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }else{
            boolQueryBuilder.must(QueryBuilders.matchQuery("all",params.getKey()));
        }
        //2 设置filter过滤条件
        if(params.getBrand() != null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand",params.getBrand()));
        }
        if(params.getCity() != null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("city",params.getCity()));
        }
        if(params.getStarName() != null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName",params.getStarName()));
        }
        if(params.getMinPrice() != null && params.getMaxPrice() != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        request.source().query(boolQueryBuilder);
    }

4.实现 我周边的酒店

4.1 请求和响应分析

在酒店列表页的右侧,有一个小地图,点击地图的定位按钮,地图会找到你所在的位置:
在这里插入图片描述
并且,在前端会发起查询请求,将你的坐标发送到服务端:
在这里插入图片描述
所以需求就是基于这个location坐标,然后按照距离对周围酒店排序。实现思路如下:

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

4.2 修改RequestParams参数,接收location字段

@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;
    // 我当前的地理坐标
    private String location;
}

4.3 修改search方法,完成距离排序

业务层修改代码:距离排序规则 由近到远排序

        if(params.getLocation() != null){
            //距离排序规则 由近到远排序
            request.source().sort(SortBuilders.geoDistanceSort("location",new GeoPoint(params.getLocation()))
                    .order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));
        }

4.4 排序距离显示

实现以下功能:
在这里插入图片描述
这个实现也很简单,之前学习es时,在es终端输入这个距离排序,得到的结果是:
在这里插入图片描述
因此,我们在结果解析阶段,除了解析source部分以外,还要得到sort部分,也就是排序的距离,然后放到响应结果中。

我们要做两件事:

  • 修改HotelDoc,添加排序距离字段,用于页面显示
  • 修改HotelService类中的handleResponse方法,添加对sort值的获取

首先查看前端页面:
在这里插入图片描述
这里前端接受的是一个叫做distance的值(且保留两位小数),因此,HotelDoc实体类中应该添加一个distance成员变量
在这里插入图片描述
之后修改业务层代码:

                //获取距离
                Object[] sortValues = hit.getSortValues();
                if(sortValues.length > 0){
                    hotelDoc.setDistance(sortValues[0]);
                }

5 酒店竞价排名

5.1 请求和响应分析

充钱了就是牛逼
要让指定酒店在搜索结果中排名置顶,并且像淘宝一样有个“广告”标识

那怎样才能让指定的酒店排名置顶呢?
的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的酒店增加权重

5.2 修改Hoteldoc实体类 以及 es添加doc属性

修改实体类:
在这里插入图片描述
给几个酒店的doc添加isAD标签
首先,我们es原来索引的mapping中没有isAD,那么怎么进行添加?
其实不用改动mapping,es相对于mysql这中关系型数据库有个很强大的特性:我们只需要给某个索引doc添加isAD属性,那么hotel的索引mapping自动会检测到并添加isAD的mapping属性。如下所示:

POST /hotel/_update/2359697
{
  "doc": {
    "isAD":true
  }
}

之后查一下hotel的mapping
在这里插入图片描述

5.3 修改业务层代码

首先我们回一下算分查询的语法把:
在这里插入图片描述
那么根据es的语法,修改代码:

        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                QueryBuilders.termQuery("isAD","true"),
                                ScoreFunctionBuilders.weightFactorFunction(20f)
                        )
                });

最终达到这种效果:
在这里插入图片描述

6 实现品牌城市星级价格的聚合

6.1 什么意思

在这里插入图片描述
搜索页面的品牌、城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的
那么如何解决这个问题呢?

使用聚合功能,利用Bucket聚合,对搜索结果中的文档基于品牌分组、基于城市分组,就能得知包含哪些品牌、哪些城市了,之后在选项中显示包含的结果。
因为是对搜索结果聚合,因此聚合是限定范围的聚合,也就是说聚合的限定条件跟搜索文档的条件一致。

6.2 请求和响应分析

首先,这个功能是通过filters接口实现的:
在这里插入图片描述
那么,我们从前端页面看一下这个request和response的参数:
其中request和上面是一样的
在这里插入图片描述
response大概的样子:
在这里插入图片描述

6.3 Controller层实现

要求:

  • 请求方式:POST
  • 请求路径:/hotel/filters
  • 请求参数:RequestParams,与搜索文档的参数一致
  • 返回值类型:Map<String, List<String>>
    @PostMapping("/filters")
    public Map<String, List<String>> getFilters(@RequestBody RequestParams params){
        return hotelService.getFilters(params);
    }

6.4 业务层实现

service代码如下:

    /**
     * 根据传入的条件 动态过滤出品牌星级城市价格等信息
     * @param params
     * @return
     */
    @Override
    public Map<String, List<String>> getFilters(RequestParams params) throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        request.source().size(0);
        buildBasicSearch(request,params);
        //在上面条件的基础上构建聚合:brand city starName
        buildAggs(request);
        //发起请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //解析聚合数据
        List<String> cities = getBuckNames(response,"brandAggs");
        List<String> brands = getBuckNames(response,"cityAggs");
        List<String> starNames = getBuckNames(response,"starNameAggs");
        //组装为响应结果
        Map<String,List<String>> info = new HashMap<>();
        info.put("city",cities);
        info.put("brand",brands);
        info.put("starName",starNames);
        return info;
    }

封装了两个函数
buildAggs:

    /**
     * 构建brand city starName的聚合
     * @param request
     */
    private void buildAggs(SearchRequest request) {
        request.source().aggregation(AggregationBuilders
                .terms("cityAggs").field("city").size(20));
        request.source().aggregation(AggregationBuilders
                .terms("brandAggs").field("brand").size(20));
        request.source().aggregation(AggregationBuilders
                .terms("starNameAggs").field("starName").size(20));
    }

getBuckNames:

    /**
     * 根据response和聚合名称获取桶的数据
     * @param response
     * @param aggName
     * @return
     */
    private List<String> getBuckNames(SearchResponse response, String aggName) {
        List<String> result = new ArrayList<>();
        Aggregations aggregations = response.getAggregations();
        Terms aggregation = aggregations.get(aggName);
        if(aggregation == null || aggregation.getBuckets().size() == 0){
            return result;
        }
        List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            result.add(key);
        }
        return result;
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java-stream流案例

需求 代码 Vote类 // 1. 定义一个投票类 public class Vote {private String name;private ArrayList<String> voteList;public Vote(String name, ArrayList<String> voteList) {this.name name;this.voteList voteList;}public String getName() {return nam…

比较LLM和RAG技术:塑造AI的未来

在人工智能&#xff08;AI&#xff09;的动态领域中&#xff0c;两项突破性技术——大型语言模型&#xff08;LLM&#xff09;和检索增强生成&#xff08;RAG&#xff09;因其在理解和生成类人文本方面的变革潜力而脱颖而出。本文开始了LLM和RAG之间的比较之旅&#xff0c;阐明…

ROS2专栏(三) | 理解ROS2的动作

​ 1. 创建一个动作 目标&#xff1a; 在ROS 2软件包中定义一个动作。 1.1 新建包 设置一个 workspace 并创建一个名为 action_tutorials_interfaces 的包&#xff1a; mkdir -p ros2_ws/src #you can reuse existing workspace with this naming convention cd ros2_ws/s…

C++:拷贝构造函数与赋值的区别

目录 拷贝构造函数 拷贝构造函数的使用方法 拷贝构造函数与赋值运算符的区别 谈深拷贝和浅拷贝 浅拷贝 注意: 深拷贝 拷贝构造函数 拷贝构造函数的也是一种构造函数,它的作用是将一个类的成员拷贝到另一个类中,类似于赋值。拷贝构造函数分为深拷贝和浅拷贝。 先来定义一…

【MySQL 5.7安装时候 出现2503报错,解决方案】

MySQL5.7 安装遇 2503问题如何解决 1.能正常安装就点这里2.出现2503问题就看这2.1先看问题2.1.1在官网下载好安装包后&#xff0c;首先先确认安装包是否完整&#xff0c;排除安装包损坏的问题2.1.2 安装时候出现这个2503问题 2.2上解决方案2.2.1 打开任务管理器2.2.2 解决 1.能…

网盘—上传文件

本文主要讲解网盘里面关于文件操作部分的上传文件&#xff0c;具体步骤如下 目录 1、实施步骤&#xff1a; 2、代码实现 2.1、添加上传文件协议 2.2、添加上传文件槽函数 2.3、添加槽函数定义 2.4、关联上传槽函数 2.5、服务器端 2.6、在服务器端添加上传文件请求的ca…

4G远程温湿度传感器在农业中的应用—福建蜂窝物联网科技有限公司

解决方案 农业四情监测预警解决方案 农业四情指的是田间的虫情、作物的苗情、气候的灾情和土壤墒情。“四情”监测预警系统的组成包括管式土壤墒情监测站、虫情测报灯、气象站、农情监测摄像机&#xff0c;可实时监测基地状况,可以提高监测的效率和准确性&#xff0c;为农业生…

分布式系统事务一致性解决方案(基于事务消息)

参考&#xff1a;https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage/ 文章目录 概要错误的方案方案一&#xff1a;业务方自己实现方案二&#xff1a;RocketMQ 事务消息什么是事务消息事务消息处理流程事务消息生命周期使用限制使用示例使用建议 概要 …

进迭时空宣布开源RISC-V芯片的AI核心技术

仟江水商业电讯&#xff08;4月29日 北京 委托发布&#xff09;4月29日&#xff0c;在“创芯生生不息——进迭时空2024年度产品发布会”上&#xff0c;进迭时空CEO、创始人&#xff0c;陈志坚博士宣布将开源进迭时空在自研RISC-V AI CPU上的核心技术&#xff0c;包括AI扩展指令…

数据科学导论续

一、大数据采集的流程和方法 大数据采集的流程和方法 系统日志采集方法 很多互联网企业都有自己的海量数据采集工具&#xff0c;多用于系统日志采集&#xff0c;例如&#xff1a; Flume&#xff1a;分布式日志收集系统&#xff0c;最初由Cloudera开发&#xff0c;现是Apache的…

SPSS之判别分析

SPSS的判别分析过程中默认使用的是Fisher判别法和Bayes判别法&#xff0c;并以前者为主&#xff0c;在指定选项后也可以给出Bayes判别法的结果。 SPSS中判别分析在【分析】—【分类】—【判别】中完成。选定类别变量放入【分组变量】框中&#xff0c;单击定义范围(D)按钮给出类…

《Fundamentals of Power Electronics》——Buck、Boost、Buck-Boost三个电路的CCM-DCM工作特性总结

Buck、Boost、Buck-Boost这三个电路的CCM-DCM工作特性总结如下表所示&#xff1a; Buck、Boost、Buck-Boost这三个电路工作在DCM模式下电压传输比的对比图如下所示&#xff1a; 由上图可知&#xff0c;Buck-Boost电路的工作特性是一条斜率为的直线&#xff0c;Buck电路和Boost电…

IDEA 中的奇技淫巧

IDEA 中的奇技淫巧 书签 在使用ctrlalt方向键跳转时&#xff0c;或者追踪代码时&#xff0c;经常遇到的情况是层级太多&#xff0c;找不到代码的初始位置&#xff0c;入口。可以通过书签的形式去打上一个标记&#xff0c;后续可以直接跳转到书签位置。 标记书签&#xff1a;c…

Qt窗口

QMainWindow Qt 窗⼝ 是通过 QMainWindow类 来实现的。 QMainWindow 是⼀个为⽤⼾提供主窗⼝程序的类&#xff0c;继承⾃ QWidget 类&#xff0c;并且提供了⼀个预定义的 布局。QMainWindow 包含 ⼀个菜单栏&#xff08;menu bar&#xff09;、多个⼯具栏(tool bars)、多个浮动…

Python并发编程:揭开多线程与异步编程的神秘面纱

第一章&#xff1a;并发编程导论 1.1 并发与并行概念解析 1.1.1 并发性与并行性的区别 想象一下繁忙的厨房中多位厨师同时准备不同的菜肴——即使他们共享有限的空间和资源&#xff0c;也能协同工作&#xff0c;这就是并发性的一个生动比喻。并发性意味着多个任务在同一时间…

getchar和putchar的用法

getchar() 和 putchar() 是一对字符输入/输出函数.他们通常比scanf() 和printf() 函数更快更便捷。 getchar()不带任何参数&#xff0c;其实getchar() 和putchar()与scanf() 和printf()功能相似。 接下来博主简单的跟大家解释一下。 1.getchar 通常把输入的字符赋予一个字符变…

uReport2 报表设计

最近刚好用到这个报表工具&#xff0c;刚开始接触都还不会用&#xff0c;学习了一下&#xff0c;在这边做个记录。 数据源 目前报表框架支持和使用的数据源连接有两种方式&#xff1a;添加数据库连接 和添加内置数据源连接。 进入报表设计 http://IP:端口/context-path/urepor…

计算机网络之传输层TCP\UDP协议

UDP协议 用户数据报协议UDP概述 UDP只在IP数据报服务之上增加了很少功能&#xff0c;即复用分用和差错检测功能 UDP的主要特点&#xff1a; UDP是无连接的&#xff0c;减少开销和发送数据之前的时延 UDP使用最大努力交付&#xff0c;即不保证可靠交付&#xff0c;可靠性由U…

一款神奇的地理数据可视化python库

在地理信息系统&#xff08;GIS&#xff09;和地理数据可视化领域&#xff0c;Python的易用性和强大的库支持使其成为处理地理数据的理想选择之一。今天我们介绍Cartopy库&#xff0c;它为地理数据可视化提供了强大的支持。无论是对于GIS专业人士还是对地理数据可视化感兴趣的初…

网络编程——TCP

socket socket类型 流式套接字(SOCK_STREAM) TCP 提供了一个面向连接、可靠的数据传输服务&#xff0c;数据无差错、无重复、无丢失、无失序的发送且按发送顺序接收。内设置流量控制&#xff0c;避免数据流淹没慢的接收方。数据被看作是字节流&#xff0c;无长度限制。 数据报…