ElasticSearch - 基于 “黑马旅游” 案例,实现搜索框、分页、条件过滤、附近酒店、广告置顶功能

news2024/11/24 12:04:42

目录

一、黑马旅游案例

1.1、实现 搜索框 和 分页 功能

1.1.1、需求分析

a)首先搜索框需求

b)分页需求

1.1.2、定义实体类

1.1.2、定义 controller

1.1.3、注入 RestHighLevelClient

1.1.4、实现 IHotelService 接口的 search 方法

1.1.5、功能展示

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

1.2.1、需求分析

1.2.2、给 RequestParam 添加参数

1.2.3、修改 search 方法

1.3、附近的酒店

1.3.1、需求分析

1.3.2、给 RequestParam 添加参数

1.3.3、修改 search 方法

1.3.4、修改解析方式

1.4、广告置顶(让指定酒店在搜索排名中置顶)

1.4.1、需求分析

1.4.2、HotelDoc 实体类添加属性

1.4.3、修改查询,使用 function score 进行算分排序


一、黑马旅游案例


1.1、实现 搜索框 和 分页 功能

1.1.1、需求分析

a)首先搜索框需求

在搜索框中输入 “如家酒店”,之后点击搜索,可以看到发送了如下请求,参数如下

  • key:搜索框的内容
  • page:页码.
  • size:一页展示多少条数据.
  • sortBy:排序规则(默认按照查询关键词的匹配度,降序排序)

调到请求头可以看到,请求的格式为 JSON,如下图

那么需求就是当用户点击搜索框后,后端进行处理,然后返回搜索到的数据总数,以及酒店数据列表(返回格式为 JSON).

Ps:响应的数据和格式是提前约定好的.

b)分页需求
点击分页后,也会触发相同的请求,响应格式也一样.

1.1.2、定义实体类

定义实体类,用于接收前端请求以及返回响应.

请求实体类如下:

@Data
public class RequestParams {

    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;

}

响应实体类如下:

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

1.1.2、定义 controller

Controller 这边负责接收前端传入的 JSON 参数,然后调用接口 IHotelService 的 search 方法.

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

    @Autowired
    private IHotelService hotelService;

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

}

IHotelService 接口如下: 


public interface IHotelService extends IService<Hotel> {
    PageResult search(RequestParams params);
}

1.1.3、注入 RestHighLevelClient

将 ElasticSearch 的高级客户端注入到 Spring 容器中,方便后续通过 es 实现搜索功能.

@Component
public class ESComponent {

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

}

1.1.4、实现 IHotelService 接口的 search 方法

这里的实现思路就和上一章中讲到 JavaRestClient 文档操作步骤一样~

  1. 创建 search 请求.
  2. 准备请求参数,具体的参数如下
    1. 通过 QueryBuilders 构建 match 查询(如果用户没有输入,直接点击搜索,那么这里构建 match_all 查询所有即可).
    2. 添加分页数据:from(offset 偏移量) 和 size(数据显示条数).
  3. 发送请求,接收响应.
  4. 解析响应,获取查询总数和酒店信息列表.

代码如下:

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

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public PageResult search(RequestParams params) {
        try {
            //1.创建请求
            SearchRequest request = new SearchRequest("hotel");
            //2.准备参数
            // 1) 查询
            String searchContent = params.getKey();
            if(!StringUtils.hasLength(searchContent)) {
                request.source().query(QueryBuilders.matchAllQuery());
            } else {
                request.source().query(QueryBuilders.matchQuery("all", searchContent));
            }
            // 2) 分页
            Integer page = params.getPage();
            Integer size = params.getSize();
            if(page == null || size == null) {
                throw new IOException("分页数据不能为空!");
            }
            request.source().from((page - 1) * size).size(size);
            //3.发送请求,接收响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //4.解析响应
            return handlerResponse(response);
        } catch (IOException e) {
            System.out.println("[HotelService] 搜索失败!");
            e.printStackTrace();
            return null;
        }
    }

    private PageResult handlerResponse(SearchResponse response) throws JsonProcessingException {
        //1.解析结果
        SearchHits hits = response.getHits();
        long total = hits.getTotalHits().value;
        SearchHit[] hits1 = hits.getHits();
        List<HotelDoc> hotelDocList = new ArrayList<>();
        for(SearchHit searchHit : hits1) {
            //获取source
            String json = searchHit.getSourceAsString();
            hotelDocList.add(objectMapper.readValue(json, HotelDoc.class));
        }
        return new PageResult(total, hotelDocList);
    }

}

1.1.5、功能展示

在搜索框中输入 “如家” 关键字,点击搜索,下方展示有关 “如家” 关键词的酒店数据.

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

1.2.1、需求分析

给搜索增加如下图过滤条件后,点击搜索,展示过滤后的数据.

因此请求中需要增加 5 个参数:

  1. city:城市
  2. starName:星级
  3. brand:品牌.
  4. minPrice:价格下限.
  5. maxPrice:价格上限.

后端根据请求计算响应数据:搜索出的数据总数、酒店数据列表.

1.2.2、给 RequestParam 添加参数

@Data
public class RequestParams {

    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;

}

1.2.3、修改 search 方法

根据需求所述,这里可以使用 复合查询(BoolQuery).

需要过滤的字段有 city、brand、starName、price.  前三个字段都有一个特点——不可分词,都是 keyword 类型,因此这里可以使用 term 精确查询.  而 price 这里就是用 range 范围查询即可.

Ps:这里为了提高代码的可读性,我这里将查询过滤逻辑封装到一个方法中了.

    private BoolQueryBuilder getHandlerBoolQueryBuilder(RequestParams params) {
        //使用 boolean 查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 1) 查询
        String searchContent = params.getKey();
        if(!StringUtils.hasLength(searchContent)) {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchContent));
        }
        // 2) 城市过滤
        String city = params.getCity();
        if(StringUtils.hasLength(city)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", city));
        }
        // 3) 品牌过滤
        String brand = params.getBrand();
        if(StringUtils.hasLength(brand)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", brand));
        }
        // 4) 星级过滤
        String star = params.getStarName();
        if(StringUtils.hasLength(star)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", star));
        }
        // 5) 价格范围过滤
        if(params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders
                    .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        return boolQueryBuilder;
    }

    @Override
    public PageResult search(RequestParams params) {
        try {
            //1.创建请求
            SearchRequest request = new SearchRequest("hotel");
            //2.准备参数
            BoolQueryBuilder boolQuery = getHandlerBoolQueryBuilder(params);
            request.source().query(boolQuery);
            //3.分页
            Integer page = params.getPage();
            Integer size = params.getSize();
            if(page == null || size == null) {
                throw new IOException("分页数据不能为空!");
            }
            request.source().from((page - 1) * size).size(size);
            //4.发送请求,接收响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //5.解析响应
            return handlerResponse(response);
        } catch (IOException e) {
            System.out.println("[HotelService] 搜索失败!");
            e.printStackTrace();
            return null;
        }
    }

1.3、附近的酒店

1.3.1、需求分析

当用户点击小地图上的定位点时,可以自动定位到自己的位置,然后显示附近的酒店列表,并展示出距离.

点击定位按钮以后前端会返回当前你所在位置的经纬度信息,如下

那么请求中就需要增加一个 经纬度 参数.

1.3.2、给 RequestParam 添加参数

这里只需要添加一个 String 参数即可,用来接收经纬度信息.

@Data
public class RequestParams {

    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;

}

1.3.3、修改 search 方法

需要给 search 方法添加按照距离排序的逻辑.

            //4.根据距离排序
            request.source().sort(SortBuilders.geoDistanceSort("location",
                    new GeoPoint(params.getLocation()))
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS));

这里如果不太清楚,可以对比一下 DSL 语句.

1.3.4、修改解析方式

从需求分析中可以看出,酒店信息中还需要显示 “距离您 xx km” 的信息,因此,这里我们还需要解析出响应中的排序后的 “目的地与你当前位置的距离” 这个属性,如下:

因此这里还需要给 HotelDoc 添加一个 Object 属性,表示距离.

@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 handlerResponse(SearchResponse response) throws JsonProcessingException {
        //1.解析结果
        SearchHits hits = response.getHits();
        long total = hits.getTotalHits().value;
        SearchHit[] hits1 = hits.getHits();
        List<HotelDoc> hotelDocList = new ArrayList<>();
        for(SearchHit searchHit : hits1) {
            //获取source
            String json = searchHit.getSourceAsString();
            HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class);
            Object[] sortValues = searchHit.getSortValues();
            //通过 getSortValues 得到的是一个 Object 类型数组,因为有可能是根据多个条件排序(价格、评价、距离...)
            //获取的下标,就要看你先给谁排序,谁就是 0 下标(以此类推)
            if(sortValues != null && sortValues.length > 0) {
                hotelDoc.setDistance(sortValues[0]);
            }
            hotelDocList.add(hotelDoc);
        }
        return new PageResult(total, hotelDocList);
    }

Ps:通过 getSortValues 得到的是一个 Object 类型数组,因为有可能是根据多个条件排序(价格、评价、距离...) 获取的下标,就要看你先给谁排序,谁就是 0 下标(以此类推).

1.3.5、演示效果

点击定位点后,会根据与你的距离进行升序排序,然后通过分页显示出对应酒店数据.

Ps:下图中的距离过远,是因为我没有像数据库中添加我当前位置附近的酒店(都是跨省的). 

1.4、广告置顶(让指定酒店在搜索排名中置顶)

1.4.1、需求分析

用户点击搜索之后,会优先将广告数据(特殊标记的酒店信息)置顶.

因此需要在 HotelDoc 中添加一个字段,用来标记当前酒店信息是否存在是广告,然后后端对广告信息多分配一些算分权重,让广告数据置顶.

1.4.2、HotelDoc 实体类添加属性

在 HotelDoc 中添加一个 Boolean 类型的字段,用来标记当前酒店信息是否存在是广告.

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

    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();
    }
}

1.4.3、修改查询,使用 function score 进行算分排序

function score 进行查询的过滤条件,就可以使用 term 精确查询 isAD 的值是否 true,如果是,就通过 weight 修改权重(这里没有指定加权模式,因此默认是相乘).

    private void HandlerBoolQueryBuilder(SearchRequest request, RequestParams params) {
        //1.使用 boolean 查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 1) 查询
        String searchContent = params.getKey();
        if(!StringUtils.hasLength(searchContent)) {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchContent));
        }
        // 2) 城市过滤
        String city = params.getCity();
        if(StringUtils.hasLength(city)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", city));
        }
        // 3) 品牌过滤
        String brand = params.getBrand();
        if(StringUtils.hasLength(brand)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", brand));
        }
        // 4) 星级过滤
        String star = params.getStarName();
        if(StringUtils.hasLength(star)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", star));
        }
        // 5) 价格范围过滤
        if(params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders
                    .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }

        //2.算分控制
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                //原始查询,相关性算分查询
                boolQueryBuilder,
                //function score 的数组
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        //其中的一个 function score 元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                //算分函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );
        request.source().query(functionScoreQuery);
    }

这里可以对应着 DSL 语句去看

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【OLSR路由协议】链路状态路由(OLSR)协议中选择多点中继节点算法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

AIGC 绘画Stable Diffusion工具的安装与使用

我们先让ChatGPT来帮我们回答一下,什么是Stable Diffusion Stable Diffusion 是一种基于概率模型的图像生成技术。它通过对图像空间中每个像素的颜色值进行推断,从而生成具有高度真实感和细节的图像。 Stable Diffusion 使用一种称为扩散过程的方法来生成图像。在生成过程中…

Gd-DOTA,CAS:72573-82-1,钆特酸

产品简介&#xff1a;螯合剂是金属原子或离头子与樤含有两个或连两个以上配位原子的配位体作用&#xff0c;生成具有环状结构的络合物&#xff0c;该络合物叫做螯合物。能生成螯合物的这种配体物质叫螯合剂&#xff0c;也成为络合剂。 CAS号&#xff1a;72573-82-1 中文名&am…

基于VR元宇宙技术搭建林业生态模拟仿真教学系统

随着科技的飞速发展&#xff0c;教学方式也正在经历着巨大的变革。林业经济学元宇宙虚拟教学系统作为一种新兴的教学方式&#xff0c;为学生和教师提供了一个全新的、沉浸式的学习和教学环境。 森林管理和监测 元宇宙技术可以用于森林管理和监测。通过无人机、传感器和虚拟现实…

缓冲区溢出漏洞分析

一、实验目的 熟悉软件安全需求分析方法&#xff0c;掌握软件安全分析技术。 二、实验软硬件要求 1、操作系统&#xff1a;windows 7/8/10等 2、开发环境&#xff1a;VS 6.0&#xff08;C&#xff09;、OllyDbg 三、实验预习 《软件安全技术》教材第3章 四、实验内容&#…

中间相遇法(分治类问题非等大分治的平衡做法)

分治&#xff0c;如果分成两半大小不一样&#xff0c;很容易被卡到 O ( n 2 ) O(n^2) O(n2) 在某些题目中&#xff0c;利用中间相遇法&#xff0c;我们可以优化这个过程 其优化的前提是分治的大头在找分界点 复杂度不用证&#xff0c;很好理解吧 这层找地越久&#xff0c;下…

常识判断 --- 科技常识

目录 力与热 光和声 航空成就 垃圾分类 百科知识 血型 二十四节气歌 春雨惊春清谷天 夏满忙夏暑相连 秋处露秋寒霜降 冬雪雪冬小大寒 力与热 光和声 航空成就 垃圾分类 百科知识 血型

机器人中的数值优化|【四】L-BFGS理论推导与延伸

机器人中的数值优化|【四】L-BFGS理论推导与延伸 往期内容回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法&#xff0c;可行牛顿法的python实现&#xff0c;以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化&#xff0c;拟牛…

丰田的国际化与转型困境:对中国车企的欧洲策略启示

摘要&#xff1a;欧洲市场的消费者汽车偏好多样,中国车企进军欧洲时,需考虑产品设计和当地法规。回顾历史,丰田汽车通过其独特管理理念,在美国从廉价品牌形象成功转型为高质量、受信赖的全球品牌。但进入电动汽车时代&#xff0c;日本车企因深度共生的传统供应链而转型坎坷。中…

如何使用ES6+特性进行现代前端开发?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用ES6 特性进行现代前端开发1. 使用 let 和 const2. 使用箭头函数3. 使用模板字符串4. 使用解构赋值5. 使用类和模块6. 使用扩展运算符和剩余参数7. 异步编程8. 使用模块打包工具9. 使用现代浏览器 ⭐ 写在最后 ⭐ 专栏简介 前端入门之…

ChatGPT推出全新功能,引发人工智能合成声音担忧|百能云芯

人工智能AI科技企业OpenAI公司25日宣布&#xff0c;其聊天应用程序ChatGPT如今具备「看、听、说」能力&#xff0c;至少能够理解口语、用合成语音回应并且处理图像&#xff1b;但专家忧心&#xff0c;以假乱真与深度伪造的乱象可能变本加厉。 国家广播公司新闻网(NBC News)报导…

1.算法——数据结构学习

算法是解决特定问题求解步骤的描述。 从1加到100的结果 # include <stdio.h> int main(){ int i, sum 0, n 100; // 执行1次for(i 1; i < n; i){ // 执行n 1次sum sum i; // 执行n次} printf("%d", sum); // 执行1次return 0; }高斯求和…

孙哥Spring源码第26集

第26集、AnnotationAwareAspectJAutoProxyCreator源码 【视频来源于&#xff1a;B站up主孙帅suns Spring源码视频】【微信号&#xff1a;suns45】 26.1、postProcessAfterInitialization分析 26.2、wrapIfNecessary分析 26.3、createProxy分析 26.4、getProxy 26.5、BeanPost…

xxl-job分布式调度框架

课程目标 1、 掌握xxl-job部署以及开发的方式 2、 掌握xxl-job特性以及架构设计 3、 掌握xxl-job运行原理 内容定位 适合已经掌握了Quartz的同学 quartz这节课是本节课的基础&#xff0c;这个要求大家一定掌握&#xff0c;因为xxl-job早期就是使用quartz改造的&#xff0…

使用华为eNSP组网试验⑵-通过端口地址进行静态路由

有了网络模拟器可以对很多网络应用场景进行模拟&#xff0c;既方便学习又有利于实际的网络实施。 之前因为没有用过&#xff0c;用过了才知道eNSP的好处。但是与思科模拟器不同&#xff0c;连接是自动连接&#xff0c;不能确定端口&#xff0c;比如使用指定的光纤端口或者RJ45的…

地球的某一片红薯地中秋圆辉少许《乡村振兴战略下传统村落文化旅游设计》——2023学生旅行季许少辉八月新书想象和世界一样宽广

地球的某一片红薯地中秋圆辉少许《乡村振兴战略下传统村落文化旅游设计》——2023学生旅行季许少辉八月新书想象和世界一样宽广 地球的某一片红薯地中秋圆辉少许《乡村振兴战略下传统村落文化旅游设计》——2023学生旅行季许少辉八月新书想象和世界一样宽广

基于微信小程序的民宿短租酒店预订系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

存储引擎InnoDB与MyISAM

操作引擎 查看存储引擎 show engines;设置系统默认的存储引擎 查看默认的存储引擎 show variables like %storage_engine%;修改默认的存储引擎 set default_storage_engineMyISAM;或者修改my.cnf文件&#xff1a; :::tips default-storage-engineMyISAM #重启服务systemct…

CentOS下安装MySQL 8.1及备份配置

1 卸载原来的MySQL版本 移除之前部署的mysql软链接 # unlink /etc/init.d/mysql # unlink /usr/bin/mysql2 下载最新的MySQL版本 https://dev.mysql.com/downloads/mysql/8.0.html 我这里直接把地址放在这里&#xff1a;https://cdn.mysql.com//Downloads/MySQL-8.1/mysql…

数据结构_链表

查询慢&#xff1a;链表中地址不是连续的&#xff0c;每次查询元素都必须从 头 开始查询增删快&#xff1a;链表结构&#xff0c;增加/删除一个元素&#xff0c;对链表的整体结构没有影响&#xff0c;所以增删快链表中的每一个元素也称为一个 节点一个节点包含了一个数据源&…