商城项目-分布式高级-06-商城首页检索以及异步编排

197 阅读5分钟

商城搜索页-检索服务

搜索已经上架商品的信息

根据用户输入条件进行检索

image-20201210121550188

检索条件分析

封装页面所有可能传递过来的查询条件
 1.全文检索:skuTitle -> keyword
 2.排序:saleCount,hotScore,skuPrice
 3.过滤:hasStock,skuPrice,brandID,catalog3Id,attrs
 4.聚合:attrs
public class SearchParam {

    /**
     * 关键字,页面传过来的全为匹配字段(skuTitle)
     */
    private String keyword;

    /**
     * 三级分类 id
     */
    private Long catalog3Id;

    /**
     * 排序条件
     * saleCount,hotScore,skuPrice
     * saleCount_desc or saleCount_asc
     */
    private String sort;

    /**
     * 是否只显示有货
     */
    private Integer hasStock;

    /**
     * sku价格
     */
    private String skuPrice;

    /**
     * 品牌Id列表
     */
    private List<Long> brandId;

    /**
     * 属性 attrs=1_其他:安卓&attrs=2_六寸:五寸:七寸
     * 属性id_属性值:属性值
     */
    private List<String> attrs;

    /**
     * 页面号
     */
    private Integer pageNum;
}

返回页面的数据

@Data
public class SearchResData {

    /**
     * 查询到的所有的商品信息
     */
    private List<SkuEsModel> products;

    /**
     * 页面号
     */
    private Integer pageNum;

    /**
     * 总共有多少条数据
     */
    private Long total;

    /**
     * 总页数
     */
    private Integer totalPage;

    /**
     * 当前结果查询到的所有品牌
     */
    private List<BrandVo> brands;

    /**
     * 当前结果查询到的所有属性
     */
    private List<AttrVo> attrs;
    /**
     * 当前结果查询到的所有分类信息
     */
    private List<CatalogVo> catalogs;


    @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> attrValues;
    }


    @Data
    public static class CatalogVo{
        private Long catalogId;
        private String catalogName;
    }
}

接口实现

@Controller
public class SearchController {

    @Autowired
    private MallSearchService mallSearchService;

    /**
     * 在es中搜索已经上架商品的信息
     *
     * @param param 参数
     * @param model 模型
     * @return {@link String}* @throws IOException ioexception
     */
    @GetMapping("/list.html")
    public String list(SearchParam param, Model model) throws IOException {
        SearchResData data = mallSearchService.search(param);
        model.addAttribute("result", data);
        return "list";
    }
}

业务方法

@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Resource
    private RestHighLevelClient esClient;

    @Override
    public SearchResData search(SearchParam param) throws IOException {


        // 1.准备检索请求
        SearchRequest searchRequest = buildSearchConditions(param);
        // 2.执行检索
        SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);

        // 3.封装对象
        return getData(response, param);
    }

    /**
     * 构建搜索条件
     *
     * @param param 参数
     * @return {@link SearchRequest}
     */
    private SearchRequest buildSearchConditions(SearchParam param) {

        // 构建dsl查询语句
        SearchSourceBuilder searchSource = SearchSourceBuilder.searchSource();

        // 1.构建boolQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 2.must keyword
        if (StringUtils.isNoneBlank(param.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
        }

        // 3.filter 过滤
        // 三级分类id
        if (param.getCatalog3Id() != null) {
            boolQuery.filter(QueryBuilders.matchQuery("catalogId", param.getCatalog3Id()));
        }
        // 品牌id
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }
        // 查询是否有库存
        if (param.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termsQuery("hasStock", param.getHasStock() == 1));
        }
        // 价格区间
        if (StringUtils.isNoneBlank(param.getSkuPrice())) {
            // 1_500 _500 500_
            String[] s = param.getSkuPrice().split("_");
            RangeQueryBuilder query = QueryBuilders.rangeQuery("skuPrice");
            if (s.length == 2) {
                query.gte(s[0]).lte(s[1]);
            } else if (s.length == 1) {
                if (param.getSkuPrice().startsWith("_")) {
                    query.lte(s[0]);
                } else if (param.getSkuPrice().endsWith("_")) {
                    query.gte(s[0]);
                }
            }
            boolQuery.filter(query);
        }
        // 属性
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            for (String attrStr : param.getAttrs()) { // attrs=1_其他:安卓&attrs=2_六寸:五寸:七寸  属性id_属性值:属性值
                BoolQueryBuilder bool = QueryBuilders.boolQuery();

                String[] s = attrStr.split("_");
                // 获取需要查询的属性的信息
                String attrId = s[0];
                String[] attrValues = s[1].split(":");

                bool.must(QueryBuilders.matchQuery("attrs.attrId", attrId));
                bool.must(QueryBuilders.termsQuery("attrs.attrName", attrValues));

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

        // 4.排序
        if (StringUtils.isNoneBlank(param.getSort())) { // saleCount,hotScore,skuPrice  saleCount_desc or saleCount_asc
            String[] s = param.getSort().split("_");
            String field = s[0];
            String sort = s[1];

            // 构建排序条件
            SortOrder sortOrder = SortOrder.ASC;
            if (sort.equals("desc"))
                sortOrder = SortOrder.DESC;
            searchSource.sort(field, sortOrder);
        }

        // 5.分页
        searchSource.size(PRODUCT_PAGE_SIZE)
                .from((param.getPageNum() - 1) * PRODUCT_PAGE_SIZE);

        // 6.高亮
        if (StringUtils.isNoneBlank(param.getKeyword())) {
            searchSource.highlighter(SearchSourceBuilder.highlight()
                    .field("skuTitle")
                    .preTags("<b style='color:red'>").postTags("</b>"));
        }

        // 7.聚合分析
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId").size(50);  // 找出每一个品牌的id
        brandAgg.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1));      // 名字
        brandAgg.subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(1));       // 图片
        searchSource.aggregation(brandAgg);

        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId").size(50);  // 找出每一个分类的id
        catalogAgg.subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catalogName").size(1));      // 名字
        catalogAgg.subAggregation(AggregationBuilders.terms("catalogImgAgg").field("catalogImg").size(1));       //  图片
        searchSource.aggregation(catalogAgg);

        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs");          // 找出属性的相关信息
        AggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"));
        attrAgg.subAggregation(attrIdAgg);
        searchSource.aggregation(attrAgg);


        searchSource.query(boolQuery);

        return new SearchRequest().source(searchSource).indices(EsConstant.PRODUCT_INDEX);
    }

    /**
     * 返回页面需要的数据
     *
     * @param response 响应
     * @param param
     * @return {@link SearchResData}
     */
    private SearchResData getData(SearchResponse response, SearchParam param) {
        SearchResData data = new SearchResData();

        // 准备数据
        // 封装product需要的数据
        SearchHits hits = response.getHits();
        SearchHit[] hitsHits = hits.getHits();
        // 封装其他需要的数据
        Aggregations aggregations = response.getAggregations();

        List<SkuEsModel> skuEsModels = new ArrayList<>();
        List<SearchResData.BrandVo> brandVos = new ArrayList<>();
        List<SearchResData.AttrVo> attrVos = new ArrayList<>();
        List<SearchResData.CatalogVo> catalogVos = new ArrayList<>();

        // 设置产品总信息
        for (SearchHit h : hitsHits) {
            Map<String, Object> source = h.getSourceAsMap();
            SkuEsModel skuEsModel = BeanUtil.mapToBean(source, SkuEsModel.class, true);

            // 设置高亮
            if (StringUtils.isNotBlank(param.getKeyword())) {
                skuEsModel.setSkuTitle(String.valueOf(h.getHighlightFields().get("skuTitle")));
            }
            skuEsModels.add(skuEsModel);
        }

        // 设置分类
        ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResData.CatalogVo catalogVo = new SearchResData.CatalogVo();
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalogNameAgg");

            catalogVo.setCatalogId((Long) bucket.getKey());
            catalogVo.setCatalogName(catalogNameAgg.getBuckets().get(0).getKeyAsString());

            catalogVos.add(catalogVo);
        }

        // 设置品牌
        ParsedLongTerms brandAgg = aggregations.get("brandAgg");
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResData.BrandVo brandVo = new SearchResData.BrandVo();
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brandImgAgg");
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");

            brandVo.setBrandId((Long) bucket.getKeyAsNumber());
            brandVo.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString());
            brandVo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());

            brandVos.add(brandVo);
        }

        // 设置属性
        ParsedNested attrAgg = aggregations.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");

        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResData.AttrVo attrVo = new SearchResData.AttrVo();
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");

            attrVo.setAttrId((Long) bucket.getKey());
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            attrVo.setAttrValue(attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList()));

            attrVos.add(attrVo);
        }

        data.setProducts(skuEsModels);
        data.setBrands(brandVos);
        data.setAttrs(attrVos);
        data.setCatalogs(catalogVos);

        data.setPageNum(param.getPageNum());
        data.setTotal(hits.getTotalHits().value);
        data.setTotalPage(Math.toIntExact(
                hits.getTotalHits().value % PRODUCT_PAGE_SIZE == 0
                ? hits.getTotalHits().value / PRODUCT_PAGE_SIZE
                : (hits.getTotalHits().value / PRODUCT_PAGE_SIZE) + 1));

        return data;
    }
}

添加面包屑导航功能

SearchParam

image-20201211180202917

SearchResData

image-20201211180220808

image-20201211180246923

业务方法

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

    List<SearchResData.NavVo> navVos = param.getAttrs().stream().map(i -> {
        // 分析每个attrs传过来查询的数值;
        SearchResData.NavVo navVo = new SearchResData.NavVo();
        String[] s = i.split("_");
        navVo.setNavValue(s[1]);

        R r = productServiceFeign.attrInfo(Long.valueOf(s[0]));
        if (r.getCode() == 0) {
            AttrRespVo attr = (AttrRespVo) r.get("attr");

            navVo.setNavName(attr.getAttrName());
        }

        // 取消这个面包屑后,我们要跳转到哪个地方,将请求地址置空
        String replace = param.get_queryString().replace("attrs=" + Arrays.toString(s), "");
        navVo.setLink("http://localhost:8889/list.html?" + replace);

        return navVo;
    }).collect(Collectors.toList());

    data.setNavs(navVos);
}

异步编排

completableFuture

image-20201212163441946

启动异步编排任务

public class ThreadTest {

    static ExecutorService executor = new ThreadPoolExecutor(10, 100 * 100, 10L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("main-start");
        // 沒有返回值
        CompletableFuture<Void> voidThread = CompletableFuture.runAsync(() -> {
            System.out.println("当前线程" + Thread.currentThread().getName());
            int i = 10 / 2;
            System.out.println("结果:" + i);
        }, executor);
		
        // 有返回值
        CompletableFuture<Integer> intThread = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程" + Thread.currentThread().getName());
            return 100 / 2;
        }, executor);
        System.out.println("结果"+intThread.get());
        System.out.println("main-end");
    }
}

任务完成时回调方法

public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("main-start");
    CompletableFuture<Integer> fu = CompletableFuture
            .supplyAsync(() -> 10, executor)
            // 当上面的任务完成后调用
            .whenComplete((res, ex) -> System.out.println(res))
            // 出现异常后
            .exceptionally(ex-> 100);
    System.out.println(fu.get());
    System.out.println("main-end");
}

image-20201212165924797

任务完成后对结果进行修改

public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("main-start");

    CompletableFuture<Integer> handle = CompletableFuture
            .supplyAsync(() -> {
                System.out.println("开始执行业务");

                System.out.println("执行完毕");
                return 10;
            }, executor)
            .handle((res, e) -> {
                res = 20;
                return res;
            });
    System.out.println(handle.get());
    
    System.out.println("main-end");
}

线程串行化

image-20201212170407279

两个任务组合-都要完成

image-20201212171045226image-20201212171054033

image-20201212171058716

商品详情页

@Override
public SkuItemVo item(Long skuId) {
    SkuItemVo res = new SkuItemVo();
    // 1.sku基本信息
    SkuInfoEntity skuInfo = getById(skuId);
    res.setSkuInfoEntity(skuInfo);

    Long spuId = skuInfo.getSpuId();
    Long catalogId = skuInfo.getCatalogId();

    // 2.sku的图片信息
    List<SkuImagesEntity> images = skuImagesService.getBySkuId(skuId);
    res.setImages(images);

    // 3.spu的销售属性
    List<SkuItemSaleAttrVo> saleAttrVo =  skuSaleAttrValueService.getSpuId(skuId);
    res.setSaleAttrs(saleAttrVo);

    // 4.spu的介绍
    SpuInfoDescEntity desc = spuInfoDescService.getById(spuId);
    res.setDesc(desc);

    // 5.spu的规格参数
    List<SpuItemAttrGroupVo> groupVos = attrGroupService.getBySpuId(spuId,catalogId);
    res.setGroupAttrs(groupVos);
    return res;
}

异步编排后

引入配置提示jar包

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

配置资源类

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

    private Integer coreSize;
    private Integer maxSize;
    private Integer keepAlive;
}

配置类

@Configuration
public class ThreadConfig {
    @Bean
    public ThreadPoolExecutor threadPool(ThreadPoolConfigProperties poolConfig) {
        return new ThreadPoolExecutor(poolConfig.getCoreSize(), poolConfig.getMaxSize(), poolConfig.getKeepAlive(), TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10000), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }
}

配置

image-20201212215821566

异步编排后

@Autowired
ThreadPoolExecutor threadPool;

@Override
public SkuItemVo item(Long skuId) {
    SkuItemVo res = new SkuItemVo();
    AtomicReference<Long> spuId = new AtomicReference<>();
    AtomicReference<Long> catalogId = new AtomicReference<>();

    CompletableFuture<SkuInfoEntity> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
        // 1.sku基本信息
        SkuInfoEntity skuInfo = getById(skuId);
        res.setSkuInfoEntity(skuInfo);

        spuId.set(skuInfo.getSpuId());
        catalogId.set(skuInfo.getCatalogId());
        return skuInfo;
    }, threadPool);

    CompletableFuture<Void> spuSaleFuture = skuInfoFuture.thenAcceptAsync((r) -> {
        // 3.spu的销售属性
        List<SkuItemSaleAttrVo> saleAttrVo = skuSaleAttrValueService.getSpuId(skuId);
        res.setSaleAttrs(saleAttrVo);
    }, threadPool);

    CompletableFuture<Void> spuDescFuture = skuInfoFuture.thenAcceptAsync((r) -> {
        // 4.spu的介绍
        SpuInfoDescEntity desc = spuInfoDescService.getById(spuId);
        res.setDesc(desc);
    }, threadPool);

    CompletableFuture<Void> spuAttrFuture = skuInfoFuture.thenAcceptAsync((r) -> {
        // 5.spu的规格参数
        List<SpuItemAttrGroupVo> groupVos = attrGroupService.getBySpuId(spuId.get(), catalogId.get());
        res.setGroupAttrs(groupVos);
    }, threadPool);

    CompletableFuture<Void> imagesFuture = CompletableFuture.runAsync(() -> {
        // 2.sku的图片信息
        List<SkuImagesEntity> images = skuImagesService.getBySkuId(skuId);
        res.setImages(images);
    }, threadPool);

    try {
        CompletableFuture.allOf(imagesFuture, skuInfoFuture, spuAttrFuture, spuDescFuture, spuSaleFuture).get();
    } catch (Exception e) {
        e.printStackTrace();
    }

    return res;
}