1. 程式人生 > 實用技巧 >暢購商城(六):商品搜尋

暢購商城(六):商品搜尋

好好學習,天天向上

本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star,更多文章請前往:目錄導航

品牌統計

當我們在京東上搜索智慧手機的時候,會將相關品牌羅列出來供使用者選擇

我們要實現的也是這個功能,就是將搜尋結果中的品牌進行分類統計

這個功能和上一篇文章中提到的分類統計是一毛一樣的,所以新增幾行程式碼就搞定了。

但是我在搜尋“智慧手機”的時候,品牌只出現了兩個,這顯然和實際不符。原因很簡單,就是“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行。現在搜尋功能都已經實現了,可以去優化一下了。下面是我優化過的程式碼