因此,我们实现业务的流程如下:
-
步骤一:定义实体类
RequestParams
,接收请求参数的JSON对象 -
步骤二:编写
controller
,接收页面的请求 -
步骤三:编写业务实现,利用
RestHighLevelClient
实现搜索、分页
1.2 定义实体类
实体类有两个:
-
一个是前端的请求参数实体
RequestParams
-
一个是服务端应该返回的响应结果实体
PageResult
(1)请求参数
前端请求的json结构如下:
{
“key”: “搜索关键字”,
“page”: 1,
“size”: 3,
“sortBy”: “default”
}
因此,我们在cn.itcast.hotel.pojo
包下定义一个实体类RequestParams
:
@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
中定义返回结果类PageResult
:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult {
private Long total;
private List hotels;
}
1.3 定义controller
定义一个HotelController
,声明查询接口,满足下列要求:
-
请求方式:
Post
-
请求路径:
/hotel/list
-
请求参数:对象,类型为
RequestParam
-
返回值:
PageResult
,包含两个属性 -
Long total
:总条数 -
List<HotelDoc> hotels
:酒店数据
因此,我们在cn.itcast.hotel.web
中定义HotelController
:
@RestController
@RequestMapping(“/hotel”)
public class HotelController {
@Autowired
private IHotelService hotelService;
// 搜索酒店数据
@PostMapping(“/list”)
public PageResult search(@RequestBody RequestParams params){
return hotelService.search(params);
}
}
1.4 实现搜索业务
(1)在cn.itcast.hotel.service
中的IHotelService
接口中定义一个方法:
/**
-
根据关键字搜索酒店信息
-
@param params 请求参数对象,包含用户输入的关键字
-
@return 酒店文档列表
*/
PageResult search(RequestParams params);
(2)实现搜索业务,肯定离不开RestHighLevelClient
,我们需要把它注册到Spring中作为一个Bean
。在cn.itcast.hotel
中的HotelDemoApplication
中声明这个Bean:
@MapperScan(“cn.itcast.hotel.mapper”)
@SpringBootApplication
public class HotelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HotelDemoApplication.class, args);
}
@Bean
public RestHighLevelClient client(){
return new RestHighLevelClient(RestClient.builder(
HttpHost.create(“http://192.168.75.130:9200”)
));
}
}
(3)在cn.itcast.hotel.service.impl
中的HotelService
中实现search
方法:
@Override
public PageResult search(RequestParams params) {
try {
// 1.准备Request
SearchRequest request = new SearchRequest(“hotel”);
// 2.准备DSL
// 2.1.query
String key = params.getKey();
if (key == null || “”.equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery(“all”, key));
}
// 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 handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
解析相响应方法handleResponse
:
// 结果解析
private PageResult handleResponse(SearchResponse response) {
// 4.解析响应
SearchHits searchHits = response.getHits();
// 4.1.获取总条数
long total = searchHits.getTotalHits().value;
// 4.2.文档数组
SearchHit[] hits = searchHits.getHits();
// 4.3.遍历
List hotels = new ArrayList<>();
for (SearchHit hit : hits) {
// 获取文档source
String json = hit.getSourceAsString();
// 反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
// 放入集合
hotels.add(hotelDoc);
}
// 4.4.封装返回
return new PageResult(total, hotels);
}
2.1 需求分析
需求:添加品牌、城市、星级、价格等过滤功能
如图:在页面搜索框下面,会有一些过滤项:
传递的参数如图:
包含的过滤条件有:
-
brand
:品牌值 -
city
:城市 -
minPrice~maxPrice
:价格范围 -
starName
:星级
我们需要做两件事情:
-
修改请求参数的对象
RequestParams
,接收上述参数 -
修改业务逻辑,在搜索条件之外,添加一些过滤条件
2.2 修改实体类RequestParams
修改在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;
}
2.3 修改搜索业务
在HotelService
的search
方法中,只有一个地方需要修改:requet.source().query( ... )
其中的查询条件。
在之前的业务中,只有match
查询,根据关键字搜索,现在要添加条件过滤,包括:
-
品牌过滤:是
keyword
类型,用term
查询 -
星级过滤:是
keyword
类型,用term
查询 -
价格过滤:是数值类型,用
range
查询 -
城市过滤:是
keyword
类型,用term
查询
多个查询条件组合,肯定是boolean
查询来组合:
-
关键字搜索放到
must
中,参与算分 -
其它过滤条件放到
filter
中,不参与算分
buildBasicQuery
的代码如下:
private void buildBasicQuery(RequestParams params, SearchRequest request) {
// 1.构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 2.关键字搜索
String key = params.getKey();
if (key == null || “”.equals(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery(“all”, key));
}
// 3.城市条件
if (params.getCity() != null && !params.getCity().equals(“”)) {
boolQuery.filter(QueryBuilders.termQuery(“city”, params.getCity()));
}
// 4.品牌条件
if (params.getBrand() != null && !params.getBrand().equals(“”)) {
boolQuery.filter(QueryBuilders.termQuery(“brand”, params.getBrand()));
}
// 5.星级条件
if (params.getStarName() != null && !params.getStarName().equals(“”)) {
boolQuery.filter(QueryBuilders.termQuery(“starName”, params.getStarName()));
}
// 6.价格
if (params.getMinPrice() != null && params.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders
.rangeQuery(“price”)
.gte(params.getMinPrice())
.lte(params.getMaxPrice())
);
}
// 7.放入source
request.source().query(boolQuery);
}
3.1 需求分析
需求:查询我附近的酒店
在酒店列表页的右侧,有一个小地图,点击地图的定位按钮,地图会找到你所在的位置:
并且,在前端会发起查询请求,将你的坐标发送到服务端:
我们要做的事情就是基于这个location
坐标,然后按照距离对周围酒店排序。实现思路如下:
-
修改
RequestParams
参数,接收location
字段 -
修改
search
方法业务逻辑,如果location
有值,添加根据geo_distance
排序的功能
3.2 修改实体类
修改在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;
// 我当前的地理坐标
private String location;
}
3.3 距离排序API
地理坐标排序的DSL语法,如下:
GET /indexName/_search
{
“query”: {
“match_all”: {}
},
“sort”: [
{
“price”: “asc”
},
{
“_geo_distance” : {
“FIELD” : “纬度,经度”,
“order” : “asc”,
“unit” : “km”
}
}
]
}
对应的java代码示例:
3.4 添加距离排序
在cn.itcast.hotel.service.impl
的HotelService
的search
方法中,添加一个排序功能:
代码如下:
@Override
public PageResult search(RequestParams params) {
try {
// 1.准备Request
SearchRequest request = new SearchRequest(“hotel”);
// 2.准备DSL
// 2.1.query
buildBasicQuery(params, request);
// 2.2.分页
int page = params.getPage();
int size = params.getSize();
request.source().from((page - 1) * size).size(size);
// 2.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)
);
}
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3.5 排序距离显示
重启服务后,测试我的酒店功能:
发现确实可以实现对我附近酒店的排序,不过并没有看到酒店到底距离我多远
排序完成后,页面还要获取我附近每个酒店的具体距离值,这个值在响应结果中是独立的:
因此,我们在结果解析阶段,除了解析source
部分以外,还要得到sort
部分,也就是排序的距离,然后放到响应结果中。
我们要做两件事:
-
修改
HotelDoc
,添加排序距离字段,用于页面显示 -
修改
HotelService
类中的handleResponse
方法,添加对sort
值的获取
(1)修改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;
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();
}
}
(2)修改HotelService
中的handleResponse
方法:
private PageResult handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
// 4.1.总条数
long total = searchHits.getTotalHits().value;
// 4.2.获取文档数组
SearchHit[] hits = searchHits.getHits();
// 4.3.遍历
List hotels = new ArrayList<>(hits.length);
for (SearchHit hit : hits) {
// 4.4.获取source
String json = hit.getSourceAsString();
// 4.5.反序列化,非高亮的
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
// 4.6.排序距离信息
Object[] sortValues = hit.getSortValues();
if (sortValues.length > 0) {
hotelDoc.setDistance(sortValues[0]);
}
// 4.7.放入集合
hotels.add(hotelDoc);
}
return new PageResult(total, hotels);
}
重启后测试,发现页面能成功显示距离了:
4.1 需求分析
需求:让指定的酒店(打了广告的)在搜索结果中排名置顶
要让指定酒店在搜索结果中排名置顶,效果如图:
那怎样才能让指定的酒店排名置顶呢?
我们之前学习过的function_score
查询可以影响算分,算分高了,自然排名也就高了。而function_score
包含3个要素:
-
过滤条件:哪些文档要加分
-
算分函数:如何计算function score
-
加权方式:function score 与 query score如何运算
解决办法:让指定酒店排名靠前。因此我们需要给这些酒店添加一个标记,这样在过滤条件中就可以根据这个标记来判断,是否要提高算分。
比如,我们给酒店添加一个字段:isAD
,Boolean
类型:
-
true:是广告
-
false:不是广告
这样function_score
包含3个要素就很好确定了:
-
过滤条件:判断isAD 是否为true
-
算分函数:我们可以用最简单暴力的weight,固定加权值
-
加权方式:可以用默认的相乘,大大提高算分
因此,业务的实现步骤包括:
-
给
HotelDoc
类添加isAD
字段,Boolean
类型 -
挑选几个你喜欢的酒店,给它的文档数据添加
isAD
字段,值为true
-
修改
search
方法,添加function score
功能,给isAD
值为true
的酒店增加权重
4.2 修改HotelDoc实体
给cn.itcast.hotel.pojo
包下的HotelDoc
类添加isAD
字段:
4.3 添加广告标记
接下来,我们挑几个酒店,添加isAD字段,设置为true:
POST /hotel/_update/1902197537
{
“doc”: {
“isAD”: true
}
}
POST /hotel/_update/2056126831
{
“doc”: {
“isAD”: true
}
}
POST /hotel/_update/1989806195
{
“doc”: {
“isAD”: true
}
}
POST /hotel/_update/2056105938
{
“doc”: {
“isAD”: true
}
}
4.4 添加算分函数查询
接下来我们就要修改查询条件了。之前是用的boolean
查询,现在要改成function_socre
查询。
function_score
查询结构如下:
对应的JavaAPI如下:
我们可以将之前写的boolean
查询作为原始查询条件放到query
中,接下来就是添加过滤条件、算分函数、加权模式了。所以原来的代码依然可以沿用。
修改cn.itcast.hotel.service.impl
包下的HotelService
类中的buildBasicQuery
方法,添加算分函数查询:
private void buildBasicQuery(RequestParams params, SearchRequest request) {
// 1.构建BooleanQuery
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键字搜索
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.termQuery(“city”, params.getCity()));
}
// 品牌条件
if (params.getBrand() != null && !params.getBrand().equals(“”)) {
boolQuery.filter(QueryBuilders.termQuery(“brand”, params.getBrand()));
}
// 星级条件
if (params.getStarName() != null && !params.getStarName().equals(“”)) {
boolQuery.filter(QueryBuilders.termQuery(“starName”, params.getStarName()));
}
// 价格
if (params.getMinPrice() != null && params.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders
.rangeQuery(“price”)
.gte(params.getMinPrice())
.lte(params.getMaxPrice())
);
}
// 2.算分控制
FunctionScoreQueryBuilder functionScoreQuery =
QueryBuilders.functionScoreQuery(
// 原始查询,相关性算分的查询
boolQuery,
// function score的数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一个function score 元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件
QueryBuilders.termQuery(“isAD”, true),
// 算分函数
ScoreFunctionBuilders.weightFactorFunction(10)
)
});
request.source().query(functionScoreQuery);
}
5.1 需求分析
需求:搜索页面的品牌、城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的:
分析:
首先我们看看为什么需要实现聚合?
目前,页面的城市列表、星级列表、品牌列表都是写死的,并不会随着搜索结果的变化而变化。但是用户搜索条件改变时,搜索结果会跟着变化。
例如:用户搜索“东方明珠”,那搜索的酒店肯定是在上海东方明珠附近,因此,城市只能是上海,此时城市列表中就不应该显示北京、深圳、杭州这些信息了。也就是说,搜索结果中包含哪些城市,页面就应该列出哪些城市;搜索结果中包含哪些品牌,页面就应该列出哪些品牌。
如何得知搜索结果中包含哪些品牌?如何得知搜索结果中包含哪些城市?
解决办法:
使用聚合功能,利用Bucket
聚合,对搜索结果中的文档基于品牌分组、基于城市分组,就能得知包含哪些品牌、哪些城市了。
因为是对搜索结果聚合,因此聚合是限定范围的聚合,也就是说聚合的限定条件跟搜索文档的条件一致。
查看浏览器可以发现,前端其实已经发出了这样的一个请求:
请求参数与搜索文档的参数完全一致。
返回值类型就是页面要展示的最终结果:
结果是一个Map
结构:
-
key
是字符串,城市、星级、品牌、价格 -
value
是集合,例如多个城市的名称
5.2 业务实现
在cn.itcast.hotel.web
包的HotelController
中添加一个方法,遵循下面的要求:
-
请求方式:
POST
-
请求路径:
/hotel/filters
-
请求参数:
RequestParams
,与搜索文档的参数一致 -
返回值类型:
Map<String, List<String>>
代码:
@PostMapping(“filters”)
public Map<String, List> getFilters(@RequestBody RequestParams params){
return hotelService.getFilters(params);
}
在cn.itcast.hotel.service.IHotelService
中定义新方法:
Map<String, List> filters(RequestParams params);
在cn.itcast.hotel.service.impl.HotelService
中实现该方法:
@Override
public Map<String, List> filters(RequestParams params) {
try {
// 1.准备Request
SearchRequest request = new SearchRequest(“hotel”);
// 2.准备DSL
// 2.1.query
buildBasicQuery(params, request);
// 2.2.设置size
request.source().size(0);
// 2.3.聚合
buildAggregation(request);
// 3.发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析结果
Map<String, List> result = new HashMap<>();
Aggregations aggregations = response.getAggregations();
// 4.1.根据品牌名称,获取品牌结果
List brandList = getAggByName(aggregations, “brandAgg”);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

独家面经总结,超级精彩
本人面试腾讯,阿里,百度等企业总结下来的面试经历,都是真实的,分享给大家!
Java面试准备
准确的说这里又分为两部分:
- Java刷题
- 算法刷题
Java刷题:此份文档详细记录了千道面试题与详解;
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析结果
Map<String, List> result = new HashMap<>();
Aggregations aggregations = response.getAggregations();
// 4.1.根据品牌名称,获取品牌结果
List brandList = getAggByName(aggregations, “brandAgg”);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-UdSrP9jh-1713392067936)]
[外链图片转存中…(img-xs9Uj2xJ-1713392067937)]
[外链图片转存中…(img-1nYP5Ktq-1713392067937)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

独家面经总结,超级精彩
本人面试腾讯,阿里,百度等企业总结下来的面试经历,都是真实的,分享给大家!
[外链图片转存中…(img-HgIttYQf-1713392067937)]
[外链图片转存中…(img-qiCa95xp-1713392067938)]
[外链图片转存中…(img-ZY9BbSFb-1713392067938)]
[外链图片转存中…(img-czI0eiMr-1713392067938)]
Java面试准备
准确的说这里又分为两部分:
- Java刷题
- 算法刷题
Java刷题:此份文档详细记录了千道面试题与详解;
[外链图片转存中…(img-N3HwKMpc-1713392067938)]
[外链图片转存中…(img-7XomS2y7-1713392067938)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!