商城-搜索过滤-生成分类和品牌过滤

商城-搜索过滤-生成分类和品牌过滤

  • 2.生成分类和品牌过滤
    • 2.1.扩展返回的结果
    • 2.2.聚合商品分类和品牌
      • 2.2.1.提供查询品牌接口
      • 2.2.2.搜索功能改造
    • 2.3.页面渲染数据
      • 2.3.1.过滤参数数据结构
      • 2.3.2.页面渲染数据

2.生成分类和品牌过滤

先来看分类和品牌。在我们的数据库中已经有所有的分类和品牌信息。在这个位置,是不是把所有的分类和品牌信息都展示出来呢?

显然不是,用户搜索的条件会对商品进行过滤,而在搜索结果中,不一定包含所有的分类和品牌,直接展示出所有商品分类,让用户选择显然是不合适的。

无论是分类信息,还是品牌信息,都应该从搜索的结果商品中进行聚合得到。

2.1.扩展返回的结果

原来,我们返回的结果是PageResult对象,里面只有total、totalPage、items3个属性。但是现在要对商品分类和品牌进行聚合,数据显然不够用,我们需要对返回的结果进行扩展,添加分类和品牌的数据。

那么问题来了:以什么格式返回呢?

看页面:
商城-搜索过滤-生成分类和品牌过滤_第1张图片

分类:页面显示了分类名称,但背后肯定要保存id信息。所以至少要有id和name

品牌:页面展示的有logo,有文字,当然肯定有id,基本上是品牌的完整数据

我们新建一个类,继承PageResult,然后扩展两个新的属性:分类集合和品牌集合:

public class SearchResult extends PageResult<Goods>{

    private List<Category> categories;

    private List<Brand> brands;

    public SearchResult(Long total, Integer totalPage, List<Goods> items, List<Category> categories, List<Brand> brands) {
        super(total, totalPage, items);
        this.categories = categories;
        this.brands = brands;
    }
}

2.2.聚合商品分类和品牌

我们修改搜索的业务逻辑,对分类和品牌聚合。

因为索引库中只有id,所以我们根据id聚合,然后再根据id去查询完整数据。

所以,商品微服务需要提供一个接口:根据品牌id集合,批量查询品牌。

2.2.1.提供查询品牌接口

BrandApi

@RequestMapping("brand")
public interface BrandApi {

    @GetMapping("list")
    List<Brand> queryBrandByIds(@RequestParam("ids") List<Long> ids);
}

BrandController

/**
     * 根据多个id查询品牌
     * @param ids
     * @return
     */
@GetMapping("list")
public ResponseEntity<List<Brand>> queryBrandByIds(@RequestParam("ids") List<Long> ids){
    List<Brand> list = this.brandService.queryBrandByIds(ids);
    if(list == null){
        new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    return ResponseEntity.ok(list);
}

BrandService

public List<Brand> queryBrandByIds(List<Long> ids) {
    return this.brandMapper.selectByIdList(ids);
}

BrandMapper

继承通用mapper的 SelectByIdListMapper即可

public interface BrandMapper extends Mapper<Brand>, SelectByIdListMapper<Brand,Long> {}

2.2.2.搜索功能改造

添加BrandClient

@FeignClient("item-service")
public interface BrandClient extends BrandApi {
}

修改SearchService:

@Service
public class SearchService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    private static final Logger logger = LoggerFactory.getLogger(SearchService.class);

    public PageResult<Goods> search(SearchRequest request) {
        // 判断是否有搜索条件,如果没有,直接返回null。不允许搜索全部商品
        if (StringUtils.isBlank(request.getKey())) {
            return null;
        }

        // 1、构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        
        // 1.1、基本查询
        queryBuilder.withQuery(QueryBuilders.matchQuery("all", request.getKey()));
        // 通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(
                new String[]{"id", "skus", "subTitle"}, null));

        // 1.2.分页排序
        searchWithPageAndSort(queryBuilder,request);
        
        // 1.3、聚合
        String categoryAggName = "category"; // 商品分类聚合名称
        String brandAggName = "brand"; // 品牌聚合名称
        // 对商品分类进行聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        // 对品牌进行聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        // 2、查询,获取结果
        AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());

        // 3、解析查询结果
        // 3.1、分页信息
        Long total = pageInfo.getTotalElements();
        int totalPage = (total.intValue() + request.getSize() - 1) / request.getSize();
     	// 3.2、商品分类的聚合结果
        List<Category> categories = 
            getCategoryAggResult(pageInfo.getAggregation(categoryAggName));
        // 3.3、品牌的聚合结果
        List<Brand> brands = getBrandAggResult(pageInfo.getAggregation(brandAggName));

        // 返回结果
         return new SearchResult(goodsPage.getTotalElements(), goodsPage.getTotalPages(), goodsPage.getContent(), categories, brands);
    }
    

    // 解析品牌聚合结果
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        try {
            LongTerms brandAgg = (LongTerms) aggregation;
            List<Long> bids = new ArrayList<>();
            for (LongTerms.Bucket bucket : brandAgg.getBuckets()) {
                bids.add(bucket.getKeyAsNumber().longValue());
            }
            // 根据id查询品牌
            return this.brandClient.queryBrandByIds(bids);
        } catch (Exception e){
            logger.error("品牌聚合出现异常:", e);
            return null;
        }
    }

    // 解析商品分类聚合结果
    private List<Category> getCategoryAggResult(Aggregation aggregation) {
        try{
            List<Category> categories = new ArrayList<>();
            LongTerms categoryAgg = (LongTerms) aggregation;
            List<Long> cids = new ArrayList<>();
            for (LongTerms.Bucket bucket : categoryAgg.getBuckets()) {
                cids.add(bucket.getKeyAsNumber().longValue());
            }
            // 根据id查询分类名称
            List<String> names = this.categoryClient.queryNameByIds(cids);

            for (int i = 0; i < names.size(); i++) {
                Category c = new Category();
                c.setId(cids.get(i));
                c.setName(names.get(i));
                categories.add(c);
            }
            return categories;
        } catch (Exception e){
            logger.error("分类聚合出现异常:", e);
            return null;
        }
    }

    // 构建基本查询条件
    private void searchWithPageAndSort(NativeSearchQueryBuilder queryBuilder, SearchRequest request) {
        // 准备分页参数
        int page = request.getPage();
        int size = request.getSize();

        // 1、分页
        queryBuilder.withPageable(PageRequest.of(page - 1, size));
        // 2、排序
        String sortBy = request.getSortBy();
        Boolean desc = request.getDescending();
        if (StringUtils.isNotBlank(sortBy)) {
            // 如果不为空,则进行排序
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc ? SortOrder.DESC : SortOrder.ASC));
        }
    }
}

测试:
商城-搜索过滤-生成分类和品牌过滤_第2张图片

2.3.页面渲染数据

2.3.1.过滤参数数据结构

来看下页面的展示效果:
商城-搜索过滤-生成分类和品牌过滤_第3张图片

虽然分类、品牌内容都不太一样,但是结构相似,都是key和value的结构。

而且页面结构也极为类似:
商城-搜索过滤-生成分类和品牌过滤_第4张图片

所以,我们可以把所有的过滤条件放入一个数组中,然后在页面利用v-for遍历一次生成。

其基本结构是这样的:

[
    {
        k:"过滤字段名",
        options:[{/*过滤字段值对象*/},{/*过滤字段值对象*/}]
    }
]

我们先在data中定义数组:filter,等待组装过滤参数:

data: {
    ly,
    search:{
        key: "",
        page: 1
    },
    goodsList:[], // 接收搜索得到的结果
    total: 0, // 总条数
    totalPage: 0, // 总页数
    filters:[] // 过滤参数集合
},

然后在查询搜索结果的回调函数中,对过滤参数进行封装:
商城-搜索过滤-生成分类和品牌过滤_第5张图片

然后刷新页面,通过浏览器工具,查看封装的结果:
商城-搜索过滤-生成分类和品牌过滤_第6张图片

2.3.2.页面渲染数据

首先看页面原来的代码:
商城-搜索过滤-生成分类和品牌过滤_第7张图片

我们注意到,虽然页面元素是一样的,但是品牌会比其它搜索条件多出一些样式,因为品牌是以图片展示。需要进行特殊处理。数据展示是一致的,我们采用v-for处理:

<div class="type-wrap" v-for="(f,i) in filters" :key="i" v-if="f.k !== '品牌'">
    <div class="fl key">{{f.k}}div>
    <div class="fl value">
        <ul class="type-list">
            <li v-for="(option, j) in f.options" :key="j">
                <a>{{option.name}}a>
            li>
        ul>
    div>
    <div class="fl ext">div>
div>
<div class="type-wrap logo" v-else>
    <div class="fl key brand">{{f.k}}div>
    <div class="value logos">
        <ul class="logo-list">
            <li v-for="(option, j) in f.options" v-if="option.image">
                <img :src="option.image" />
            li>
            <li style="text-align: center" v-else>
                <a style="line-height: 30px; font-size: 12px" href="#">{{option.name}}a>
            li>
        ul>
    div>
    <div class="fl ext">
        <a href="javascript:void(0);" class="sui-btn">多选a>
    div>
div>

结果:
商城-搜索过滤-生成分类和品牌过滤_第8张图片

你可能感兴趣的:(【微服务】,【商城】)