详解SpringCloud微服务技术栈:ElasticSearch实战(旅游类项目)

news2024/12/24 0:45:38

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:详解SpringCloud微服务技术栈:ElasticSearch实践2——RestClient查询并处理文档
📚订阅专栏:微服务技术全家桶
希望文章对你们有所帮助

经过前面的学习,需要扎实地掌握如何使用DSL语句对索引库、文档进行操作,如何使用DSL对搜索结果进行进一步处理(排序、分页、高亮),并且需要会使用RestClient,熟练使用API去实现DSL请求的发送,操作ElasticSearch。
现在需要用所学的东西在项目中做实践,这里要做的是一个旅游类的项目,很多的功能都由ElasticSearch来实现。

ElasticSearch实战(旅游类项目)

  • 导入工程
  • 搜索、分页
  • 条件过滤
  • 附近酒店
  • 广告置顶

导入工程

在hotel-demo中,自带了前端页面,可以从网盘中下载并导入:

链接:https://pan.baidu.com/s/15lxTgc59WqLmq5B2dr3Ofg?pwd=3gz2
提取码:3gz2

打开启动类,访问端口8089:
在这里插入图片描述
页面中打开控制台,点击搜索键查看发送的请求,携带这些参数发起了POST请求:
在这里插入图片描述
接下来就要对这请求进行处理。key就是用户搜索的关键字,page分页的页码,size为每页的大小,sortBy表示参与排序的字段,例如点击评价来搜索,sortBy就会指定为score。

搜索、分页

1、定义实体类,接收前端的JSON请求:

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

2、定义controller接口,接收页面请求,调用IHotelService的search方法
在这里插入图片描述
请求方式为post,请求路径为/hotel/list,请求参数是RequestParam独享,返回PageResult(包含2个属性:总条数和当前酒店数据)来做分页查询。
(1)先在pojo下创建实体类PageResult:

@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)新建HotelController类:

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

    @Resource
    private IHotelService hotelService;

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

}

3、定义IHotelService的search方法,利用match查询实现根据关键字搜索酒店信息。
查询的实现放在实现类HotelService中。
在之前已经在测试类中编写过很多类似的代码,流程无非就是编写请求->编写DSL->发起请求得到响应->处理请求并返回。
而发起请求的操作是给RestHighLevelClient来做的,在之前我们是直接new出一个对象,并在启动的时候提前创建:
在这里插入图片描述
在这里我们可以将其注入到Spring中去,点开启动类,使用bean注入:

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

现在,实现类HotelService可以直接去注入这个对象了,实现类代码如下:

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

    @Resource
    private RestHighLevelClient client;

    @Override
    public PageResult search(RequestParams params) {
        try {
            //准备request
            SearchRequest request = new SearchRequest("hotel");
            //准备DSL,先获取搜索的内容
            String key = params.getKey();
            if(key == null || "".equals(key)){
                request.source().query(QueryBuilders.matchAllQuery());
            }else{
                //1.关键字搜索
                request.source().query(QueryBuilders.matchQuery("all", key));
            }
            //2.分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page - 1) * size).size(size);

            //发送请求,有异常不能抛出,因为接口没有抛出异常这里也不适合抛,直接try...catch捕获异常即可
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private PageResult handleResponse(SearchResponse response) {
        //解析响应
        SearchHits searchHits = response.getHits();
        //获取总条数
        long total = searchHits.getTotalHits().value;
        //System.out.println("共搜索到" + total + "条数据");
        //文档数组
        SearchHit[] hits = searchHits.getHits();
        //准备好酒店的集合元素,放入PageResult
        List<HotelDoc> hotels = new ArrayList<>();

        for (SearchHit hit : hits) {
            //获取文档source
            String json = hit.getSourceAsString();
            //将json反序列化为对象
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            //System.out.println("hotelDoc = " + hotelDoc);
            hotels.add(hotelDoc);
        }
        //封装并返回
        return new PageResult(total, hotels);
        //System.out.println("response = " + response);
    }
}

在这里插入图片描述

条件过滤

搜索框的下放有一些可选项,用户点击以后就需要根据这些字段进行过滤,当点击之后,前端就会发起对应的请求,需要接收这些参数并且去做过滤。步骤如下:
1、修改RequestParams类,添加brand、city、starName、minPrice、maxPrice等参数

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

2、修改search方法的实现,在关键字搜索时,如果brand等参数存在,对其做过滤,不参与算分。

过滤条件包括:

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

显然,多个条件之间是AND关系,除了上述会包含的term查询和range查询,搜索框中的全文检索查询显然就是must查询,而组合多条件需要用BooleanQuery。

同时需要注意,参数存在才需要做过滤,所以要做好非空的判断。

由于参数多,所以DSL逻辑上的表达会很长,最好可以封装一下:

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

    @Resource
    private RestHighLevelClient client;

    @Override
    public PageResult search(RequestParams params) {
        try {
            //准备request
            SearchRequest request = new SearchRequest("hotel");
            //编写DSL语句
            buildBasicQuery(params, request);
            //分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page - 1) * size).size(size);
            //发送请求,有异常不能抛出,因为接口没有抛出异常这里也不适合抛,直接try...catch捕获异常即可
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        //先构建BooleanQuery,再把查询放进去组合
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1、关键字must检索
        String key = params.getKey();
        if(key == null || "".equals(key)){
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else{
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }
        //2、条件过滤,不参与算分
        //2.1 城市
        if (params.getCity() != null && !params.getCity().equals("")){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        //2.2 品牌
        if (params.getBrand() != null && !params.getBrand().equals("")){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        //2.3 星级
        if (params.getStarName() != null && !params.getStarName().equals("")){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        //3、范围过滤(价格),这里是链式编程要注意
        if (params.getMinPrice() != null && params.getMaxPrice() != null){
            boolQuery.filter(QueryBuilders
                    .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        request.source().query(boolQuery);
    }

    private PageResult handleResponse(SearchResponse response) {
    	//和之前代码一样,略
    }
}

在这里插入图片描述

附近酒店

前端页面点击定位后,会将你所在的地址location发送到后台:
在这里插入图片描述
在这里插入图片描述
需要注意,我只有Edge才能获取到位置,而谷歌浏览器和火狐浏览器都是不支持的。

后台可以根据这个坐标,将酒店的结果按照这个点的距离做升序排序。
实现流程:
1、修改RequestParams字段,接收location字段
在这里插入图片描述
2、修改search方法业务逻辑,若location有值,添加根据geo_distance排序的功能,并且还需要显示出距离,距离信息就在下面与source同级的sort下:
在这里插入图片描述
修改之前的信息处理函数handleResponse即可:

	private PageResult handleResponse(SearchResponse response) {
        //解析响应
        SearchHits searchHits = response.getHits();
        //获取总条数
        long total = searchHits.getTotalHits().value;
        //System.out.println("共搜索到" + total + "条数据");
        //文档数组
        SearchHit[] hits = searchHits.getHits();
        //准备好酒店的集合元素,放入PageResult
        List<HotelDoc> hotels = new ArrayList<>();

        for (SearchHit hit : hits) {
            //获取文档source
            String json = hit.getSourceAsString();
            //将json反序列化为对象
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            //获取排序值(距离信息)
            Object[] sortValues = hit.getSortValues();
            if (sortValues.length > 0 ){
                Object sortValue = sortValues[0];
                hotelDoc.setDistance(sortValue);
            }
            //System.out.println("hotelDoc = " + hotelDoc);
            hotels.add(hotelDoc);
        }
        //封装并返回
        return new PageResult(total, hotels);
        //System.out.println("response = " + response);
    }

注意需要修改HotelDoc,将distance也作为字段:
在这里插入图片描述

广告置顶

让指定的酒店在搜索结果中排名制定,就需要影响他们的打分。
给需要置顶的酒店文档添加一个标记,然后利用function score给带有标记的文档增加权重。实现步骤:
1、给HotelDoc类添加Boolean类型的isAD字段
在这里插入图片描述
2、挑选几个喜欢的酒店,给它的文档数据添加isAD字段,值为true。
实现的方式就不特意写后台了,直接在dev tools中暴力地用DSL语句来做:

POST /hotel/_update/2056126831
{
  "doc": {
    "isAD": true
  }
}
POST /hotel/_update/19989806195
{
  "doc": {
    "isAD": true
  }
}
POST /hotel/_update/2056105938
{
  "doc": {
    "isAD": true
  }
}

3、修改search方法,添加function score功能,给isAD值为true的酒店添加权重。
在之前,根据function score来排序的功能只用了DSL语句来实现,java代码的实现相对还是有点复杂的,最好根据DSL语句做参考来写java代码:
在这里插入图片描述
function_score分为两个部分,一个是query里面放着原始的查询方式,其会返回对应的相关性分数(query score),比较关键的还是functions里面的构造,包括了过滤的条件和权重,最后指定function score和query score的运算方式即可(默认为“multiply”)。
上述DSL语句转换为java代码为:

FunctionScoreQueryBuilder functionScoreQueryBuilder = 
	QueryBuilders.functionScoreQuery(
		QueryBuilders.matchQuery("name", "如家"),
		new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ //需要创建出这个数组
			new FunctionScoreQueryBuilder.FilterFunctionBuilder(
				QueryBuilders.termQuery("brand", "如家"),
				ScoreFunctionBuilders.weightFactorFunction(5)
			)
		}
	);

因此需要修改search中的query条件,也就是修改buildBasicQuery函数,除了原始就该有的BoolQuery,还需要增加function score:

		//算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        // 原始查询,即相关性算分
                        boolQuery,
                        // function score数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                // 具体的一个function score元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        //过滤条件,用精确查询
                                        QueryBuilders.termQuery("isAD", true),
                                        //算分函数,直接设置为10
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        });

        request.source().query(functionScoreQuery);

在这里插入图片描述
成功打上了广告标识。

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

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

相关文章

【JavaScript 基础入门】01 编程语言和计算机基础

编程语言和计算机基础 目录 编程语言和计算机基础1 - 编程语言1.1 编程1.2 计算机语言1.3 编程语言1.4 翻译器1.5 编程语言和标记语言区别1.6 总结 2 - 计算机基础2.1 计算机组成2.2 数据存储2.3 数据存储单位2.4 程序运行 1 - 编程语言 1.1 编程 编程&#xff1a; 就是让计算…

运放反馈电阻上并联小电容的作用

这是一个同相比例运算放大电路&#xff0c; Rf是反馈电阻 有的电路还会在反馈电阻上并联一个小电容&#xff0c;一般在几pF到几十pF。 那么这个电容有什么作用呢&#xff1f;其实这个电容是一个相位补偿电容&#xff0c;防止运放自激振荡的。另外这个电容也有抑制高频噪声的作用…

GoogleNet Inception v2 和 Inception v3详解

1 GoogleNet Inception v2 v1具体结构&#xff1a; v2具体结构&#xff1a; 1 引入Batch Normalization&#xff08;BN&#xff09;: Inception v2在每个卷积层之后引入了BN。这有助于解决深层网络中的梯度消失问题&#xff0c;同时加快训练过程并提高模型的收敛速度。BN通过…

鸿蒙首批原生应用!无感验证已完美适配鸿蒙系统

顶象无感验证已成功适配鸿蒙系统&#xff0c;成为首批鸿蒙原生应用&#xff0c;助力鸿蒙生态的快速发展。 作为全场景分布式操作系统&#xff0c;鸿蒙系统旨在打破不同设备之间的界限&#xff0c;实现极速发现、极速连接、硬件互助、资源共享。迄今生态设备数已突破8亿台&…

[论文阅读] |RAG评估_Retrieval-Augmented Generation Benchmark

写在前面 检索增强能够有效缓解大模型存在幻觉和知识时效性不足的问题&#xff0c;RAG通常包括文本切分、向量化入库、检索召回和答案生成等基本步骤。近期组里正在探索如何对RAG完整链路进行评估&#xff0c;辅助阶段性优化工作。上周先对评估综述进行了初步的扫描&#xff0…

PAT-Apat甲级题1003(python和c++实现)下

PTA | 1003 Emergency 书接上回&#xff0c;上次我们使用了python实现无向带权图与DFS算法的设计&#xff0c;本次我们将使用C对本题进行解答&#xff0c;思路和题目分析同上一节内容&#xff0c;本次我们将在上一节的基础上继续实现。 okok现在又是激动人心的手搓代码时间&a…

JMeter性能测试实战

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

喝酒筛子小游戏集合源码微信小程序喝酒骰子程序带流量主版本源码酒桌玩筛子源码

2023新版酒桌小游戏喝酒小程序源码-&#xff08;流量主版本&#xff09; 修改增加了广告位 根据文档直接替换&#xff0c;原版本没有广告位 直接上传源码到开发者端即可 通过后改广告代码&#xff0c;然后关闭广告展示提交&#xff0c;通过后打开即可 无广告引流 流量主版…

Spring MVC 基本知识

知识回顾 Tomcat 是 Servlet 容器&#xff0c;会解析 Java Web 应用下的 WEB-INF/web.xml 文件&#xff0c;得到相关的 Servlet 组件。 原理解析 Spring MVC 实际是定义了一个 DispatcherSevlet 来统一管理当前 Web 应用下的 Path 路径。在 DispatchSevlet 中持有了一个 Spr…

存储技术架构演进

一. 演进过程 存储技术架构的演进主要是从集中式到分布式的一种呈现&#xff0c;集中式存储模式凭借其在稳定性和可靠性方面的优势成为许多业务数据库的数据存储首选&#xff0c;顾名思义&#xff0c;集中式存储主要体现在集中性&#xff0c;一套集中式管理的存储系统&#xff…

鸿蒙开发-UI-布局-网格

鸿蒙开发-UI-布局 鸿蒙开发-UI-布局-线性布局 鸿蒙开发-UI-布局-层叠布局 鸿蒙开发-UI-布局-弹性布局 鸿蒙开发-UI-布局-相对布局 鸿蒙开发-UI-布局-格栅布局 鸿蒙开发-UI-布局-列表 文章目录 前言 一、基本概念 二、开发布局 1.排列方式 2.设置行列间距 三、应用特性 1.网格数…

数字图像处理(实践篇)三十五 OpenCV-Python在图像上进行SQRBox滤波操作实践

目录 一 方框滤波 二 涉及的函数 三 实践 一 方框滤波 方框滤波是均值滤波的一般形式。二者的不同之处在于在均值滤波中,将滤波器中所有的像素值求和后的平均值作为滤波后结果,方框滤波也是求滤波器内所有像素值的之和࿰

AGP更改gradle版本无效的解决方案

从Github下载了一个项目&#xff0c;非常激进&#xff0c;AGP版本8.4.0&#xff0c;而我的AS只支持到8.2.0 详见&#xff1a;https://developer.android.com/build/releases/gradle-plugin?buildsystemndk-build&hlzh-cn#android_gradle_plugin_and_android_studio_compa…

国标GB/T 28181详解:GB/T28181基本注册流程和注销流程

目 录 一、基本要求 二、注册流程 三、注销流程 四、产品说明 五、参考 一、基本要求 根据《GB/T 28181-2022》第9章关于注册和注销的描述&#xff0c;GB28181的注册和注销应满足下面这些要求&#xff1a; SIP 客户端网关、SIP 设备、联网系统等 SIP 代理…

Python中如何将字符串变成数字?

字符串和数字是Python中常见的数据类型&#xff0c;而且在撰写Python程序的时候&#xff0c;也经常会遇到需要将字符串转换为数字的情况&#xff0c;那么Python中如何将字符串变成数字?有多种方法可以使用&#xff0c;接下来一起来看看具体内容介绍。 1、使用int()函数 int(…

C++(6) 继承

文章目录 继承1. 继承1.1 什么是继承1.2 C 继承方式1.2.1 基本案例1.2.2 继承权限组合1.2.3 继承中构造函数的说法1.2.4 继承中析构函数的执行顺序1.2.5 继承中变量名称冲突问题1.2.6 继承中函数【重写】 继承 1. 继承 1.1 什么是继承 面向对象程序设计中最重要的一个概念是继…

STM32-电动车报警器

STM32-电动车报警器 1.振动传感器点亮LED灯 需求:当振动传感器接收到振动信号时&#xff0c;使用中断方式点亮LED1 //重写中断服务函数&#xff0c;如果检测到EXTI中断请求&#xff0c;则进入此函数 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {//一根中断线上接有多个…

基于springboot网上图书商城源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括网上图书商城的网络应用&#xff0c;在外国网上图书商城已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。网上图书商城具有网上图书信息管理功能的选择…

求两数之间的最大公约数和最小公倍数

1. 最大公约数和最小公倍数的概念 最大公约数&#xff1a;最大公因数&#xff0c;也称最大公约数、最大公因子&#xff0c;指两个或多个整数共有约数中最大的一个。a&#xff0c;b的最大公约数记为&#xff08;a&#xff0c;b&#xff09;&#xff0c;同样的&#xff0c;a&…

标准库中的string类(下)——“C++”

各位CSDN的uu们你们好呀&#xff0c;这段时间小雅兰的内容仍然是Cstring类的使用的内容&#xff0c;下面&#xff0c;让我们进入string类的世界吧&#xff01;&#xff01;&#xff01; string类的常用接口说明 string - C Reference string类的常用接口说明 string类对象的修…