欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实现搜索的功能。

程序员文章站 2022-05-31 08:32:43
...

实现搜索功能:

添加搜索库

(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;
    }
  
相关标签: 乐优商城