暢購商城(六):商品搜尋
好好學習,天天向上
本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star,更多文章請前往:目錄導航
- 暢購商城(一):環境搭建
- 暢購商城(二):分散式檔案系統FastDFS
- 暢購商城(三):商品管理
- 暢購商城(四):Lua、OpenResty、Canal實現廣告快取與同步
- 暢購商城(五):Elasticsearch實現商品搜尋
- 暢購商城(六):商品搜尋
品牌統計
當我們在京東上搜索智慧手機的時候,會將相關品牌羅列出來供使用者選擇
我們要實現的也是這個功能,就是將搜尋結果中的品牌進行分類統計
這個功能和上一篇文章中提到的分類統計是一毛一樣的,所以新增幾行程式碼就搞定了。
但是我在搜尋“智慧手機”的時候,品牌只出現了兩個,這顯然和實際不符。原因很簡單,就是“ik_smart”並沒有將“智慧手機”拆分成“智慧”和“手機”,所以將分詞模式改成“ik_max_word”就好了。將SkuInfo中name欄位從“ik_smart”改成“ik_max_word”,然後重新匯入資料,再來測試一下:
這樣就對了。
規格統計
當我們在京東上面搜尋的時候,會將規格資訊羅列出來使用者選擇。在我們的ES中也有規格資訊,但這些資訊都是json字串,我們要做的就是把這些json字串轉換成Map集合從而實現和京東相同的功能。
在SkuEsServiceImpl的searchByKeywords方法中新增以下程式碼:
從程式碼中可以看出,先是新增搜尋條件,然後從搜尋結果中取出spec的集合,遍歷存放進specMap裡面。因為搜尋結果是一條條的json字串,所以每次將json字串轉換成map集合,再遍歷map,從map中依次取出資料放入specSet。如果specMap中沒有對應的specSet就直接new一個並存入進去,有的話就直接從specMap取出。最後將specMap放入SearchEntity返回給前端即可。
條件篩選
分類和品牌過濾
當我們把品牌或分類作為條件搜尋的時候,就不用去處理品牌和分類統計了。之前只有一個keywords引數我就直接寫在了位址列裡,但現在引數多了,還是封裝進SearchEntity吧,然後引數寫在請求體裡面。
private String keywords; //前端傳過來的關鍵詞資訊
private String brand; //前端傳過來的品牌資訊
private String category; //前端傳過來的分類資訊
然後在SkuEsServiceImpl的searchByKeywords方法中新增程式碼,我現在的searchByKeywords已經寫得很臃腫了,先不管這個問題,最後再去優化程式碼。
如果category或brand引數不為空就作為條件篩選,不進行統計,否則還是進行統計。需要注意的是這裡使用了withFilter()方法,其實withQuery()也是可以的,但是用withQuery()的話高亮搜尋就不行了,所以要用withFilter()
。
可以看到,現在指定了品牌資訊但不指定分類資訊,品牌就不會進行統計,分類還是會進行統計,達到了我們預期的效果。
規格過濾
和上面一個一樣,當我們把規格作為引數傳給後端,同樣也不會去進行規格統計。要實現這個功能,首先還得在SearchEntity中加一個欄位用來接收規格引數。
private List<String> searchSpec; //前端傳過來的規格資訊
然後在SkuEsServiceImpl的searchByKeywords方法中新增程式碼:
……
Map<String,String> searchSpec = searchEntity.getSearchSpec();
if (searchSpec != null && searchSpec.size() > 0) {
for (String key:searchSpec.keySet()){
//格式:specMap.規格名字.keyword keyword表示不分詞
boolQueryBuilder.filter(QueryBuilders.termQuery("specMap."+key+".keyword",searchSpec.get(key)));
}
}
……
獲取前端傳過來的searchSpec,然後遍歷取出規格的內容,再用boolQueryBuilder.filter()進行過濾。
從圖中可以看出,當我們指定顏色為藍色,版本為“6GB+64GB”,結果中就都是我們篩選過的結果。
價格過濾
當我們在京東上面搜尋產品的時候,可以指定一個價格區間。現在要實現的也是同樣的功能。首先,還是要在SearchEntity中新增一個欄位用於接收間隔區間引數。
private String price; //前端穿過來的價格區間字串 300-500元 3000元以上
然後新增實現的程式碼:
……
if (!StringUtils.isEmpty(searchEntity.getPrice())) {
String[] price = searchEntity.getPrice().replace("元","")
.replace("以上","").split("-");
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(Integer.parseInt(price[0])));
if (price.length>1){
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lt(Integer.parseInt(price[1])));
}
}
……
這樣就可以實現對價格的過濾了。
分頁功能
分頁功能就比較簡單了,接收前端傳進來的pageNum引數,然後呼叫nativeSearchQueryBuilder.withPageable()方法就可以實現分頁。
int pageNum = 1;
if (!StringUtils.isEmpty(searchEntity.getPageNum())) {
pageNum = Integer.parseInt(searchEntity.getPageNum());
}
nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum-1,SEARCH_PAGE_SIZE));
程式碼很簡單,給pageNum一個預設值,如果前端沒有傳引數過來就顯示第一頁,每頁的條數是10條。我定義的一個常量SEARCH_PAGE_SIZE去表示10。
可以看到,一共搜尋到22條資料,10條一頁的話就是3頁,分頁是正確的。
排序
排序功能比較簡單,就是將要排序的域和排序方式傳到對應的方法裡面即可。
String sortField = searchEntity.getSortField();
String sortRule = searchEntity.getSortRule();
if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)) {
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
}
sortField是要排序的域,sortRule是要排序的方式,如果這兩個引數都不為空的話就呼叫withSort方法新增搜尋條件即可。這樣就可以了。
高亮顯示
我們平時在京東或者淘寶搜尋的時候,關鍵詞都會高亮顯示。
其實實現起來也很簡單,只要將搜尋詞用html包裹起來,樣式改為紅色即可。
首先需要配置高亮,也就是指定高亮的域:
HighlightBuilder.Field field = new HighlightBuilder.Field("name"); //指定域
field.preTags("<em style=\"color:red;\">"); //指定字首
field.postTags("</em>"); //指定字尾
nativeSearchQueryBuilder.withHighlightFields(field);
在這段程式碼中,我們指定了要高亮搜尋的域,併為其添加了高亮顯示的html程式碼。
指定完需要高亮顯示的域後,就需要進行高亮搜尋,將非高亮資料替換成高亮資料。之前搜尋的時候用的是queryForPage(SearchQuery query, Class<T> clazz)
方法,現在改成queryForPage(SearchQuery query, Class<T> clazz, SearchResultMapper mapper)
方法。
AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate
.queryForPage(nativeSearchQuery, SkuInfo.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<T> list = new ArrayList<>();
for (SearchHit hit : response.getHits()) { //遍歷所有資料
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);//非高亮資料
HighlightField highlightField = hit.getHighlightFields().get("name"); //高亮資料
//將非高亮資料替換成高亮資料
if (highlightField != null && highlightField.getFragments() != null) {
Text[] fragments = highlightField.getFragments();
StringBuilder builder = new StringBuilder();
for (Text fragment : fragments) {
builder.append(fragment.toString());
}
skuInfo.setName(builder.toString());
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable,
response.getHits().getTotalHits(),response.getAggregations());
}
});
我執行的時候發現過濾搜尋的skuInfos.getAggregations()
總是報空指標異常,我還以為是高亮搜尋和過濾搜尋不能在一起用,最後才發現我把這裡的response.getAggregations()
寫丟了,少了一個引數。所以這裡要注意一下。
程式碼優化
之前看視訊的時候發現他寫得不太好,其實只要查一次就好了,但是卻查詢了很多次,不過視訊的最後也提到了這個問題並改進了程式碼。所以我之前並沒有按照視訊中的寫,而是擠在一個方法裡想著最後再去優化,最後一個方法寫了有一百多行,簡直太臃腫了,而且《阿里巴巴Java開發手冊》裡也提到過一個方法不要超過80行。現在搜尋功能都已經實現了,可以去優化一下了。下面是我優化過的程式碼