天天看點

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

學習目标

  • 條件篩選
  • 多條件搜尋[品牌、規格條件搜尋]
  • 規格過濾
  • 價格區間搜尋
  • 搜尋分頁
  • 搜尋排序
  • 搜尋高亮

1. 品牌統計

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

使用者搜尋的時候,除了使用分類搜尋外,還有可能使用品牌搜尋,是以我們還需要顯示品牌資料和規格資料,品牌資料和規格資料的顯示比較容易,都可以考慮使用分類統計的方式進行分組實作。

1.1 品牌統計分析

看下面的SQL語句,我們在執行搜尋的時候,第1條SQL語句是執行搜,第2條語句是根據品牌名字分組檢視有多少品牌,大概執行了2個步驟就可以擷取資料結果以及品牌統計,我們可以發現他們的搜尋條件完全一樣。

1
2
3
4
              -- 查詢所有
SELECT * FROM tb_sku WHERE name LIKE '%手機%';
-- 根據品牌名字分組查詢
SELECT brand_name FROM  tb_sku WHERE name LIKE '%手機%' GROUP BY brand_name;
           

我們每次執行搜尋的時候,需要顯示商品品牌名稱,這裡要顯示的品牌名稱其實就是符合搜素條件的所有商品的品牌集合,我們可以按照上面的實作思路,使用ES根據分組名稱做一次分組查詢即可實作。

1.2 品牌分組統計實作

修改search微服務的com.changgou.search.service.impl.SkuServiceImpl類,添加一個品牌分組搜尋,如圖:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

添加的代碼如下:

1
2
              //設定分組條件  商品品牌
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrandgroup").field("brandName").size(50));
           

執行擷取分組結果:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示
黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

整體代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
              public Map search(Map<String, String> searchMap) {

    //1.擷取關鍵字的值
    String keywords = searchMap.get("keywords");

    if (StringUtils.isEmpty(keywords)) {
        keywords = "華為";//指派給一個預設的值
    }
    //2.建立查詢對象 的建構對象
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

    //3.設定查詢的條件

    //設定分組條件  商品分類
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategorygroup").field("categoryName").size(50));

    //設定分組條件  商品品牌
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrandgroup").field("brandName").size(50));




    nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", keywords));

    //4.建構查詢對象
    NativeSearchQuery query = nativeSearchQueryBuilder.build();

    //5.執行查詢
    AggregatedPage<SkuInfo> skuPage = esTemplate.queryForPage(query, SkuInfo.class);

    //擷取分組結果  商品分類
    StringTerms stringTermsCategory = (StringTerms) skuPage.getAggregation("skuCategorygroup");
    //擷取分組結果  商品品牌
    StringTerms stringTermsBrand = (StringTerms) skuPage.getAggregation("skuBrandgroup");


    List<String> categoryList = getStringsCategoryList(stringTermsCategory);

    List<String> brandList = getStringsBrandList(stringTermsBrand);



    //6.傳回結果
    Map resultMap = new HashMap<>();


    resultMap.put("categoryList", categoryList);
    resultMap.put("brandList", brandList);
    resultMap.put("rows", skuPage.getContent());
    resultMap.put("total", skuPage.getTotalElements());
    resultMap.put("totalPages", skuPage.getTotalPages());

    return resultMap;
}
/**
     * 擷取品牌清單
     *
     * @param stringTermsBrand
     * @return
     */
private List<String> getStringsBrandList(StringTerms stringTermsBrand) {
    List<String> brandList = new ArrayList<>();
    if (stringTermsBrand != null) {
        for (StringTerms.Bucket bucket : stringTermsBrand.getBuckets()) {
            brandList.add(bucket.getKeyAsString());
        }
    }
    return brandList;
}

/**
     * 擷取分類清單資料
     *
     * @param stringTerms
     * @return
     */
private List<String> getStringsCategoryList(StringTerms stringTerms) {
    List<String> categoryList = new ArrayList<>();
    if (stringTerms != null) {
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();//分組的值
            categoryList.add(keyAsString);
        }
    }
    return categoryList;
}

           

1.3 測試

使用PostMan請求http://localhost:18086/search

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

2. 規格統計

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

使用者搜尋的時候,除了使用分類、品牌搜尋外,還有可能使用規格搜尋,是以我們還需要顯示規格資料,規格資料的顯示相比上面2種實作略微較難一些,需要對資料進行處理,我們也可以考慮使用分類統計和品牌統計的方式進行分組實作。

2.1 規格統計分析

看下面的SQL語句,我們在執行搜尋的時候,第1條SQL語句是執行搜,第2條語句是根據規格分組檢視有多少規格,大概執行了2個步驟就可以擷取資料結果以及規格統計,我們可以發現他們的搜尋條件完全一樣。

1
2
3
4
              -- 查詢所有
SELECT * FROM tb_sku WHERE name LIKE '%手機%';
-- 根據規格名字分組查詢
SELECT spec FROM  tb_sku WHERE name LIKE '%手機%' GROUP BY spec;
           

上述SQL語句執行後的結果如下圖:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

擷取到的規格資料我們發現有重複,不過也可以解決,解決思路如下:

1
2
3
4
              1.擷取所有規格資料
2.将所有規格資料轉換成Map
3.定義一個Map<String,Set>,key是規格名字,防止重複是以用Map,valu是規格值,規格值有多個,是以用集合,為了防止規格重複,用Set去除重複
4.循環規格的Map,将資料填充到定義的Map<String,Set>中
           

我們每次執行搜尋的時候,需要顯示商品規格資料,這裡要顯示的規格資料其實就是符合搜素條件的所有商品的規格集合,我們可以按照上面的實作思路,使用ES根據分組名稱做一次分組查詢,并去除重複資料即可實作。

2.2 規格統計分組實作

修改search微服務的com.changgou.search.service.impl.SkuServiceImpl類,添加一個規格分組搜尋

如圖:添加規格分組條件

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖代碼如下:

1
2
              //設定分組條件  商品的規格
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpecgroup").field("spec.keyword").size(100));
           

如圖:擷取規格分組結果:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

封裝調用分組結果的方法:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
              /**
 * 擷取規格清單資料
 *
 * @param stringTermsSpec
 * @return
 */
private Map<String, Set<String>> getStringSetMap(StringTerms stringTermsSpec) {
    Map<String, Set<String>> specMap = new HashMap<String, Set<String>>();
    Set<String> specList = new HashSet<>();
    if (stringTermsSpec != null) {
        for (StringTerms.Bucket bucket : stringTermsSpec.getBuckets()) {
            specList.add(bucket.getKeyAsString());
        }
    }
    for (String specjson : specList) {
        Map<String, String> map = JSON.parseObject(specjson, Map.class);
        for (Map.Entry<String, String> entry : map.entrySet()) {//
            String key = entry.getKey();        //規格名字
            String value = entry.getValue();    //規格選項值
            //擷取目前規格名字對應的規格資料
            Set<String> specValues = specMap.get(key);
            if (specValues == null) {
                specValues = new HashSet<String>();
            }
            //将目前規格加入到集合中
            specValues.add(value);
            //将資料存入到specMap中
            specMap.put(key, specValues);
        }
    }
    return specMap;
}
           

整體代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
              public Map search(Map<String, String> searchMap) {

    //1.擷取關鍵字的值
    String keywords = searchMap.get("keywords");

    if (StringUtils.isEmpty(keywords)) {
        keywords = "華為";//指派給一個預設的值
    }
    //2.建立查詢對象 的建構對象
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

    //3.設定查詢的條件

    //設定分組條件  商品分類
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategorygroup").field("categoryName").size(50));

    //設定分組條件  商品品牌
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrandgroup").field("brandName").size(50));

    //設定分組條件  商品的規格
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpecgroup").field("spec.keyword").size(100));


    nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", keywords));

    //4.建構查詢對象
    NativeSearchQuery query = nativeSearchQueryBuilder.build();

    //5.執行查詢
    AggregatedPage<SkuInfo> skuPage = esTemplate.queryForPage(query, SkuInfo.class);

    //擷取分組結果  商品分類
    StringTerms stringTermsCategory = (StringTerms) skuPage.getAggregation("skuCategorygroup");
    //擷取分組結果  商品品牌
    StringTerms stringTermsBrand = (StringTerms) skuPage.getAggregation("skuBrandgroup");
    //擷取分組結果  商品規格資料
    StringTerms stringTermsSpec = (StringTerms) skuPage.getAggregation("skuSpecgroup");

    List<String> categoryList = getStringsCategoryList(stringTermsCategory);

    List<String> brandList = getStringsBrandList(stringTermsBrand);

    Map<String, Set<String>> specMap = getStringSetMap(stringTermsSpec);


    //6.傳回結果
    Map resultMap = new HashMap<>();

    resultMap.put("specMap", specMap);
    resultMap.put("categoryList", categoryList);
    resultMap.put("brandList", brandList);
    resultMap.put("rows", skuPage.getContent());
    resultMap.put("total", skuPage.getTotalElements());
    resultMap.put("totalPages", skuPage.getTotalPages());

    return resultMap;
}

/**
     * 擷取品牌清單
     *
     * @param stringTermsBrand
     * @return
     */
private List<String> getStringsBrandList(StringTerms stringTermsBrand) {
    List<String> brandList = new ArrayList<>();
    if (stringTermsBrand != null) {
        for (StringTerms.Bucket bucket : stringTermsBrand.getBuckets()) {
            brandList.add(bucket.getKeyAsString());
        }
    }
    return brandList;
}

/**
     * 擷取分類清單資料
     *
     * @param stringTerms
     * @return
     */
private List<String> getStringsCategoryList(StringTerms stringTerms) {
    List<String> categoryList = new ArrayList<>();
    if (stringTerms != null) {
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();//分組的值
            categoryList.add(keyAsString);
        }
    }
    return categoryList;
}

/**
     * 擷取規格清單資料
     *
     * @param stringTermsSpec
     * @return
     */
private Map<String, Set<String>> getStringSetMap(StringTerms stringTermsSpec) {
    Map<String, Set<String>> specMap = new HashMap<String, Set<String>>();
    Set<String> specList = new HashSet<>();
    if (stringTermsSpec != null) {
        for (StringTerms.Bucket bucket : stringTermsSpec.getBuckets()) {
            specList.add(bucket.getKeyAsString());
        }
    }
    for (String specjson : specList) {
        Map<String, String> map = JSON.parseObject(specjson, Map.class);
        for (Map.Entry<String, String> entry : map.entrySet()) {//
            String key = entry.getKey();        //規格名字
            String value = entry.getValue();    //規格選項值
            //擷取目前規格名字對應的規格資料
            Set<String> specValues = specMap.get(key);
            if (specValues == null) {
                specValues = new HashSet<String>();
            }
            //将目前規格加入到集合中
            specValues.add(value);
            //将資料存入到specMap中
            specMap.put(key, specValues);
        }
    }
    return specMap;
}
           

2.3 測試

使用Postman測試通路http://localhost:18086/search 效果如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

3 條件篩選

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

使用者有可能會根據分類搜尋、品牌搜尋,還有可能根據規格搜尋,以及價格搜尋和排序操作。根據分類和品牌搜尋的時候,可以直接根據指定域搜尋,而規格搜尋的域資料是不确定的,價格是一個區間搜尋,是以我們可以分為三段時間,先實作分類、品牌搜素,再實作規格搜尋,然後實作價格區間搜尋。

3.1 分類、品牌篩選

3.1.1 需求分析

頁面每次向背景傳入對應的分類和品牌,背景據分類和品牌進行條件過濾即可。

3.1.2 代碼實作

修改搜尋微服務com.changgou.search.service.impl.SkuServiceImpl的search方法,添加分類和品牌過濾,

添加過濾條件如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

PS說明: 以上,我們建議使用filter ,它的搜尋效率要優于must.可以參考官方文檔說明:

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html

執行過濾查詢如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖整體代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
              @Override
public Map search(Map<String, String> searchMap) {

    //1.擷取關鍵字的值
    String keywords = searchMap.get("keywords");

    if (StringUtils.isEmpty(keywords)) {
        keywords = "華為";//指派給一個預設的值
    }
    //2.建立查詢對象 的建構對象
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

    //3.設定查詢的條件

    //設定分組條件  商品分類
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategorygroup").field("categoryName").size(50));

    //設定分組條件  商品品牌
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrandgroup").field("brandName").size(50));

    //設定分組條件  商品的規格
    nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpecgroup").field("spec.keyword").size(1000));


    //設定主關鍵字查詢
    nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", keywords));


    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();


    if (!StringUtils.isEmpty(searchMap.get("brand"))) {
        boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
    }

    if (!StringUtils.isEmpty(searchMap.get("category"))) {
        boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName", searchMap.get("category")));
    }

    //建構過濾查詢
    nativeSearchQueryBuilder.withFilter(boolQueryBuilder);

    //4.建構查詢對象
    NativeSearchQuery query = nativeSearchQueryBuilder.build();

    //5.執行查詢
    AggregatedPage<SkuInfo> skuPage = esTemplate.queryForPage(query, SkuInfo.class, new SearchResultMapperImpl());

    //擷取分組結果  商品分類
    StringTerms stringTermsCategory = (StringTerms) skuPage.getAggregation("skuCategorygroup");
    //擷取分組結果  商品品牌
    StringTerms stringTermsBrand = (StringTerms) skuPage.getAggregation("skuBrandgroup");
    //擷取分組結果  商品規格資料
    StringTerms stringTermsSpec = (StringTerms) skuPage.getAggregation("skuSpecgroup");

    List<String> categoryList = getStringsCategoryList(stringTermsCategory);

    List<String> brandList = getStringsBrandList(stringTermsBrand);

    Map<String, Set<String>> specMap = getStringSetMap(stringTermsSpec);


    //6.傳回結果
    Map resultMap = new HashMap<>();

    resultMap.put("specMap", specMap);
    resultMap.put("categoryList", categoryList);
    resultMap.put("brandList", brandList);
    resultMap.put("rows", skuPage.getContent());
    resultMap.put("total", skuPage.getTotalElements());
    resultMap.put("totalPages", skuPage.getTotalPages());

    return resultMap;
}
           

3.1.3 測試

測試效果如下:

通路位址:http://localhost:18085/search

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

此時隻能搜到華為手環裝置

3.2 規格過濾

3.2.1 需求分析

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

規格這一塊,需要向背景發送規格名字以及規格值,我們可以按照一定要求來發送資料,例如規格名字以特殊字首送出到背景:

spec_網絡制式:電信4G、spec_顯示屏尺寸:4.0-4.9英寸

背景接到資料後,可以根據字首spec_來區分是否是規格,如果以

spec_xxx

開始的資料則為規格資料,需要根據指定規格找資訊。

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖是規格的索引存儲格式,真實資料在spechMap.規格名字.keyword中,是以找資料也是按照如下格式去找:

spechMap.規格名字.keyword

3.2.2 代碼實作

修改com.changgou.search.service.impl.SkuServiceImpl的search方法,增加規格查詢操作,代碼如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示
1
2
3
4
5
6
7
8
              //規格過濾查詢
if (searchMap != null) {
    for (String key : searchMap.keySet()) {
        if (key.startsWith("spec_")) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", searchMap.get(key)));
        }
    }
}
           

3.2.3 測試

通路位址:http://localhost:18085/search

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

3.3 價格區間查詢

3.3.1 需求分析

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

價格區間查詢,每次需要将價格傳入到背景,前端傳入背景的價格大概是

price=0-500

或者

price=500-1000

依次類推,最後一個是

price=3000

,背景可以根據-分割,如果分割得到的結果最多有2個,第1個表示

x<price

,第2個表示

price<=y

1.3.2 代碼實作

修改com.changgou.search.service.impl.SkuServiceImpl的search方法,增加價格區間查詢操作,代碼如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖代碼如下:

1
2
3
4
5
6
7
8
9
10
              //價格過濾查詢
String price = searchMap.get("price");
if (!StringUtils.isEmpty(price)) {
    String[] split = price.split("-");
    if (!split[1].equalsIgnoreCase("*")) {
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").from(split[0], true).to(split[1], true));
    } else {
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(split[0]));
    }
}
           

3.3.3 測試

通路位址:http://localhost:18085/search

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

效果如下(部分資料):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
              [
        {
            "id": 1088256019328536576,
            "name": "守護寶幼兒安全手環",
            "price": 500,
            "num": 100,
            "image": "http://img10.360buyimg.com/n1/s450x450_jfs/t3457/294/236823024/102048/c97f5825/58072422Ndd7e66c4.jpg",
            "status": "1",
            "createTime": "2019-01-24T10:03:48.000+0000",
            "updateTime": "2019-01-24T10:03:48.000+0000",
            "isDefault": null,
            "spuId": 1088256019315953664,
            "categoryId": 1108,
            "categoryName": "戶外工具",
            "brandName": "守護寶",
            "spec": "{\"顔色\":\"紅\",\"機身記憶體\":\"64G\"}",
            "specMap": {
                "顔色": "紅",
                "機身記憶體": "64G"
            }
        },
        {
            "id": 1088256014043713536,
            "name": "計步器小米手環,适用老人、小孩",
            "price": 800,
            "num": 100,
            "image": "http://img10.360buyimg.com/n1/s450x450_jfs/t3457/294/236823024/102048/c97f5825/58072422Ndd7e66c4.jpg",
            "status": "1",
            "createTime": "2019-01-24T10:03:47.000+0000",
            "updateTime": "2019-01-24T10:03:47.000+0000",
            "isDefault": null,
            "spuId": 1088256014026936320,
            "categoryId": 1192,
            "categoryName": "小家電",
            "brandName": "小米",
            "spec": "{\"顔色\":\"紅\",\"機身記憶體\":\"64G\"}",
            "specMap": {
                "顔色": "紅",
                "機身記憶體": "64G"
            }
        }
    ]
           

4 搜尋分頁

4.1 分頁分析

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

頁面需要實作分頁搜尋,是以我們背景每次查詢的時候,需要實作分頁。使用者頁面每次會傳入目前頁和每頁查詢多少條資料,當然如果不傳入每頁顯示多少條資料,預設查詢30條即可。

4.2 分頁實作

分頁使用PageRequest.of( pageNo- 1, pageSize);實作,第1個參數表示第N頁,從0開始,第2個參數表示每頁顯示多少條,實作代碼如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
              //略

		//建構過濾查詢
        nativeSearchQueryBuilder.withFilter(boolQueryBuilder);

        //建構分頁查詢
        Integer pageNum = 1;
        if (!StringUtils.isEmpty(searchMap.get("pageNum"))) {
            try {
                pageNum = Integer.valueOf(searchMap.get("pageNum"));
            } catch (NumberFormatException e) {
                e.printStackTrace();
                pageNum=1;
            }
        }
        Integer pageSize = 3;
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum - 1, pageSize));

		//略


        //4.建構查詢對象
        NativeSearchQuery query = nativeSearchQueryBuilder.build();
		//略
           

測試如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

5 搜尋排序

5.1 排序分析

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

排序這裡總共有根據價格排序、根據評價排序、根據新品排序、根據銷量排序,排序要想實作非常簡單,隻需要告知排序的域以及排序方式即可實作。

價格排序:隻需要根據價格高低排序即可,降序價格高->低,升序價格低->高

評價排序:評價分為好評、中評、差評,可以在資料庫中設計3個列,用來記錄好評、中評、差評的量,每次排序的時候,好評的比例來排序,當然還要有條數限制,評價條數需要超過N條。

新品排序:直接根據商品的釋出時間或者更新時間排序。

銷量排序:銷量排序除了銷售數量外,還應該要有時間段限制。

5.2 排序實作

這裡我們不單獨針對某個功能實作排序,我們隻需要在背景接收2個參數,分别是排序域名字和排序方式,代碼如下:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

解釋: 前端頁面傳遞要排序的字段(field)和要排序的類型(ASC,DESC),背景接收.

上圖代碼如下:

1
2
3
4
5
6
              //建構排序查詢
String sortRule = searchMap.get("sortRule");
String sortField = searchMap.get("sortField");
if (!StringUtils.isEmpty(sortRule) && !StringUtils.isEmpty(sortField)) {
    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(sortRule.equals("DESC") ? SortOrder.DESC : SortOrder.ASC));
}
           

測試

根據價格降序:

{"keywords":"手機","pageNum":"1","sortRule":"DESC","sortField":"price"}
           

根據價格升序:

{"keywords":"手機","pageNum":"1","sortRule":"ASC","sortField":"price"}
           

6 高亮顯示

6.1 高亮分析

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

高亮顯示是指根據商品關鍵字搜尋商品的時候,顯示的頁面對關鍵字給定了特殊樣式,讓它顯示更加突出,如上圖商品搜尋中,關鍵字程式設計了紅色,其實就是給定了紅色樣式。

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

6.2 高亮搜尋實作步驟解析

将之前的搜尋換掉,換成高亮搜尋,我們需要做3個步驟:

1
2
3
4
              1.指定高亮域,也就是設定哪個域需要高亮顯示
  設定高亮域的時候,需要指定字首和字尾,也就是關鍵詞用什麼html标簽包裹,再給該标簽樣式
2.高亮搜尋實作
3.将非高亮資料替換成高亮資料
           

第1點,例如在百度中搜尋資料的時候,會有2個地方高亮顯示,分别是标題和描述,商城搜尋的時候,隻是商品名稱高亮顯示了。而高亮顯示其實就是添加了樣式,例如

<span style="color:red;">筆記本</span>

,而其中span開始标簽可以稱為字首,span結束标簽可以稱為字尾。

第2點,高亮搜尋使用ElasticsearchTemplate實作。

第3點,高亮搜尋後,會搜出非高亮資料和高亮資料,高亮資料會加上第1點中的高亮樣式,此時我們需要将非高亮資料換成高亮資料即可。例如非高亮:

華為筆記本性能超強悍

 高亮資料:

華為<span style="color:red;"筆記本</span>性能超強悍

,将非高亮的換成高亮的,到頁面就能顯示樣式了。

6.3 高亮代碼實作

修改com.changgou.search.service.impl.SkuServiceImpl的search方法搜尋代碼,添加高亮顯示的域:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖代碼如下:

1
2
3
4
5
6
              //設定高亮條件
        nativeSearchQueryBuilder.withHighlightFields(new HighlightBuilder.Field("name"));
        nativeSearchQueryBuilder.withHighlightBuilder(new HighlightBuilder().preTags("<em style=\"color:red\">").postTags("</em>"));

        //設定主關鍵字查詢
        nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(keywords,"name","brandName","categoryName"));
           

修改 查詢的方法,自定義結果映射器,入下圖:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

上圖圖檔如下:

AggregatedPage<SkuInfo> skuPage = esTemplate.queryForPage(query, SkuInfo.class, new SearchResultMapperImpl());
           

自定義一個映射結果類實作接口,作用就是:自定義映射結果集,擷取高亮的資料展示,如下圖:

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
              public class SearchResultMapperImpl implements SearchResultMapper {
    @Override
    public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
        List<T> content = new ArrayList<>();
        //如果沒有結果傳回為空
        if (response.getHits() == null || response.getHits().getTotalHits() <= 0) {
            return new AggregatedPageImpl<T>(content);
        }
        for (SearchHit searchHit : response.getHits()) {
            String sourceAsString = searchHit.getSourceAsString();
            SkuInfo skuInfo = JSON.parseObject(sourceAsString, SkuInfo.class);

            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            HighlightField highlightField = highlightFields.get("name");

            //有高亮則設定高亮的值
            if (highlightField != null) {
                StringBuffer stringBuffer = new StringBuffer();
                for (Text text : highlightField.getFragments()) {
                    stringBuffer.append(text.string());
                }
                skuInfo.setName(stringBuffer.toString());
            }
            content.add((T) skuInfo);
        }


        return new AggregatedPageImpl<T>(content, pageable, response.getHits().getTotalHits(), response.getAggregations(), response.getScrollId());
    }
}
           

6.4 測試

黑馬暢購商城---6.品牌、規格統計、條件篩選、分頁排序、高亮顯示

效果如下:

"name": "HTC M8Sd (E8) 波爾多紅 電信4G<span style=\"color:red\">手機</span> 雙卡雙待雙通",
           

整體代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
              @Service
public class SkuServiceImpl implements SkuService {
    @Autowired
    private SkuEsMapper skuEsMapper;

    @Autowired
    private SkuFeign skuFeign;

    @Override
    public void importSku() {
        Result<List<Sku>> listResult = skuFeign.findByStatus("1");

        List<Sku> data = listResult.getData();

        List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(data), SkuInfo.class);

        for (SkuInfo skuInfo : skuInfos) {
            String spec = skuInfo.getSpec();
            Map map = JSON.parseObject(spec, Map.class);
            skuInfo.setSpecMap(map);
        }

        skuEsMapper.saveAll(skuInfos);
    }

    @Autowired
    private ElasticsearchTemplate esTemplate;

    /**
* @param searchMap
* @return
*/
    @Override
    public Map search(Map<String, String> searchMap) {

        //1.擷取關鍵字的值
        String keywords = searchMap.get("keywords");

        if (StringUtils.isEmpty(keywords)) {
            keywords = "華為";//指派給一個預設的值
        }
        //2.建立查詢對象 的建構對象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //3.設定查詢的條件

        //設定分組條件  商品分類
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategorygroup").field("categoryName").size(50));

        //設定分組條件  商品品牌
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrandgroup").field("brandName").size(50));

        //設定分組條件  商品的規格
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpecgroup").field("spec.keyword").size(500000));


        //設定高亮條件
        nativeSearchQueryBuilder.withHighlightFields(new HighlightBuilder.Field("name"));
        nativeSearchQueryBuilder.withHighlightBuilder(new HighlightBuilder().preTags("<em style=\"color:red\">").postTags("</em>"));

        //設定主關鍵字查詢
        nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(keywords,"name","brandName","categoryName"));


        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();


        if (!StringUtils.isEmpty(searchMap.get("brand"))) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
        }

        if (!StringUtils.isEmpty(searchMap.get("category"))) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName", searchMap.get("category")));
        }

        //規格過濾查詢
        if (searchMap != null) {
            for (String key : searchMap.keySet()) {
                if (key.startsWith("spec_")) {
                    boolQueryBuilder.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", searchMap.get(key)));
                }
            }
        }

        //價格過濾查詢
        String price = searchMap.get("price");
        if (!StringUtils.isEmpty(price)) {
            String[] split = price.split("-");
            if (!split[1].equalsIgnoreCase("*")) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").from(split[0], true).to(split[1], true));
            } else {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(split[0]));
            }
        }


        //建構過濾查詢
        nativeSearchQueryBuilder.withFilter(boolQueryBuilder);

        //建構分頁查詢
        Integer pageNum = 1;
        if (!StringUtils.isEmpty(searchMap.get("pageNum"))) {
            try {
                pageNum = Integer.valueOf(searchMap.get("pageNum"));
            } catch (NumberFormatException e) {
                e.printStackTrace();
                pageNum=1;
            }
        }
        Integer pageSize = 3;
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum - 1, pageSize));


        //建構排序查詢
        String sortRule = searchMap.get("sortRule");
        String sortField = searchMap.get("sortField");
        if (!StringUtils.isEmpty(sortRule) && !StringUtils.isEmpty(sortField)) {
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(sortRule.equals("DESC") ? SortOrder.DESC : SortOrder.ASC));
        }


        //4.建構查詢對象
        NativeSearchQuery query = nativeSearchQueryBuilder.build();

        //5.執行查詢
        AggregatedPage<SkuInfo> skuPage = esTemplate.queryForPage(query, SkuInfo.class, new SearchResultMapperImpl());

        //擷取分組結果  商品分類
        StringTerms stringTermsCategory = (StringTerms) skuPage.getAggregation("skuCategorygroup");
        //擷取分組結果  商品品牌
        StringTerms stringTermsBrand = (StringTerms) skuPage.getAggregation("skuBrandgroup");
        //擷取分組結果  商品規格資料
        StringTerms stringTermsSpec = (StringTerms) skuPage.getAggregation("skuSpecgroup");

        List<String> categoryList = getStringsCategoryList(stringTermsCategory);

        List<String> brandList = getStringsBrandList(stringTermsBrand);

        Map<String, Set<String>> specMap = getStringSetMap(stringTermsSpec);


        //6.傳回結果
        Map resultMap = new HashMap<>();

        resultMap.put("specMap", specMap);
        resultMap.put("categoryList", categoryList);
        resultMap.put("brandList", brandList);
        resultMap.put("rows", skuPage.getContent());
        resultMap.put("total", skuPage.getTotalElements());
        resultMap.put("totalPages", skuPage.getTotalPages());

        return resultMap;
    }

    /**
* 擷取品牌清單
*
* @param stringTermsBrand
* @return
*/
    private List<String> getStringsBrandList(StringTerms stringTermsBrand) {
        List<String> brandList = new ArrayList<>();
        if (stringTermsBrand != null) {
            for (StringTerms.Bucket bucket : stringTermsBrand.getBuckets()) {
                brandList.add(bucket.getKeyAsString());
            }
        }
        return brandList;
    }

    /**
* 擷取分類清單資料
*
* @param stringTerms
* @return
*/
    private List<String> getStringsCategoryList(StringTerms stringTerms) {
        List<String> categoryList = new ArrayList<>();
        if (stringTerms != null) {
            for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
                String keyAsString = bucket.getKeyAsString();//分組的值
                categoryList.add(keyAsString);
            }
        }
        return categoryList;
    }

    /**
* 擷取規格清單資料
*
* @param stringTermsSpec
* @return
*/
    private Map<String, Set<String>> getStringSetMap(StringTerms stringTermsSpec) {
        Map<String, Set<String>> specMap = new HashMap<String, Set<String>>();

        Set<String> specList = new HashSet<>();

        if (stringTermsSpec != null) {
            for (StringTerms.Bucket bucket : stringTermsSpec.getBuckets()) {
                specList.add(bucket.getKeyAsString());
            }
        }

        for (String specjson : specList) {
            Map<String, String> map = JSON.parseObject(specjson, Map.class);
            for (Map.Entry<String, String> entry : map.entrySet()) {//
                String key = entry.getKey();        //規格名字
                String value = entry.getValue();    //規格選項值
                //擷取目前規格名字對應的規格資料
                Set<String> specValues = specMap.get(key);
                if (specValues == null) {
                    specValues = new HashSet<String>();
                }
                //将目前規格加入到集合中
                specValues.add(value);
                //将資料存入到specMap中
                specMap.put(key, specValues);
            }
        }
        return specMap;
    }
}