在HotelService
的search
方法中,只有一個地方需要修改:requet.source().query( ... )
其中的查詢條件。
在之前的業(yè)務中,只有match
查詢,根據(jù)關鍵字搜索,現(xiàn)在要添加條件過濾,包括:
-
品牌過濾:是
keyword
類型,用term
查詢 -
星級過濾:是
keyword
類型,用term
查詢 -
價格過濾:是數(shù)值類型,用
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.查詢周邊的酒店
3.1 需求分析
需求:查詢我附近的酒店
在酒店列表頁的右側,有一個小地圖,點擊地圖的定位按鈕,地圖會找到你所在的位置:
并且,在前端會發(fā)起查詢請求,將你的坐標發(fā)送到服務端:
我們要做的事情就是基于這個location
坐標,然后按照距離對周圍酒店排序。實現(xiàn)思路如下:
-
修改
RequestParams
參數(shù),接收location
字段 -
修改
search
方法業(yè)務邏輯,如果location
有值,添加根據(jù)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” : “緯度,經(jīng)度”,
“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(“l(fā)ocation”, new GeoPoint(location))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS)
);
}
// 3.發(fā)送請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析響應
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3.5 排序距離顯示
重啟服務后,測試我的酒店功能:
發(fā)現(xiàn)確實可以實現(xiàn)對我附近酒店的排序,不過并沒有看到酒店到底距離我多遠
排序完成后,頁面還要獲取我附近每個酒店的具體距離值,這個值在響應結果中是獨立的:
因此,我們在結果解析階段,除了解析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.總條數(shù)
long total = searchHits.getTotalHits().value;
// 4.2.獲取文檔數(shù)組
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);
}
重啟后測試,發(fā)現(xiàn)頁面能成功顯示距離了:
4.酒店競價排名
4.1 需求分析
需求:讓指定的酒店(打了廣告的)在搜索結果中排名置頂
要讓指定酒店在搜索結果中排名置頂,效果如圖:
那怎樣才能讓指定的酒店排名置頂呢?
我們之前學習過的function_score
查詢可以影響算分,算分高了,自然排名也就高了。而function_score
包含3個要素:
-
過濾條件:哪些文檔要加分
-
算分函數(shù):如何計算function score
-
加權方式:function score 與 query score如何運算
解決辦法:讓指定酒店排名靠前。因此我們需要給這些酒店添加一個標記,這樣在過濾條件中就可以根據(jù)這個標記來判斷,是否要提高算分。
比如,我們給酒店添加一個字段:isAD
,Boolean
類型:
-
true:是廣告
-
false:不是廣告
這樣function_score
包含3個要素就很好確定了:
-
過濾條件:判斷isAD 是否為true
-
算分函數(shù):我們可以用最簡單暴力的weight,固定加權值
-
加權方式:可以用默認的相乘,大大提高算分
因此,業(yè)務的實現(xiàn)步驟包括:
-
給
HotelDoc
類添加isAD
字段,Boolean
類型 -
挑選幾個你喜歡的酒店,給它的文檔數(shù)據(jù)添加
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 添加算分函數(shù)查詢
接下來我們就要修改查詢條件了。之前是用的boolean
查詢,現(xiàn)在要改成function_socre
查詢。
function_score
查詢結構如下:
對應的JavaAPI如下:
我們可以將之前寫的boolean
查詢作為原始查詢條件放到query
中,接下來就是添加過濾條件、算分函數(shù)、加權模式了。所以原來的代碼依然可以沿用。
修改cn.itcast.hotel.service.impl
包下的HotelService
類中的buildBasicQuery
方法,添加算分函數(shù)查詢:
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的數(shù)組
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一個function score 元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 過濾條件
QueryBuilders.termQuery(“isAD”, true),
// 算分函數(shù)
ScoreFunctionBuilders.weightFactorFunction(10)
)
});
request.source().query(functionScoreQuery);
}
5.酒店實現(xiàn)聚合
5.1 需求分析
需求:搜索頁面的品牌、城市等信息不應該是在頁面寫死,而是通過聚合索引庫中的酒店數(shù)據(jù)得來的:
分析:
首先我們看看為什么需要實現(xiàn)聚合?
目前,頁面的城市列表、星級列表、品牌列表都是寫死的,并不會隨著搜索結果的變化而變化。但是用戶搜索條件改變時,搜索結果會跟著變化。
例如:用戶搜索“東方明珠”,那搜索的酒店肯定是在上海東方明珠附近,因此,城市只能是上海,此時城市列表中就不應該顯示北京、深圳、杭州這些信息了。也就是說,搜索結果中包含哪些城市,頁面就應該列出哪些城市;搜索結果中包含哪些品牌,頁面就應該列出哪些品牌。
如何得知搜索結果中包含哪些品牌?如何得知搜索結果中包含哪些城市?
解決辦法:
使用聚合功能,利用Bucket
聚合,對搜索結果中的文檔基于品牌分組、基于城市分組,就能得知包含哪些品牌、哪些城市了。
因為是對搜索結果聚合,因此聚合是限定范圍的聚合,也就是說聚合的限定條件跟搜索文檔的條件一致。
查看瀏覽器可以發(fā)現(xiàn),前端其實已經(jīng)發(fā)出了這樣的一個請求:
請求參數(shù)與搜索文檔的參數(shù)完全一致。
返回值類型就是頁面要展示的最終結果:
結果是一個Map
結構:
-
key
是字符串,城市、星級、品牌、價格 -
value
是集合,例如多個城市的名稱
5.2 業(yè)務實現(xiàn)
在cn.itcast.hotel.web
包的HotelController
中添加一個方法,遵循下面的要求:
-
請求方式:
POST
-
請求路徑:
/hotel/filters
-
請求參數(shù):
RequestParams
,與搜索文檔的參數(shù)一致 -
返回值類型:
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
中實現(xiàn)該方法:
@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.發(fā)出請求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析結果
Map<String, List> result = new HashMap<>();
Aggregations aggregations = response.getAggregations();
// 4.1.根據(jù)品牌名稱,獲取品牌結果
List brandList = getAggByName(aggregations, “brandAgg”);
result.put(“品牌”, brandList);
// 4.2.根據(jù)品牌名稱,獲取品牌結果
List cityList = getAggByName(aggregations, “cityAgg”);
result.put(“城市”, cityList);
// 4.3.根據(jù)品牌名稱,獲取品牌結果
List starList = getAggByName(aggregations, “starAgg”);
result.put(“星級”, starList);
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//把對需要字段的聚合抽取出來,這里是品牌,城市,星級
private void buildAggregation(SearchRequest request) {
request.source().aggregation(AggregationBuilders
.terms(“brandAgg”)
.field(“brand”)
.size(100)
);
request.source().aggregation(AggregationBuilders
.terms(“cityAgg”)
.field(“city”)
.size(100)
);
request.source().aggregation(AggregationBuilders
.terms(“starAgg”)
.field(“starName”)
.size(100)
);
}
//把通過聚合名稱獲取聚合結果封裝成一個方法
private List getAggByName(Aggregations aggregations, String aggName) {
// 4.1.根據(jù)聚合名稱獲取聚合結果
Terms brandTerms = aggregations.get(aggName);
// 4.2.獲取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 4.3.遍歷
List brandList = new ArrayList<>();
for (Terms.Bucket bucket : buckets) {
// 4.4.獲取key
String key = bucket.getKeyAsString();
brandList.add(key);
}
return brandList;
}
6.酒店數(shù)據(jù)自動補全
6.1 需求分析
現(xiàn)在,我們的hotel索引庫還沒有設置拼音分詞器,需要修改索引庫中的配置。但是我們知道索引庫是無法修改的,只能刪除然后重新創(chuàng)建。
另外,我們需要添加一個字段,用來做自動補全,將brand、suggestion等都放進去,作為自動補全的提示。
因此,總結一下,我們需要做的事情包括:
-
修改
hotel
索引庫結構,設置自定義拼音分詞器 -
修改索引庫的
name
、all
字段,使用自定義分詞器 -
索引庫添加一個新字段
suggestion
,類型為completion
類型,使用自定義的分詞器 -
給
HotelDoc
類添加suggestion
字段,內容包含brand
、business
-
重新導入數(shù)據(jù)到
hotel
庫
6.2 修改酒店映射結構
代碼如下:
// 酒店數(shù)據(jù)索引庫
PUT /hotel
{
“settings”: {
“analysis”: {
“analyzer”: {
“text_anlyzer”: {
“tokenizer”: “ik_max_word”,
“filter”: “py”
},
“completion_analyzer”: {
“tokenizer”: “keyword”,
“filter”: “py”
}
},
“filter”: {
“py”: {
“type”: “pinyin”,
“keep_full_pinyin”: false,
“keep_joined_full_pinyin”: true,
“keep_original”: true,
“l(fā)imit_first_letter_length”: 16,
“remove_duplicated_term”: true,
“none_chinese_pinyin_tokenize”: false
}
}
}
},
“mappings”: {
“properties”: {
“id”:{
“type”: “keyword”
},
“name”:{
“type”: “text”,
“analyzer”: “text_anlyzer”,
“search_analyzer”: “ik_smart”,
“copy_to”: “all”
},
“address”:{
“type”: “keyword”,
“index”: false
},
“price”:{
“type”: “integer”
},
“score”:{
“type”: “integer”
},
“brand”:{
“type”: “keyword”,
“copy_to”: “all”
},
“city”:{
“type”: “keyword”
},
“starName”:{
“type”: “keyword”
},
“business”:{
“type”: “keyword”,
“copy_to”: “all”
},
“l(fā)ocation”:{
“type”: “geo_point”
},
“pic”:{
“type”: “keyword”,
“index”: false
},
“all”:{
“type”: “text”,
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進入阿里一直到現(xiàn)在。
深知大多數(shù)Java工程師,想要提升技能,往往是自己摸索成長或者是報班學習,但對于培訓機構動則幾千的學費,著實壓力不小。自己不成體系的自學效果低效又漫長,而且極易碰到天花板技術停滯不前!
因此收集整理了一份《2024年Java開發(fā)全套學習資料》,初衷也很簡單,就是希望能夠幫助到想自學提升又不知道該從何學起的朋友,同時減輕大家的負擔。
既有適合小白學習的零基礎資料,也有適合3年以上經(jīng)驗的小伙伴深入學習提升的進階課程,基本涵蓋了95%以上Java開發(fā)知識點,真正體系化!
由于文件比較大,這里只是將部分目錄截圖出來,每個節(jié)點里面都包含大廠面經(jīng)、學習筆記、源碼講義、實戰(zhàn)項目、講解視頻,并且會持續(xù)更新!
如果你覺得這些內容對你有幫助,可以掃碼獲?。。。▊渥ava獲?。?/strong>

最后
現(xiàn)在正是金三銀四的春招高潮,前陣子小編一直在搭建自己的網(wǎng)站,并整理了全套的**【一線互聯(lián)網(wǎng)大廠Java核心面試題庫+解析】:包括Java基礎、異常、集合、并發(fā)編程、JVM、Spring全家桶、MyBatis、Redis、數(shù)據(jù)庫、中間件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**文章來源:http://www.zghlxwxcb.cn/news/detail-853007.html
《互聯(lián)網(wǎng)大廠面試真題解析、進階開發(fā)核心學習筆記、全套講解視頻、實戰(zhàn)項目源碼講義》點擊傳送門即可獲??!
_to": “all”
},
“city”:{
“type”: “keyword”
},
“starName”:{
“type”: “keyword”
},
“business”:{
“type”: “keyword”,
“copy_to”: “all”
},
“l(fā)ocation”:{
“type”: “geo_point”
},
“pic”:{
“type”: “keyword”,
“index”: false
},
“all”:{
“type”: “text”,
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進入阿里一直到現(xiàn)在。
深知大多數(shù)Java工程師,想要提升技能,往往是自己摸索成長或者是報班學習,但對于培訓機構動則幾千的學費,著實壓力不小。自己不成體系的自學效果低效又漫長,而且極易碰到天花板技術停滯不前!
因此收集整理了一份《2024年Java開發(fā)全套學習資料》,初衷也很簡單,就是希望能夠幫助到想自學提升又不知道該從何學起的朋友,同時減輕大家的負擔。[外鏈圖片轉存中…(img-ZMdtqdnm-1712702137995)]
[外鏈圖片轉存中…(img-01jfcgtB-1712702137996)]
[外鏈圖片轉存中…(img-FbCDT7yl-1712702137996)]
既有適合小白學習的零基礎資料,也有適合3年以上經(jīng)驗的小伙伴深入學習提升的進階課程,基本涵蓋了95%以上Java開發(fā)知識點,真正體系化!
由于文件比較大,這里只是將部分目錄截圖出來,每個節(jié)點里面都包含大廠面經(jīng)、學習筆記、源碼講義、實戰(zhàn)項目、講解視頻,并且會持續(xù)更新!
如果你覺得這些內容對你有幫助,可以掃碼獲取?。。▊渥ava獲?。?/strong>

最后
現(xiàn)在正是金三銀四的春招高潮,前陣子小編一直在搭建自己的網(wǎng)站,并整理了全套的**【一線互聯(lián)網(wǎng)大廠Java核心面試題庫+解析】:包括Java基礎、異常、集合、并發(fā)編程、JVM、Spring全家桶、MyBatis、Redis、數(shù)據(jù)庫、中間件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**
[外鏈圖片轉存中…(img-GChtUa1K-1712702137996)]
《互聯(lián)網(wǎng)大廠面試真題解析、進階開發(fā)核心學習筆記、全套講解視頻、實戰(zhàn)項目源碼講義》點擊傳送門即可獲??!文章來源地址http://www.zghlxwxcb.cn/news/detail-853007.html
到了這里,關于【Elasticsearch】學習筆記-黑馬旅游網(wǎng)實踐的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!