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

學習目標

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

1. 品牌統計

用戶搜索的時候,除了使用分類搜索外,還有可能使用品牌搜索,所以我們還需要顯示品牌數據和規格數據,品牌數據和規格數據的顯示比較容易,都可以考慮使用分類統計的方式進行分組實現。

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類,添加一個品牌分組搜索,如圖:

添加的代碼如下:

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

執行獲取分組結果:

整體代碼如下:

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

2. 規格統計

用戶搜索的時候,除了使用分類、品牌搜索外,還有可能使用規格搜索,所以我們還需要顯示規格數據,規格數據的顯示相比上面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語句執行後的結果如下圖:

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

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類,添加一個規格分組搜索

如圖:添加規格分組條件

上圖代碼如下:

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

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

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

上圖代碼如下:

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 效果如下:

3 條件篩選

用戶有可能會根據分類搜索、品牌搜索,還有可能根據規格搜索,以及價格搜索和排序操作。根據分類和品牌搜索的時候,可以直接根據指定域搜索,而規格搜索的域數據是不確定的,價格是一個區間搜索,所以我們可以分爲三段時間,先實現分類、品牌搜素,再實現規格搜索,然後實現價格區間搜索。

3.1 分類、品牌篩選

3.1.1 需求分析

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

3.1.2 代碼實現

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

添加過濾條件如下:

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

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

執行過濾查詢如下:

上圖整體代碼如下:

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

此時只能搜到華爲手環設備

3.2 規格過濾

3.2.1 需求分析

規格這一塊,需要向後臺發送規格名字以及規格值,我們可以按照一定要求來發送數據,例如規格名字以特殊前綴提交到後臺:spec_網絡制式:電信4G、spec_顯示屏尺寸:4.0-4.9英寸

後臺接到數據後,可以根據前綴spec_來區分是否是規格,如果以spec_xxx開始的數據則爲規格數據,需要根據指定規格找信息。

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

spechMap.規格名字.keyword

3.2.2 代碼實現

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

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

3.3 價格區間查詢

3.3.1 需求分析

價格區間查詢,每次需要將價格傳入到後臺,前端傳入後臺的價格大概是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方法,增加價格區間查詢操作,代碼如下:

上圖代碼如下:

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

效果如下(部分數據):

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 分頁分析

頁面需要實現分頁搜索,所以我們後臺每次查詢的時候,需要實現分頁。用戶頁面每次會傳入當前頁和每頁查詢多少條數據,當然如果不傳入每頁顯示多少條數據,默認查詢30條即可。

4.2 分頁實現

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

上圖代碼如下:

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();
		//略

測試如下:

5 搜索排序

5.1 排序分析

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

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

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

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

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

5.2 排序實現

這裏我們不單獨針對某個功能實現排序,我們只需要在後臺接收2個參數,分別是排序域名字和排序方式,代碼如下:

解釋: 前端頁面傳遞要排序的字段(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.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方法搜索代碼,添加高亮顯示的域:

上圖代碼如下:

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"));

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

上圖圖片如下:

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

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

代碼如下:

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 測試

效果如下:

"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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章