SpringCloudAlibaba云商场-海量数据搜索实现(五)

90 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情

每日英语:

One sees clearly only with the heart;What is essential is invisible to the eye.

翻译:只有用心才能看清真相,真正重要的东西用眼睛是看不到的。 ——《小王子》

1. 商品搜索条件属性回显

1.1 属性条件回显分析

属性条件其实就是当前搜索的所有商品属性信息,所以我们可以把所有属性信息全部查询出来,然后把属性名作为key,属性值用集合存起来,就是我们页面要的属性条件了。

1.2 属性条件回显查询

1.2.1 属性分组查询

修改com.xz.mall.search.service.impl.SkuSearchServiceImplgroup方法,添加分组查询,代码如下:

/**
 * 分组搜素
 * @param queryBuilder
 * @param searchMap
 * @return
 */
private void group(NativeSearchQueryBuilder queryBuilder, Map<String, Object> searchMap) {
    //用户如果没有输入分类条件,则需要将分类搜索出来,作为条件提供给用户
    if (StringUtils.isEmpty(searchMap.get("category"))) {
        queryBuilder.addAggregation(
                AggregationBuilders
                        .terms("categoryList")//别名,类似Map的key
                        .field("categoryName")//根据categoryName域进行分组
                        .size(100)//分组结果显示100个
        );
    }

    //用户如果没有输入品牌条件,则需要将分类搜索出来,作为条件提供给用户
    if (StringUtils.isEmpty(searchMap.get("brand"))) {
        queryBuilder.addAggregation(
                AggregationBuilders
                        .terms("brandList")//别名,类似Map的key
                        .field("brandName")//根据brandName域进行分组
                        .size(100)//分组结果显示100个
        );
    }

    //属性分组查询
    queryBuilder.addAggregation(
            AggregationBuilders
                    .terms("attrmaps")//别名,类似Map的key
                    .field("skuAttribute")//根据skuAttribute域进行分组
                    .size(100000)//分组结果显示100000个
    );
}

1.2.2 属性数据分析

com.xz.mall.search.service.impl.SkuSearchServiceImpl中添加attrParse方法,添加熟悉数据分析方法,代码如下:

/**
 * 属性数据分析
 * @param searchMap
 */
private void attrParse(Map<String, Object> searchMap) {
    //先获取attrMaps
    Object attrMaps = searchMap.get("attrmaps");
    if (!ObjectUtils.isEmpty(attrMaps)) {
        //集合数据
        List<String> groupList = (List<String>) attrMaps;
        //定义一个集合Map<String,Set<String>>,存储所有汇总数据
        Map<String, Set<String>> allMaps = new HashMap<>();

        //循环集合
        for (String attr : groupList) {
            Map<String, String> attrMap = JSON.parseObject(attr, Map.class);

            for (Map.Entry<String, String> entry : attrMap.entrySet()) {
                //获取每条记录,将记录转成Map
                String key = entry.getKey();
                Set<String> values = allMaps.get(key);
                if (values == null) {
                    values = new HashSet<>();
                }
                //存在,则取出来,再添加当前值
                values.add(entry.getValue());
                //覆盖之前的数据
                allMaps.put(key,values);
            }
        }
        //覆盖之前的attrMaps
        searchMap.put("attrmaps", allMaps);
    }
}

1.2.3 执行调用

com.xz.mall.search.service.impl.SkuSearchServiceImpl#search中执行调用,如下代码:

/**
 * 商品关键词搜索
 * @param searchMap
 * @return
 */
@Override
public Map<String, Object> search(Map<String, Object> searchMap) {
    //QueryBuilder->构建搜索条件
    NativeSearchQueryBuilder queryBuilder = queryBuilder(searchMap);
    //分组搜索调用
    group(queryBuilder, searchMap);

    //skuSearchMapper进行搜索
    //Page<SkuEs> page = skuSearchMapper.search(queryBuilder.build());
    AggregatedPage<SkuEs> page = (AggregatedPage<SkuEs>) skuSearchMapper.search(queryBuilder.build());

    //获取结果集:集合列表、记录总数
    Map<String, Object> resultMap = new HashMap<>();
    //分组数据解析
    parseGroup(page.getAggregations(), resultMap);

    //动态属性解析
    attrParse(resultMap);
    
    List<SkuEs> list = page.getContent();
    resultMap.put("list", list);
    resultMap.put("totalElements", page.getTotalElements());
    return resultMap;
}

2. 商品搜索条件筛选实现

2.1 搜索条件筛选分析

用户在前端执行条件搜索的时候,有可能会选择分类、品牌、价格、属性,每次选择条件传入后台,后台按照指定参数进行条件查询,我们这里制定一个传参数的规则:

1、分类参数:category
2、品牌参数:brand
3、价格参数:price
4、属性参数:attr_属性名:属性值
5、分页参数:page

2.2 条件筛选查询实现

2.2.1 分类、品牌、价格查询

分别获取category,brand,price的值,并根据这三个只分别实现分类过滤、品牌过滤、价格过滤,其中价格过滤传入的数据以-分割,实现代码如下:

修改com.xz.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder添加代码,完整代码如下图:

/**
 * 搜索条件构建
 * @param searchMap
 * @return
 */
private NativeSearchQueryBuilder queryBuilder(Map<String, Object> searchMap) {
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

    //组合查询对象
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

    //判断关键词是否为空,不为空,则设置条件
    if (!CollectionUtils.isEmpty(searchMap)) {
        //获取关键词
        Object keyWords = searchMap.get("keywords");
        if (!ObjectUtils.isEmpty(keyWords)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("name", keyWords.toString()));
        }
        //分类查询
        Object category = searchMap.get("category");
        if (!ObjectUtils.isEmpty(category)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("categoryName", category.toString()));
        }
        //品牌查询
        Object brand = searchMap.get("brand");
        if (!ObjectUtils.isEmpty(brand)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("brandName", brand.toString()));
        }
        //价格区间查询 price 0-500元 500-1000元 1000元以上
        Object price = searchMap.get("price");
        if (!ObjectUtils.isEmpty(price)) {
            String[] prices = price.toString().replace("元", "").replace("以上", "").split("-");
            //price > x
            boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.valueOf(prices[0])));
            //price <= y
            if (prices.length == 2) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.valueOf(prices[1])));
            }
        }
    }

    return queryBuilder.withQuery(boolQueryBuilder);
}

2.2.2 分页查询

编写分页实现,先获取当前页,再在com.xz.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder调用该方法。

2.2.2.1 获取当前分页方法

/**
 * 获取当前分页
 * @param searchMap
 * @return
 */
private int currentPage(Map<String, Object> searchMap) {
    try {
        Object page = searchMap.get("page");
        return Integer.valueOf(page.toString()) - 1;
    } catch (Exception e) {
        return 0;
    }
}

2.2.2.2 分页调用

com.xz.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder中添加一下调用

//分页查询
queryBuilder.withPageable(PageRequest.of(currentPage(searchMap), 5));

2.2.3 属性查询

属性查询,每次传到后台的参数都是以attr_开始,我们可以遍历传过来的参数searchMap,判断是否是以attr_开始的参数,如果是,则查询属性。

com.xz.mall.search.service.impl.SkuSearchServiceImpl#queryBuilder中添加如下代码 即可:

//动态属性查询
for (Map.Entry<String, Object> entry : searchMap.entrySet()) {
    //以attr_开始,动态属性
    if (entry.getKey().startsWith("attr_")) {
        String key = "attrMap." + entry.getKey().replaceFirst("attr_", "") + ".keyword";
        boolQueryBuilder.must(QueryBuilders.termQuery(key, entry.getValue().toString()));
    }
}

最终完整代码如下图:

/**
 * 搜索条件构建
 * @param searchMap
 * @return
 */
private NativeSearchQueryBuilder queryBuilder(Map<String, Object> searchMap) {
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

    //组合查询对象
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

    //判断关键词是否为空,不为空,则设置条件
    if (!CollectionUtils.isEmpty(searchMap)) {
        //获取关键词
        Object keyWords = searchMap.get("keywords");
        if (!ObjectUtils.isEmpty(keyWords)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("name", keyWords.toString()));
        }
        //分类查询
        Object category = searchMap.get("category");
        if (!ObjectUtils.isEmpty(category)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("categoryName", category.toString()));
        }
        //品牌查询
        Object brand = searchMap.get("brand");
        if (!ObjectUtils.isEmpty(brand)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("brandName", brand.toString()));
        }
        //价格区间查询 price 0-500元 500-1000元 1000元以上
        Object price = searchMap.get("price");
        if (!ObjectUtils.isEmpty(price)) {
            String[] prices = price.toString().replace("元", "").replace("以上", "").split("-");
            //price > x
            boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.valueOf(prices[0])));
            //price <= y
            if (prices.length == 2) {
                boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.valueOf(prices[1])));
            }
        }
        //动态属性查询
        for (Map.Entry<String, Object> entry : searchMap.entrySet()) {
            //以attr_开始,动态属性
            if (entry.getKey().startsWith("attr_")) {
                String key = "attrMap." + entry.getKey().replaceFirst("attr_", "") + ".keyword";
                boolQueryBuilder.must(QueryBuilders.termQuery(key, entry.getValue().toString()));
            }
        }
    }

    //分页查询
    queryBuilder.withPageable(PageRequest.of(currentPage(searchMap), 5));
    return queryBuilder.withQuery(boolQueryBuilder);
}

总结:

本篇主要介绍了一下商品搜索条件属性回显的具体实现,还有商品搜索条件筛选查询的具体实现。