1.過濾功能分析
首先看下頁面要實現的效果:
整個過濾部分有3塊:
- 頂部的導航,已經選擇的過濾條件展示:
- 商品分類面包屑,根據用戶選擇的商品分類變化
- 其它已選擇過濾參數
- 過濾條件展示,又包含3部分
- 商品分類展示
- 品牌展示
- 其它規格參數
- 展開或收起的過濾條件的按鈕
頂部導航要展示的內容跟用戶選擇的過濾條件有關。
- 比如用戶選擇了某個商品分類,則面包屑中才會展示具體的分類
- 比如用戶選擇了某個品牌,列表中才會有品牌信息。
所以,這部分需要依賴第二部分:過濾條件的展示和選擇。因此我們先不着急去做。
展開或收起的按鈕是否顯示,取決於過濾條件有多少,如果很少,那么就沒必要展示。所以也是跟第二部分的過濾條件有關。
這樣分析來看,我們必須先做第二部分:過濾條件展示。
2.生成分類和品牌過濾
先來看分類和品牌。在我們的數據庫中已經有所有的分類和品牌信息。在這個位置,是不是把所有的分類和品牌信息都展示出來呢?
顯然不是,用戶搜索的條件會對商品進行過濾,而在搜索結果中,不一定包含所有的分類和品牌,直接展示出所有商品分類,讓用戶選擇顯然是不合適的。
無論是分類信息,還是品牌信息,都應該從搜索的結果商品中進行聚合得到。
2.1.擴展返回的結果
原來,我們返回的結果是PageResult對象,里面只有total、totalPage、items3個屬性。但是現在要對商品分類和品牌進行聚合,數據顯然不夠用,我們需要對返回的結果進行擴展,添加分類和品牌的數據。
那么問題來了:以什么格式返回呢?
看頁面:
分類:頁面顯示了分類名稱,但背后肯定要保存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; }