02谷粒商城-高级篇二

前言

不必纠结当下,也不必太担忧未来,

人生没有无用的经历,

所以,一直走,天一定亮

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;
}
posted @ 2024-10-14 00:58  peng_boke  阅读(18)  评论(0编辑  收藏  举报