目录

一、构造搜索条件

二、搜索

三、将结果封装为List对象

四、遍历集合,封装查询面板

五、代码整体展示

六、总结


这是商品搜索相关文章的最后一篇了。前几篇在这里👇

【电商项目】商品搜索开发复盘(1):根据需求拆解搜索接口的设计逻辑

【电商项目】商品搜索开发复盘(2):构造ES搜索条件

【电商项目】商品搜索开发复盘(3):搜索与封装实现

我们继续(3)往下编写封装查询面板功能。

需求如下:查询面板中包含品牌、分类、规格项。根据查询条件,找到查询结果关联度前20名的商品数据,遍历这些数据,找到它们的品牌、分类、规格项,最后封装到查询结果中。

样式参考京东页面:


一、构造搜索条件

封装查询面板,首先就是根据需求构建搜索条件,把这最符合的20条数据查出来。

其实本质就是一个分页查询,构造出一页,一页有20条嘛,并且遵循默认排序。

 // 1.构造搜索条件
  goodsSearchParam.setPage(1);
  goodsSearchParam.setSize(20);
  goodsSearchParam.setSort(null);  // 默认排序方式
  goodsSearchParam.setSortFiled(null); //默认排序字段

  NativeQuery query = buildQuery(goodsSearchParam); //构建查询条件

二、搜索

拿到条件后搜索,详细讲解见(3).二。

// 2.搜索
SearchHits<GoodsES> search = elasticsearchTemplate.search(nativeQuery, GoodsES.class);

三、将结果封装为List对象

详解见(3).三。

// 3.将结果封装为List对象
List<GoodsES> list = new ArrayList<>();
for (SearchHit<GoodsES> goodsESSearchHit : search) {
    GoodsES content = goodsESSearchHit.getContent();
    list.add(content);
  }

四、遍历集合,封装查询面板

思路就是 获取搜索数据 -> 按需求分类收集封装。参考1图的京东页面可知查询面板封装的有“品牌、类型、规格”。具体实现如下:

  1. 创建需要的空集合,用于聚合筛选数据
  2. 遍历ES返回的所有商品数据
  3. 收集商品需求对象,并完成去重
  4. 封装,返回结果对象
// 4.遍历集合,封装查询面板
// 商品相关的品牌列表
Set<String> brands = new HashSet<>();
// 商品相关的类型列表
Set<String> productType = new HashSet<>();
// 商品相关的规格列表
HashMap<String, Set<String>> specifications = new HashMap<>();

for (GoodsES l : list) {
    // 获取品牌
    brands.add(l.getBrand());
    // 获取类型
    productType.addAll(l.getProductType());
    // 获取规格
    Map<String, List<String>> specification = l.getSpecification();
    Set<Map.Entry<String, List<String>>> entries = specification.entrySet();
    for (Map.Entry<String, List<String>> entry : entries) {
        String key = entry.getKey();
        List<String> values = entry.getValue();
        // 如果没有遍历出该规格,新增键值对,如果已经遍历出该规格,则向规格中添加规格项
        if(!specifications.containsKey(key)){
            specifications.put(key, new HashSet(values)); // 新增键值对
        }
        specifications.get(key).addAll(values); // 向规格中添加规格项
    }
}
goodsSearchResult.setBrands(brands);
goodsSearchResult.setProductType(productType);
goodsSearchResult.setSpecifications(specifications);

Q1: HashSet 和 HashMap 的区别?为什么品牌、类目用 HashSet,规格用 HashMap?

A:

  • HashSet单列集合,只存值,元素不可重复;底层就是HashMap,只保留 key, value 占位。适合存无分组的零散字符串。
  • HashMap双列集合,存【key:value 键值对】,key 唯一。适合存需分组键值结构的数据。
  • 品牌、类目只是独立的字符串,不需要分组 → Set<String>
  • 规格是 【 规格名:[规格值 1,规格值 2] 】的分组结构 → Map<String,Set<String>>
  • 此举利用了 Hash 自动去重的特性

Q2:为什么要先单独定义集合,最后统一 set 进 Result,不能遍历直接 get** ().add (**)?

我最开始就这么写的哈,真是错极了,运行报错,我去查了下为何不妥。

A:

  • 根因:

        拿 goodsSearchResult.getBrands().add() 举例:如果goodsSearchResult内部的 brands 集合初始是 null,那么getBrands().add()会直接空指针报错(getBrands()返回 null,null.add()就炸)。

  • 从业务逻辑层面考虑:

        搜索页需求:前端筛选框只展示不重复的品牌、分类、规格,必须先用 Set 全局汇总所有商品数据、自动去重,全部遍历完再整体赋值给返回体,不能边遍历边往 Result 里塞。

  • 标准写法:

        先手动new HashSet初始化空集合,遍历全部商品把数据全部汇总完毕,最后一次性 set 进 Result 对象,即可避免上述问题。

五、代码整体展示

/*
封装查询面板,即根据查询条件,找到查询结果关联度前20名的商品进行封装
@param goodsSearchParam
@param goodsSearchResult
 */
public void buildSearchPanel(GoodsSearchParam goodsSearchParam, GoodsSearchResult goodsSearchResult){
    // 1.构造搜索条件
    goodsSearchParam.setPage(1);
    goodsSearchParam.setSize(20);
    goodsSearchParam.setSort(null);
    goodsSearchParam.setSortFiled(null);
    NativeQuery nativeQuery = buildQuery(goodsSearchParam);
    // 2.搜索
    SearchHits<GoodsES> search = elasticsearchTemplate.search(nativeQuery, GoodsES.class);
    // 3.将结果封装为List对象
    List<GoodsES> list = new ArrayList<>();
    for (SearchHit<GoodsES> goodsESSearchHit : search) {
        GoodsES content = goodsESSearchHit.getContent();
        list.add(content);
    }
    // 4.遍历集合,封装查询面板
    // 商品相关的品牌列表
    Set<String> brands = new HashSet<>();
    // 商品相关的类型列表
    Set<String> productType = new HashSet<>();
    // 商品相关的规格列表
    HashMap<String, Set<String>> specifications = new HashMap<>();
    for (GoodsES l : list) {
        // 获取品牌
        brands.add(l.getBrand());
        // 获取类型
        productType.addAll(l.getProductType());
        // 获取规格
        Map<String, List<String>> specification = l.getSpecification();
        Set<Map.Entry<String, List<String>>> entries = specification.entrySet();
        for (Map.Entry<String, List<String>> entry : entries) {
            String key = entry.getKey();
            List<String> values = entry.getValue();
            // 如果没有遍历出该规格,新增键值对,如果已经遍历出该规格,则向规格中添加规格项
            if(!specifications.containsKey(key)){
                specifications.put(key, new HashSet(values)); // 新增键值对
            }
            specifications.get(key).addAll(values); // 向规格中添加规格项
        }
    }
    goodsSearchResult.setBrands(brands);
    goodsSearchResult.setProductType(productType);
    goodsSearchResult.setSpecifications(specifications);
}

六、总结

所写即为总结,感谢您的阅读🤝给予我坚持记录的动力。虽然很多问题不够有深度甚至可以说是拿出来说都招笑,不过我会继续努力快速成长的。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐