实现搜索的功能。

实现搜索功能:

添加搜索库

(1)首先分析,输入搜索字段之后,页面会显示的数据,把需要的数据封装成一个类。然后创建相应的类。goods。主要是第一张图。

对分类和品牌进行的聚合,也主要是给第二张图进行渲染。
  ​​      ​​
​​在这里插入图片描述


@Document(indexName = "goods", type = "docs", shards = 1, replicas = 0)
public class Goods {
    @Id
    private Long id; // spuId
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌
    @Field(type = FieldType.Keyword, index = false)
    private String subTitle;// 卖点
    private Long brandId;// 品牌id
    private Long cid1;// 1级分类id
    private Long cid2;// 2级分类id
    private Long cid3;// 3级分类id
    private Date createTime;// 创建时间
    private List<Long> price;// 价格
    @Field(type = FieldType.Keyword, index = false)
    private String skus;// List<sku>信息的json结构
    private Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
}

在这里思考我们需要的数据,然后我们需要哪些服务。然后写这些接口

第一:分批查询spu的服务,已经写过。

第二:根据spuId查询sku的服务,已经写过

第三:根据spuId查询SpuDetail的服务,已经写过

第四:根据商品分类id,查询商品分类名称,没写过

第五:根据商品品牌id,查询商品的品牌,没写过

第六:规格参数接口

(2)将mysql数据库中的数据按照每页查出来,以一页为单位进行循环。将每页的数据集合转换成goods类型的集合List<.Goods>。然后在存储到elasticsearch中。

public interface GoodsRepository extends  
ElasticsearchRepository<Goods, Long> {
}

  导入数据其实就是查询数据,然后把查询到的Spu转变为Goods来保存,因此我们先编写一个SearchService,然后在里面定义一个方法, 把Spu转为Goods,执行this.goodsRepository.saveAll(goodsList)将结果保存在Elasticsearch中去。

blic void test(){
        int page = 1;
        int rows = 100;
        do{
            this.elasticsearchTemplate.createIndex(Goods.class);
            this.elasticsearchTemplate.putMapping(Goods.class);
            // 分页查询spu,获取分页数据,page:表示当前页。
            PageResult<SpuBo> result = this.goodsClient.querySpuBoByPage(null, null, page, rows);
            // 获取当前页的数据
            List<SpuBo> items = result.getItems();
            // 处理List<SpuBO> ==> List<Goods>
            List<Goods> goodsList = items.stream().map(spuBo -> {
                try {
                    return this.searchService.buildGoods(spuBo);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }).collect(Collectors.toList());
            // 执行新增数据的方法
            this.goodsRepository.saveAll(goodsList);
            rows = items.size();
            page++;
        }while(rows==100);

其中将spu转换成goods代码如下:下面的反序列化:是将字符串转换成对象。

 public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();
        // 根据分类的id查询分类名称
        List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        // 根据品牌id 查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());

        // 根据spuId查询所有的sku
        List<Sku> skus = this.goodsClient.querySkusBySpuId(spu.getId());
        // 初始化一个价格的集合,收集所有sku 的价格
        ArrayList<Long> prices = new ArrayList<>();
        // 收集sku必要字段信息
        ArrayList<Map<String, Object>> skuMapList = new ArrayList<>();
        skus.forEach(sku -> {
            prices.add(sku.getPrice());
            HashMap<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            // 获取sku中的图片,数据库的中图片可能是多张,多张是以","分割,所以也以逗号来切割图片数组,获取第一张图片
            map.put("image", StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(),",")[0]);
            skuMapList.add(map);
        });
        // 根据spu中的cid3,查询所有的搜索规格参数
        List<SpecParam> params = this.specificationClient.queryParams(null, spu.getCid3(), null, true);
        // 根据spuId 查询spuDetail
        SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spu.getId());
        // 把通用的规格参数值,进行反序列化
        Map<String,Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<String, Object>>() {});
        // 把特殊的规格的参数值,进行反序列化
        Map<String,List<Object>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<String, List<Object>>>() {});

        HashMap<String, Object> specs = new HashMap<>();
        params.forEach(param -> {
            // 判断规格参数的类型,是否是通用的规格参数类型
            if (param.getGeneric()){
                // 如果是通用类型的参数,从genericSpecMap获取规格参数值
                String value = genericSpecMap.get(param.getId().toString()).toString();
                // 判断是否是数值类型,如果是数值类型,应该返回一个区间
                if (param.getNumeric()){
                    value = chooseSegment(value, param);
                }
                specs.put(param.getName(), value);
            } else {
                // 特殊规格的参数类型
                List<Object> value = specialSpecMap.get(param.getId().toString());
                specs.put(param.getName(), value);
            }
        });

        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        // 拼接all字段,需要分类名称以及品牌名称
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names," ") + " " + brand.getId());
        // 获取spu下的所有的sku的价格
        goods.setPrice(prices);
        // 获取spu下的所有sku,并转化成json字符串
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        // 获取所有查询的规格参数
        goods.setSpecs(specs);
        return goods;
    }

查询

(3)。聚合的结果集转换成Terms,StringTerms,LongTerms。才能获取桶的集合。

例如:LongTerms terms = (LongTerms)aggregation;

terms.getBuckets();
 下面需要注意的是:
(1)首先// 添加分类和品牌的聚合。其中categoryAggName:是聚合的名称,而field:是按照什么进行聚合。

queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

(2)然后通过下面来进行解析

  // 获取聚合结果集并解析
 List<Map<String, Object>> categories = getCategoryAggResult(goodsPage.getAggregation(categoryAggName));
 List<Brand> brands = getBrandAggResult(goodsPage.getAggregation(brandAggName));
 /**
     * 根据搜索参数查询
     * @param searchRequest
     * @return
     */
    public SearchResult search(SearchRequest searchRequest) {
        if (StringUtils.isBlank(searchRequest.getKey())){
            return null;
        }
        // 自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加查询条件
      QueryBuilder basicQuery = QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND);
//     BoolQueryBuilder basicQuery = buildBoolQueryBuilder(searchRequest);
        queryBuilder.withQuery(basicQuery);
        //queryBuilder.withQuery(QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND));
        // 添加分页,分页页码从0开始
        queryBuilder.withPageable(PageRequest.of(searchRequest.getPage()-1, searchRequest.getSize()));
        // 添加结果集过滤,否则将返回一堆没用的数据,影响查询的效率。
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));

        // 添加分类和品牌的聚合。其中categoryAggName:是聚合的名称,而field:是按照什么进行聚合。
        String categoryAggName = "categories";
        String brandAggName = "brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        // 执行查询,获取结果集
        //Page<Goods> goodsPage = this.goodsRepository.search(queryBuilder.build());
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)this.goodsRepository.search(queryBuilder.build());
        // 获取聚合结果集并解析
        List<Map<String, Object>> categories = getCategoryAggResult(goodsPage.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(goodsPage.getAggregation(brandAggName));

        // 判断是否是一个分类,只有是一个分类是才做参数聚合
        List<Map<String, Object>> specs = null;
        if(!CollectionUtils.isEmpty(categories) && categories.size() == 1){
            // 对规格参数进行聚合
            specs  = getParamAggResult((Long)categories.get(0).get("id"),basicQuery);
        }
        //getTotalElements:查询到的数据的总条数。getContent:查询到的数据
        return new SearchResult(goodsPage.getTotalElements(), goodsPage.getTotalPages(), goodsPage.getContent(),categories,brands,specs);

    }

(3)对结果聚合结果进行,解析,只有一个分类才做参数聚合,下面是对规格参数的聚合方法:

    private List<Map<String, Object>> getParamAggResult(Long cid, QueryBuilder basicQuery) {
        //自定义查询对象构建
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加基本查询条件
        queryBuilder.withQuery(basicQuery);
        //查询要聚合的规格参数 gid:组id   generic:通用的还是特殊的。  cid:表示分类的id
        List<SpecParam> params = this.specificationClient.queryParams(null, cid, null, true);
        //添加规格参数的聚合  。此时的keyword是分词还是不需要分词。
        params.forEach(param -> {
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword"));
        });
        //添加结果集过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
        //执行聚合
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)this.goodsRepository.search(queryBuilder.build());
        List<Map<String, Object>> specs = new ArrayList<>();
        // 解析聚合结果集 key- 聚合名称(规格参数) value-聚合参数
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
            //  初始化一个map {k: 规格参数名 options: 聚合的规格参数}
            HashMap<String, Object> map = new HashMap<>();
            map.put("k",entry.getKey());
            // 初始化一个options集合,收集桶中的key
            ArrayList<String> options = new ArrayList<>();
            // 获取聚合
            StringTerms terms = (StringTerms) entry.getValue();
            // 获取桶集合
            terms.getBuckets().forEach(bucket -> {
                options.add(bucket.getKeyAsString());
            });
            map.put("options",options);
            specs.add(map);
        }

        return specs;
    }
  
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值