02穀粒商城-高階篇二

peng_boke發表於2024-10-14

前言

不必糾結當下,也不必太擔憂未來,

人生沒有無用的經歷,

所以,一直走,天一定亮

173~202

6.商城業務-檢索服務

6.1檢索服務-搭建環境頁面

把搜尋的靜態頁面複製到gulimall-search下的src/main/resources/templates

image-20240722215141049

修改html宣告和thymeleaf名稱空間

image-20240722215008802

修改index.html裡的靜態資源地址

href="
替換為
href="/static/search/

image-20240722215328230

src="
替換為
src="/static/search/

image-20240722215516268

上傳靜態資源到nginx/root/mall/nginx/html/static/search

image-20240722215945462

匯入thymeleaf依賴

 <!-- 模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

image-20240722215613947

管理員執行SwicthHosts,配置hosts檔案,配置搜尋頁轉發地址

192.168.188.180     search.gulimall.com

image-20240722215750665

配置/root/mall/nginx/conf/conf.d/gulimall.conf

server_name *.gulimall.com;

image-20240722220235683

然後重啟nginx

docker restart nginx

image-20240722223609588

配置閘道器

        - id: gulimall_search_route
          uri: lb://gulimall-search
          predicates:
            - Host=search.gulimall.com

image-20240722222145666

訪問http://search.gulimall.com/

image-20240722222229488

6.2檢索服務-調整頁面跳轉

配置nginx

配置gulimall.conf

 server_name gulimall.com *.gulimall.com;

image-20240722224348935

然後重啟nginx

docker restart nginx

image-20240722224841643

配置thymeleaf

spring:
  thymeleaf:
    cache: false

image-20240722230241043

安裝devtools

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

image-20240722230400267

首頁跳轉

image-20240722225706998

# 第一處
 <a href="http://gulimall.com" class="header_head_p_a1" style="width:73px;">
      穀粒商城首頁
</a>
# 第二處
<div class="logo">
        <a href="http://gulimall.com"><img src="/static/search/./image/logo1.jpg" alt=""></a>
 </div>

image-20240722224221954

二級分類跳轉

image-20240722225743287

重新命名index.htmllist.html,並且新增SearchController


@Controller
public class SearchController {
    @GetMapping(value = "/list.html")
    public String listPage(){
        return "list";
    }
}

image-20240722225600421

修改catalogLoader.js

var cata3link = $("<a href=\"http://search.gulimall.com/list.html?catalog3Id="+ctg3.id+"\" style=\"color: #999;\">" + ctg3.name + "</a>");       

image-20240722225811290

搜尋跳轉

image-20240722225937045

配置gulimall-productsrc/main/resources/templates/index.html

<a href="javascript:search();" ><img src="/static/index/img/img_09.png" /></a>

image-20240722230025463

6.3檢索服務-檢索查詢引數模型分析抽取

查詢引數模型:

  • keyword:頁面傳遞過來的全文匹配關鍵字
  • brandIdList<Long>,品牌id,可以多選
  • catalog3Id:三級分類id
  • sort:排序條件:sort=price/salecount/hotscore_desc/asc
  • hasStock:是否顯示有貨
  • skuPrice:價格區間查詢
  • attrs:按照屬性進行篩選
  • pageNum:頁碼
  • _queryString:原生的所有查詢條件
public class SearchParam {
    /**
     * 頁面傳遞過來的全文匹配關鍵字
     */
    private String keyword;

    /**
     * 品牌id,可以多選
     */
    private List<Long> brandId;

    /**
     * 三級分類id
     */
    private Long catalog3Id;

    /**
     * 排序條件:sort=price/salecount/hotscore_desc/asc
     */
    private String sort;

    /**
     * 是否顯示有貨
     */
    private Integer hasStock;

    /**
     * 價格區間查詢
     */
    private String skuPrice;

    /**
     * 按照屬性進行篩選
     */
    private List<String> attrs;

    /**
     * 頁碼
     */
    private Integer pageNum = 1;

    /**
     * 原生的所有查詢條件
     */
    private String _queryString;

}

6.4檢索服務-檢索返回結果模型分析抽取

返回結果模型:

  • productList<SkuEsModel>,查詢到的所有商品資訊

  • pageNum:當前頁碼

  • total:總記錄數

  • totalPages:總頁碼

  • pageNavs

  • brandsList<BrandVo>,當前查詢到的結果,所有涉及到的品牌

    • brandId:品牌Id
    • brandName:品牌名稱
    • brandImg:品牌圖片
  • attrsList<AttrVo>,當前查詢到的結果,所有涉及到的所有屬性

    • attrId:屬性Id
    • attrName:屬性名稱
    • attrValue:屬性值,可能是多個
  • catalogsList<CatalogVo>,當前查詢到的結果,所有涉及到的所有分類

    • catalogId:分類Id
    • catalogName:分類名稱
  • navsList<NavVo>,麵包屑導航資料

    • navName:名稱
    • navValue:值
      • link:連結

@Data
public class SearchResult {

    /**
     * 查詢到的所有商品資訊
     */
    private List<SkuEsModel> product;

    /**
     * 當前頁碼
     */
    private Integer pageNum;

    /**
     * 總記錄數
     */
    private Long total;

    /**
     * 總頁碼
     */
    private Integer totalPages;

    private List<Integer> pageNavs;

    /**
     * 當前查詢到的結果,所有涉及到的品牌
     */
    private List<BrandVo> brands;

    /**
     * 當前查詢到的結果,所有涉及到的所有屬性
     */
    private List<AttrVo> attrs;

    /**
     * 當前查詢到的結果,所有涉及到的所有分類
     */
    private List<CatalogVo> catalogs;


    //===========================以上是返回給頁面的所有資訊============================//


    /* 麵包屑導航資料 */
    private List<NavVo> navs;

    @Data
    public static class NavVo {
        private String navName;
        private String navValue;
        private String link;
    }
    @Data
    public static class BrandVo {
        private Long brandId;
        private String brandName;
        private String brandImg;
    }
    @Data
    public static class AttrVo {
        private Long attrId;
        private String attrName;
        private List<String> attrValue;
    }
    @Data
    public static class CatalogVo {
        private Long catalogId;
        private String catalogName;
    }
}

6.5檢索服務-檢索DSL測試-查詢部分

這個查詢是在名為 product 的索引中查詢 skuTitle 包含 "華為",catalogId 為 225,brandId 為 1、2 或 3,巢狀屬性 attrs.attrId 為 "1" 且 attrs.attrValue 為 "華為 Mate60 Pro" 或 "2023",並且 hasStock 為 "true",skuPrice 在 0 到 6000 之間的商品。結果按 skuPrice 降序排序,並返回第1個結果。同時,高亮顯示 skuTitle 中的匹配部分。

GET product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "華為"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": 225
          }
        },
        {
          "terms": {
            "brandId": [
              1,
              2,
              3
            ]
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "1"
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "華為 Mate60 Pro",
                        "2023"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "term": {
            "hasStock": {
              "value": "true"
            }
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 0,
              "lte": 6000
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "skuPrice": {
        "order": "desc"
      }
    }
  ],
  "from": 1,
  "size": 1,
  "highlight": {
    "fields": {
      "skuTitle": {}
    },
    "pre_tags": "<b style='color:red'>",
    "post_tags": "</b>"
  }
}

6.6檢索服務-檢索DSL測試-聚合部分

聚合brandNamebrandImg catelogName attrName欄位時失敗了

image-20240723021306219

之前在對映中將 brandNamebrandImg catelogName attrName欄位設定為 keyword 型別,但禁用了 indexdoc_values。這意味著這些欄位不能用於搜尋、排序或聚合。如果你需要在這些欄位上進行聚合操作,需要確保這些欄位啟用了 doc_values

image-20240723021417121

為了保留資料需要進行資料遷移

先查詢product對映

GET product

然後在修改,刪除

"index": false,
"doc_values": false

以下是完整程式碼

PUT gulimall_product
{
  "mappings": {
    "properties": {
      "skuId": {
        "type": "long"
      },
      "spuId": {
        "type": "long"
      },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuImg": {
        "type": "keyword"
      },
      "saleCount": {
        "type": "long"
      },
      "hosStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "brandId": {
        "type": "long"
      },
      "catelogId": {
        "type": "long"
      },
      "brandName": {
        "type": "keyword"
      },
      "brandImg": {
        "type": "keyword"
      },
      "catalogName": {
        "type": "keyword"
      },
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword"
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

image-20240723021833998

資料遷移

POST _reindex
{
    "source": {
        "index": "product"
    },
    "dest": {
        "index": "gulimall_product"
    }
}

image-20240723022756732

資料遷移成功,我們要修改程式碼裡的es索引名稱

image-20240723023111648

這個查詢是在名為 gulimall_product 的索引中查詢 skuTitle 包含 "手機",catalogId 為 "225",brandId 為 "3" 或 "5",巢狀屬性 attrs.attrId 為 "6" 且 attrs.attrValue 為 "海思(Hisilicon)" 或 "以官網資訊為準",並且 hasStock 為 "true",skuPrice 在 0 到 7000 之間的商品。結果按 skuPrice 降序排序,並返回前2個結果。同時,高亮顯示 skuTitle 中的匹配部分。聚合部分包括品牌、目錄和屬性的相關資訊。

GET gulimall_product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "手機"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": "225"
          }
        },
        {
          "terms": {
            "brandId": [
              "3",
              "5"
            ]
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "6"
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "海思(Hisilicon)",
                        "以官網資訊為準"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "term": {
            "hasStock": {
              "value": "true"
            }
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 0,
              "lte": 7000
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "skuPrice": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 2,
  "highlight": {
    "fields": {
      "skuTitle": {}
    },
    "pre_tags": "<b style='color:red'>",
    "post_tags": "</b>"
  },
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brandId",
        "size": 10
      },
      "aggs": {
        "brand_name_agg": {
          "terms": {
            "field": "brandName",
            "size": 10
          }
        },
        "brand_img_agg":{
          "terms": {
            "field": "brandImg",
            "size": 10
          }
        }
      }
    },
    "catalog_agg": {
      "terms": {
        "field": "catalogId",
        "size": 10
      },
      "aggs": {
        "catalog_name_agg": {
          "terms": {
            "field": "catalogName",
            "size": 10
          }
        }
      }
    },
    "attr_agg":{
      "nested": {
        "path": "attrs"
      },
      "aggs": {
        "attr_id_agg": {
          "terms": {
            "field": "attrs.attrId",
            "size": 10
          },
          "aggs": {
            "attr_name_agg": {
              "terms": {
                "field": "attrs.attrName",
                "size": 10
              }
            },
            "attr_value_agg": {
              "terms": {
                "field": "attrs.attrValue",
                "size": 10
              }
            }
          }
        }
      }
    }
  }
}

6.7檢索服務-SearchRequest構建-檢索

主要步驟:

  • 1.準備檢索請求

    • 構建bool-queryBoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();
    • bool-mustskuTitle商品名稱查詢
    • bool-fiter:
      • boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()))catalogId三級分類查詢
      • boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()))brandId品牌查詢
      • attrs基礎屬性查詢
      • boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1))hasStock庫存查詢
      • skuPrice商品價格區間查詢
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        /**
         * 模糊匹配,過濾(按照屬性,分類,品牌,價格區間,庫存)
         */
        //1. 構建bool-query
        BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();

        //1.1 bool-must
        if(!StringUtils.isEmpty(param.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
        }

        //1.2 bool-fiter
        //1.2.1 catelogId
        if(null != param.getCatalog3Id()){
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));
        }

        //1.2.2 brandId
        if(null != param.getBrandId() && param.getBrandId().size() >0){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
        }

        //1.2.3 attrs
        if(param.getAttrs() != null && param.getAttrs().size() > 0){

            param.getAttrs().forEach(item -> {
                //attrs=1_5寸:8寸&2_16G:8G
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();


                //attrs=1_5寸:8寸
                String[] s = item.split("_");
                String attrId=s[0];
                String[] attrValues = s[1].split(":");//這個屬性檢索用的值
                boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));

                NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);
                boolQueryBuilder.filter(nestedQueryBuilder);
            });

        }

        //1.2.4 hasStock
        if(null != param.getHasStock()){
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));
        }


        //1.2.5 skuPrice
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            //skuPrice形式為:1_500或_500或500_
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
            String[] price = param.getSkuPrice().split("_");
            if(price.length==2){
                rangeQueryBuilder.gte(price[0]).lte(price[1]);
            }else if(price.length == 1){
                if(param.getSkuPrice().startsWith("_")){
                    rangeQueryBuilder.lte(price[1]);
                }
                if(param.getSkuPrice().endsWith("_")){
                    rangeQueryBuilder.gte(price[0]);
                }
            }
            boolQueryBuilder.filter(rangeQueryBuilder);
        }

        //封裝所有的查詢條件
        searchSourceBuilder.query(boolQueryBuilder);

6.8檢索服務-SearchRequest構建-排序、分頁、高亮&測試

主要步驟:

  • 排序:searchSourceBuilder.sort(sortFileds[0],sortOrder);

  • 分頁

    • searchSourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);:頁碼
    • searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);:每頁條數
  • 高亮

    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("skuTitle");
    highlightBuilder.preTags("<b style='color:red'>");
    highlightBuilder.postTags("</b>");
    searchSourceBuilder.highlighter(highlightBuilder);
    
  • 測試:

/**
         * 排序,分頁,高亮
         */

        //排序
        //形式為sort=hotScore_asc/desc
        if(!StringUtils.isEmpty(param.getSort())){
            String sort = param.getSort();
            String[] sortFileds = sort.split("_");

            SortOrder sortOrder="asc".equalsIgnoreCase(sortFileds[1])?SortOrder.ASC:SortOrder.DESC;

            searchSourceBuilder.sort(sortFileds[0],sortOrder);
        }

        //分頁
        searchSourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

        //高亮
        if(!StringUtils.isEmpty(param.getKeyword())){

            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");

            searchSourceBuilder.highlighter(highlightBuilder);
        }

測試http://localhost:8208/list.html?keyword=華為&brandId=2&catalog3Id=225&sort=skuPrice_asc&hasStock=1&skuPrice=0_7000

image-20240723215406812

把構建的語句列印出來

image-20240723215618757

kibana執行列印出來的查詢語句,能夠查詢出來即可

image-20240723215458935

6.9檢索服務-SearchRequest構建-聚合

主要步驟:

  • 品牌聚合
  • 分類聚合
  • 屬性資訊聚合

程式碼

/**
         * 聚合分析
         */
        //1. 按照品牌進行聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);


        //1.1 品牌的子聚合-品牌名聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
                .field("brandName").size(1));
        //1.2 品牌的子聚合-品牌圖片聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg")
                .field("brandImg").size(1));

        searchSourceBuilder.aggregation(brand_agg);

        //2. 按照分類資訊進行聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
        catalog_agg.field("catalogId").size(20);

        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));

        searchSourceBuilder.aggregation(catalog_agg);

        //2. 按照屬性資訊進行聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        //2.1 按照屬性ID進行聚合
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attr_agg.subAggregation(attr_id_agg);
        //2.1.1 在每個屬性ID下,按照屬性名進行聚合
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        //2.1.1 在每個屬性ID下,按照屬性值進行聚合
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        searchSourceBuilder.aggregation(attr_agg);

        log.debug("構建的DSL語句 {}",searchSourceBuilder.toString());

        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},searchSourceBuilder);

        return searchRequest;

測試,檢視聚合出來的品牌、分類、屬性資訊

image-20240723221016057

6.10檢索服務-SearchRequest分析&封裝

主要步驟:

  • 1.商品資訊
  • 2.當前商品涉及到的所有屬性資訊
  • 3.當前商品涉及到的所有品牌資訊
  • 4.當前商品涉及到的所有分類資訊
  • 5.分頁資訊
    • 頁碼
    • 總記錄數
    • 總頁碼

image-20240723230642032

程式碼

SearchResult result = new SearchResult();

        //1、返回的所有查詢到的商品
        SearchHits hits = response.getHits();

        List<SkuEsModel> esModels = new ArrayList<>();
        //遍歷所有商品資訊
        if (hits.getHits() != null && hits.getHits().length > 0) {
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);

                //判斷是否按關鍵字檢索,若是就顯示高亮,否則不顯示
                if (!StringUtils.isEmpty(param.getKeyword())) {
                    //拿到高亮資訊顯示標題
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String skuTitleValue = skuTitle.getFragments()[0].string();
                    esModel.setSkuTitle(skuTitleValue);
                }
                esModels.add(esModel);
            }
        }
        result.setProduct(esModels);

        //2、當前商品涉及到的所有屬性資訊
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();
        //獲取屬性資訊的聚合
        ParsedNested attrsAgg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //1、得到屬性的id
            long attrId = bucket.getKeyAsNumber().longValue();
            attrVo.setAttrId(attrId);

            //2、得到屬性的名字
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);

            //3、得到屬性的所有值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());
            attrVo.setAttrValue(attrValues);

            attrVos.add(attrVo);
        }

        result.setAttrs(attrVos);

        //3、當前商品涉及到的所有品牌資訊
        List<SearchResult.BrandVo> brandVos = new ArrayList<>();
        //獲取到品牌的聚合
        ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();

            //1、得到品牌的id
            long brandId = bucket.getKeyAsNumber().longValue();
            brandVo.setBrandId(brandId);

            //2、得到品牌的名字
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandName(brandName);

            //3、得到品牌的圖片
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandImg(brandImg);

            brandVos.add(brandVo);
        }
        result.setBrands(brandVos);

        //4、當前商品涉及到的所有分類資訊
        //獲取到分類的聚合
        List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
        ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            //得到分類id
            String keyAsString = bucket.getKeyAsString();
            catalogVo.setCatalogId(Long.parseLong(keyAsString));

            //得到分類名
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalogName);
            catalogVos.add(catalogVo);
        }

        result.setCatalogs(catalogVos);
        //===============以上可以從聚合資訊中獲取====================//
        //5、分頁資訊-頁碼
        result.setPageNum(param.getPageNum());
        //5、1分頁資訊、總記錄數
        long total = hits.getTotalHits().value;
        result.setTotal(total);

        //5、2分頁資訊-總頁碼-計算
        int totalPages = (int)total % EsConstant.PRODUCT_PAGESIZE == 0 ?
                (int)total / EsConstant.PRODUCT_PAGESIZE : ((int)total / EsConstant.PRODUCT_PAGESIZE + 1);
        result.setTotalPages(totalPages);

6.11檢索服務-驗證結果封裝正確性

檢視SearchResponse返回結果

image-20240723230933284

獲取高亮顯示的標題

if (!StringUtils.isEmpty(param.getKeyword())) {
        //拿到高亮資訊顯示標題
        HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
        String skuTitleValue = skuTitle.getFragments()[0].string();
        esModel.setSkuTitle(skuTitleValue);
}

image-20240723231202830

6.12檢索服務-頁面基本資料渲染

主要步驟:

  • 商品列表:rig_tab
  • 品牌資訊:JD_nav_wrap
  • 分類資訊:JD_pre
  • 屬性資訊:JD_pre

商品列表:rig_tab

<div class="rig_tab">
    <div th:each="product : ${result.getProduct()}">
        <div class="ico">
            <i class="iconfont icon-weiguanzhu"></i>
            <a href="/static/search/#">關注</a>
        </div>
        <p class="da">
            <a th:href="|http://item.gulimall.com/${product.skuId}.html|" >
                <img   class="dim" th:src="${product.skuImg}">
            </a>
        </p>
        <ul class="tab_im">
            <li><a href="/static/search/#" title="黑色">
                <img th:src="${product.skuImg}"></a></li>
        </ul>
        <p class="tab_R">
            <span th:text="'¥' + ${product.skuPrice}">¥5199.00</span>
        </p>
        <p class="tab_JE">
            <a href="/static/search/#" th:utext="${product.skuTitle}">
                Apple iPhone 7 Plus (A1661) 32G 黑色 移動聯通電信4G手機
            </a>
        </p>
        <p class="tab_PI">已有<span>11萬+</span>熱門評價
            <a href="/static/search/#">二手有售</a>
        </p>
        <p class="tab_CP"><a href="/static/search/#" title="穀粒商城Apple產品專營店">穀粒商城Apple產品...</a>
            <a href='#' title="聯絡供應商進行諮詢">
                <img src="/static/search/img/xcxc.png">
            </a>
        </p>
        <div class="tab_FO">
            <div class="FO_one">
                <p>自營
                    <span>穀粒商城自營,品質保證</span>
                </p>
                <p>滿贈
                    <span>該商品參加滿贈活動</span>
                </p>
            </div>
        </div>
    </div>
</div>

品牌資訊:JD_nav_wrap

<!--品牌-->
<div class="JD_nav_wrap">
    <div class="sl_key">
        <span><b>品牌:</b></span>
    </div>
    <div class="sl_value">
        <div class="sl_value_logo">
            <ul>
                <li th:each="brand : ${result.brands}">
                    <a href="#"
                       th:href="${'javascript:searchProducts(&quot;brandId&quot;,'+brand.brandId+')'}">
                        <img th:src="${brand.brandImg}" alt="">
                        <div th:text="${brand.brandName}">
                            華為(HUAWEI)
                        </div>
                    </a>
                </li>
            </ul>
        </div>
    </div>
    <div class="sl_ext">
        <a href="/static/search/#">
            更多
            <i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
            <b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
        </a>
        <a href="/static/search/#">
            多選
            <i>+</i>
            <span>+</span>
        </a>
    </div>
</div>

分類資訊:JD_pre

<div class="JD_pre">
    <div class="sl_key">
        <span><b>分類:</b></span>
    </div>
    <div class="sl_value">
        <ul>
            <li th:each="catalog : ${result.catalogs}">
                <a href="/static/search/#"
                   th:href="${'javascript:searchProducts(&quot;catalog3Id&quot;,'+catalog.catalogId+')'}"
                   th:text="${catalog.catalogName}">5.56英寸及以上

                </a>
            </li>
        </ul>
    </div>
    <div class="sl_ext">
        <a href="/static/search/#">
            更多
            <i style='background: url("image/search.ele.png")no-repeat 3px 7px'></i>
            <b style='background: url("image/search.ele.png")no-repeat 3px -44px'></b>
        </a>
        <a href="/static/search/#">
            多選
            <i>+</i>
            <span>+</span>
        </a>
    </div>
</div>

屬性資訊

<!--其它的所有需要展示的屬性-->
<div class="JD_pre" th:each="attr : ${result.attrs}">
    <div class="sl_key">
        <span th:text="${attr.attrName}">螢幕尺寸:</span>
    </div>
    <div class="sl_value">
        <ul>
            <li th:each="attrValue : ${attr.attrValue}">
                <a href="/static/search/#"
                   th:href="${'javascript:searchProducts(&quot;attrs&quot;,&quot;'+attr.attrId+'_'+attrValue+'&quot;)'}"
                   th:text="${attrValue}">5.56英寸及以上
                </a>
            </li>
        </ul>
    </div>
</div>

預設查詢所有商品,不管有沒有庫存

image-20240724000758955

6.13檢索服務-頁面篩選條件渲染

主要步驟:

  • 條件拼接方法

  • 品牌資訊條件拼接

  • 分類資訊條件拼接

  • 基礎屬性條件拼接

條件拼接方法,需要判斷一開始的查詢連結有沒有"?"

function searchProducts(name, value) {
        //原來的頁面
        var href = location.href + "";
        if (href.indexOf("?") != -1) {
            location.href = location.href + "&" + name + "=" + value;
        } else {
            location.href = location.href + "?" + name + "=" + value;
        }
    }

image-20240724003211630

品牌資訊條件拼接,注意使用動態拼接後雙引號(''')使用&quot轉義

 <a href="#"                                      th:href="${'javascript:searchProducts(&quot;brandId&quot;,'+brand.brandId+')'}">
      <img th:src="${brand.brandImg}" alt="">
          <div th:text="${brand.brandName}">
                華為(HUAWEI)
          </div>
 </a>

image-20240724003110953

分類資訊條件拼接,注意使用動態拼接後雙引號(''')使用&quot轉義

<a href="/static/search/#" th:href="${'javascript:searchProducts(&quot;catalog3Id&quot;,'+catalog.catalogId+')'}"
   th:text="${catalog.catalogName}">5.56英寸及以上

</a>

image-20240724003139213

基礎屬性條件拼接,注意使用動態拼接後雙引號(''')使用&quot轉義

<a href="/static/search/#"
   th:href="${'javascript:searchProducts(&quot;attrs&quot;,&quot;'+attr.attrId+'_'+attrValue+'&quot;)'}"
   th:text="${attrValue}">5.56英寸及以上
</a>

image-20240724003156860

6.14檢索服務-頁面分頁資料渲染

主要步驟:

  • 1.搜尋頁搜尋功能
  • 2.分頁
    • th:attr="pn=${result.pageNum - 1}":自定義特性,代表當前頁數
    • th:if="${result.pageNum>1}":當前頁數>1顯示上一頁
    • style=${navs == result.pageNum?'border: 0;color:#ee2222;background: #fff':''}":選中時顯示樣式
    • <a class="page_a" th:attr="pn=${navs}" th:each="navs : ${result.pageNavs}">\[\[${navs}\]\]</a>:迴圈顯示頁數
    • th:if="${result.pageNum<result.totalPages}":當前頁<總頁數顯示下一頁

搜尋頁搜尋功能

image-20240724022341981

分頁樣式

 <!--分頁-->
<div class="filter_page">
                        <div class="page_wrap">
                            <span class="page_span1">
                                <a class="page_a" th:attr="pn=${result.pageNum - 1}" href="/static/search/#"
                                   th:if="${result.pageNum>1}">
                                    < 上一頁
                                </a>
                                <a class="page_a"
                                   th:attr="pn=${navs},style=${navs == result.pageNum?'border: 0;color:#ee2222;background: #fff':''}"
                                   th:each="navs : ${result.pageNavs}">[[${navs}]]</a>
                                <a class="page_a" th:attr="pn=${result.pageNum + 1}"
                                   th:if="${result.pageNum<result.totalPages}">
                                    下一頁 >
                                </a>
                            </span>
                            <span class="page_span2">
                                <em>共<b>[[${result.totalPages}]]</b>頁&nbsp;&nbsp;到第</em>
                                <input type="number" value="1">
                                <em>頁</em>
                                <a class="page_submit">確定</a>
                            </span>
                        </div>
                    </div>

image-20240724024100668

分頁事件

$(".page_a").click(function () {
    var pn = $(this).attr("pn");
    var href = location.href;
    if (href.indexOf("pageNum") != -1) {
        //替換pageNum
        location.href = replaceParamVal(href, "pageNum", pn,false);
    } else {
        location.href = location.href + "&pageNum=" + pn;
    }
    return false;
})

6.15檢索服務-頁面排序功能

主要步驟:

  • 1.自定義特性

    • sort="hotScore"
    • sort="saleCount"
    • sort="skuPrice"
  • 2.點選事件:

    • 清除sort_a的所有樣式(選中高亮)和升序降序的標誌
    • 切換sort_a的升序降序的樣式
    • 清除之前的升序降序的標誌,如果有desc降序樣式就新增降序的標誌,沒有desc降序樣式就新增升序的標誌,

樣式

<div class="filter_top_left">
   <a class="sort_a" sort="hotScore" href="/static/search/#">綜合排序</a>
   <a class="sort_a" sort="saleCount" href="/static/search/#">銷量</a>
   <a class="sort_a" sort="skuPrice" href="/static/search/#">價格</a>
</div>                          

點選事件

// 排序
$(".sort_a").click(function () {
    changeStyle(this);
    let sort = $(this).attr("sort");
    sort = $(this).hasClass("desc") ? sort + "_desc" : sort + "_asc";
    location.href = replaceParamVal(location.href, "sort", sort,false);
    // 禁用預設行為 防止a標籤跳轉
    return false;
});

function changeStyle(ele) {
    // location.href = replaceParamVal(href, "pageNum", pn,flase);
    // color: #333; border-color: #ccc; background: #fff
    // color: #fff; border-color: #e4393c; background: #e4393c
    $(".sort_a").css({"color": "#333", "border-color": "#ccc", "background": "#fff"});
    $(".sort_a").each(function () {
        let text = $(this).text().replace("↓", "").replace("↑", "");
        $(this).text(text);
    })

    $(ele).css({"color": "#FFF", "border-color": "#e4393c", "background": "#e4393c"});
    $(ele).toggleClass("desc");

    if ($(ele).hasClass("desc")) {
        let text = $(ele).text().replace("↓", "").replace("↑", "");
        text = text + "↓";
        $(ele).text(text);
    } else {
        let text = $(ele).text().replace("↓", "").replace("↑", "");
        text = text + "↑";
        $(ele).text(text);
    }
};

6.16檢索服務-頁面排序欄位回顯

主要步驟:

  • th:with="p = ${param.sort}"param.sort獲取位址列中sort的引數,不支援直接使用param比較

  • th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}":如果param不為空,並且sort引數hotScore開始,desc結尾,classsort_a desc,否則為sort_a

  • th:attr="style=${(#strings.isEmpty(p) || #strings.startsWith(p,'hotScore'))?'color: #fff; border-color: #e4393c; background: #e4393c;':'color: #333; border-color: #ccc; background: #fff;' }":預設是綜合排序,如果param為空,並且sort引數hotScore開始,設定為選中高亮樣式

  • [[${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc')) ?'↑':'↓' }]]:如果param不為空,並且sort引數hotScore開始,desc結尾,新增升序標誌

樣式

 <!--綜合排序-->
                    <div class="filter_top">
                        <div class="filter_top_left" th:with="p = ${param.sort}, priceRange = ${param.skuPrice}">
                            <a sort="hotScore"
                               th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}"
                               th:attr="style=${(#strings.isEmpty(p) || #strings.startsWith(p,'hotScore')) ?
                                   'color: #fff; border-color: #e4393c; background: #e4393c;':'color: #333; border-color: #ccc; background: #fff;' }">
                                綜合排序[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'hotScore') &&
                                #strings.endsWith(p,'desc')) ?'↑':'↓' }]]</a>
                            <a sort="saleCount"
                               th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}"
                               th:attr="style=${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount')) ?
                                   'color: #fff; border-color: #e4393c; background: #e4393c;':'color: #333; border-color: #ccc; background: #fff;' }">
                                銷量[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'saleCount') &&
                                #strings.endsWith(p,'desc'))?'↑':'↓' }]]</a>
                            <a sort="skuPrice"
                               th:class="${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice') && #strings.endsWith(p,'desc')) ? 'sort_a desc' : 'sort_a'}"
                               th:attr="style=${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice')) ?
                                   'color: #fff; border-color: #e4393c; background: #e4393c;':'color: #333; border-color: #ccc; background: #fff;' }">
                                價格[[${(!#strings.isEmpty(p) && #strings.startsWith(p,'skuPrice') &&
                                #strings.endsWith(p,'desc'))?'↑':'↓' }]]</a>
                            <a sort="hotScore" class="sort_a">評論分</a>
                            <a sort="hotScore" class="sort_a">上架時間</a>
                            <input id="skuPriceFrom" type="number"
                                   th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}"
                                   style="width: 100px; margin-left: 30px">
                            -
                            <input id="skuPriceTo" type="number"
                                   th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}"
                                   style="width: 100px">
                            <button id="skuPriceSearchBtn">確定</button>
                        </div>
                        <div class="filter_top_right">
                            <span class="fp-text">
                               <b>1</b><em>/</em><i>169</i>
                           </span>
                            <a href="/static/search/#" class="prev"><</a>
                            <a href="/static/search/#" class="next"> > </a>
                        </div>
                    </div>

6.17檢索服務-頁面價格區間搜尋

價格區間

主要步驟:

  • th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}"param.skuPrice獲取位址列skuPrice引數

  • th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}":如果priceRange不為空,按照_擷取priceRange,獲取priceRange之前的數字,比如5000_8000,就是5000

  • th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}":如果priceRange不為空,按照_擷取priceRange,獲取priceRange之後的數字,比如5000_8000,就是8000

  • 查詢時拼接skuPriceFromskuPriceTo文字框的引數新增到位址列

樣式

<input id="skuPriceFrom" type="number"
       th:value="${#strings.isEmpty(priceRange)?'':#strings.substringBefore(priceRange,'_')}"
       style="width: 100px; margin-left: 30px">
-
<input id="skuPriceTo" type="number"
       th:value="${#strings.isEmpty(priceRange)?'':#strings.substringAfter(priceRange,'_')}"
       style="width: 100px">
<button id="skuPriceSearchBtn">確定</button>

事件

// 價格區間
$("#skuPriceSearchBtn").click(function () {
    let from = $(`#skuPriceFrom`).val();
    let to = $(`#skuPriceTo`).val();

    let query = from + "_" + to;
    location.href = replaceParamVal(location.href, "skuPrice", query,false);
});

僅顯示有貨

主要步驟:

  • th:with="check = ${param.hasStock}":獲取位址列hasStock引數
  • th:checked="${#strings.equals(check,'1')?true:false}":如果hasStock為1就選中
  • 選中和取消選中的時候位址列更新hasStock引數

樣式

<a th:with="check = ${param.hasStock}">
    <input id="showHasStock" type="checkbox" th:checked="${#strings.equals(check,'1')?true:false}">
    僅顯示有貨
</a>

事件

// 是否有庫存
$("#showHasStock").change(function () {
    //alert( $(this).prop("checked") );
    if( $(this).prop("checked") ) {
        location.href = replaceParamVal(location.href,"hasStock",1,false);
    } else {
        let re = eval('/(hasStock=)([^&]*)/gi');
        location.href = (location.href+"").replace(re,"");
    }
    return false;
});

keyword

function searchProducts(name, value) {
    //原來的頁面
    location.href = replaceParamVal(location.href,name,value,true)
}

6.18檢索服務-麵包屑導航

主要步驟:

  • 1.獲取基礎屬性attrs(attrs=1_5寸:8寸)
  • 2.遠端呼叫商品服務根據attrId查詢屬性名稱

gulimall-search匯入遠端呼叫依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

image-20240728164548014

遠端呼叫

image-20240728164444384

獲取基礎屬性attrs(attrs=1_5寸:8寸)

image-20240728164318258

6.19檢索服務-條件刪除與URL編碼問題

主要步驟:

  • 修改編碼為UTF-8
  • 替換空格,將後端的空格符號+替換為%20
  • 前端動態生成麵包屑導航

修改編碼為UTF-8,將後端的空格符號+替換為%20

image-20240728170611761

前端動態生成麵包屑導航

<div class="JD_ipone_one c">
    <!-- 遍歷麵包屑功能 -->
    <a th:href="${nav.link}" th:each="nav:${result.navs}"><span th:text="${nav.navName}"></span>:<span th:text="${nav.navValue}"></span> x</a>
</div>

image-20240728170657280

6.20檢索服務-條件篩選聯動

主要步驟:

  • 1.遠端呼叫gulimall-product查詢品牌服務
  • 2.替換品牌連結地址
  • 3.遠端呼叫gulimall-product查詢商品分類
 if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            List<SearchResult.NavVo> navVos = result.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setNavName("品牌");
            R r = productFeignService.brandsInfo(param.getBrandId());
            if (r.getCode() == 0) {
                List<BrandVo> brands = r.getData("brand", new TypeReference<List<BrandVo>>() {
                });
                String replace = "";
                StringBuffer buffer = new StringBuffer();
                for (BrandVo brandVo : brands) {
                    buffer.append(brandVo.getName() + ";");
                    replace = replaceQueryString(param, "brandId", brandVo.getBrandId().toString());
                }
                navVo.setNavValue(buffer.toString());// 品牌拼接值
                navVo.setLink("http://search.gulimall.com/list.html?" + replace);// 回退品牌面包屑等於刪除所有品牌條件
                navVos.add(navVo);
            }
        }

        // 構建麵包屑導航資料_分類
        if (param.getCatalog3Id() != null) {
            List<SearchResult.NavVo> navs = result.getNavs();
            SearchResult.NavVo nav = new SearchResult.NavVo();
            nav.setNavName("分類");
            R r = productFeignService.categoryinfo(param.getCatalog3Id());
            if (r.getCode() == 0) {
                CategoryVo categoryVo = r.getData(new TypeReference<CategoryVo>() {
                });					
                nav.setNavValue(categoryVo.getName());// 分類名
            }
            // StringBuffer buffer = new StringBuffer();
            // String replace = replaceQueryString(param, "catalog3Id", param.getCatalog3Id().toString());
            // nav.setLink("http://search.gulimall.com/list.html?" + replace);
            navs.add(nav);
        }

image-20240728231528677

7.商城業務-非同步

7.1非同步複習

主要步驟:

  • 1.繼承Thread
  • 2.實現Runnable介面
  • 3.實現Callable介面
  • 4.執行緒池:執行緒池直接提交任務

區別:

  • 1、2不能得到返回值
  • 1、2、3都不能直接控制資源
  • 4可以控制資源
 public static ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        /*
        * 1.繼承Thread
        * 2.實現Runnable介面
        * 3.實現Callable介面
        * 4.執行緒池:執行緒池直接提交任務
        *
        * 區別:
        *  1、2不能得到返回值
        *  1、2、3都不能直接控制資源
        *  4可以控制資源
        * */

        System.out.println("main......start.....");
        Thread thread = new Thread01();
        thread.start();
        System.out.println("main......end.....");

        System.out.println("main......start.....");
        Runable01 runable01 = new Runable01();
        new Thread(runable01).start();
        System.out.println("main......start.....");

        System.out.println("main......start.....");
        FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
        System.out.println("main......end.....");

        System.out.println("main......start.....");
        service.execute(new Runable01());
        Future<Integer> submit = service.submit(new Callable01());
        submit.get();
        System.out.println("main......end.....");
    }

    private static void threadPool() {

        ExecutorService threadPool = new ThreadPoolExecutor(
                200,
                10,
                10L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(10000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        //定時任務的執行緒池
        ExecutorService service = Executors.newScheduledThreadPool(2);
    }


    public static class Thread01 extends Thread {
        @Override
        public void run() {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("執行結果:" + i);
        }
    }


    public static class Runable01 implements Runnable {
        @Override
        public void run() {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("執行結果:" + i);
        }
    }


    public static class Callable01 implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("執行結果:" + i);
            return i;
        }
    }

7.2執行緒池詳解

執行緒池的七大引數:

  • corePoolSize:池中一直保持的執行緒的數量,即使執行緒空閒。除非設定allowCoreThreadTimeOut
  • maximumPoolSize:池中允許的最大的執行緒數
  • keepAliveTime:當執行緒數大於核心執行緒數的時候,執行緒在最大多長時間沒有接到新任務就會終止釋放, 最終執行緒池維持在 corePoolSize 大小
  • unit:時間單位
  • workQueue:阻塞佇列,用來儲存等待執行的任務,如果當前對執行緒的需求超過了 corePoolSize 大小,就會放在這裡等待空閒執行緒執行。
  • threadFactory:建立執行緒的工廠,比如指定執行緒名等
  • handler:拒絕策略,如果執行緒滿了,執行緒池就會使用拒絕策略。

一個執行緒池 core 7;max 20;queue:50;100併發進來怎麼分配ide

7個立即執行,50個放入佇列,在執行13個(max-core),剩下30個使用拒絕策略

常見的 4 種執行緒池

  • newCachedThreadPool:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
  • newFixedThreadPool:建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
  • newScheduledThreadPool:建立一個定長執行緒池,支援定時及週期性任務執行。
  • newSingleThreadExecutor:建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
        200,
        10,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(100000),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());
// Executors.newCachedThreadPool()     core是0,所有都可回收()
// Executors.newFixedThreadPao()        固定大小,core=max;都不可回收
// Executors.newScheduledThreudPool()  定時任務的執行緒池
// Executors.newsingleThreadExecutor() 單執行緒的執行緒池,後臺從佇列裡面獲取任務,挨個執行

7.3CompletableFuture

為什麼使用執行緒池

  • 降低資源的消耗
    • 透過重複利用已經建立好的執行緒降低執行緒的建立和銷燬帶來的損耗
  • 提高響應速度
    • 因為執行緒池中的執行緒數沒有超過執行緒池的最大上限時,有的執行緒處於等待分配任務 的狀態,當任務來時無需建立新的執行緒就能執行
  • 提高執行緒的可管理性
    • 執行緒池會根據當前系統特點對池內的執行緒進行最佳化處理,減少建立和銷燬執行緒帶來 的系統開銷。無限的建立和銷燬執行緒不僅消耗系統資源,還降低系統的穩定性,使用執行緒池進行統一分配

CompletableFuture 非同步編排

業務場景

image-20240729003821215

在 Java 8 中, 新增加了一個包含 50 個方法左右的類: CompletableFuture,提供了非常強大的 Future 的擴充套件功能,可以幫助我們簡化非同步程式設計的複雜性,提供了函數語言程式設計的能力,可以 透過回撥的方式處理計算結果,並且提供了轉換和組合 CompletableFuture 的方法。 CompletableFuture 類實現了 Future 介面,所以你還是可以像以前一樣透過get方法阻塞或 者輪詢的方式獲得結果,但是這種方式不推薦使用。 CompletableFuture 和 FutureTask 同屬於 Future 介面的實現類,都可以獲取執行緒的執行結果

image-20240729004040770

7.4CompletableFuture-啟動非同步任務

CompletableFuture

  • runxxx 都是沒有返回結果的

  • supplyxxx 都是可以獲取返回結果的

  • 可以傳入自定義的執行緒池,否則就用預設的執行緒池

 public static ExecutorService executor = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("main......start.....");
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("執行結果:" + i);
        }, executor);

        CompletableFuture<Integer> CFres = CompletableFuture.supplyAsync(() -> {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("執行結果:" + i);
            return i;
        }, executor);
        Integer res = CFres.get();
        System.out.println(res);
        System.out.println("main......end.....");

    }

7.5CompletableFuture-完成回撥與非同步感知

計算完成時回撥方法

whenComplete 可以處理正常和異常的計算結果,exceptionally 處理異常情況

whenComplete 和 whenCompleteAsync 的區別:

  • whenComplete:是執行當前任務的執行緒執行繼續執行 whenComplete 的任務。
  • whenCompleteAsync:是執行把 whenCompleteAsync 這個任務繼續提交給執行緒池
 System.out.println("main......start.....");
 CompletableFuture<Integer> CFres = CompletableFuture.supplyAsync(() -> {
     System.out.println("當前執行緒:" + Thread.currentThread().getId());
     int i = 10 / 2;
     System.out.println("執行結果:" + i);
     return i;
 }, executor).whenComplete((res,ex)->{
 System.out.println("非同步任務執行完成...結果是:"+res+";異常是:"+ex);
 }).exceptionally(throwable -> {
     // 可以感知異常 同時返回預設值
     return 10;
 });

Integer res = CFres.get();
System.out.println(res);
System.out.println("main......end.....");

image-20240729010004089

7.6CompletableFuture-handle最終處理

handle

和 complete 一樣,可對結果做最後的處理(可處理異常),可改變返回值。

        // handle
        System.out.println("main......start.....");
        CompletableFuture<Integer> CFres = CompletableFuture.supplyAsync(() -> {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 2;
            // int i = 10 / 0; //exception
            System.out.println("執行結果:" + i);
            return i;
        }, executor).handle((res, ex) -> {
            if (res != null) {
                return res * 2;
            }
            if (ex != null) {
                return 0;
            }
            return 0;
        });
        Integer res = CFres.get();
        System.out.println(res);
        System.out.println("main......end.....");

image-20240729010211223

7.7CompletableFuture-執行緒序列化

thenRun、thenAccept、thenApply

  • thenRun 方法:只要上面的任務執行完成,就開始執行 thenRun,只是處理完任務後,執行 thenRun 的後續操作
  • thenAccept 方法:消費處理結果。接收任務的處理結果,並消費處理,無返回結果。
  • thenApply 方法:當一個執行緒依賴另一個執行緒時,獲取上一個任務返回的結果,並返回當前 任務的返回值。
  • 帶有 Async 預設是非同步執行的。
  • 同之前。 以上都要前置任務成功完成

image-20240729011407597

        // 執行緒序列化
        System.out.println("main......start.....");

        CompletableFuture.supplyAsync(() -> {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 5;
            // int i = 10 / 0; //exception
            System.out.println("執行結果:" + i);
            return i;
        }, executor).thenRunAsync(() -> {
            System.out.println("任務執行完成" + Thread.currentThread().getId());
        }, executor);

        CompletableFuture.supplyAsync(() -> {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 5;
            // int i = 10 / 0; //exception
            System.out.println("執行結果:" + i);
            return i;
        }, executor).thenAcceptAsync(res -> {
            System.out.println("任務執行完成" + Thread.currentThread().getId());
            System.out.println("上一步結果:"+res);
        }, executor);

        // supplyAsync:接受上一步結果,有返回值
        CompletableFuture<String> CFres = CompletableFuture.supplyAsync(() -> {
            System.out.println("當前執行緒:" + Thread.currentThread().getId());
            int i = 10 / 5;
            // int i = 10 / 0; //exception
            System.out.println("執行結果:" + i);
            return i;
        }, executor).thenApplyAsync(res -> {
            return "hello" + res;
        }, executor);

        String res = CFres.get();
        System.out.println(res);
        System.out.println("main......end.....");

7.8CompletableFuture-倆任務組合-都要完成

倆任務組合-都要完成

  • runAfterBoth:組合兩個 future,不需要獲取 future 的結果,只需兩個 future 處理完任務後, 處理該任務。
  • thenAcceptBoth:組合兩個 future,獲取兩個 future 任務的返回結果,然後處理任務,沒有 返回值。
  • thenCombine:組合兩個 future,獲取兩個 future 的返回結果,並返回當前任務的返回值

建立2個任務

// 組合任務  都要完成
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    System.out.printf("任務1執行緒:" + Thread.currentThread().getId());
    System.out.println("任務1結束");
    return "hello2";
}, executor);

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    System.out.printf("任務2執行緒:" + Thread.currentThread().getId());
    System.out.println("任務2結束");
    return "hello1";
}, executor);

runAfterBothAsync

future1.runAfterBothAsync(future2,()->{
      System.out.printf("任務1,任務2完成");
},executor);

image-20240729020240061

thenAcceptBothAsync

 future1.thenAcceptBothAsync(future2,(f1,f2)->{
       System.out.println("任務1結果:"+f1);
       System.out.println("任務2結果:"+f2);
       System.out.println("任務結束");
 },executor);

image-20240729020507112

CompletableFuture

CompletableFuture<String> future3 = future1.thenCombineAsync(future2, (f1, f2) -> {
            System.out.println("任務1結果:" + f1);
            System.out.println("任務2結果:" + f2);
            return f1 + " " + f2 + " " + "hello3";
        }, executor);
        System.out.printf("任務3結果:" + future3.get());

image-20240729020841483

7.9CompletableFuture-倆任務組合-一個完成

倆任務組合-一個完成

  • runAfterEither:兩個任務有一個執行完成,不需要獲取 future 的結果,處理任務,也沒有返 回值。
  • acceptEither:兩個任務有一個執行完成,獲取它的返回值,處理任務,沒有新的返回值。
  • applyToEither:兩個任務有一個執行完成,獲取它的返回值,處理任務並有新的返回值。

建立2個任務

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    System.out.println("任務1執行緒:" + Thread.currentThread().getId());
    // try {
    //     Thread.sleep(1000);
    // } catch (InterruptedException e) {
    //     throw new RuntimeException(e);
    // }
    System.out.println("任務1結束");
    return "hello1";
}, executor);

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    System.out.println("任務2執行緒:" + Thread.currentThread().getId());
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println("任務2結束");
    return "hello2";
}, executor);

runAfterEitherAsync

future1.runAfterEitherAsync(future2,()->{
    System.out.println("任務3執行緒:" + Thread.currentThread().getId());
    System.out.println("任務3結束");
});

image-20240729022025191

acceptEitherAsync

future1.acceptEitherAsync(future2, (res) -> {
    System.out.println("接受結果:" + res);
    System.out.println("任務3執行緒:" + Thread.currentThread().getId());
    System.out.println("任務3結束");
});

image-20240729022246715

applyToEitherAsync

CompletableFuture<String> future3 = future1.applyToEitherAsync(future2, (res) -> {
    System.out.println("接受結果:" + res);
    System.out.println("任務3執行緒:" + Thread.currentThread().getId());
    System.out.println("任務3結束");
    return res + " hello3";
});
System.out.println("任務3結果:" + future3.get());

image-20240729022459702

7.10CompletableFuture-多工組合

多工組合

  • allOf:等待所有任務完成
  • anyOf:只要有一個任務完成

建立3個任務,任務2休眠3s,模擬耗時操作

System.out.println("main......start.....");
// 多工組合
CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
    System.out.println("獲取圖片");
    return "hello1.jpg";
}, executor);

CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(3000);
        System.out.println("獲取屬性");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return "白色12+512GB";
}, executor);

CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
    System.out.println("獲取描述");
    return "華為Mate70";
}, executor);

allOf

CompletableFuture<Void> allof = CompletableFuture.allOf(futureImg, futureAttr, futureDesc);
allof.get(); //阻塞等待三個任務完成
// // allof.join();
System.out.println(futureImg.get());
System.out.println(futureAttr.get());
System.out.println(futureDesc.get());

image-20240729024308330

anyOf

CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);
anyOf.get();
System.out.println(anyOf.get());
System.out.println("main......end.....");

image-20240729024344578

8.商城業務-商品詳情

8.1環境搭建

主要步驟:

  • 1.配置Hosts
  • 2.配置nginx
  • 3.上傳商品服務的靜態資源到nginxstatic/item目錄下
  • 4.配置item.gulimall.com的閘道器
  • 5.gulimall-product新增控制器跳轉和商品靜態頁面
  • 6.gulimall-search修改商品詳情跳轉地址

管理員執行SwitchHosts

192.168.188.180     item.gulimall.com

image-20240729030335894

因為*.gulimall.com可以匹配item.gulimall.com,這裡不需要配置,檢查一下即可

image-20240729030427355

上傳商品服務的靜態資源到nginxstatic/item目錄下

image-20240729030558145

配置item.gulimall.com的閘道器,讓item.gulimall.com可以轉發到商品服務

- id: gulimall_host_route
  uri: lb://gulimall-product
  predicates:
    - Host=gulimall.com,item.gulimall.com

image-20240729030721229

gulimall-product新增商品靜態頁面,並改名item.html

image-20240729030844166

替換靜態資源地址為nginx地址

href="
href="/static/item/

image-20240729030016750

替換圖片地址為nginx地址

src="
src="/static/item/

image-20240729030055680

gulimall-product新增控制器跳轉

@Controller
public class ItemController {
    /**
     * 展示當前sku的詳情
     * @param skuId
     * @return
     */
    @GetMapping("/{skuId}.html")
    public String skuItem(@PathVariable("skuId") Long skuId, Model model) throws ExecutionException, InterruptedException {

        System.out.println("準備查詢" + skuId + "詳情");

        return "item";
    }
}

image-20240729031040797

gulimall-search修改商品詳情的連結地址

<p class="da">
    <a th:href="|http://item.gulimall.com/${product.skuId}.html|">
        <img class="dim" th:src="${product.skuImg}">
    </a>
</p>

image-20240729030206606

訪問http://item.gulimall.com/1.html

image-20240729031406764

8.2模型抽取

主要步驟:

  • 1.sku基本資訊獲取 pms_sku_info
  • 2.sku的圖片資訊 pms_sku_images
  • 3.獲取spu的銷售屬性組合
  • 4.獲取spu的介紹
  • 5.獲取spu的規格引數資訊。
@Data
public class SkuItemVo {

    //1、sku基本資訊的獲取  pms_sku_info
    private SkuInfoEntity info;

    private boolean hasStock = true;

    //2、sku的圖片資訊    pms_sku_images
    private List<SkuImagesEntity> images;

    //3、獲取spu的銷售屬性組合
    private List<SkuItemSaleAttrVo> saleAttr;

    //4、獲取spu的介紹
    private SpuInfoDescEntity desc;

    //5、獲取spu的規格引數資訊
    private List<SpuItemAttrGroupVo> groupAttrs;

}

8.3規格引數

主要步驟:

  • 1.sku基本資訊的獲取
  • 2.sku的圖片資訊
  • 4.獲取spu的介紹
  • 5.獲取spu的規格引數資訊

1、2、3簡單的查詢

@Override
public SkuItemVo item(Long skuId) {
    SkuItemVo skuItemVo = new SkuItemVo();

    // 1、sku基本資訊的獲取  pms_sku_info
    SkuInfoEntity info = this.getById(skuId);
    skuItemVo.setInfo(info);

    // 2、sku的圖片資訊    pms_sku_images
    List<SkuImagesEntity> imagesEntities = skuImagesService.getImagesBySkuId(skuId);
    skuItemVo.setImages(imagesEntities);

    // 3、獲取spu的銷售屬性組合

    // 4、獲取spu的介紹
    SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(info.getSpuId());
    skuItemVo.setDesc(spuInfoDescEntity);

    // 5、獲取spu的規格引數資訊
    List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(info.getSpuId(), info.getCatalogId());
    skuItemVo.setGroupAttrs(attrGroupVos);

    return skuItemVo;
}

獲取spu的規格引數資訊

AttrGroupServiceImpl

 @Override
    public List<SpuItemAttrGroupVo> getAttrGroupWithAttrsBySpuId(Long spuId, Long catalogId) {
        //1、查出當前spu對應的所有屬性的分組資訊以及當前分組下的所有屬性對應的值
        AttrGroupDao baseMapper = this.getBaseMapper();
        List<SpuItemAttrGroupVo> vos = baseMapper.getAttrGroupWithAttrsBySpuId(spuId,catalogId);
        return vos;
    }

AttrGroupDao.xml

<resultMap id="spuAttrGroup" type="com.peng.product.vo.SpuItemAttrGroupVo">
    <result property="groupName" column="attr_group_name"/>
    <collection property="attrs" ofType="com.peng.product.vo.Attr">
        <result property="attrId" column="attr_id"></result>
        <result property="attrName" column="attr_name"></result>
        <result property="attrValue" column="attr_value"></result>
    </collection>
</resultMap>

<select id="getAttrGroupWithAttrsBySpuId" resultMap="spuAttrGroup">

    SELECT
        product.spu_id,
        pag.attr_group_id,
        pag.attr_group_name,
        product.attr_id,
        product.attr_name,
        product.attr_value
    FROM
        pms_product_attr_value product
        LEFT JOIN pms_attr_attrgroup_relation paar ON product.attr_id = paar.attr_id
        LEFT JOIN pms_attr_group pag ON paar.attr_group_id = pag.attr_group_id
    WHERE
        product.spu_id = #{spuId}
      AND pag.catelog_id = #{catalogId}

</select>

image-20240729205406786

單元測試

@Test
public  void test(){
    List<SpuItemAttrGroupVo> attrGroupWithAttrsBySpuId = attrGroupService.getAttrGroupWithAttrsBySpuId(1L, 225L);
    System.out.println(attrGroupWithAttrsBySpuId);
}

image-20240729205947526

8.4銷售屬性組合

主要步驟:

  • 查詢sku對應的銷售屬性
  • 單元測試

SkuSaleAttrValueDao

List<SkuItemSaleAttrVo> getSaleAttrBySpuId(@Param("spuId")Long spuId);

SkuSaleAttrValueDao.xml

<select id="getSaleAttrBySpuId" resultMap="skuItemSaleAttrVo">
    SELECT
        ssav.attr_id attr_id,
        ssav.attr_name attr_name,
        ssav.attr_value,
        group_concat( DISTINCT info.sku_id ) sku_ids
    FROM
        pms_sku_info info
        LEFT JOIN pms_sku_sale_attr_value ssav ON ssav.sku_id = info.sku_id
    WHERE
        info.spu_id = #{spuId}
    GROUP BY
        ssav.attr_id,
        ssav.attr_name,
        ssav.attr_value
</select>

image-20240729211259387

單元測試

@Test
public void test2(){
    List<SkuItemSaleAttrVo> saleAttrBySpuId = skuSaleAttrValueService.getSaleAttrBySpuId(1L);
    System.out.println(saleAttrBySpuId);
}

image-20240729211214988

8.5詳情頁渲染

主要步驟:

  • 預設圖片
  • 商品標題
  • 有/無貨(庫存)狀態
  • 銷售屬性
  • 商品介紹
  • 規格與包裝

預設圖片

<div class="imgbox">
    <div class="probox">
       <img class="img1" alt="" th:src="${item.info.skuDefaultImg}">
       <div class="hoverbox"></div>
    </div>
    <div class="showbox">
       <img class="img1" alt="" th:src="${item.info.skuDefaultImg}">
    </div>
</div>


<div class="box-lh">

    <div class="box-lh-one">
       <ul>
          <li th:each="img : ${item.images}"><img th:src="${img.imgUrl}"/></li>
       </ul>
    </div>
    <div id="left">
       <
    </div>
    <div id="right">
       >
    </div>

</div>

<div class="boxx-one">
    <ul>
       <li>
                <span>
                   <img src="/static/item/img/b769782fe4ecca40913ad375a71cb92d.png" alt="" />關注
                </span>
          <span>
                   <img src="/static/item/img/9224fcea62bfff479a6712ba3a6b47cc.png" alt="" />
                   對比
                </span>
       </li>
       <li>

       </li>
    </ul>
</div>

image-20240729221244466

商品標題

<div class="box-name" th:text="${item.info.skuTitle}">
    華為 HUAWEI Mate 10 6GB+128GB 亮黑色 移動聯通電信4G手機 雙卡雙待
</div>
<div class="box-hide" th:text="${item.info.skuSubtitle}">預訂使用者預計11月30日左右陸續發貨!麒麟970晶片!AI智慧拍照!
    <a href="/static/item/"><u></u></a>
</div>

image-20240729221215816

無貨

<li>
    <span th:text="${item.hasStock?'有貨':'無貨'}">無貨</span>, 此商品暫時售完
</li>

image-20240729221335897

銷售屬性

<div class="box-attr-3">
    <div class="box-attr clear" th:each="attr : ${item.saleAttr}">
       <dl>
          <dt>選擇[[${attr.attrName}]]</dt>
          <dd th:each="val : ${attr.attrValues}">
             <a th:attr=" class=${#lists.contains(#strings.listSplit(val.skuIds,','),item.info.skuId.toString())
                               ? 'sku_attr_value checked': 'sku_attr_value'}, skus=${val.skuIds} "
             >
                [[${val.attrValue}]]
                <!--                                 <img src="/static/item/img/59ddfcb1Nc3edb8f1.jpg" /> -->
             </a>
          </dd>
       </dl>
    </div>
</div>

image-20240729221356744

商品介紹

    <div class="shanpinsssss">
                      <img class="xiaoguo" th:src="${descp}"
                          th:each="descp : ${#strings.listSplit(item.desc.decript,',')}"/>
                    </div>

image-20240729221442852

規格與包裝

<li class="baozhuang actives" id="li2">
    <div class="guiGebox">
       <div class="guiGe" th:each="group : ${item.groupAttrs}">
          <h3 th:text="${group.groupName}">主體</h3>
          <dl>
             <div th:each="attr : ${group.attrs}">
                <dt th:text="${attr.attrName}">品牌</dt>
                <dd th:text="${attr.attrValue}">華為(HUAWEI)</dd>
             </div>
          </dl>
       </div>
       <div class="package-list">
          <h3>包裝清單</h3>
          <p>手機(含內建電池) X 1、5A大電流華為SuperCharge充電器X 1、5A USB資料線 X 1、半入耳式線控耳機 X 1、快速指南X 1、三包憑證 X
             1、取卡針 X 1、保護殼 X 1</p>
       </div>
    </div>
</li>

image-20240729221530957

8.6銷售屬性渲染

主要步驟:

  • 銷售屬性渲染的時候如果當前skuId包含銷售屬性就新增選中的class
  • 頁面重新整理時給屬性選中的class加樣式

銷售屬性渲染的時候如果當前skuId包含銷售屬性就新增選中的class

<dl>
    <dt>選擇[[${attr.attrName}]]</dt>
    <dd th:each="val : ${attr.attrValues}">
       <a th:attr=" class=${#lists.contains(#strings.listSplit(val.skuIds,','),item.info.skuId.toString())
                             ? 'sku_attr_value checked': 'sku_attr_value'}, skus=${val.skuIds} "
       >
          [[${val.attrValue}]]
          <!--                                 <img src="/static/item/img/59ddfcb1Nc3edb8f1.jpg" /> -->
       </a>
    </dd>
</dl>

image-20240729225615490

頁面重新整理時給屬性選中的class加樣式

$(function () {
    changeCheckedStyle();
});

function changeCheckedStyle() {
    $(".sku_attr_value").parent().css({"border": "solid 1px #ccc"});
    $("a[class='sku_attr_value checked']").parent().css({"border": "solid 1px red"});
};

image-20240729225825031

8.7sku組合切換

主要步驟:

  • 點選屬性時清空當前銷售屬性組的所有選中樣式(checked),給當前點選元素新增checked類名

  • 獲取所有選中的銷售屬性skusskus是擁有該銷售屬性的所有商品skuId集合

  • 根據skus求交集,得到skuId

  • 跳轉到對應的商品詳情頁面

程式碼

$(".sku_attr_value").click(function () {
    // 1、點選的元素新增上自定義的屬性
    let skus = new Array();
    let curr = $(this).attr("skus").split(",");

    $(this).parent().parent().find(".sku_attr_value").removeClass("checked");
    $(this).addClass("checked");
    changeCheckedStyle();

    $("a[class='sku_attr_value checked']").each(function () {
       skus.push($(this).attr("skus").split(","));
    });

    let filterEle = skus[0];
    for (let i = 1; i < skus.length; i++) {
       filterEle = $(filterEle).filter(skus[i])[0];
    }

    location.href = "http://item.gulimall.com/" + filterEle + ".html";

    return false;
});

8.8非同步編排最佳化

主要步驟:

  • 匯入自定義配置依賴configuration-processor

  • gulimall-product增加執行緒池配置

  • 使用非同步編排最佳化商品詳情查詢

    • supplyAsync:用於非同步執行一個返回結果的任務
    • runAsync:用於非同步執行一個不返回結果的任務。
    • thenAcceptAsync:用於在 CompletableFuture 完成後非同步執行一個操作,該操作使用 CompletableFuture 的結果但不返回新結果。它接受一個 Consumer 介面實現,並返回一個新的 CompletableFuture,該物件在操作完成時表示完成狀態。

建立配置檔案ThreadPoolConfigProperties,並在application.yaml配置執行緒池引數

匯入自定義配置依賴configuration-processor

<!--configuration-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

image-20240729235542858

ThreadPoolConfigProperties

@ConfigurationProperties(prefix = "gulimall.thread")
// @Component
@Data
public class ThreadPoolConfigProperties {

    private Integer coreSize;

    private Integer maxSize;

    private Integer keepAliveTime;

}

application.yaml

gulimall:
  thread:
    coreSize: 20
    maxSize: 200
    keepAliveTime: 10

image-20240729235349535

執行緒池配置

@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {

    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
        return new ThreadPoolExecutor(
                pool.getCoreSize(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }

}

image-20240729235501994

使用非同步編排最佳化商品詳情查詢

@Override
public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {

    SkuItemVo skuItemVo = new SkuItemVo();

    CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
        // 1、sku基本資訊的獲取  pms_sku_info
        SkuInfoEntity info = this.getById(skuId);
        skuItemVo.setInfo(info);
        return info;
    }, executor);


    CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {
        // 3、獲取spu的銷售屬性組合
        List<SkuItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrBySpuId(res.getSpuId());
        skuItemVo.setSaleAttr(saleAttrVos);
    }, executor);


    CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((res) -> {
        // 4、獲取spu的介紹    pms_spu_info_desc
        SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
        skuItemVo.setDesc(spuInfoDescEntity);
    }, executor);


    CompletableFuture<Void> baseAttrFuture = infoFuture.thenAcceptAsync((res) -> {
        // 5、獲取spu的規格引數資訊
        List<SpuItemAttrGroupVo> attrGroupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());
        skuItemVo.setGroupAttrs(attrGroupVos);
    }, executor);


    // 2、sku的圖片資訊    pms_sku_images
    CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
        List<SkuImagesEntity> imagesEntities = skuImagesService.getImagesBySkuId(skuId);
        skuItemVo.setImages(imagesEntities);
    }, executor);


    // 等到所有任務都完成
    CompletableFuture.allOf(infoFuture, saleAttrFuture, descFuture, baseAttrFuture, imageFuture).get();

    return skuItemVo;
}

相關文章